keycloak-uncached
Changes
picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java 37(+37 -0)
pom.xml 2(+1 -1)
Details
diff --git a/audit/api/src/main/java/org/keycloak/audit/Errors.java b/audit/api/src/main/java/org/keycloak/audit/Errors.java
index 04be394..d6a5be2 100755
--- a/audit/api/src/main/java/org/keycloak/audit/Errors.java
+++ b/audit/api/src/main/java/org/keycloak/audit/Errors.java
@@ -19,6 +19,7 @@ public interface Errors {
String USERNAME_MISSING = "username_missing";
String USERNAME_IN_USE = "username_in_use";
+ String EMAIL_IN_USE = "email_in_use";
String INVALID_REDIRECT_URI = "invalid_redirect_uri";
String INVALID_CODE = "invalid_code";
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index 1813e8a..eed9912 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -162,7 +162,6 @@ public class LDAPFederationProvider implements UserFederationProvider {
User user = BasicModel.getUser(identityManager, attributes.get(USERNAME));
if (user != null) {
results.put(user.getLoginName(), user);
- return results;
}
}
@@ -170,7 +169,6 @@ public class LDAPFederationProvider implements UserFederationProvider {
User user = queryByEmail(identityManager, attributes.get(EMAIL));
if (user != null) {
results.put(user.getLoginName(), user);
- return results;
}
}
@@ -236,16 +234,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
}
protected User queryByEmail(IdentityManager identityManager, String email) throws IdentityManagementException {
- List<User> agents = identityManager.createIdentityQuery(User.class)
- .setParameter(User.EMAIL, email).getResultList();
-
- if (agents.isEmpty()) {
- return null;
- } else if (agents.size() == 1) {
- return agents.get(0);
- } else {
- throw new IdentityManagementException("Error - multiple Agent objects found with same email");
- }
+ return LDAPUtils.getUserByEmail(identityManager, email);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
index f862d03..51c5529 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
@@ -1,5 +1,7 @@
package org.keycloak.federation.ldap;
+import org.keycloak.models.ModelDuplicateException;
+import org.picketlink.idm.IdentityManagementException;
import org.picketlink.idm.IdentityManager;
import org.picketlink.idm.PartitionManager;
import org.picketlink.idm.credential.Credentials;
@@ -19,13 +21,21 @@ import java.util.List;
public class LDAPUtils {
public static User addUser(PartitionManager partitionManager, String username, String firstName, String lastName, String email) {
- IdentityManager idmManager = getIdentityManager(partitionManager);
+ IdentityManager identityManager = getIdentityManager(partitionManager);
+
+ if (BasicModel.getUser(identityManager, username) != null) {
+ throw new ModelDuplicateException("User with same username already exists");
+ }
+ if (getUserByEmail(identityManager, email) != null) {
+ throw new ModelDuplicateException("User with same email already exists");
+ }
+
User picketlinkUser = new User(username);
picketlinkUser.setFirstName(firstName);
picketlinkUser.setLastName(lastName);
picketlinkUser.setEmail(email);
picketlinkUser.setAttribute(new Attribute("fullName", getFullName(username, firstName, lastName)));
- idmManager.add(picketlinkUser);
+ identityManager.add(picketlinkUser);
return picketlinkUser;
}
@@ -64,6 +74,20 @@ public class LDAPUtils {
return BasicModel.getUser(idmManager, username);
}
+
+ public static User getUserByEmail(IdentityManager idmManager, String email) throws IdentityManagementException {
+ List<User> agents = idmManager.createIdentityQuery(User.class)
+ .setParameter(User.EMAIL, email).getResultList();
+
+ if (agents.isEmpty()) {
+ return null;
+ } else if (agents.size() == 1) {
+ return agents.get(0);
+ } else {
+ throw new IdentityManagementException("Error - multiple users found with same email");
+ }
+ }
+
public static boolean removeUser(PartitionManager partitionManager, String username) {
IdentityManager idmManager = getIdentityManager(partitionManager);
User picketlinkUser = BasicModel.getUser(idmManager, username);
diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
index a1df56a..a171b1a 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
+++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
@@ -50,6 +50,7 @@ successTotp=Google authenticator configured.
successTotpRemoved=Google authenticator removed.
usernameExists=Username already exists
+emailExists=Email already exists
socialEmailExists=User with email already exists. Please login to account management to link the account.
diff --git a/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java
index 7844e8e..dea1208 100644
--- a/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java
+++ b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java
@@ -1,7 +1,15 @@
package org.keycloak.picketlink.idm;
+import javax.naming.directory.SearchResult;
+
import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.config.LDAPMappingConfiguration;
+import org.picketlink.idm.credential.UsernamePasswordCredentials;
+import org.picketlink.idm.credential.storage.CredentialStorage;
+import org.picketlink.idm.ldap.internal.LDAPIdentityStore;
+import org.picketlink.idm.ldap.internal.LDAPOperationManager;
import org.picketlink.idm.ldap.internal.LDAPPlainTextPasswordCredentialHandler;
+import org.picketlink.idm.model.Account;
import org.picketlink.idm.model.basic.BasicModel;
import org.picketlink.idm.model.basic.User;
import org.picketlink.idm.spi.IdentityContext;
@@ -24,4 +32,33 @@ public class LDAPKeycloakCredentialHandler extends LDAPPlainTextPasswordCredenti
return BasicModel.getUser(identityManager, loginName);
}
+
+
+ @Override
+ protected boolean validateCredential(IdentityContext context, CredentialStorage credentialStorage, UsernamePasswordCredentials credentials, LDAPIdentityStore ldapIdentityStore) {
+ Account account = getAccount(context, credentials.getUsername());
+ char[] password = credentials.getPassword().getValue();
+ String userDN = getDNOfUser(ldapIdentityStore, account);
+ if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+ CREDENTIAL_LOGGER.debugf("Using DN [%s] for authentication of user [%s]", userDN, credentials.getUsername());
+ }
+
+ if (ldapIdentityStore.getOperationManager().authenticate(userDN, new String(password))) {
+ return true;
+ }
+
+ return false;
+ }
+
+ protected String getDNOfUser(LDAPIdentityStore ldapIdentityStore, Account user) {
+ LDAPMappingConfiguration userMappingConfig = ldapIdentityStore.getConfig().getMappingConfig(User.class);
+ SearchResult sr = ldapIdentityStore.getOperationManager().lookupById(userMappingConfig.getBaseDN(), user.getId(), userMappingConfig);
+
+ if (sr != null) {
+ return sr.getNameInNamespace();
+ } else {
+ // Fallback
+ return ldapIdentityStore.getBindingDN(user, true);
+ }
+ }
}
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index 07cb8cb..b78caca 100755
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
<resteasy.version>2.3.7.Final</resteasy.version>
<resteasy.version.latest>3.0.8.Final</resteasy.version.latest>
<undertow.version>1.0.15.Final</undertow.version>
- <picketlink.version>2.7.0.Beta1-20140731</picketlink.version>
+ <picketlink.version>2.7.0.Beta1</picketlink.version>
<picketbox.ldap.version>1.0.2.Final</picketbox.ldap.version>
<mongo.driver.version>2.11.3</mongo.driver.version>
<jboss.logging.version>3.1.4.GA</jboss.logging.version>
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 740d971..329d12a 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -59,6 +59,8 @@ public class Messages {
public static final String USERNAME_EXISTS = "usernameExists";
+ public static final String EMAIL_EXISTS = "emailExists";
+
public static final String ACTION_WARN_TOTP = "actionTotpWarning";
public static final String ACTION_WARN_PROFILE = "actionProfileWarning";
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 13de4df..8f0b0bf 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -20,6 +20,7 @@ import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.adapters.action.UserStats;
@@ -136,6 +137,14 @@ public class UsersResource {
public Response createUser(final @Context UriInfo uriInfo, final UserRepresentation rep) {
auth.requireManage();
+ // Double-check duplicated username and email here due to federation
+ if (session.users().getUserByUsername(rep.getUsername(), realm) != null) {
+ return Flows.errors().exists("User exists with same username");
+ }
+ if (session.users().getUserByEmail(rep.getEmail(), realm) != null) {
+ return Flows.errors().exists("User exists with same email");
+ }
+
try {
UserModel user = session.users().addUser(realm, rep.getUsername());
updateUserFromRep(user, rep);
@@ -146,6 +155,9 @@ public class UsersResource {
return Response.created(uriInfo.getAbsolutePathBuilder().path(user.getUsername()).build()).build();
} catch (ModelDuplicateException e) {
+ if (session.getTransaction().isActive()) {
+ session.getTransaction().setRollbackOnly();
+ }
return Flows.errors().exists("User exists with same username or email");
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index c247dc6..0907b16 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -635,12 +635,18 @@ public class TokenService {
return Flows.forms(session, realm, client, uriInfo).setError(error).setFormData(formData).createRegistration();
}
- // Validate that user with this username doesn't exist in realm or any authentication provider
+ // Validate that user with this username doesn't exist in realm or any federation provider
if (session.users().getUserByUsername(username, realm) != null) {
audit.error(Errors.USERNAME_IN_USE);
return Flows.forms(session, realm, client, uriInfo).setError(Messages.USERNAME_EXISTS).setFormData(formData).createRegistration();
}
+ // Validate that user with this email doesn't exist in realm or any federation provider
+ if (session.users().getUserByEmail(email, realm) != null) {
+ audit.error(Errors.EMAIL_IN_USE);
+ return Flows.forms(session, realm, client, uriInfo).setError(Messages.EMAIL_EXISTS).setFormData(formData).createRegistration();
+ }
+
UserModel user = session.users().addUser(realm, username);
user.setEnabled(true);
user.setFirstName(formData.getFirst("firstName"));
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
index 3caae8b..868092b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
@@ -209,10 +209,15 @@ public class FederationProvidersIntegrationTest {
loginPage.clickRegister();
registerPage.assertCurrent();
+ // check existing username
registerPage.register("firstName", "lastName", "email", "existing", "password", "password");
-
registerPage.assertCurrent();
Assert.assertEquals("Username already exists", registerPage.getError());
+
+ // Check existing email
+ registerPage.register("firstName", "lastName", "existing@email.org", "nonExisting", "password", "password");
+ registerPage.assertCurrent();
+ Assert.assertEquals("Email already exists", registerPage.getError());
}
@Test