Merge pull request #650 from mposolda/master

More Federation & LDAP fixes
This commit is contained in:
Marek Posolda 2014-08-27 12:41:38 +02:00
commit b50ab14a0a
10 changed files with 94 additions and 17 deletions

View file

@ -19,6 +19,7 @@ public interface Errors {
String USERNAME_MISSING = "username_missing"; String USERNAME_MISSING = "username_missing";
String USERNAME_IN_USE = "username_in_use"; String USERNAME_IN_USE = "username_in_use";
String EMAIL_IN_USE = "email_in_use";
String INVALID_REDIRECT_URI = "invalid_redirect_uri"; String INVALID_REDIRECT_URI = "invalid_redirect_uri";
String INVALID_CODE = "invalid_code"; String INVALID_CODE = "invalid_code";

View file

@ -162,7 +162,6 @@ public class LDAPFederationProvider implements UserFederationProvider {
User user = BasicModel.getUser(identityManager, attributes.get(USERNAME)); User user = BasicModel.getUser(identityManager, attributes.get(USERNAME));
if (user != null) { if (user != null) {
results.put(user.getLoginName(), user); results.put(user.getLoginName(), user);
return results;
} }
} }
@ -170,7 +169,6 @@ public class LDAPFederationProvider implements UserFederationProvider {
User user = queryByEmail(identityManager, attributes.get(EMAIL)); User user = queryByEmail(identityManager, attributes.get(EMAIL));
if (user != null) { if (user != null) {
results.put(user.getLoginName(), user); 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 { protected User queryByEmail(IdentityManager identityManager, String email) throws IdentityManagementException {
List<User> agents = identityManager.createIdentityQuery(User.class) return LDAPUtils.getUserByEmail(identityManager, email);
.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");
}
} }

View file

@ -1,5 +1,7 @@
package org.keycloak.federation.ldap; package org.keycloak.federation.ldap;
import org.keycloak.models.ModelDuplicateException;
import org.picketlink.idm.IdentityManagementException;
import org.picketlink.idm.IdentityManager; import org.picketlink.idm.IdentityManager;
import org.picketlink.idm.PartitionManager; import org.picketlink.idm.PartitionManager;
import org.picketlink.idm.credential.Credentials; import org.picketlink.idm.credential.Credentials;
@ -19,13 +21,21 @@ import java.util.List;
public class LDAPUtils { public class LDAPUtils {
public static User addUser(PartitionManager partitionManager, String username, String firstName, String lastName, String email) { 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); User picketlinkUser = new User(username);
picketlinkUser.setFirstName(firstName); picketlinkUser.setFirstName(firstName);
picketlinkUser.setLastName(lastName); picketlinkUser.setLastName(lastName);
picketlinkUser.setEmail(email); picketlinkUser.setEmail(email);
picketlinkUser.setAttribute(new Attribute("fullName", getFullName(username, firstName, lastName))); picketlinkUser.setAttribute(new Attribute("fullName", getFullName(username, firstName, lastName)));
idmManager.add(picketlinkUser); identityManager.add(picketlinkUser);
return picketlinkUser; return picketlinkUser;
} }
@ -64,6 +74,20 @@ public class LDAPUtils {
return BasicModel.getUser(idmManager, username); 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) { public static boolean removeUser(PartitionManager partitionManager, String username) {
IdentityManager idmManager = getIdentityManager(partitionManager); IdentityManager idmManager = getIdentityManager(partitionManager);
User picketlinkUser = BasicModel.getUser(idmManager, username); User picketlinkUser = BasicModel.getUser(idmManager, username);

View file

@ -50,6 +50,7 @@ successTotp=Google authenticator configured.
successTotpRemoved=Google authenticator removed. successTotpRemoved=Google authenticator removed.
usernameExists=Username already exists usernameExists=Username already exists
emailExists=Email already exists
socialEmailExists=User with email already exists. Please login to account management to link the account. socialEmailExists=User with email already exists. Please login to account management to link the account.

View file

@ -1,7 +1,15 @@
package org.keycloak.picketlink.idm; package org.keycloak.picketlink.idm;
import javax.naming.directory.SearchResult;
import org.picketlink.idm.IdentityManager; 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.ldap.internal.LDAPPlainTextPasswordCredentialHandler;
import org.picketlink.idm.model.Account;
import org.picketlink.idm.model.basic.BasicModel; import org.picketlink.idm.model.basic.BasicModel;
import org.picketlink.idm.model.basic.User; import org.picketlink.idm.model.basic.User;
import org.picketlink.idm.spi.IdentityContext; import org.picketlink.idm.spi.IdentityContext;
@ -24,4 +32,33 @@ public class LDAPKeycloakCredentialHandler extends LDAPPlainTextPasswordCredenti
return BasicModel.getUser(identityManager, loginName); 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);
}
}
} }

