Recording and resources for Ping Identity | ForgeRock Developer: Customizing Journeys with Scripted Nodes. Example Autonomous Access Logic

In this session, Marcin Zimny, Principal Solutions Architect, dived into into customizations, focusing on improving customer experience and orchestrating fraud signals using Autonomous Access logic as an example. This session is filled with quite a lot of useful information, tips and best practices on using scripted decision nodes. Marcin also walkthrough some widely used examples, while answering great questions for the attendees.

1 Like

aaCustomDecision.js

/*
  Risk Policy - Signal Scores
*/
var aa_impossible_travel_score = 40;
var aa_credential_stuffing_score = 100;
var aa_automated_user_agent_score = 25;
var aa_brute_force_score = 100;
var aa_suspicious_ip_score = 35;
/*
  Risk Policy - Custom Signals
*/
var aa_use_anonymizer_detection = 1;
var aa_use_hibp_detection = 1;
var custom_aa_tor_detected_score = 50;
var custom_aa_vpn_detected_score = 20;
var custom_aa_proxy_detected_score = 5;
var custom_aa_hibp_score = 40;
/*
  Risk Policy - Thresholds And Extra Features
*/
var aa_medium_risk_threshold = 30;
var aa_high_risk_threshold = 75;
var aa_max_signal_count_high_risk_override = 99;
/*
  Risk Policy - Method
    0 - highest score out of all triggered signals
    1 - summary of all triggered signals
*/
var aa_risk_method=1;
/*
  Risk Policy - UEBA method
    0 - highest score out of 3 models
    1 - average score out of 3 models
*/
var aa_ueba_method=0;
/*
  Risk Policy - Overrides

  Whitelist - false positive control
  Blacklist - preventative block
  VPN whitelist - overrides Impossible Travel signal if moved to own VPN range
  Example - ip_whitelist or ip_blacklist = ["62.21.63.30-62.21.63.30","82.21.168.1-82.21.168.255"];
*/
var ip_whitelist = [];
var ip_blacklist = [];
var ip_vpn_whitelist = [];
/*
  Custom signals parameters
*/
//IPQualityScore API
var USER_AGENT = "IPQS User agent"
var API_KEY = "Insert your IPQS API Key here"
var HIBP_API_KEY = "HIBP API KEY HERE"

/********************************************************
  The engine *
*/
outcome = "failed"; //default outcome
//Define variables
var signal_count = 0;
var pos = 0;
var arr_scores = [];
var arr_scores_models = [];
var score = 0;
var predictionResultChopped;
var predictionResultChoppedVal;
//Define signal variables and assign defaults (negative)
var is_impossible_travel = 0;
var is_credential_stuffing = 0;
var is_automated_user_agent = 0;
var is_brute_force = 0;
var is_suspicious_ip = 0;
var model1_score = 0;
var model2_score = 0;
var model3_score = 0;
var isAnonymizedResult;
var isPwnedResult;
var impossibleTravellerOverride = 0;
//Get risk data
var predictionResultRaw = sharedState.get("predictionResult");
var predictionResultString = predictionResultRaw.toString();

var result;

function inet_aton (ip) {
    return ip.split(".").reduce((int, v) => int * 256 + +v);
}

