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

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);