From bc31fde4c0de1fbc8c0ceadd104424bd77563f30 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Wed, 6 Sep 2023 20:28:01 -0300 Subject: [PATCH] Broker claim mapper not recognizing claims from user info endpoint Closes #12137 --- .../oidc/mappers/AbstractClaimMapper.java | 12 +-- .../BrokerLinkAndTokenExchangeTest.java | 74 +++++++++++++++++++ 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java b/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java index 02e41c7348..057e4e42ed 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java +++ b/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java @@ -83,7 +83,7 @@ public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper } { // search ID Token Object rawIdToken = context.getContextData().get(OIDCIdentityProvider.VALIDATED_ID_TOKEN); - JsonWebToken idToken; + JsonWebToken idToken = null; if (rawIdToken instanceof String) { try { @@ -93,13 +93,13 @@ public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper } } else if (rawIdToken instanceof JsonWebToken) { idToken = (JsonWebToken) rawIdToken; - } else { - return null; } - Object value = getClaimValue(idToken, claim); - if (value != null) - return value; + if (idToken != null) { + Object value = getClaimValue(idToken, claim); + if (value != null) + return value; + } } { // Search the OIDC UserInfo claim set (if any) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/BrokerLinkAndTokenExchangeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/BrokerLinkAndTokenExchangeTest.java index 0333ad859e..7215d1e814 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/BrokerLinkAndTokenExchangeTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/BrokerLinkAndTokenExchangeTest.java @@ -16,6 +16,7 @@ */ package org.keycloak.testsuite.adapter.servlet; +import com.google.common.collect.ImmutableMap; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.OperateOnDeployment; import org.jboss.arquillian.graphene.page.Page; @@ -25,27 +26,35 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; +import org.keycloak.broker.oidc.mappers.UserAttributeMapper; import org.keycloak.common.Profile; import org.keycloak.exportimport.ExportImportConfig; import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory; import org.keycloak.jose.jws.JWSInput; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; +import org.keycloak.models.IdentityProviderMapperModel; +import org.keycloak.models.IdentityProviderMapperSyncMode; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; +import org.keycloak.protocol.oidc.mappers.HardcodedClaim; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; +import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; @@ -762,6 +771,71 @@ public class BrokerLinkAndTokenExchangeTest extends AbstractServletsAdapterTest } } + @Test + public void testExternalExchangeCreateNewUserUsingMappers() throws Exception { + RealmResource parentRealm = adminClient.realms().realm(PARENT_IDP); + ProtocolMapperRepresentation claimMapper = new ProtocolMapperRepresentation(); + claimMapper.setName("custom-claim-hardcoded-mapper"); + claimMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + claimMapper.setProtocolMapper(HardcodedClaim.PROVIDER_ID); + Map config = new HashMap<>(); + config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "claim-from-idp"); + config.put(HardcodedClaim.CLAIM_VALUE, "true"); + config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + config.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + claimMapper.setConfig(config); + ClientRepresentation client = parentRealm.clients().findByClientId(PARENT_CLIENT).get(0); + ClientResource clientResource = parentRealm.clients().get(client.getId()); + clientResource.getProtocolMappers().createMapper(claimMapper).close(); + + RealmResource childRealm = adminClient.realms().realm(CHILD_IDP); + IdentityProviderMapperRepresentation attributeMapper = new IdentityProviderMapperRepresentation(); + attributeMapper.setName("attribute-mapper"); + attributeMapper.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID); + attributeMapper.setIdentityProviderAlias(PARENT_IDP); + attributeMapper.setConfig(ImmutableMap.builder() + .put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.INHERIT.toString()) + .put(UserAttributeMapper.CLAIM, "claim-from-idp") + .put(UserAttributeMapper.USER_ATTRIBUTE, "claim-to-broker") + .build()); + childRealm.identityProviders().get(PARENT_IDP).addMapper(attributeMapper).close(); + + String idToken = oauth.doGrantAccessTokenRequest(PARENT_IDP, PARENT3_USERNAME, "password", null, PARENT_CLIENT, "password").getAccessToken(); + Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size()); + + try (Client httpClient = AdminClientUtil.createResteasyClient()) { + WebTarget exchangeUrl = childTokenExchangeWebTarget(httpClient); + IdentityProviderRepresentation rep = adminClient.realm(CHILD_IDP).identityProviders().get(PARENT_IDP).toRepresentation(); + rep.getConfig().put(OIDCIdentityProviderConfig.VALIDATE_SIGNATURE, String.valueOf(false)); + adminClient.realm(CHILD_IDP).identityProviders().get(PARENT_IDP).update(rep); + + AccessToken token; + try (Response response = exchangeUrl.request() + .header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password")) + .post(Entity.form( + new Form() + .param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE) + .param(OAuth2Constants.SUBJECT_TOKEN, idToken) + .param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE) + .param(OAuth2Constants.SUBJECT_ISSUER, PARENT_IDP) + .param(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID) + ))) { + Assert.assertEquals(200, response.getStatus()); + + AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class); + JWSInput jws = new JWSInput(tokenResponse.getToken()); + token = jws.readJsonContent(AccessToken.class); + } + + UserRepresentation newUser = childRealm.users().search(PARENT3_USERNAME).get(0); + Assert.assertNotNull(newUser.getAttributes()); + Assert.assertTrue(newUser.getAttributes().containsKey("claim-to-broker")); + + // cleanup remove the user + childRealm.users().get(token.getSubject()).remove(); + } + } + public void logoutAll() { adminClient.realm(CHILD_IDP).logoutAll(); adminClient.realm(PARENT_IDP).logoutAll();