Broker claim mapper not recognizing claims from user info endpoint

Closes #12137
This commit is contained in:
Pedro Igor 2023-09-06 20:28:01 -03:00 committed by Marek Posolda
parent 2eb37dbe4f
commit bc31fde4c0
2 changed files with 80 additions and 6 deletions

View file

@ -83,7 +83,7 @@ public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper
} }
{ // search ID Token { // search ID Token
Object rawIdToken = context.getContextData().get(OIDCIdentityProvider.VALIDATED_ID_TOKEN); Object rawIdToken = context.getContextData().get(OIDCIdentityProvider.VALIDATED_ID_TOKEN);
JsonWebToken idToken; JsonWebToken idToken = null;
if (rawIdToken instanceof String) { if (rawIdToken instanceof String) {
try { try {
@ -93,13 +93,13 @@ public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper
} }
} else if (rawIdToken instanceof JsonWebToken) { } else if (rawIdToken instanceof JsonWebToken) {
idToken = (JsonWebToken) rawIdToken; idToken = (JsonWebToken) rawIdToken;
} else {
return null;
} }
Object value = getClaimValue(idToken, claim); if (idToken != null) {
if (value != null) Object value = getClaimValue(idToken, claim);
return value; if (value != null)
return value;
}
} }
{ {
// Search the OIDC UserInfo claim set (if any) // Search the OIDC UserInfo claim set (if any)

View file

@ -16,6 +16,7 @@
*/ */
package org.keycloak.testsuite.adapter.servlet; 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.Deployment;
import org.jboss.arquillian.container.test.api.OperateOnDeployment; import org.jboss.arquillian.container.test.api.OperateOnDeployment;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
@ -25,27 +26,35 @@ import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.broker.oidc.mappers.UserAttributeMapper;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.exportimport.ExportImportConfig; import org.keycloak.exportimport.ExportImportConfig;
import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory; import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService; 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.AccessToken;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation; 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<String, String> 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.<String,String>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() { public void logoutAll() {
adminClient.realm(CHILD_IDP).logoutAll(); adminClient.realm(CHILD_IDP).logoutAll();
adminClient.realm(PARENT_IDP).logoutAll(); adminClient.realm(PARENT_IDP).logoutAll();