diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java index 283089a0ba..438c57d729 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java @@ -16,7 +16,9 @@ import org.keycloak.federation.ldap.idm.model.LDAPObject; import org.keycloak.federation.ldap.idm.query.Condition; import org.keycloak.federation.ldap.idm.query.QueryParameter; import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.LDAPConstants; +import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationProvider; @@ -74,6 +76,9 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap // we have java property on UserModel String ldapAttrValue = ldapUser.getAttributeAsString(ldapAttrName); + + checkDuplicateEmail(userModelAttrName, ldapAttrValue, realm, ldapProvider.getSession(), user); + setPropertyOnUserModel(userModelProperty, user, ldapAttrValue); } else { @@ -130,8 +135,20 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap } } + // throw ModelDuplicateException if there is different user in model with same email + protected void checkDuplicateEmail(String userModelAttrName, String email, RealmModel realm, KeycloakSession session, UserModel user) { + if (UserModel.EMAIL.equalsIgnoreCase(userModelAttrName)) { + UserModel that = session.userStorage().getUserByEmail(email, realm); + if (that != null && !that.getId().equals(user.getId())) { + session.getTransaction().setRollbackOnly(); + String exceptionMessage = String.format("Can't import user '%s' from LDAP because email '%s' already exists in Keycloak. Existing user with this email is '%s'", user.getUsername(), email, that.getUsername()); + throw new ModelDuplicateException(exceptionMessage, UserModel.EMAIL); + } + } + } + @Override - public UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, final LDAPObject ldapUser, UserModel delegate, RealmModel realm) { + public UserModel proxy(UserFederationMapperModel mapperModel, final LDAPFederationProvider ldapProvider, final LDAPObject ldapUser, UserModel delegate, final RealmModel realm) { final String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE); final String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE); boolean isAlwaysReadValueFromLDAP = parseBooleanParameter(mapperModel, ALWAYS_READ_VALUE_FROM_LDAP); @@ -162,6 +179,8 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap @Override public void setEmail(String email) { + checkDuplicateEmail(userModelAttrName, email, realm, ldapProvider.getSession(), this); + setLDAPAttribute(UserModel.EMAIL, email); super.setEmail(email); } diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties index f783b49379..2c3b8c7e48 100755 --- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties +++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties @@ -109,6 +109,9 @@ invalidPasswordExistingMessage=Invalid existing password. invalidPasswordConfirmMessage=Password confirmation doesn''t match. invalidTotpMessage=Invalid authenticator code. +usernameExistsMessage=Username already exists. +emailExistsMessage=Email already exists. + readOnlyUserMessage=You can''t update your account as it is read only. readOnlyPasswordMessage=You can''t update your password as your account is read only. diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html index 7ebaa74a9c..51f3f98195 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html @@ -98,7 +98,8 @@
- All values of LDAP objectClass attribute for users in LDAP divided by comma + All values of LDAP objectClass attribute for users in LDAP divided by comma. For example: 'inetOrgPerson, organizationalPerson' . Newly created Keycloak users will be written to LDAP + with all those object classes and existing LDAP user records are found just if they contain all those object classes.
diff --git a/model/api/src/main/java/org/keycloak/models/ModelDuplicateException.java b/model/api/src/main/java/org/keycloak/models/ModelDuplicateException.java index 1d6e69aa98..eb4776d365 100644 --- a/model/api/src/main/java/org/keycloak/models/ModelDuplicateException.java +++ b/model/api/src/main/java/org/keycloak/models/ModelDuplicateException.java @@ -5,6 +5,8 @@ package org.keycloak.models; */ public class ModelDuplicateException extends ModelException { + private String duplicateFieldName; + public ModelDuplicateException() { } @@ -12,6 +14,11 @@ public class ModelDuplicateException extends ModelException { super(message); } + public ModelDuplicateException(String message, String duplicateFieldName) { + super(message); + this.duplicateFieldName = duplicateFieldName; + } + public ModelDuplicateException(String message, Throwable cause) { super(message, cause); } @@ -20,4 +27,7 @@ public class ModelDuplicateException extends ModelException { super(cause); } + public String getDuplicateFieldName() { + return duplicateFieldName; + } } diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java index 66b75929cb..52ff887de6 100755 --- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java +++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java @@ -223,7 +223,7 @@ public class ClientSessionAdapter implements ClientSessionModel { @Override public UserModel getAuthenticatedUser() { - return session.users().getUserById(entity.getAuthUserId(), realm); } + return entity.getAuthUserId() == null ? null : session.users().getUserById(entity.getAuthUserId(), realm); } @Override public void setAuthenticatedUser(UserModel user) { diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java index e76811ea98..1de321eaeb 100755 --- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java +++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java @@ -301,7 +301,7 @@ public class ClientSessionAdapter implements ClientSessionModel { @Override public UserModel getAuthenticatedUser() { - return session.users().getUserById(entity.getUserId(), realm); + return entity.getUserId() == null ? null : session.users().getUserById(entity.getUserId(), realm); } @Override diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java index 458278a4fa..c54d341d11 100755 --- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java +++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java @@ -189,7 +189,7 @@ public class ClientSessionAdapter implements ClientSessionModel { @Override public UserModel getAuthenticatedUser() { - return session.users().getUserById(entity.getAuthUserId(), realm); } + return entity.getAuthUserId() == null ? null : session.users().getUserById(entity.getAuthUserId(), realm); } @Override public void setAuthenticatedUser(UserModel user) { diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java index 55013ed16e..7545dd09c3 100755 --- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java +++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java @@ -205,7 +205,7 @@ public class ClientSessionAdapter extends AbstractMongoAdapter