function haveIBP() {
  isPwnedResult = "error";
  var password = transientState.get("password");
  if (password) {
    var DIGITS_UPPER = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
    var md = java.security.MessageDigest.getInstance("SHA-1");
    var passwordObject = {password: java.lang.String(password)};
    var hashedPasswordBytes = md.digest(passwordObject.password.getBytes("utf8"));
    var l = hashedPasswordBytes.length;
    var out = [];
    for (var i = 0, j = 0; i < l; i++) {
        out[j++] = DIGITS_UPPER[(0xF0 & hashedPasswordBytes[i]) >>> 4];
        out[j++] = DIGITS_UPPER[0x0F & hashedPasswordBytes[i]];
    };

    var hash = out.join("")
    var lookup = hash.slice(0, 5)
    var suffix = hash.slice(5)

    var requestHIBP = new org.forgerock.http.protocol.Request();
    requestHIBP.setMethod('GET');
    requestHIBP.setUri('https://api.pwnedpasswords.com/range/' + lookup);
    requestHIBP.getHeaders().add("Accept","*/*");
    requestHIBP.getHeaders().add("Content-Type","application/json");
    requestHIBP.getHeaders().add("User-Agent", USER_AGENT);
    requestHIBP.getHeaders().add("hibp-api-key", HIBP_API_KEY);

    var responseHIBP = httpClient.send(requestHIBP).get();
    if (responseHIBP.getStatus().getCode() === 200) {
      var payloadHIBP = responseHIBP.getEntity().getString();
      if (payloadHIBP.indexOf(suffix) == -1) {
        isPwnedResult = "clear";
      } else {
        isPwnedResult = "failed";
      }
    }
  }
}

function isAnonymized() {
  var payload = sharedState.get("IPQualityScore")

  if (payload) {
    var jsonResult = JSON.parse(payload);
  }
  else {
    var ipaddress = requestHeaders.get("X-FORWARDED-FOR").get(0).split(",")[0].trim();
    var request = new org.forgerock.http.protocol.Request();
    request.setMethod("GET");
    request.setUri("https://ipqualityscore.com/api/json/ip/" + API_KEY + "/" + ipaddress + "?strictness=0&allow_public_access_points=false&fast=false&lighter_penalties=false&mobile=false");
    request.getHeaders().add("Accept","application/json");
    request.getHeaders().add("User-Agent", USER_AGENT);

    var response = httpClient.send(request).get();
    if (response.getStatus().getCode() === 200) {
      var payload = response.getEntity().getString();
      var jsonResult = JSON.parse(payload)
  	  if (jsonResult.success === true) {
        sharedState.put("Debug-IPQualityScore", payload);
      }
    }
  }

  if (jsonResult) {
    if (jsonResult.tor === true) {
      isAnonymizedResult = "tor";
    } else if (jsonResult.vpn === true) {
      isAnonymizedResult= "vpn";
    } else if (jsonResult.proxy === true) {
      isAnonymizedResult = "proxy";
    } else {
      isAnonymizedResult = "not_detected";
    }
  }
}


if(aa_use_anonymizer_detection==1)
{
  isAnonymized();
  sharedState.put("custom_aa_isAnonymized",isAnonymizedResult);
  if(isAnonymizedResult=="vpn")
  {
    arr_scores.push(custom_aa_vpn_detected_score);
  }
  else if(isAnonymizedResult=="proxy")
  {
    arr_scores.push(custom_aa_proxy_detected_score);
  }
  else if(isAnonymizedResult=="tor")
  {
    arr_scores.push(custom_aa_tor_detected_score);
  }
  else
  {
    arr_scores.push(0);
  }
}

if(aa_use_hibp_detection==1) {
  haveIBP();
  sharedState.put("custom_aa_isPwned",isPwnedResult);
  if(isPwnedResult!="error" && isPwnedResult!="clear") {
    arr_scores.push(0);
  }
  else {
    arr_scores.push(custom_aa_hibp_score);
  }
}

if(predictionResultString.search("risk_score_data")>=0)
{
  outcome = "low"; //default if there's data from risk API
}

