From a74e60f4d7cf62c61848b2b1c642b01382bcea8b Mon Sep 17 00:00:00 2001 From: rmartinc Date: Tue, 8 Oct 2024 18:04:05 +0200 Subject: [PATCH] Check email with ignorecase when setting basic attributes in IdP Closes #31848 Signed-off-by: rmartinc --- .../resources/IdentityBrokerService.java | 10 +- .../testsuite/broker/KcOidcBrokerTest.java | 99 +++++++++++++++++++ 2 files changed, 104 insertions(+), 5 deletions(-) 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 96eaf14644..462bb4ef47 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -1060,14 +1060,14 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal } private void setBasicUserAttributes(BrokeredIdentityContext context, UserModel federatedUser) { - setDiffAttrToConsumer(federatedUser.getEmail(), context.getEmail(), email -> setEmail(context, federatedUser, email)); - setDiffAttrToConsumer(federatedUser.getFirstName(), context.getFirstName(), federatedUser::setFirstName); - setDiffAttrToConsumer(federatedUser.getLastName(), context.getLastName(), federatedUser::setLastName); + setDiffAttrToConsumer(federatedUser.getEmail(), context.getEmail(), email -> setEmail(context, federatedUser, email), true); + setDiffAttrToConsumer(federatedUser.getFirstName(), context.getFirstName(), federatedUser::setFirstName, false); + setDiffAttrToConsumer(federatedUser.getLastName(), context.getLastName(), federatedUser::setLastName, false); } - private void setDiffAttrToConsumer(String actualValue, String newValue, Consumer consumer) { + private void setDiffAttrToConsumer(String actualValue, String newValue, Consumer consumer, boolean ignoreCase) { String actualValueNotNull = Optional.ofNullable(actualValue).orElse(""); - if (newValue != null && !newValue.equals(actualValueNotNull)) { + if (newValue != null && !(ignoreCase? newValue.equalsIgnoreCase(actualValueNotNull) : newValue.equals(actualValueNotNull))) { consumer.accept(newValue); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerTest.java index 5da29ece08..8fbad42ca3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerTest.java @@ -10,6 +10,7 @@ import org.junit.Before; import org.junit.Test; import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.ClientsResource; +import org.keycloak.admin.client.resource.ClientScopeResource; import org.keycloak.admin.client.resource.IdentityProviderResource; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.UserResource; @@ -27,6 +28,7 @@ import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.mappers.HardcodedClaim; import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.idm.ClientRepresentation; @@ -39,6 +41,7 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.broker.util.SimpleHttpDefault; import org.keycloak.testsuite.updaters.RealmAttributeUpdater; import org.keycloak.testsuite.util.AccountHelper; @@ -707,6 +710,102 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { } } + @Test + public void checkUpdatedEmailAttributeIdPSameValueDifferentCase() throws Exception { + final String IDP_NAME = getBrokerConfiguration().getIDPAlias(); + final String USERNAME = "demo-user"; + final String PASSWORD = "demo-pwd"; + + final String FIRST_NAME = "John"; + final String LAST_NAME = "Doe"; + final String EMAIL = "mail@example.com"; + + RealmResource providerRealmResource = realmsResouce().realm(bc.providerRealmName()); + allowUserEdit(providerRealmResource); + + UsersResource providerUsersResource = providerRealmResource.users(); + + String providerUserID = createUser(bc.providerRealmName(), USERNAME, PASSWORD, FIRST_NAME, LAST_NAME, EMAIL, + user -> user.setEmailVerified(true)); + + try { + IdentityProviderResource consumerIdentityResource = getIdentityProviderResource(); + IdentityProviderRepresentation idProvider = consumerIdentityResource.toRepresentation(); + + updateIdPSyncMode(idProvider, consumerIdentityResource, IdentityProviderSyncMode.FORCE, false); + + // login to create the user in the consumer realm + oauth.clientId("broker-app"); + loginPage.open(bc.consumerRealmName()); + + WaitUtils.waitForPageToLoad(); + + assertThat(driver.getTitle(), Matchers.containsString("Sign in to " + bc.consumerRealmName())); + logInWithIdp(IDP_NAME, USERNAME, PASSWORD); + + UserRepresentation userRepresentation = AccountHelper.getUserRepresentation(adminClient.realm(bc.providerRealmName()), USERNAME); + + assertThat(userRepresentation.getUsername(), Matchers.equalTo(USERNAME)); + assertThat(userRepresentation.getEmail(), Matchers.equalTo(EMAIL)); + assertThat(userRepresentation.getFirstName(), Matchers.equalTo(FIRST_NAME)); + assertThat(userRepresentation.getLastName(), Matchers.equalTo(LAST_NAME)); + + RealmResource consumerRealmResource = realmsResouce().realm(bc.consumerRealmName()); + List foundUsers = consumerRealmResource.users().searchByUsername(USERNAME, true); + assertThat(foundUsers, Matchers.hasSize(1)); + UserRepresentation consumerUser = foundUsers.get(0); + assertThat(consumerUser, Matchers.notNullValue()); + String consumerUserID = consumerUser.getId(); + UserResource consumerUserResource = consumerRealmResource.users().get(consumerUserID); + + checkFederatedIdentityLink(consumerUserResource, providerUserID, USERNAME); + Assert.assertFalse(consumerUserResource.toRepresentation().isEmailVerified()); + + AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), USERNAME); + AccountHelper.logout(adminClient.realm(bc.providerRealmName()), USERNAME); + + // set email verified to true on the consumer resource + consumerUser = consumerUserResource.toRepresentation(); + consumerUser.setEmailVerified(true); + consumerUserResource.update(consumerUser); + Assert.assertTrue(consumerUserResource.toRepresentation().isEmailVerified()); + + // Change the client scope for email to set the hardcoded email in capitals + ProtocolMapperRepresentation hardcodedEmail = new ProtocolMapperRepresentation(); + hardcodedEmail.setName("email"); + hardcodedEmail.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + hardcodedEmail.setProtocolMapper(HardcodedClaim.PROVIDER_ID); + hardcodedEmail.getConfig().put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "email"); + hardcodedEmail.getConfig().put(OIDCAttributeMapperHelper.JSON_TYPE, "String"); + hardcodedEmail.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + hardcodedEmail.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + hardcodedEmail.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + hardcodedEmail.getConfig().put(HardcodedClaim.CLAIM_VALUE, EMAIL.toUpperCase()); + ClientScopeResource emailClientScope = ApiUtil.findClientScopeByName(providerRealmResource, "email"); + ProtocolMapperRepresentation emailMapper = ApiUtil.findProtocolMapperByName(emailClientScope, "email"); + emailClientScope.getProtocolMappers().delete(emailMapper.getId()); + emailClientScope.getProtocolMappers().createMapper(hardcodedEmail); + + // login again to force sync + oauth.clientId("broker-app"); + loginPage.open(bc.consumerRealmName()); + + WaitUtils.waitForPageToLoad(); + + assertThat(driver.getTitle(), Matchers.containsString("Sign in to " + bc.consumerRealmName())); + logInWithIdp(IDP_NAME, USERNAME, PASSWORD); + assertThat(driver.getCurrentUrl(), Matchers.containsString("/app/auth?")); + + consumerUserResource = consumerRealmResource.users().get(consumerUserID); + checkFederatedIdentityLink(consumerUserResource, providerUserID, USERNAME); + + // the email should be verified as it's just a different case + Assert.assertTrue(consumerUserResource.toRepresentation().isEmailVerified()); + } finally { + providerUsersResource.delete(providerUserID); + } + } + private void allowUserEdit(RealmResource realmResource) { RealmRepresentation realm = realmResource.toRepresentation(); realm.setEditUsernameAllowed(true);