Authentication for Inbound Interaction in Adobe Campaign Classic

I am working to enable some security for the inbound interaction for Adobe Campaign Classic Offer. We are working to get the inbound call via SOAP for call center channel. I have setup the offer space will name "callCtr" under Recipient Environment. The goal is to ensure the caller passes an username and password while making the SOAP call. Also I want to enable the authentication for specific offer spaces only.
The SOAP request for Propose method has the <urn:sessiontoken> node. But for offer service that is not used. So I planned to use the node in the same way as Message Center enables the authentication.
So the caller needs to pass the  value as username/password inside the sessionToken element.
Here are the changes I did and note I made changes to the nms:interaction.js file. Be sure to backup the original file before you try and Adobe may not support if you make changes. So be ready to fly on your own!!!

Step 1: Create an option variable to store the name of the offer spaces in a comma separate way
I created an option variable of type String and named as "tgSecuredOfferSpaces"

Step 2: Create an external account
I created an external account of type "HTTP". I just need a place to store the username and password. You can do the same by creating an user account or storing the username and password in two option variables.

Step 3: Create a function to validate the credentials
Created JS file named as "cus:data.js" to validate the user name and password passed by the SOAP caller. I am using external account so the below codes, you need to change if you are using the option variable or operator account.
function TGDataAPI() {
  this.errorMessage = "";
}
TGDataAPI.prototype.validateExternalAccount = function (username, password) {
  var validateMsg = false;
  logInfo("Username:" + username + " Password:" + password);
  var accounts = xtk.queryDef.create(<queryDef operation="select" schema="nms:extAccount">
    <select>
      <node expr="@account" />
      <node expr="@active" />
      <node expr="@password" alias="@password" />
      <node expr="@name" />
    </select>
    <where>
      <condition expr={"@account = '" + username + "'"} />
      <condition expr={"@active = 1"} />
    </where>
  </queryDef>).ExecuteQuery();
  //logInfo("Executed 1");
  logInfo("Accounts:" + accounts.toXMLString());
  var userExists = false;
  var notvalid = 0;
  for each(user in accounts.extAccount)
  {   
    userExists = true;
    var encPass = user.@password;
   
    var passDecrypted ="";
    if(encPass != null & encPass.toString().length > 0)
    {
      //passDecrypted = decryptPassword(encPass);
      passDecrypted = decryptString(encPass.toString()); //Refer to the note section for some important details
    }
   
    if (passDecrypted == password) {
      return true;
    }
  }
  if (userExists) {
    this.errorMessage = "Invalid password";
  }
  else {
    this.errorMessage = "Invalid Username";
  }
  return validateMsg;
}

If using Operator account, you can do the validating using below function
xtk.session.Logon (username, password, <param/>));
If username or password is invalid, then the Logon method throws error. If you do more validation on access, then logon returns an array and the 2nd value has all the information of the user.

Something to note here, I was trying to use decryptPassword as the decryptString is deprecated since Build 8947. But the function decryptPassword only available inside SOAP call. I am not sure, but the offer is responded more like web page. So when I tried to use it, it is giving reference error, no matter which reference I add. So I had to use the decryptPassword. To make the function working since 8947 and up, you have to have an option variable XtkSecurity_Unsafe_DecryptString with value = 1. I added it as numeric as it was not there in my version and set the value to 1.
I have added a question on this issue via this forum link and will update if I receive any solution.

Step 3: Update interation.js file
This is the most complex section and be careful while identifying the section. I will add few extra lines of OTB code so that you can identify the line where you need to add the code also adding the approximate line number if that help. The Highlighted lines are added

Around line 4388
     _DEPENDANCIES_();
      /*Added by Tarun Ghosh - Custom dependency patching */
      _CUSTOMDEPENDANCIES_();     
      /*End Adde by Tarun Ghosh */
      _HOOKINIT_();

Around line 4430
// Logon to enable SQL queries
      var adminUserContext = logonEscalation('interaction')

      // Custom Code for Authentication  -  Tarun Ghosh           
      /*  Start - Function used to validate Session User for SOAP  -  CEM-9810  */ 
      if(bSoap)
      {
        var securedOfferSpaces = getOption("tgSecuredOfferSpaces");
        var currentOfferSpace = "_OFFERSPACENAME_";
        var spaces =  securedOfferSpaces.split(",");
        var isSecuredSpace = false;
          for(var i=0; i<spaces.length; i++)
          {
            if(spaces[i].toLowerCase() == currentOfferSpace.toLowerCase())
            {
              isSecuredSpace = true;
              break;
            }
          }

        if(isSecuredSpace)
        {  
          logInfo("Secured offer space found..");
          var sessionToken = "";
          sessionToken = soapMethod.*::sessiontoken.toString();
          if(sessionToken == "")
          {
            throw "Session token can't be empty";
          }

          var sessions = sessionToken.split("/");
          if(sessions.length <2)
          {
            throw("Please provide session token in the format username/password");
          }
          var  login =  sessions[0];
          var  pass  =  sessions[1];
         
          var dataApi = new TGDataAPI();

          if(!dataApi.validateExternalAccount(login, pass))
          {
            var message = dataApi.errorMessage;
            throw "Authentication Error:" + message;         
          }
        }       
      }
    /*  End - Function used to validate Session User for SOAP  */


      // Read context parameter
      var interaction = undefined
      if( bSoap )

Around line 5700
patch('_DEPENDANCIES_()', translator.getDependancies())
/* Added by Tarun Ghosh - Load Custom Library */
  var custtomDependencies = '\n\t\t\tloadLibrary("xtk:shared/nl.js");\n\t\t\tNL.require("/nl/core/shared/dataTypes.js")\n\t\t\t\t.require("/nl/core/shared/view.js")\n\t\t\t\t.require("/nl/core/shared/xtk.js");';
  custtomDependencies += '\n\t\t\tloadLibrary("cus:data.js");\n';
  /* End Added by Tarun Ghosh */


  //[cf]
//[of]:Process presentation rules
  // Modes for presentation rules
  var MODE_ANY = 0
  var MODE_SAMEOFFER = 1
Note: Ideally you don't need to add those OTB dependencies like nl.js and others via the first line of the code. I did those while trying to make the encryptPassword function working. The function didn't work after adding those dependencies.

Step 5: Deploy and publish offer space
Deploy and publish offer space and run the offer notification workflow to ensure changes are deployed properly. If deployment is successful, then open the offer space in the go to the Audit > Code section and click on the generate code. You should see changes like below. The lines added by this change is highlighted
Around line 20
        sTotalTime = getCurrentDate().getTime();
        loadLibrary("xtk:common.js")
loadLibrary("xtk:xtkexpr.js")
;
            loadLibrary("xtk:shared/nl.js");
            NL.require("/nl/core/shared/dataTypes.js")
                .require("/nl/core/shared/view.js")
                .require("/nl/core/shared/xtk.js");
            loadLibrary("cus:data.js");


Other changes are embedded into there and feel free to search for them.
SOAP Call post change



Call with Invalid Password

Comments

Popular posts from this blog

Avoid Proxy for HttpClientRequest - IOB-090007 Network error (send(), errno=10054: an existing connection was forcibly closed by the remote host

Adobe Campaign: Call Java Script from Input Form

Broadlog Resequencing in Adobe Campaign Classic