//Check if we're assessing impossible travel and assign result
pos = predictionResultString.search("impossibleTravellerCheck=true");
if(pos>0)
{
  pos = predictionResultString.search("is_impossible_travel=false");
  if(pos<0)
  {
    //Check if it's a whitelisted VPN
    pos = predictionResultString.search("dest_ip=");
    predictionResultChopped=predictionResultString.substring(pos);
    predictionResultChoppedVal=predictionResultChopped.substring(8,predictionResultChopped.search(","));
    src_ipaddress_dec = inet_aton(predictionResultChoppedVal);

    if(ip_vpn_whitelist.length>0)
    {
      for (var i = 0; i < ip_vpn_whitelist.length; i++)
      {
        list_entry = ip_vpn_whitelist[i].split("-");
        list_first_ipaddress_dec=inet_aton(list_entry[0]);
        list_last_ipaddress_dec=inet_aton(list_entry[1]);

        if(src_ipaddress_dec>=list_first_ipaddress_dec && src_ipaddress_dec<=list_last_ipaddress_dec)
        {
          impossibleTravellerOverride=1;
          sharedState.put("debug-whitelist-vpn","condition met for: " + predictionResultChoppedVal + ", outcome->no impossible travel");
        }
      }
    }
    if(impossibleTravellerOverride==0)
    {
      signal_count++;
      is_impossible_travel=1;
      arr_scores.push(aa_impossible_travel_score);
    }
  }
}
//Check if we're assessing credential stuffing and assign result
pos = predictionResultString.search("credentialStuffing=true");
if(pos>0)
{
  pos = predictionResultString.search("is_credential_stuffing=false");
  if(pos<0)
  {
    signal_count++;
    is_credential_stuffing=1;
    arr_scores.push(aa_credential_stuffing_score);
  }
}
//Check if we're assessing automated user angent (antibot) and assign result
pos = predictionResultString.search("automatedUserAgentsFilter=true");
if(pos>0)
{
  pos = predictionResultString.search("is_automated_user_agent=false");
  if(pos<0)
  {
    signal_count++;
    is_automated_user_agent=1;
    arr_scores.push(aa_automated_user_agent_score);
  }
}
//Check if we're assessing brute-force and assign result
pos = predictionResultString.search("bruteForcePreventionCheck=true");
if(pos>0)
{
  pos = predictionResultString.search("is_brute_force=false");
  if(pos<0)
  {
    signal_count++;
    is_brute_force=1;
    arr_scores.push(aa_brute_force_score);
  }
}
//Check if we're assessing suspicious IP and assign result
pos = predictionResultString.search("suspiciousIPCheck=true");
if(pos>0)
{
  pos = predictionResultString.search("is_suspicious_ip=false");
  if(pos<0)
  {
    signal_count++;
    is_suspicious_ip=1;
    arr_scores.push(aa_suspicious_ip_score);
  }
}
//Check if we're assessing UEBA and assign result
pos = predictionResultString.search("anomalyDetection=true");
if(pos>0)
{
  pos = predictionResultString.search("clustering_result=");
  if(pos>0)
  {
      predictionResultChopped=predictionResultString.substring(pos);
      predictionResultChopped=predictionResultChopped.substring(predictionResultChopped.search("risk_score="));
      predictionResultChoppedVal=predictionResultChopped.substring(11,predictionResultChopped.search(","));
      predictionResultChopped=predictionResultChopped.substring(11);
      model1score=parseInt(predictionResultChoppedVal,10);

      predictionResultChopped=predictionResultChopped.substring(predictionResultChopped.search("risk_score="));
      predictionResultChoppedVal=predictionResultChopped.substring(11,predictionResultChopped.search(","));
      predictionResultChopped=predictionResultChopped.substring(11);
      model2score=parseInt(predictionResultChoppedVal,10);

      predictionResultChopped=predictionResultChopped.substring(predictionResultChopped.search("risk_score="));
      predictionResultChoppedVal=predictionResultChopped.substring(11,predictionResultChopped.search(","));
      predictionResultChopped=predictionResultChopped.substring(11);
      model3score=parseInt(predictionResultChoppedVal,10);

      arr_scores_models.push(model1score,model2score,model3score);
  }
}

//Deliver risk score
if(aa_ueba_method==0)
{
  score = Math.max.apply(null, arr_scores_models);
}
else if(aa_ueba_method==1)
{
  score = arr_scores_models.reduce((a, b) => a + b, 0)/arr_scores_models.length;
}
arr_scores.push(score);


