From e22016db7f33246b1b9e4eae9bfecb3ada00543e Mon Sep 17 00:00:00 2001 From: Stefan Guilhen Date: Fri, 9 Jan 2026 15:30:29 -0300 Subject: [PATCH] Use dedicated error message when user is already linked to idp Closes #45322 Signed-off-by: Stefan Guilhen --- .../keycloak/services/messages/Messages.java | 2 ++ .../resources/IdentityBrokerService.java | 21 ++++++++++++++----- .../login/messages/messages_en.properties | 3 ++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java index f44438b6add..a3e4de7edc9 100755 --- a/services/src/main/java/org/keycloak/services/messages/Messages.java +++ b/services/src/main/java/org/keycloak/services/messages/Messages.java @@ -177,6 +177,8 @@ public class Messages { public static final String IDENTITY_PROVIDER_ALREADY_LINKED = "identityProviderAlreadyLinkedMessage"; + public static final String IDENTITY_PROVIDER_ALREADY_LINKED_TO_CURRENT_USER = "identityProviderAlreadyLinkedToCurrentUserMessage"; + public static final String INSUFFICIENT_PERMISSION = "insufficientPermissionMessage"; public static final String COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST = "couldNotProceedWithAuthenticationRequestMessage"; 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 67920fd6b10..c0389917a95 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -87,6 +87,7 @@ import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.IdentityProviderSyncMode; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; @@ -749,10 +750,14 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(context.getIdpConfig().getAlias(), context.getId(), context.getUsername(), context.getToken()); - session.users().addFederatedIdentity(realmModel, federatedUser, federatedIdentityModel); + try { + session.users().addFederatedIdentity(realmModel, federatedUser, federatedIdentityModel); + } catch (ModelDuplicateException de) { + String idpDisplayName = KeycloakModelUtils.getIdentityProviderDisplayName(session, context.getIdpConfig()); + return redirectToErrorPage(authSession, Status.CONFLICT, Messages.IDENTITY_PROVIDER_ALREADY_LINKED_TO_CURRENT_USER, de, idpDisplayName); + } } - String isRegisteredNewUser = authSession.getAuthNote(BROKER_REGISTERED_NEW_USER); if (Boolean.parseBoolean(isRegisteredNewUser)) { @@ -785,7 +790,6 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider } return finishOrRedirectToPostBrokerLogin(authSession, context, true); - } catch (Exception e) { return redirectToErrorPage(authSession, Response.Status.INTERNAL_SERVER_ERROR, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e); } @@ -1027,8 +1031,15 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider } } } else { - this.session.users().addFederatedIdentity(this.realmModel, authenticatedUser, newModel); - federatedUser = authenticatedUser; + try { + this.session.users().addFederatedIdentity(this.realmModel, authenticatedUser, newModel); + federatedUser = authenticatedUser; + } catch(ModelDuplicateException e) { + logger.warnf(e,"Cannot link user '%s' to identity provider '%s' as the link already exists for this user and identity provider", + authenticatedUser.getUsername(), context.getIdpConfig().getAlias()); + String idpDisplayName = KeycloakModelUtils.getIdentityProviderDisplayName(session, context.getIdpConfig()); + return redirectToErrorWhenLinkingFailed(authSession, Messages.IDENTITY_PROVIDER_ALREADY_LINKED_TO_CURRENT_USER, idpDisplayName); + } } updateFederatedIdentity(context, federatedUser); diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties index 2b5375b8056..d6ba240e192 100644 --- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties +++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties @@ -373,6 +373,8 @@ identityProviderInvalidResponseMessage=Invalid response from identity provider. identityProviderInvalidSignatureMessage=Invalid signature in response from identity provider. identityProviderNotFoundMessage=Could not find an identity provider with the identifier. identityProviderLinkSuccess=You successfully verified your email. Please go back to your original browser and continue there with the login. +identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user. +identityProviderAlreadyLinkedToCurrentUserMessage=Your account is already linked to the identity provider {0}. staleCodeMessage=This page is no longer valid, please go back to your application and sign in again realmSupportsNoCredentialsMessage=Realm does not support any credential type. credentialSetupRequired=Cannot login, credential setup required. @@ -383,7 +385,6 @@ emailVerifiedAlreadyMessageHeader=Email address verified emailVerifiedAlreadyMessage=Your email address has been verified already. staleEmailVerificationLink=The link you clicked is an old stale link and is no longer valid. Maybe you have already verified your email. emailVerificationCancelled=This email verification has been cancelled by an administrator. -identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user. confirmAccountLinking=Confirm linking the account {0} of identity provider {1} with your account. confirmAccountLinkingBody=If you link the account, you will also be able to login using account {0} of the identity provider {1}. Do not proceed if you did not initiate this process or you do not want to link the account. confirmEmailAddressVerificationHeader=Email address validation