diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java index 37154e5ff3..865c13cf56 100644 --- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java +++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java @@ -295,7 +295,7 @@ public class AuthorizationTokenService { AuthenticationManager.setClientScopesInSession(authSession); clientSessionCtx = TokenManager.attachAuthenticationSession(keycloakSession, userSessionModel, authSession); } else { - clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession); + clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession, keycloakSession); } TokenManager tokenManager = request.getTokenManager(); diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java index bbefd81bf7..0ac4cb901d 100644 --- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java +++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java @@ -124,7 +124,7 @@ public class KeycloakIdentity implements Identity { ClientModel client = realm.getClientByClientId(token.getIssuedFor()); AuthenticatedClientSessionModel clientSessionModel = userSession.getAuthenticatedClientSessions().get(client.getId()); - ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSessionModel); + ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSessionModel, keycloakSession); this.accessToken = new TokenManager().createClientAccessToken(keycloakSession, realm, client, userSession.getUser(), userSession, clientSessionCtx); } diff --git a/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java b/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java index fd1133ebfe..3b1b3f6f55 100755 --- a/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java +++ b/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java @@ -148,4 +148,7 @@ public class ProtocolMapperUtils { return priority; } + public static boolean isEnabled(KeycloakSession session, ProtocolMapperModel mapper) { + return session.getKeycloakSessionFactory().getProviderFactory(ProtocolMapper.class, mapper.getProtocolMapper()) != null; + } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index 945e875977..82202e34fb 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -181,7 +181,7 @@ public class TokenManager { MigrationUtils.migrateOldOfflineToken(session, realm, client, user); } - ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, oldTokenScope); + ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, oldTokenScope, session); // Check user didn't revoke granted consent if (!verifyConsentStillAvailable(session, user, client, clientSessionCtx.getClientScopes())) { @@ -441,7 +441,7 @@ public class TokenManager { // Remove authentication session now new AuthenticationSessionManager(session).removeAuthenticationSession(userSession.getRealm(), authSession, true); - ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndClientScopeIds(clientSession, clientScopeIds); + ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndClientScopeIds(clientSession, clientScopeIds, session); return clientSessionCtx; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java index f928215c88..12479558ef 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java @@ -388,7 +388,7 @@ public class TokenEndpoint { throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE, "Client no longer has requested consent from user", Response.Status.BAD_REQUEST); } - ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndClientScopes(clientSession, clientScopes); + ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndClientScopes(clientSession, clientScopes, session); // Set nonce as an attribute in the ClientSessionContext. Will be used for the token generation clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, codeData.getNonce()); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java index 14d41201b8..35939642ff 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java @@ -194,7 +194,7 @@ public class UserInfoEndpoint { AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(clientModel.getId()); // Retrieve by latest scope parameter - ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession); + ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession, session); AccessToken userInfo = new AccessToken(); tokenManager.transformUserInfoAccessToken(session, userInfo, userSession, clientSessionCtx); diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index d967efd510..0815dd0153 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -273,7 +273,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal RoleModel manageAccountRole = accountService.getRole(AccountRoles.MANAGE_ACCOUNT); // Ensure user has role and client has "role scope" for this role - ClientSessionContext ctx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession); + ClientSessionContext ctx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession, session); Set userAccountRoles = ctx.getRoles(); if (!userAccountRoles.contains(manageAccountRole)) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateResource.java index 770ac6aecc..c9ef21fa93 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateResource.java @@ -17,6 +17,8 @@ package org.keycloak.services.resources.admin; +import static org.keycloak.protocol.ProtocolMapperUtils.isEnabled; + import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -124,7 +126,7 @@ public class ClientScopeEvaluateResource { for (ClientScopeModel mapperContainer : clientScopes) { Set currentMappers = mapperContainer.getProtocolMappers(); for (ProtocolMapperModel current : currentMappers) { - if (current.getProtocol().equals(client.getProtocol())) { + if (isEnabled(session, current) && current.getProtocol().equals(client.getProtocol())) { ProtocolMapperEvaluationRepresentation rep = new ProtocolMapperEvaluationRepresentation(); rep.setMapperId(current.getId()); rep.setMapperName(current.getName()); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java index 10650e9948..e264edbffd 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java @@ -16,6 +16,8 @@ */ package org.keycloak.services.resources.admin; +import static org.keycloak.protocol.ProtocolMapperUtils.isEnabled; + import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; import javax.ws.rs.NotFoundException; @@ -102,7 +104,7 @@ public class ProtocolMappersResource { List mappers = new LinkedList(); for (ProtocolMapperModel mapper : client.getProtocolMappers()) { - if (mapper.getProtocol().equals(protocol)) mappers.add(ModelToRepresentation.toRepresentation(mapper)); + if (isEnabled(session, mapper) && mapper.getProtocol().equals(protocol)) mappers.add(ModelToRepresentation.toRepresentation(mapper)); } return mappers; } @@ -166,7 +168,9 @@ public class ProtocolMappersResource { List mappers = new LinkedList(); for (ProtocolMapperModel mapper : client.getProtocolMappers()) { - mappers.add(ModelToRepresentation.toRepresentation(mapper)); + if (isEnabled(session, mapper)) { + mappers.add(ModelToRepresentation.toRepresentation(mapper)); + } } return mappers; } diff --git a/services/src/main/java/org/keycloak/services/util/DefaultClientSessionContext.java b/services/src/main/java/org/keycloak/services/util/DefaultClientSessionContext.java index 758b415692..339cb08be8 100644 --- a/services/src/main/java/org/keycloak/services/util/DefaultClientSessionContext.java +++ b/services/src/main/java/org/keycloak/services/util/DefaultClientSessionContext.java @@ -28,11 +28,13 @@ import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientScopeModel; import org.keycloak.models.ClientSessionContext; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.RoleUtils; +import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.util.TokenUtil; @@ -48,6 +50,7 @@ public class DefaultClientSessionContext implements ClientSessionContext { private final AuthenticatedClientSessionModel clientSession; private final Set clientScopeIds; + private final KeycloakSession session; private Set clientScopes; @@ -60,38 +63,39 @@ public class DefaultClientSessionContext implements ClientSessionContext { private Map attributes = new HashMap<>(); - private DefaultClientSessionContext(AuthenticatedClientSessionModel clientSession, Set clientScopeIds) { + private DefaultClientSessionContext(AuthenticatedClientSessionModel clientSession, Set clientScopeIds, KeycloakSession session) { this.clientSession = clientSession; this.clientScopeIds = clientScopeIds; + this.session = session; } /** * Useful if we want to "re-compute" client scopes based on the scope parameter */ - public static DefaultClientSessionContext fromClientSessionScopeParameter(AuthenticatedClientSessionModel clientSession) { - return fromClientSessionAndScopeParameter(clientSession, clientSession.getNote(OAuth2Constants.SCOPE)); + public static DefaultClientSessionContext fromClientSessionScopeParameter(AuthenticatedClientSessionModel clientSession, KeycloakSession session) { + return fromClientSessionAndScopeParameter(clientSession, clientSession.getNote(OAuth2Constants.SCOPE), session); } - public static DefaultClientSessionContext fromClientSessionAndScopeParameter(AuthenticatedClientSessionModel clientSession, String scopeParam) { + public static DefaultClientSessionContext fromClientSessionAndScopeParameter(AuthenticatedClientSessionModel clientSession, String scopeParam, KeycloakSession session) { Set requestedClientScopes = TokenManager.getRequestedClientScopes(scopeParam, clientSession.getClient()); - return fromClientSessionAndClientScopes(clientSession, requestedClientScopes); + return fromClientSessionAndClientScopes(clientSession, requestedClientScopes, session); } - public static DefaultClientSessionContext fromClientSessionAndClientScopeIds(AuthenticatedClientSessionModel clientSession, Set clientScopeIds) { - return new DefaultClientSessionContext(clientSession, clientScopeIds); + public static DefaultClientSessionContext fromClientSessionAndClientScopeIds(AuthenticatedClientSessionModel clientSession, Set clientScopeIds, KeycloakSession session) { + return new DefaultClientSessionContext(clientSession, clientScopeIds, session); } - public static DefaultClientSessionContext fromClientSessionAndClientScopes(AuthenticatedClientSessionModel clientSession, Set clientScopes) { + public static DefaultClientSessionContext fromClientSessionAndClientScopes(AuthenticatedClientSessionModel clientSession, Set clientScopes, KeycloakSession session) { Set clientScopeIds = new HashSet<>(); for (ClientScopeModel clientScope : clientScopes) { clientScopeIds.add(clientScope.getId()); } - return new DefaultClientSessionContext(clientSession, clientScopeIds); + return new DefaultClientSessionContext(clientSession, clientScopeIds, session); } @@ -261,7 +265,7 @@ public class DefaultClientSessionContext implements ClientSessionContext { for (ClientScopeModel clientScope : clientScopes) { Set currentMappers = clientScope.getProtocolMappers(); for (ProtocolMapperModel currentMapper : currentMappers) { - if (protocol.equals(currentMapper.getProtocol())) { + if (protocol.equals(currentMapper.getProtocol()) && ProtocolMapperUtils.isEnabled(session, currentMapper)) { protocolMappers.add(currentMapper); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptMapperTest.java index add9ca4f56..beac354b89 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptMapperTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptMapperTest.java @@ -19,7 +19,6 @@ package org.keycloak.testsuite.script; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.keycloak.common.Profile.Feature.SCRIPTS; -import static org.keycloak.common.Profile.Feature.UPLOAD_SCRIPTS; import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId; import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT; import static org.keycloak.testsuite.util.ProtocolMapperUtil.createScriptMapper; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/UndeployedScriptMapperNotAvailableTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/UndeployedScriptMapperNotAvailableTest.java new file mode 100644 index 0000000000..56fa1800ee --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/UndeployedScriptMapperNotAvailableTest.java @@ -0,0 +1,114 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.script; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.keycloak.common.Profile.Feature.SCRIPTS; +import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId; +import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT; +import static org.keycloak.testsuite.util.ProtocolMapperUtil.createScriptMapper; + +import java.io.IOException; + +import org.jboss.arquillian.container.test.api.Deployer; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.TargetsContainer; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.mappers.ScriptBasedOIDCProtocolMapper; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.provider.ScriptProviderDescriptor; +import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; +import org.keycloak.testsuite.arquillian.annotation.EnableFeature; +import org.keycloak.testsuite.util.ContainerAssume; +import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.util.JsonSerialization; + +/** + * @author Pedro Igor + */ +public class UndeployedScriptMapperNotAvailableTest extends AbstractTestRealmKeycloakTest { + + private static final String SCRIPT_DEPLOYMENT_NAME = "scripts.jar"; + + @Deployment(name = SCRIPT_DEPLOYMENT_NAME, managed = false, testable = false) + @TargetsContainer(AUTH_SERVER_CURRENT) + public static JavaArchive deploy() throws IOException { + ScriptProviderDescriptor representation = new ScriptProviderDescriptor(); + + representation.addMapper("My Mapper", "mapper-a.js"); + + return ShrinkWrap.create(JavaArchive.class, SCRIPT_DEPLOYMENT_NAME) + .addAsManifestResource(new StringAsset(JsonSerialization.writeValueAsPrettyString(representation)), + "keycloak-scripts.json") + .addAsResource("scripts/mapper-example.js", "mapper-a.js"); + } + + @BeforeClass + public static void verifyEnvironment() { + ContainerAssume.assumeNotAuthServerUndertow(); + } + + @ArquillianResource + private Deployer deployer; + + @Before + public void configureFlows() { + deployer.deploy(SCRIPT_DEPLOYMENT_NAME); + } + + @After + public void onAfter() { + deployer.undeploy(SCRIPT_DEPLOYMENT_NAME); + } + + @Override + public void configureTestRealm(RealmRepresentation testRealm) { + + } + + @Test + @EnableFeature(SCRIPTS) + public void testMapperNotRecognizedWhenDisabled() throws Exception { + ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app"); + + { + ProtocolMapperRepresentation mapper = createScriptMapper("test-script-mapper1", "computed-via-script", + "computed-via-script", "String", true, true, "'hello_' + user.username", false); + + mapper.setProtocolMapper("script-mapper-a.js"); + + app.getProtocolMappers().createMapper(mapper).close(); + } + + deployer.undeploy(SCRIPT_DEPLOYMENT_NAME); + assertTrue(app.getProtocolMappers().getMappers().isEmpty()); + assertTrue(app.getProtocolMappers().getMappersPerProtocol(app.toRepresentation().getProtocol()).isEmpty()); + } +}