View file

@ -18,7 +18,7 @@
<resteasy.version>2.3.7.Final</resteasy.version> <resteasy.version>2.3.7.Final</resteasy.version>
<resteasy.version.latest>3.0.8.Final</resteasy.version.latest> <resteasy.version.latest>3.0.8.Final</resteasy.version.latest>
<undertow.version>1.0.15.Final</undertow.version> <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> <picketbox.ldap.version>1.0.2.Final</picketbox.ldap.version>
<mongo.driver.version>2.11.3</mongo.driver.version> <mongo.driver.version>2.11.3</mongo.driver.version>
<jboss.logging.version>3.1.4.GA</jboss.logging.version> <jboss.logging.version>3.1.4.GA</jboss.logging.version>

View file

@ -59,6 +59,8 @@ public class Messages {
public static final String USERNAME_EXISTS = "usernameExists"; 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_TOTP = "actionTotpWarning";
public static final String ACTION_WARN_PROFILE = "actionProfileWarning"; public static final String ACTION_WARN_PROFILE = "actionProfileWarning";

View file

@ -635,12 +635,18 @@ public class TokenService {
return Flows.forms(session, realm, client, uriInfo).setError(error).setFormData(formData).createRegistration(); 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) { if (session.users().getUserByUsername(username, realm) != null) {
audit.error(Errors.USERNAME_IN_USE); audit.error(Errors.USERNAME_IN_USE);
return Flows.forms(session, realm, client, uriInfo).setError(Messages.USERNAME_EXISTS).setFormData(formData).createRegistration(); 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); UserModel user = session.users().addUser(realm, username);
user.setEnabled(true); user.setEnabled(true);
user.setFirstName(formData.getFirst("firstName")); user.setFirstName(formData.getFirst("firstName"));

View file

@ -20,6 +20,7 @@ import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.adapters.action.UserStats; import org.keycloak.representations.adapters.action.UserStats;
@ -136,6 +137,14 @@ public class UsersResource {
public Response createUser(final @Context UriInfo uriInfo, final UserRepresentation rep) { public Response createUser(final @Context UriInfo uriInfo, final UserRepresentation rep) {
auth.requireManage(); 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 { try {
UserModel user = session.users().addUser(realm, rep.getUsername()); UserModel user = session.users().addUser(realm, rep.getUsername());
updateUserFromRep(user, rep); updateUserFromRep(user, rep);
@ -146,6 +155,9 @@ public class UsersResource {
return Response.created(uriInfo.getAbsolutePathBuilder().path(user.getUsername()).build()).build(); return Response.created(uriInfo.getAbsolutePathBuilder().path(user.getUsername()).build()).build();
} catch (ModelDuplicateException e) { } catch (ModelDuplicateException e) {
if (session.getTransaction().isActive()) {
session.getTransaction().setRollbackOnly();
}
return Flows.errors().exists("User exists with same username or email"); return Flows.errors().exists("User exists with same username or email");
} }
} }

View file

@ -209,10 +209,15 @@ public class FederationProvidersIntegrationTest {
loginPage.clickRegister(); loginPage.clickRegister();
registerPage.assertCurrent(); registerPage.assertCurrent();
// check existing username
registerPage.register("firstName", "lastName", "email", "existing", "password", "password"); registerPage.register("firstName", "lastName", "email", "existing", "password", "password");
registerPage.assertCurrent(); registerPage.assertCurrent();
Assert.assertEquals("Username already exists", registerPage.getError()); 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 @Test