Mapping SAML attributes to OIDC claims with Javascript
When the simple mapping and transform rules do not meet your requirements you can add a Javascript rule. This is a significantly more advanced operation though and an understanding of both scripting and SAML is... advantageous.
Writing a scripted rule
Considerations before you start
- Scripted rules are designed such that they can produce multiple OIDC claims, and you should not expect to need more than one per OpenAthens Keystone connection
- Execution time is limited to one second. After this time the script is stopped and the login attempt will fail before the user is passed to you
- Any exceptions generated by your script will stop execution and the login attempt will fail before the user is passed to you
- Input attributes from SAML can be multi-valued and are unordered
Global objects
At the point of execution the script has access to 3 global objects
- encapsulates the SAML response from the IdP.response
output
- javascript object used to store OIDC claims.logger
- lets you write a limited number of debug statements.
Scripted ruleset example
/**
* parameter entitlements
* List of users entitlements
* parameter roles
* List of users roles
* return The membership type: gold, silver, bronze or tin.
*/
function getMembershipType(entitlements, roles) {
logger.log("Entitlements: %s", entitlements);
if (entitlements.includes("https://auth.example.com/terms-and-conditions")) {
var rolesNoSuffix = removeScope(roles);
logger.log("User has entitlement");
logger.log("Roles without suffix: %s", rolesNoSuffix);
if (rolesNoSuffix.includes("staff")) {
logger.log("User is staff");
return "gold";
} else if (rolesNoSuffix.includes("student")) {
logger.log("User is student");
return "silver";
} else if (role.includes("member")) {
rolesNoSuffix.log("User is member");
return "bronze";
}
}
return "tin";
}
/**
* parameter roles
* List of users roles
* return
* List of users roles with the scope (suffix) removed if present.
* E.g staff@idp.domain.com -> staff.
*/
function removeScope(roles) {
return roles.map((value) => {
var matches = value.match(/^(.*)@.*$/);
if (matches) {
return matches[1];
} else {
return value;
}
});
}
//Execution starts here.
var xp = '//saml:SubjectLocality/@Address[1]';
//Should return a single valued array.
var nodes = response.evalXPath(xp);
if (nodes) {
logger.log("Found %d address attributes", nodes.length);
//Assign userIp to the output. Will be released as an OIDC claim
output.userIp = nodes[0].textContent;
} else {
logger.log("Could not get users IP address");
}
var entitlements = response.getAttributeValues("urn:oid:1.3.6.1.4.1.5923.1.1.1.7");
var roles = response.getAttributeValues("urn:oid:1.3.6.1.4.1.5923.1.1.1.9");
//Assign membershipType to the output. Will be released as an OIDC claim
output.membershipType = getMembershipType(entitlements, roles);
Response
Public Methods
response.getIssuer();
Return Value:
- a
String
identifying the issuer of the SAML response.
- a
response.getAttributeValues(name);
Parameters:
name
the name of the SAML Attribute.
Return value:
- a
String[]
containing all values associated with the given name.
The array will beempty
if there is no Attribute in the response with the give name, or there are no values contained within the Attribute.
- a
response.getFirstAttributeValue(name);
Parameters:
name
the name of the SAML Attribute.
Return value:
- A
string
containing the first value associated with the given name (the order is indeterminate).null
will be returned if there is no Attribute with the given name, or there are no values contained within the Attribute.
- A
response.evalXPath(exp);
The following xml namespaces
are pre-registered
Prefix | Namespace |
---|---|
samlp | urn:oasis:names:tc:SAML:2.0:protocol |
saml | urn:oasis:names:tc:SAML:2.0:assertion |
Therefore any elements within these namespaces can be access via the prefix. E.g //samlp:StatusCode/@Value.
If you require access to anything not defined in these namespaces you need to register them in the xpath expression. E.g //*[local-name()='ElemName' and namespace-uri()='http://ext.namespace.com']/@AttrName
Parameters:
- exp the xpath expression which will be evaluated over the SAML response (xml)
Return value:
- An
Element[]
. The array will be empty if nothing was found for the given xpath expression.
- An
Logger
The logger is provided to enable script debugging and is limited to the last 100 messages.
Public Methods
logger.log(obj1 [, obj2, ..., objN]);
Parameters:
- A list of java script object you wish to inspect.
logger.log(msg [, subst1, ..., substN]);
Parameters:
- JavaScript objects with which to replace substitution strings within
msg
.
E.glogger.log("SAML Issuer: %s", issuerString);
- JavaScript objects with which to replace substitution strings within
Output
A standard javascript Object
which you can assign any arbitrary property and associated value (String
or String[]
).
E.g If you wish to assert a OIDC claim indicating membershipType
you would do the following
//Recommended method.
output.membershipType = 'gold';
//Or not recommended method
output['membershipType'] = 'silver';