[KEYCLOAK-12511] - Mapper not visible in client's mapper list

This commit is contained in:
Pedro Igor 2020-01-07 09:52:55 -03:00 committed by Stian Thorgersen
parent fea7b4e031
commit 03bbf77b35
12 changed files with 147 additions and 21 deletions

View file

@ -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();

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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());

View file

@ -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);

View file

@ -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<RoleModel> userAccountRoles = ctx.getRoles();
if (!userAccountRoles.contains(manageAccountRole)) {

View file

@ -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<ProtocolMapperModel> 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());

View file

@ -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<ProtocolMapperRepresentation> mappers = new LinkedList<ProtocolMapperRepresentation>();
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<ProtocolMapperRepresentation> mappers = new LinkedList<ProtocolMapperRepresentation>();
for (ProtocolMapperModel mapper : client.getProtocolMappers()) {
mappers.add(ModelToRepresentation.toRepresentation(mapper));
if (isEnabled(session, mapper)) {
mappers.add(ModelToRepresentation.toRepresentation(mapper));
}
}
return mappers;
}

View file

@ -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<String> clientScopeIds;
private final KeycloakSession session;
private Set<ClientScopeModel> clientScopes;
@ -60,38 +63,39 @@ public class DefaultClientSessionContext implements ClientSessionContext {
private Map<String, Object> attributes = new HashMap<>();
private DefaultClientSessionContext(AuthenticatedClientSessionModel clientSession, Set<String> clientScopeIds) {
private DefaultClientSessionContext(AuthenticatedClientSessionModel clientSession, Set<String> 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<ClientScopeModel> requestedClientScopes = TokenManager.getRequestedClientScopes(scopeParam, clientSession.getClient());
return fromClientSessionAndClientScopes(clientSession, requestedClientScopes);
return fromClientSessionAndClientScopes(clientSession, requestedClientScopes, session);
}
public static DefaultClientSessionContext fromClientSessionAndClientScopeIds(AuthenticatedClientSessionModel clientSession, Set<String> clientScopeIds) {
return new DefaultClientSessionContext(clientSession, clientScopeIds);
public static DefaultClientSessionContext fromClientSessionAndClientScopeIds(AuthenticatedClientSessionModel clientSession, Set<String> clientScopeIds, KeycloakSession session) {
return new DefaultClientSessionContext(clientSession, clientScopeIds, session);
}
public static DefaultClientSessionContext fromClientSessionAndClientScopes(AuthenticatedClientSessionModel clientSession, Set<ClientScopeModel> clientScopes) {
public static DefaultClientSessionContext fromClientSessionAndClientScopes(AuthenticatedClientSessionModel clientSession, Set<ClientScopeModel> clientScopes, KeycloakSession session) {
Set<String> 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<ProtocolMapperModel> currentMappers = clientScope.getProtocolMappers();
for (ProtocolMapperModel currentMapper : currentMappers) {
if (protocol.equals(currentMapper.getProtocol())) {
if (protocol.equals(currentMapper.getProtocol()) && ProtocolMapperUtils.isEnabled(session, currentMapper)) {
protocolMappers.add(currentMapper);
}
}

View file

@ -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;

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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());
}
}