if(aa_risk_method==0)
{
  score = Math.max.apply(null, arr_scores);
}
else if (aa_risk_method===1)
{
  score = arr_scores.reduce((a, b) => a + b, 0);
}
//Deliver risk outcome
if(score>aa_medium_risk_threshold)
{
  outcome="medium";
}
if(score>aa_high_risk_threshold)
{
  outcome="high";
}
if(signal_count>=aa_max_signal_count_high_risk_override && outcome=="medium")
{
  outcome="high";
  sharedState.put("debug-signal-count-override","true");
}

//process the blacklist and whitelist
var src_ipaddress;
var src_ipaddress_dec;
var list_first_ipaddress_dec;
var list_last_ipaddress_dec;
var list_entry;
var logmessage;
src_ipaddress = requestHeaders.get("X-FORWARDED-FOR").get(0).split(",")[0].trim();
src_ipaddress_dec = inet_aton(src_ipaddress);

if(ip_blacklist.length>0)
{
  for (var i = 0; i < ip_blacklist.length; i++)
  {
    list_entry = ip_blacklist[i].split("-");
    list_first_ipaddress_dec=inet_aton(list_entry[0]);
    list_last_ipaddress_dec=inet_aton(list_entry[1]);

    if(src_ipaddress_dec>=list_first_ipaddress_dec && src_ipaddress_dec<=list_last_ipaddress_dec)
    {
        sharedState.put("debug-blacklist","condition met for: " + src_ipaddress + ", " + outcome + "->high");
        outcome="high";
    }
  }
}
if(ip_whitelist.length>0)
{
  for (var i = 0; i < ip_whitelist.length; i++)
  {
    list_entry = ip_whitelist[i].split("-");
    list_first_ipaddress_dec=inet_aton(list_entry[0]);
    list_last_ipaddress_dec=inet_aton(list_entry[1]);
    if(src_ipaddress_dec>=list_first_ipaddress_dec && src_ipaddress_dec<=list_last_ipaddress_dec)
    {
        sharedState.put("debug-whitelist","condition met for: " + src_ipaddress + ", " + outcome + "->low");
        outcome = "low";
    }
  }
}



sharedState.put('debug-score',score.toString());
sharedState.put('debug-signal-count',signal_count.toString());
sharedState.put('debug-outcome',outcome);

customAttrHandler.js

/* These functions read and write to custom attributes in Identity Cloud.
Custom attributes are part of a single JSON blob,
so we need to iterate through them

To read a single attribute use:
idRepository.getAttribute(id, "name of attribute").toArray()[0];
To write to a single attribute use:
idRepository.setAttribute(id, "name of attribute",[value]);

*/

function getCustomAttrValue (customAttrName) {
  var id = sharedState.get('_id');
  var frIdmCustomAttributes = idRepository.getAttribute(id, 'fr-idm-custom-attrs');
  if (frIdmCustomAttributes) {
      var frIdmCustomAttributesObject = JSON.parse(String(frIdmCustomAttributes.toArray()[0]));
  	  return frIdmCustomAttributesObject[customAttrName];
  }
}

function setCustomAttrValue (customAttrName,customAttrValue) {
  var id = sharedState.get('_id');
  var frIdmCustomAttributes = idRepository.getAttribute(id, 'fr-idm-custom-attrs');
  if (frIdmCustomAttributes) {
    var frIdmCustomAttributesObject = JSON.parse(String(frIdmCustomAttributes.toArray()[0]));
    frIdmCustomAttributesObject[customAttrName] = customAttrValue;
	var json_string = JSON.stringify(frIdmCustomAttributesObject);
	idRepository.setAttribute(id, "fr-idm-custom-attrs",[json_string]);
  }
}

fingerprint.js

/*This scripted node fingerprints device/browser using fingerprintjs Pro https://github.com/fingerprintjs/fingerprintjs
and adds the hash/fingerprint to sharedState as visitorId
Set node's outcome to 'true'
*/

