Parsing SAML response for Active Directory Memberships

In our SAML response we can get a list of active directories that a person is part of, we intend to use this to control access and access level. The data contained in the attribute value is an array.

When I use the following syntax I get server error 500:


The image I have in my head is that we get that array into the “Roles” attribute but am not quite sure how. Once in Roles, we would have to look over the list of AD’s to see if the correct one is included.

Any advice appreciated.



I’m now able to get the AD group data into roles with the following syntax but I’m not sure how to parse out the individual AD names.

Also, if we do manage to parse out all the individual AD group names, should we be naming our roles the same as the AD groups?

Its not clear in documentation how to handle this case so any advice is appreciated.




For what it is worth I found by using online XPath tools like that it is not necessary to specify the absolute paths to the element nodes and their atomic values. It suffices with the following relative path selector syntax:


or more concretely:

  • ID: //saml:Attribute[@Name="accountname"]
  • Username: //saml:Attribute[@Name="name"]
  • Email: //saml:NameID

and similarly

  • //saml:Attribute[@Name="adgroups"]

The problem remains of how to tokenize the atomic value (string) returned by the attribute selector expressions. For example, //saml:Attribute[@Name="name"] returns a space-separated string John Doe as the atomic value.

Unfortunately it seems the 8.1.x gateway release that we are using does not support string functions like tokenize() that were introduced with XPath 2.0. FWIW tokenize() explodes a complex atomic value into an array of string elements given a regex pattern.

All of the following XPaths work in but yield empty strings when specified as direct paths in Config > Security > Identity Providers > User Attribute Mapping

  • First Name: //saml:Attribute[@Name="name"]/tokenize(.,' ')[1]
  • Last Name: //saml:Attribute[@Name="name"]/tokenize(.,' ')[last()]
  • First Name: tokenize(//saml:Attribute[@Name="name"],' ')[1]
  • Last Name: tokenize(//saml:Attribute[@Name="name"],' ')[last()]
  • First Name: tokenize(normalize-space(//saml:Attribute[@Name="name"]),' ')[1]
  • Last Name: tokenize(normalize-space(//saml:Attribute[@Name="name"]),' ')[last()]
  • First Name: string-join(//saml:Attribute[@Name="name"]/tokenize(., ' ')[position() < last()],' ')
  • Last Name: string-join(//saml:Attribute[@Name="name"]/tokenize(., ' ')[position() > 1],' ')
1 Like

@jspecht if you can offer any advice on the current correct expression syntax for parsing through SAML2 responses it’d be greatly appreciated.

I’ve sent you details via DM.



We seem to have found a method that works using these steps:

Step 1 (Designer): Include the following syntax in a script

def filter_roles(roles_array):
	import java.util.ArrayList as ArrayList
	arr = ArrayList()
	for i in roles_array:
		arr.add(i.split(",")[0].replace("CN=", ""))
	return arr

Step 2 (Gateway): make sure the script is accessible by the gateway

Step 3 (Gateway): use the following for roles user attribute mapping, expression:


Again, what we are using this for is to remove excess text from the AD groups portion of the SAML response, all we want is the text after the “CN=” portion.

That’s correct. The Gateway uses the Java API for XML Processing (JAXP) which only supports the XPath 1.0 spec.

@nicholas.robinson - I replied to your DM. For posterity: I don’t believe it is required to strip out the common name value from the full distinguished name of each ad group returned, it just might be more convenient for you when referencing a simpler string as opposed to the full DN. The only illegal security level name character is the forward-slash, and if encountered, Ignition sanitizes those by replacing with underscore characters. Just make sure that transforming the full DN to simpler strings such as the common name value by itself will still result in unique roles for your purposes (for example: if there are two separate nodes in the AD tree with CN=foo but with different parent structures (i.e. one is CN=foo,OU=bar and the other is CN=foo,OU=baz), stripping out only foo from each would result in ambiguity which previously didn’t exist with the fully qualified DN).

As far as your approach goes using the runScript() expression function, I see nothing wrong with that. It might even be the most straightforward approach compared to using the Ignition expression language (or XPath) to transform the string.

@jspecht just replying here so our “solution” is clearly written. This is working for us:

Project Script:

def assign_roles(roles_array):
	Takes a 'memberOf' list from a SAML response
	returns mapped AD group names as a java array list
	Python & Java arrays are differentiated because returning a python array causes an exception
	import java.util.ArrayList as ArrayList
	java_array = ArrayList()

	# Extract all CN values from SAML response
	python_array = [i.split(",")[0].replace("CN=", "") for i in roles_array]	

	dev_roles = ["dev1", "dev2"]
	admin_roles = ["admin1", "admin2"]
	user_roles = ["user1" , "user2"]
	for CN in python_array:
		if CN in dev_roles:
			if "Developer" not in java_array:
	for CN in python_array:
		if CN in admin_roles:
			if "Administrator" not in java_array:
	for CN in python_array:
		if CN in user_roles:
			if "User" not in java_array:

	return java_array

Roles Expression in the IDP:


Our main remaining issue is that when logging in with SSO to a vision client or the designer, it takes significantly longer than for perspective or the gateway (around 13 seconds) and the message “Chunked IDP Authentication” is flashed repeatedly. We have a ticket open with the helpdesk for it [#11650] and are waiting for a solution.

We can still login, it’s just not the best user experience.



Updating this, we eventually moved everything to a module which reduces several dependencies including python scripting on the VM and the gateway scripting project. Deployment is also greatly simplified by using a module.

Eventually when we call it “” is passed in which when run in the gateway context, allows use to extract the site number from the DNS. This goes in the roles user mapping


Its not really appropriate to share all publicly but the java code is very similar to the python code. Here are some key extracts:

Common - Abstract Script Module

    @ScriptFunction(docBundlePrefix = "AbstractScriptModule")
    public ArrayList<String> processIdpResponse(
            @ScriptArg("arg0") List<String> samlResponse,
            @ScriptArg("arg1") String hostName){

        return assignRoles(samlResponse, hostName);

Gateway Script Module

    protected ArrayList<String> assignRoles(List<String> samlResponse, String hostName) {
        // Takes the "memberOf" field from a SAML response
        // Returns an array of assigned roles

        ArrayList<String> adGroups = new ArrayList<String>();
        ArrayList<String> assignedRoles = new ArrayList<String>();
        HashMap<String, List> roleMap = new HashMap<String, List>();

        for (String member: samlResponse){
            adGroups.add(member.split(",")[0].replace("CN=", ""));

        String siteNum = getSiteNum(hostName);

snip snip

        for (String role : roleMap.keySet()) {
            for (String adGroup : adGroups) {
                if (roleMap.get(role).contains(adGroup)) {
                    if (!assignedRoles.contains(role)) {
        return assignedRoles;



1 Like