IdP/Database Hybrid

Is there a way to setup an IdP using SAML for authentication but use a database for getting the roles? Our application has roles that are managed outside of active directory so the roles that the IdP provides have nothing to do with the security that is needed inside the project. I was thinking there would be some like User Source AD/Database Hybrid but for Identity Providers.

I could build something custom to look up the users roles from the database inside the project but then i would not be able to use the Ignition security features inside components such as buttons. And i also would not be able to use the security levels on starting up the session.

There is no first-class way to do what you are asking.

I was going to recommend using an expression attribute mapper for the Roles attribute. Assuming you have a user source set up with all of the users that can log in from your IdP and their associated roles, you could write an expression that uses the runScript() function which invokes a script which calls system.user.getUser("userSourceName", "userName").getRoles(). Unfortunately, there is a bug that I just discovered and logged when I tried to do just that, which prevents you from using the runScript() expression function in an attribute mapper expression. Once this bug is fixed, I’d probably recommend this as the best solution.

Only other idea I can think of for now is to create a custom security level for each set of roles/permissions you need. In the IdP’s security level rules, you can author an expression for each of these custom security levels which uses the hasRole expression function. It will return true or false depending on if the user with the given username and usersource has the target role. This is not an ideal solution since you’d have to call hasRole for each role you require and you’d need to create a security level for each set of roles/permissions you need in your project, which I could see as growing unreasonably large for more complex projects. Also, this solution wouldn’t work for areas of the product which do not use security levels and instead rely on roles/security zones (such as Vision), but just thought I’d throw it out there in case this might be an acceptable workaround for your situation.

This bug was resolved with version 8.1.1 and the solution recommended by Joel now works as described.

I am getting around to trying to set this up again. How do you write the expression to reference an already mapped attribute?

This is what I have so far

runScript("system.user.getUser('MyUserSourceName', WHAT_GOES_HERE?).getRoles()")

I have tried copy and pasting the direct path from the Username attribute which looks like this

/saml2p:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name='samaccountname']/saml2:AttributeValue/text()

I also tried making my own {} expression based on the ignition manual like this

{Response:Assertion:AttributeStatement:Attribute[@Name:'samaccountname']:AttributeValue:text}

Neither of these work

This works for me:

runScript("system.user.getUser('MySQL_User_Source', '" + {idp-attributes:/saml2p:Response/saml2:Assertion/saml2:Subject/saml2:NameID/text()} + "').getRoles()")

where MySQL_User_Source is the name of my user source profile.

Since we are passing jython source code as a string to the runScript expression function, we need to embed the username in the part of the source code where the username argument goes to the system.user.getUser script function using string concatenation (string concatenation is handled by the Ignition expression itself). Just swap my XPath expression (/saml2p:Response/saml2:Assertion/saml2:Subject/saml2:NameID/text()) with the XPath expression you need to dereference the username in your SAML response and it should work.

1 Like

got it to work. I needed the ‘"++"’ around my idp expression. thanks.

runScript("system.user.getUser('MyUserSource', '"+{idp-attributes:/saml2p:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name='samaccountname']/saml2:AttributeValue/text()}+"').getRoles()")
1 Like

Would this work for OIDC? If so, what would the runScript expression look like then?

Sure, you’d just have to swap the expression path to the IdP response attribute to one that makes sense for your OIDC response. Example:

runScript("system.user.getUser('MyUserSource', '" + {attribute-source:idTokenClaims:preferred_username} + "').getRoles()")

Also for OIDC, if it helps anyone.

For Rules in Custom Authenticated Security Levels, I used this in the contains functions to get the roles.

containsAny(
runScript(“system.user.getUser(‘test-DB-only’, '” + {attribute-source:userInfo:name} + “’).getRoles()”),
‘Administrator’,
‘area1’
)

The User Source test-DB-only is a Database Only type. There was an object in the Auth response called ‘userInfo’ with a parameter ‘name’ that had the username value I needed.

1 Like