The CreatorCon Call for Content is officially open! Get started here.

SSO SAML 2.0: On-Demand Login/User Provisioning

enojardi
ServiceNow Employee
ServiceNow Employee

Overview

Following somewhat similar principles as the LDAP On-Demand Login functionality, this extension to the SAML 2.0 plugin allows the configuration of auto user provisioning. Out of the box, the SAML2.0 plugin requires that the user record exists in ServiceNow for authentication to be successful.

Detailed Explanation


This solution leverages the AttributeStatement element found in the SAML Assertion Response (XML), once authentication takes place. Most IdPs embed user attributes in the AttributeStatement element. This solution parses all the attribute-value pairs and then creating an import set. A Transform Map can then be defined to create/update the user in the sys_user (target) table. All of this happens before the Installation Exit performs the query on the user table to see if a user is found.

Technical Components


  • (Script Include) SAML2_UserProvisioning: This is the main class used for parsing the XML Response.
  • (Script Include) ImportSetUtilExtended: Extension of the out of the box ImportSetUtil Script Include.
  • (Installation Exit) SAML2SingleSignon_update1: Extension of the existing SAML2.0 Installation Exit (Note: only 4 lines of code were added from the original)
  • (Import Set Table) u_imp_saml_user: This is where all the staging data parsed from the XML will be saved
  • (Transform Map) Import SAML User: Transform map used for transforming the user data
  • ... And a few little things not worth mentioning...

If you want to watch a demo video on how it works, I have uploaded a short 2 min video here.

Steps to Implement


Attached is the update set which contains all the required configuration. Very simple configuration is required after installed. It goes without saying that the SAML2.0_update1 plugin must be installed prior to loading the update set.

Important: In Eureka patch 5, there's been an upgrade to the SAML2_update1 Script Include. This requires some small modifications to the extended Installation Exit included in the update set. Contact me if you get stuck. I will upload a new version once Fuji goes GA.

Some important things to note/configure after installation:

  1. The update set will create an import set table called Import SAML User (u_imp_saml_user)
  2. The import set table will not contain any fields initially (apart from the fields already inherited from sys_import_set_row)
  3. Familiarise yourself with the Properties page and ensure no changes are needed for the two properties
  4. For the first ever login, ensure the user already exists in ServiceNow. This will allow for the new fields to be added* to the import set table dynamically (that is, it will depend on how many Attributes are included in the AttributeStatement)
  5. The first login may take a couple of extra seconds as the import set table will create the new fields
  6. After first login, you're ready to map fields in the "Import SAML User" Transform Map, since the data should have been loaded already.
  7. Ensure you use the same field for coalescing as the field you're using to match the user in the SAML Properties page
  8. I recommend mapping at least the user_name and email in the Transform Map, otherwise it will be blank for new users.
  9. New users created will have a random password set - this is to avoid local login if external authentication is not enforced (or if accessing side_door.do)


* If fields are not added, it could be because the AttributeStatement does not follow standard SAML format. You may need to tweak the Script Include "SAML_UserProvisioning". There are some comments/instructions in the _process method.


Bonus: Override NameId Policy

I've found that a few IdPs do not provide a persistent or unique key in the NameId Policy element. Instead, they provide a variable string (transient), which can sometimes be a sessionID. As you may have guessed, this value could never be used to match a user in ServiceNow, as the value will always be different. If for some X reason, you cannot change this, I have included some functionality to allow you to override the NameID policy. This works by providing the "Name" of the Attribute (from the AttributeStatement) you want to use, instead. To configure this is very simple and can be done through this new System Property: glide.authenticate.sso.saml2.nameid_policy_override.


Things to Remember


This is a custom solution. That being said, I have tested it successfully with 3 different IdPs: SSOCircle, Microsoft ADFS and OpenIdP - the last one even needing the NameId override. You could also apply the same logic from this solution for Unencrypted HTTP Header and Digest Token SSO methods as described in this other post here.

Message was edited by: Jonatan Jardi

1 ACCEPTED SOLUTION

Lawrence D
Giga Contributor

For us, this breaks SAML deep linking.



'action' looks to be overwritten by the user import.   I updated the installation exit script, SAML2SingleSignon_update1 with:



/*


* {action} is changed by the auto provisioning process and breaks SAML deep linking, store it.


* Changes from the object com.glide.processors.LoginAction to a string, e.g. 'update'.


*/


this._action = action;




/*Auto Provisioning changes */


sup.loadUserDetails();


/*Auto Provisioning changes */




if (action && (JSUtil.type_of(action) === 'string')) {


  action = this._action;


}


View solution in original post

45 REPLIES 45

Thanks Jonatan, that makes perfect sense.   I do have an issue with Fuji then, these two lines do not work once I upgrade to Fuji patch 3.   I thought I had everything working, but realized that these lines had been commented out to make the script work.   Any ideas on why this would break after upgrading to Fuji?


Thanks,


Brian


enojardi
ServiceNow Employee
ServiceNow Employee

Hi Brian



Haven't tested myself, but it seems that JSUtil is now treated as global in scope. So maybe try replacing to global.JSUtul?


http://wiki.servicenow.com/index.php?title=JSUtil#gsc.tab=0



Faiing that, you could just do a standard javascript comparison:



if(action != undefined && typeof action == 'string'){


//..code here...


}



Let me know how you go


Thanks for the quick response Jonatan, I tested both of these and cannot get either one to work, I am missing something?   I tried the global prefix, that did not work and also the standard javascript comparison and could not get that to work either.   I have the two lines listed below that I have been toggling back and forth to see if I could get either one of them to log me into ServiceNow successfully.



/* Begin Auto Provisioning changes */
this._action = action;
   
sup.loadImportSet();
   
//if (action && (global.JSUtil.type_of(action) === 'string')) {
  if(action != undefined && typeof action == 'string'){
action = this._action;
}


    /* End Auto Provisioning changes */


gigi3
Mega Contributor

Hi ,


I have the same problem.


action has a value like com.glide.processors.LoginAction@xxx


I substituted the the part


  1. if (action && (JSUtil.type_of(action) === 'string')) {  
  2.   action = this._action;  
  3. }  

with a simple


action = 'update';


I don't know if this have some side effect, but I saw that if the user is new or existing, all works fine




Obviously I already have create the transform map.




It's only a workaround.



I suppose the Jonatan is the only that can approve or not this kind of workaround



Let me know


and thanks to all


Giovanni


enojardi
ServiceNow Employee
ServiceNow Employee

Mmmm that would work, but can anyone test if RelayState / deeplinking still works like that? I.e. instead of going directly to instance, try to go to an incident record something (using nav_to.do=uri=incident.do?sys_id=BLAH).



Otherwise, can any of you try this (and again do one direct and one with deep linking):



/*Auto Provisioning changes */


      this._action = action;


      sup.loadImportSet();


      action = this._action;  


/*Auto Provisioning changes */



Thanks!