From 57c633967aba42d76c351e104f130dc987aaa389 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Fri, 22 Sep 2017 22:49:28 +0200 Subject: [PATCH] KEYCLOAK-3599 Revise Script based OIDC ProtocolMapper We now use the `ScriptingProvider` API instead of using the `ScriptEngineManager` because dynamic `ScriptEngineManager` lookups might fail in some environments like JBoss EAP. Refactored `AbstractOIDCProtocolMapper` to provide a new version of the `setClaim(..)` method which takes a `KeycloakSession` as additional argument. The old `setClaim(..)` method is marked as deprecated and should be scheduled for removal in a later release. To ensure backwards compatibility we call the old `setClaim(..)` from the new `setClaim(..,keycloakSession)` method in order to not break user implementations of OIDC ProtocolMappers. The existing OIDC ProtocolMappers which override the old `setClaim(..)` method should be updated to use the new version `setClaim(..,keycloakSession)`. This was necessary to be able to lookup a `ScriptingProvider`. --- .../mappers/AbstractOIDCProtocolMapper.java | 21 ++++++++-- .../ScriptBasedOIDCProtocolMapper.java | 40 ++++++++++++------- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java index d267f9128d..a7eeb5d82b 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java @@ -67,7 +67,7 @@ public abstract class AbstractOIDCProtocolMapper implements ProtocolMapper { return token; } - setClaim(token, mappingModel, userSession); + setClaim(token, mappingModel, userSession, session); return token; } @@ -78,7 +78,7 @@ public abstract class AbstractOIDCProtocolMapper implements ProtocolMapper { return token; } - setClaim(token, mappingModel, userSession); + setClaim(token, mappingModel, userSession, session); return token; } @@ -89,7 +89,7 @@ public abstract class AbstractOIDCProtocolMapper implements ProtocolMapper { return token; } - setClaim(token, mappingModel, userSession); + setClaim(token, mappingModel, userSession, session); return token; } @@ -98,7 +98,22 @@ public abstract class AbstractOIDCProtocolMapper implements ProtocolMapper { * @param token * @param mappingModel * @param userSession + * + * @deprecated override {@link #setClaim(IDToken, ProtocolMapperModel, UserSessionModel, KeycloakSession)} instead. */ + @Deprecated protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) { } + + /** + * Intended to be overridden in {@link ProtocolMapper} implementations to add claims to an token. + * @param token + * @param mappingModel + * @param userSession + * @param keycloakSession + */ + protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession) { + // we delegate to the old #setClaim(...) method for backwards compatibility + setClaim(token, mappingModel, userSession); + } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/ScriptBasedOIDCProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/ScriptBasedOIDCProtocolMapper.java index 8b5025f1a8..2dd6be087b 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/ScriptBasedOIDCProtocolMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/ScriptBasedOIDCProtocolMapper.java @@ -18,17 +18,19 @@ package org.keycloak.protocol.oidc.mappers; import org.jboss.logging.Logger; +import org.keycloak.common.Profile; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; +import org.keycloak.models.ScriptModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigurationBuilder; import org.keycloak.representations.IDToken; +import org.keycloak.scripting.EvaluatableScriptAdapter; +import org.keycloak.scripting.ScriptingProvider; -import javax.script.Bindings; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; import java.util.List; /** @@ -59,7 +61,8 @@ public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper im " 'user' - the current user.\n" + // " 'realm' - the current realm.\n" + // " 'token' - the current token.\n" + // - " 'userSession' - the current userSession.\n" // + " 'userSession' - the current userSession.\n" + // + " 'keycloakSession' - the current keycloakSession.\n" // ) .defaultValue("/**\n" + // " * Available variables: \n" + // @@ -67,6 +70,7 @@ public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper im " * realm - the current realm\n" + // " * token - the current token\n" + // " * userSession - the current userSession\n" + // + " * keycloakSession - the current userSession\n" + // " */\n\n\n//insert your code here..." // ) .add() @@ -96,27 +100,33 @@ public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper im @Override public String getHelpText() { - return "Evaluates a javascript function to produce a token claim based on context information."; + return "Evaluates a JavaScript function to produce a token claim based on context information."; } - protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) { + public boolean isSupported() { + return Profile.isFeatureEnabled(Profile.Feature.SCRIPTS); + } + + protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession) { UserModel user = userSession.getUser(); - String script = mappingModel.getConfig().get(SCRIPT); + String scriptSource = mappingModel.getConfig().get(SCRIPT); RealmModel realm = userSession.getRealm(); - ScriptEngineManager engineManager = new ScriptEngineManager(); - ScriptEngine scriptEngine = engineManager.getEngineByName("javascript"); + ScriptingProvider scripting = keycloakSession.getProvider(ScriptingProvider.class); + ScriptModel scriptModel = scripting.createScript(realm.getId(), ScriptModel.TEXT_JAVASCRIPT, "token-mapper-script_" + mappingModel.getName(), scriptSource, null); - Bindings bindings = scriptEngine.createBindings(); - bindings.put("user", user); - bindings.put("realm", realm); - bindings.put("token", token); - bindings.put("userSession", userSession); + EvaluatableScriptAdapter script = scripting.prepareEvaluatableScript(scriptModel); Object claimValue; try { - claimValue = scriptEngine.eval(script, bindings); + claimValue = script.eval((bindings) -> { + bindings.put("user", user); + bindings.put("realm", realm); + bindings.put("token", token); + bindings.put("userSession", userSession); + bindings.put("keycloakSession", keycloakSession); + }); } catch (Exception ex) { LOGGER.error("Error during execution of ProtocolMapper script", ex); claimValue = null;