====== Keycloak Script Providers ======
===== Documentation =====
See:
[[https://www.keycloak.org/docs/latest/server_development/#_script_providers|Server Developer Guide - Service Provider Interfaces (SPI) - JavaScript providers]]\\
[[https://github.com/frank-fegert/keycloak-script-provider|GitHub Keycloak Script Providers Repository]]
on how to prepare the //Keycloak// script providers for deployment into a //Keycloak// instance.
===== Protocol Mapper =====
With the so-called //protocol mapper//, //Keycloak// offers a method for the mapping of arbitrary attributes onto a specific authentication protocol (e.g. //OpenID Connect//) and its protocol specific attributes (e.g. //Claims// in //OpenID Connect//).
===== Script Mapper =====
If, for a specific use-case, there is no built-in //protocol mapper// available in //Keycloak//, it is possible to implement a //protocol mapper// in //JavaScript//. Such a //script mapper// will be running within the //Keycloak// application and will be executed via the //Java Nashorn// scripting interface.
The possibilities for what can be achieved with a //script mapper// are numerous. The following will only show the general approach with a basic example. It implements a recursive mapper for a role attribute (in this case the attribute ''policy'') or rather its values onto the //OpenID Connect// attribute ''policy''.
Unfortunately both the role attribute ''policy'' as well as the //OpenID Connect// attribute ''policy'' are currently hard coded in the //script mapper//, since i couldn't figure out a way to dynamically pass a value from the //Keycloak// configuration of the script mapper. Such a functionality seems to be only available for //protocol mappers// implemented in //Java//.
==== Development and Test ====
On a //Keycloak// **test** or **development** system edit the file:
vi /standalone/configuration/profile.properties
and set the following parameters:
feature.scripts=enabled
feature.upload_scripts=enabled
restart the //Keycloak// application.
After this the different //mapper// dialogs within the WebUI of the //Keycloak// application show an additional entry ''Script Mapper'' in the drop-down menu ''Mapper Type''. If the ''Script Mapper'' entry is selected, a editor dialog will be presented which can be used for the development and testing of the //script mapper//.
**Attention:** After finishing the development and testing of the //script mapper// the parameter ''feature.upload_scripts'' shown above should be disabled again, since it poses a security risk!
==== Deployment ====
For the actual deployment of the previously developed //script mapper// onto a **production** //Keycloak// system the following preparation steps are necessary:
Directory creation:
mkdir -p /tmp/script_provider/role_attribute_mapper_policy/META-INF/
Copy the source code of the newly developed //script mapper// and save it into a file:
vi /tmp/script_provider/role_attribute_mapper_policy/role_attribute_mapper_policy.js
File contents:
/**
* Available variables:
* user - the current user
* realm - the current realm
* token - the current token
* userSession - the current userSession
* keycloakSession - the current keycloakSession
*/
/**
* Change value to "true" in order to enable debugging
* output to /var/log/keycloak/keycloak.log.
*/
var debug = false
var ArrayList = Java.type("java.util.ArrayList");
var policies = new ArrayList();
/**
* The actual debug output function
*/
function debugOutput(msg) {
if (debug) print("Debug script mapper: " + msg);
}
/**
* Helper function to determine **all** roles assigned
* to a given user, even the indirectly assigned ones
* (e.g. through group memberships). The built-in method
* "getRealmRoleMappings" does unfortunately not work
* here, since it only returns the **directly** assigned
* roles (see: https://www.keycloak.org/docs-api//javadocs/org/keycloak/models/RoleMapperModel.html#getRealmRoleMappings--
* for details).
*/
function getAllUserRoles() {
var userRoles = [];
realm.getRoles().forEach(function(role) {
if(user.hasRole(role)) {
debugOutput('found role "' + role.getName() + '" assigned to user.');
userRoles.push(role);
} else {
debugOutput('role "' + role.getName() + '" is not assigned to user.');
}
});
return userRoles;
}
/**
* Check all roles assigned to a given user for a "policy"
* role attribute. If the role attribute is present, split
* the value into individual elements and insert each element
* into the array to be returned.
*/
var roles = getAllUserRoles();
roles.forEach(function(role) {
var policy = role.getAttribute("policy");
if (policy.length) {
debugOutput('attribute "policy" found in role ' + role.getName());
policy.forEach(function(value) {
var arrayOfValues = value.replace(/, /g, ',').split(',');
arrayOfValues.forEach(function(value) {
debugOutput('adding "policy" attribute value ' + value + ' to array');
if (policies.indexOf(value) < 0) policies.add(value);
});
});
}
});
/**
* Return the array populated above to Keycloak
*/
debugOutput('final "policy" array ' + policies);
exports = policies;
Create a deployment descriptor for the //script mapper//:
vi /tmp/script_provider/role_attribute_mapper_policy/META-INF/keycloak-scripts.json
File contents:
{
"mappers": [
{
"name": "Role Attribute Mapper - policy",
"fileName": "role_attribute_mapper_policy.js",
"description": "Maps the 'policy' role attribute to the OIDC token of a user"
}
]
}
Pack the //script mapper// and the deployment descriptor into a deployable JAR file:
cd /tmp/script_provider/role_attribute_mapper_policy/
zip -r role_attribute_mapper_policy.jar META-INF/ role_attribute_mapper_policy.js
On the //Keycloak// system edit the file:
vi /standalone/configuration/profile.properties
and set the following parameters:
feature.scripts=enabled
restart the //Keycloak// application.
Put the JAR file created above into the directory ''/standalone/deployments/''. The deployment of the //script mapper// into the //Keycloak// application should happen automatically. This can be verified in the //Keycloak// log file by checking for log lines like:
INFO [org.jboss.as.repository] (DeploymentScanner-threads - 2) WFLYDR0001: Content added at location /standalone/data/content/e1/714bbd9b178cd2004d0a2f999584030f06a54c/content
INFO [org.jboss.as.server.deployment] (MSC service thread 1-1) WFLYSRV0027: Starting deployment of "role_attribute_mapper_policy.jar" (runtime-name: "role_attribute_mapper_policy.jar")
INFO [org.keycloak.subsystem.server.extension.KeycloakProviderDeploymentProcessor] (MSC service thread 1-2) Deploying Keycloak provider: role_attribute_mapper_policy.jar
INFO [org.jboss.as.server] (DeploymentScanner-threads - 2) WFLYSRV0010: Deployed "role_attribute_mapper_policy.jar" (runtime-name : "role_attribute_mapper_policy.jar")
Within the //Keycloak// application the availability of the new //script mapper// can be verified with:
* ''Admin'' -> ''Server Info'' -> ''Providers'' -> ''protocol-mapper'' -> ''script-role_attribute_mapper_policy.js'' <
* or in one of the different //mapper// dialogs within whe WebUI of the //Keycloak// application in the drop-down menu ''Mapper Type'' as a new entry ''Role Attribute Mapper - policy'' <