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 71807e9a58..b54c4bb8e8 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -26,6 +26,8 @@ import org.keycloak.authentication.AuthenticationFlowException;
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
import org.keycloak.authentication.authenticators.broker.util.PostBrokerLoginConstants;
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
+import org.keycloak.broker.oidc.mappers.UserAttributeMapper;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException;
@@ -54,6 +56,7 @@ import org.keycloak.models.Constants;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
@@ -120,6 +123,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
+import java.util.function.Consumer;
/**
*
@@ -1007,6 +1011,10 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
private void updateFederatedIdentity(BrokeredIdentityContext context, UserModel federatedUser) {
FederatedIdentityModel federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, context.getIdpConfig().getAlias(), this.realmModel);
+ if (context.getIdpConfig().getSyncMode() == IdentityProviderSyncMode.FORCE) {
+ setBasicUserAttributes(context, federatedUser);
+ }
+
// Skip DB write if tokens are null or equal
updateToken(context, federatedUser, federatedIdentityModel);
context.getIdp().updateBrokeredUser(session, realmModel, federatedUser, context);
@@ -1021,6 +1029,19 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
+ private void setBasicUserAttributes(BrokeredIdentityContext context, UserModel federatedUser) {
+ setDiffAttrToConsumer(federatedUser.getEmail(), context.getEmail(), federatedUser::setEmail);
+ setDiffAttrToConsumer(federatedUser.getFirstName(), context.getFirstName(), federatedUser::setFirstName);
+ setDiffAttrToConsumer(federatedUser.getLastName(), context.getLastName(), federatedUser::setLastName);
+ }
+
+ private void setDiffAttrToConsumer(String actualValue, String newValue, Consumer consumer) {
+ String actualValueNotNull = Optional.ofNullable(actualValue).orElse("");
+ if (newValue != null && !newValue.equals(actualValueNotNull)) {
+ consumer.accept(newValue);
+ }
+ }
+
private void migrateFederatedIdentityId(BrokeredIdentityContext context, UserModel federatedUser) {
FederatedIdentityModel identityModel = this.session.users().getFederatedIdentity(federatedUser, context.getIdpConfig().getAlias(), this.realmModel);
FederatedIdentityModel migratedIdentityModel = new FederatedIdentityModel(identityModel, context.getId());
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 f66fd9ef95..a62ca2573f 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
@@ -16,6 +16,8 @@ import org.keycloak.broker.oidc.mappers.UserAttributeMapper;
import org.keycloak.crypto.Algorithm;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.ClientRepresentation;
@@ -26,6 +28,7 @@ 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.util.WaitUtils;
import javax.ws.rs.core.Response;
import java.util.Collections;
@@ -445,6 +448,102 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest {
}
}
+ @Test
+ public void testIdPForceSyncUserAttributes() {
+ checkUpdatedUserAttributesIdP(true);
+ }
+
+ @Test
+ public void testIdPNotForceSyncUserAttributes() {
+ checkUpdatedUserAttributesIdP(false);
+ }
+
+ private void checkUpdatedUserAttributesIdP(boolean isForceSync) {
+ final String IDP_NAME = getBrokerConfiguration().getIDPAlias();
+ final String USERNAME = "demoUser";
+
+ final String FIRST_NAME = "John";
+ final String LAST_NAME = "Doe";
+ final String EMAIL = "mail@example.com";
+
+ final String NEW_FIRST_NAME = "Jack";
+ final String NEW_LAST_NAME = "Doee";
+ final String NEW_EMAIL = "mail123@example.com";
+
+ UsersResource providerUserResource = Optional.ofNullable(realmsResouce().realm(bc.providerRealmName()).users()).orElse(null);
+ assertThat("Cannot get User Resource from Provider realm", providerUserResource, Matchers.notNullValue());
+
+ String userID = createUser(bc.providerRealmName(), USERNAME, USERNAME, FIRST_NAME, LAST_NAME, EMAIL);
+ assertThat("Cannot create user : " + USERNAME, userID, Matchers.notNullValue());
+
+ try {
+ UserRepresentation user = Optional.ofNullable(providerUserResource.get(userID).toRepresentation()).orElse(null);
+ assertThat("Cannot get user from provider", user, Matchers.notNullValue());
+
+ IdentityProviderResource consumerIdentityResource = Optional.ofNullable(getIdentityProviderResource()).orElse(null);
+ assertThat("Cannot get Identity Provider resource", consumerIdentityResource, Matchers.notNullValue());
+
+ IdentityProviderRepresentation idProvider = Optional.ofNullable(consumerIdentityResource.toRepresentation()).orElse(null);
+ assertThat("Cannot get Identity Provider", idProvider, Matchers.notNullValue());
+
+ updateIdPSyncMode(idProvider, consumerIdentityResource, isForceSync ? IdentityProviderSyncMode.FORCE : IdentityProviderSyncMode.IMPORT);
+
+ driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
+ WaitUtils.waitForPageToLoad();
+
+ assertThat(driver.getTitle(), Matchers.containsString("Log in to " + bc.consumerRealmName()));
+ logInWithIdp(IDP_NAME, USERNAME, USERNAME);
+ accountUpdateProfilePage.assertCurrent();
+
+ logoutFromRealm(getProviderRoot(), bc.providerRealmName());
+ logoutFromRealm(getConsumerRoot(), bc.consumerRealmName());
+
+ driver.navigate().to(getAccountUrl(getProviderRoot(), bc.providerRealmName()));
+ WaitUtils.waitForPageToLoad();
+
+ assertThat(driver.getTitle(), Matchers.containsString("Log in to " + bc.providerRealmName()));
+
+ loginPage.login(USERNAME, USERNAME);
+ WaitUtils.waitForPageToLoad();
+
+ accountUpdateProfilePage.assertCurrent();
+ accountUpdateProfilePage.updateProfile(NEW_FIRST_NAME, NEW_LAST_NAME, NEW_EMAIL);
+ logoutFromRealm(getProviderRoot(), bc.providerRealmName());
+
+ driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
+ WaitUtils.waitForPageToLoad();
+
+ assertThat(driver.getTitle(), Matchers.containsString("Log in to " + bc.consumerRealmName()));
+ logInWithIdp(IDP_NAME, USERNAME, USERNAME);
+
+ accountUpdateProfilePage.assertCurrent();
+
+ assertThat(accountUpdateProfilePage.getEmail(), Matchers.equalTo(isForceSync ? NEW_EMAIL : EMAIL));
+ assertThat(accountUpdateProfilePage.getFirstName(), Matchers.equalTo(isForceSync ? NEW_FIRST_NAME : FIRST_NAME));
+ assertThat(accountUpdateProfilePage.getLastName(), Matchers.equalTo(isForceSync ? NEW_LAST_NAME : LAST_NAME));
+ } finally {
+ providerUserResource.delete(userID);
+ assertThat("User wasn't deleted", providerUserResource.search(USERNAME).size(), Matchers.is(0));
+ }
+ }
+
+ private void updateIdPSyncMode(IdentityProviderRepresentation idProvider, IdentityProviderResource idProviderResource, IdentityProviderSyncMode syncMode) {
+ assertThat(idProvider, Matchers.notNullValue());
+ assertThat(idProviderResource, Matchers.notNullValue());
+ assertThat(syncMode, Matchers.notNullValue());
+
+ if (idProvider.getConfig().get(IdentityProviderModel.SYNC_MODE).equals(syncMode.name())) {
+ return;
+ }
+
+ idProvider.getConfig().put(IdentityProviderModel.SYNC_MODE, syncMode.name());
+ idProviderResource.update(idProvider);
+
+ idProvider = Optional.ofNullable(idProviderResource.toRepresentation()).orElse(null);
+ assertThat("Cannot get Identity Provider", idProvider, Matchers.notNullValue());
+ assertThat("Sync mode didn't change", idProvider.getConfig().get(IdentityProviderModel.SYNC_MODE), Matchers.equalTo(syncMode.name()));
+ }
+
private UserRepresentation getFederatedIdentity() {
List users = realmsResouce().realm(bc.consumerRealmName()).users().search(bc.getUserLogin());