====== 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'' <