var API_KEY = systemEnv.getProperty("esv.fpcom.public.key");
var fr = JavaImporter(
	org.forgerock.openam.auth.node.api,
	com.sun.identity.authentication.callbacks.ScriptTextOutputCallback,
	com.sun.identity.authentication.callbacks.HiddenValueCallback
);

var script = String("const fpPromise = import('https://fpjscdn.net/v3/" + API_KEY + "') \n" +
                    ".then(FingerprintJS => FingerprintJS.load({ \n" +
	" region: \"eu\" \n" +
	"})) \n" +
  "fpPromise \n" +
  ".then(fp => fp.get()) \n" +
  ".then(result => { \n" +
  "  var visitorId = (result.visitorId)  \n" +
  "  var requestId = (result.requestId) \n" +
  "  var confidence = JSON.stringify(result.confidence) \n" +
  "  var output = JSON.parse(confidence) \n" +
  "  output[\"requestID\"] = requestId \n " +
  "  output[\"visitorID\"] = visitorId \n " +
  "  console.log(output) \n" +
  "  document.getElementById('clientScriptOutputData').value=JSON.stringify(output)   \n" +
  "  document.getElementById('loginButton_0').click() })");


with (fr)
{

	if (callbacks.isEmpty())
	{
      action = Action.send(new HiddenValueCallback("clientScriptOutputData", "false"),
                           new ScriptTextOutputCallback(script)).build();
	}
	else
	{
      var visitorId = JSON.parse(String(callbacks.get(0).getValue()));
      var fingerPrint = visitorId.visitorID;
	  var fingerPrintScore = visitorId.score;
	  var reuestId = visitorId.requestID;


      sharedState.put("deviceFingerPrint",fingerPrint);
      sharedState.put("deviceConfidenceScore",fingerPrintScore);
	  sharedState.put("deviceRequestId",reuestId);

	  var objectAttributes = new java.util.LinkedHashMap();
	  objectAttributes.put("frIndexedString4",fingerPrint);
	  sharedState.put("objectAttributes", objectAttributes);


      action = Action.goTo("true").build();
	}
}

ipqs-isAnonymized.js

/*
IPQualityScore's Proxy Detection & Fraud Scoring API allows you to
Proactively Prevent Fraudβ„’ via a simple API that provides over 25 data
points for risk analysis, geo location, and IP intelligence.
​
https://www.ipqualityscore.com/documentation/proxy-detection/overview
​
Outcomes:
  "error"
  "tor"
  "vpn"
  "proxy"
  "not_detected"
*/
​
var USER_AGENT = "Put Your User Agent Here"
var API_KEY = "Put Your API Key Here"
​
outcome = "error";
​
var payload = sharedState.get("IPQualityScore")
​
if (payload) {
  var jsonResult = JSON.parse(payload);
} else {
  var ipaddress = requestHeaders.get("X-FORWARDED-FOR").get(0).split(",")[0].trim();
​
  var request = new org.forgerock.http.protocol.Request();
  request.setMethod("GET");
  request.setUri("https://ipqualityscore.com/api/json/ip/" + API_KEY + "/" + ipaddress + "?strictness=0&allow_public_access_points=false&fast=false&lighter_penalties=false&mobile=false");
  request.getHeaders().add("Accept","application/json");
  request.getHeaders().add("User-Agent", USER_AGENT);
​
  var response = httpClient.send(request).get();
  if (response.getStatus().getCode() === 200) {
    var payload = response.getEntity().getString();
    var jsonResult = JSON.parse(payload)
	  if (jsonResult.success === true) {
      sharedState.put("IPQualityScore", payload);
    }
  }
}
​
if (jsonResult) {
  if (jsonResult.tor === true) {
    outcome = "tor";
  } else if (jsonResult.vpn === true) {
    outcome = "vpn";
  } else if (jsonResult.proxy === true) {
    outcome = "proxy";
  } else {
    outcome = "not_detected";
  }
}

Resources used in the session:

Example scripts used in the session:

Please take a few seconds to give us your feedback to help us deliver content.

Interested in our upcoming developer community events? Register here!

1 Like