diff --git a/core/src/main/java/org/keycloak/representations/idm/AuthenticationMappingRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AuthenticationMappingRepresentation.java
deleted file mode 100644
index ecbfb8d6bb..0000000000
--- a/core/src/main/java/org/keycloak/representations/idm/AuthenticationMappingRepresentation.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.keycloak.representations.idm;
-
-import java.util.List;
-
-/**
- * @author Marek Posolda
- */
-public class AuthenticationMappingRepresentation {
-
- protected String self; // link
- protected String username;
- protected List authenticationLinks;
-
- public String getSelf() {
- return self;
- }
-
- public void setSelf(String self) {
- this.self = self;
- }
-
- public String getUsername() {
- return username;
- }
-
- public void setUsername(String username) {
- this.username = username;
- }
-
- public List getAuthenticationLinks() {
- return authenticationLinks;
- }
-
- public void setAuthenticationLinks(List authenticationLinks) {
- this.authenticationLinks = authenticationLinks;
- }
-
-
-}
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index fc72ef02c7..a4278d254f 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -39,7 +39,6 @@ public class RealmRepresentation {
protected Map> applicationRoleMappings;
protected Map> applicationScopeMappings;
protected List socialMappings;
- protected List authenticationMappings;
protected List applications;
protected List oauthClients;
protected Map socialProviders;
@@ -181,18 +180,6 @@ public class RealmRepresentation {
return mapping;
}
- public List getAuthenticationMappings() {
- return authenticationMappings;
- }
-
- public AuthenticationMappingRepresentation authenticationMapping(String username) {
- AuthenticationMappingRepresentation mapping = new AuthenticationMappingRepresentation();
- mapping.setUsername(username);
- if (authenticationMappings == null) authenticationMappings = new ArrayList();
- authenticationMappings.add(mapping);
- return mapping;
- }
-
public Set getRequiredCredentials() {
return requiredCredentials;
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index 5fc55f0b7c..43aa368f7a 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -20,6 +20,7 @@ public class UserRepresentation {
protected String firstName;
protected String lastName;
protected String email;
+ protected AuthenticationLinkRepresentation authenticationLink;
protected Map attributes;
protected List credentials;
protected List requiredActions;
@@ -96,6 +97,14 @@ public class UserRepresentation {
this.emailVerified = emailVerified;
}
+ public AuthenticationLinkRepresentation getAuthenticationLink() {
+ return authenticationLink;
+ }
+
+ public void setAuthenticationLink(AuthenticationLinkRepresentation authenticationLink) {
+ this.authenticationLink = authenticationLink;
+ }
+
public Map getAttributes() {
return attributes;
}
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 827f628048..1fd0843adf 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -134,11 +134,9 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
boolean removeSocialLink(UserModel user, String socialProvider);
- UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink);
+ AuthenticationLinkModel getAuthenticationLink(UserModel user);
- Set getAuthenticationLinks(UserModel user);
-
- void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink);
+ void setAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink);
boolean isSocial();
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 08e6f3989e..c0483afdab 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -395,7 +395,9 @@ public class RealmAdapter implements RealmModel {
private void removeUser(UserEntity user) {
em.createQuery("delete from " + UserRoleMappingEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
em.createQuery("delete from " + SocialLinkEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
- em.createQuery("delete from " + AuthenticationLinkEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
+ if (user.getAuthenticationLink() != null) {
+ em.remove(user.getAuthenticationLink());
+ }
em.remove(user);
}
@@ -614,43 +616,22 @@ public class RealmAdapter implements RealmModel {
}
@Override
- public UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink) {
- TypedQuery query = em.createNamedQuery("findUserByAuthLinkAndRealm", UserEntity.class);
- query.setParameter("realm", realm);
- query.setParameter("authProvider", authenticationLink.getAuthProvider());
- query.setParameter("authUserId", authenticationLink.getAuthUserId());
- List results = query.getResultList();
- if (results.isEmpty()) {
- return null;
- } else if (results.size() > 1) {
- throw new IllegalStateException("More results found for authenticationProvider=" + authenticationLink.getAuthProvider() +
- ", authUserId=" + authenticationLink.getAuthUserId() + ", results=" + results);
- } else {
- UserEntity user = results.get(0);
- return new UserAdapter(user);
- }
+ public AuthenticationLinkModel getAuthenticationLink(UserModel user) {
+ UserEntity userEntity = ((UserAdapter) user).getUser();
+ AuthenticationLinkEntity authLinkEntity = userEntity.getAuthenticationLink();
+ return authLinkEntity == null ? null : new AuthenticationLinkModel(authLinkEntity.getAuthProvider(), authLinkEntity.getAuthUserId());
}
@Override
- public Set getAuthenticationLinks(UserModel user) {
- TypedQuery query = em.createNamedQuery("findAuthLinkByUser", AuthenticationLinkEntity.class);
- query.setParameter("user", ((UserAdapter) user).getUser());
- List results = query.getResultList();
- Set set = new HashSet();
- for (AuthenticationLinkEntity entity : results) {
- set.add(new AuthenticationLinkModel(entity.getAuthProvider(), entity.getAuthUserId()));
- }
- return set;
- }
-
- @Override
- public void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
+ public void setAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
AuthenticationLinkEntity entity = new AuthenticationLinkEntity();
- entity.setRealm(realm);
entity.setAuthProvider(authenticationLink.getAuthProvider());
entity.setAuthUserId(authenticationLink.getAuthUserId());
- entity.setUser(((UserAdapter) user).getUser());
+
+ UserEntity userEntity = ((UserAdapter) user).getUser();
+ userEntity.setAuthenticationLink(entity);
em.persist(entity);
+ em.persist(userEntity);
em.flush();
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java
index 0eecfba5be..e6ae8d421c 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java
@@ -6,16 +6,13 @@ import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
+import javax.persistence.OneToOne;
import org.hibernate.annotations.GenericGenerator;
/**
* @author Marek Posolda
*/
-@NamedQueries({
- @NamedQuery(name="findAuthLinkByUser", query="select link from AuthenticationLinkEntity link where link.user = :user"),
- @NamedQuery(name="findUserByAuthLinkAndRealm", query="select link.user from AuthenticationLinkEntity link where link.realm = :realm and link.authProvider = :authProvider and link.authUserId = :authUserId")
-})
@Entity
public class AuthenticationLinkEntity {
@@ -24,12 +21,6 @@ public class AuthenticationLinkEntity {
@GeneratedValue(generator = "keycloak_generator")
private String id;
- @ManyToOne
- private UserEntity user;
-
- @ManyToOne
- protected RealmEntity realm;
-
protected String authProvider;
protected String authUserId;
@@ -41,22 +32,6 @@ public class AuthenticationLinkEntity {
this.id = id;
}
- public UserEntity getUser() {
- return user;
- }
-
- public void setUser(UserEntity user) {
- this.user = user;
- }
-
- public RealmEntity getRealm() {
- return realm;
- }
-
- public void setRealm(RealmEntity realm) {
- this.realm = realm;
- }
-
public String getAuthProvider() {
return authProvider;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index ab25a647db..9d8435d61d 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -15,6 +15,8 @@ import javax.persistence.MapKeyColumn;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -64,6 +66,9 @@ public class UserEntity {
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true)
protected Collection credentials = new ArrayList();
+ @OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
+ protected AuthenticationLinkEntity authenticationLink;
+
public String getId() {
return id;
}
@@ -160,6 +165,14 @@ public class UserEntity {
this.credentials = credentials;
}
+ public AuthenticationLinkEntity getAuthenticationLink() {
+ return authenticationLink;
+ }
+
+ public void setAuthenticationLink(AuthenticationLinkEntity authenticationLink) {
+ this.authenticationLink = authenticationLink;
+ }
+
public int getNotBefore() {
return notBefore;
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index fb9619a70f..e5f66966b4 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -927,41 +927,26 @@ public class RealmAdapter extends AbstractMongoAdapter implements R
}
@Override
- public UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink) {
- DBObject query = new QueryBuilder()
- .and("authenticationLinks.authProvider").is(authenticationLink.getAuthProvider())
- .and("authenticationLinks.authUserId").is(authenticationLink.getAuthUserId())
- .and("realmId").is(getId())
- .get();
- UserEntity userEntity = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext);
- return userEntity==null ? null : new UserAdapter(userEntity, invocationContext);
- }
-
- @Override
- public Set getAuthenticationLinks(UserModel user) {
+ public AuthenticationLinkModel getAuthenticationLink(UserModel user) {
UserEntity userEntity = ((UserAdapter)user).getUser();
- List linkEntities = userEntity.getAuthenticationLinks();
+ AuthenticationLinkEntity authLinkEntity = userEntity.getAuthenticationLink();
- if (linkEntities == null) {
- return Collections.EMPTY_SET;
+ if (authLinkEntity == null) {
+ return null;
+ } else {
+ return new AuthenticationLinkModel(authLinkEntity.getAuthProvider(), authLinkEntity.getAuthUserId());
}
-
- Set result = new HashSet();
- for (AuthenticationLinkEntity authLinkEntity : linkEntities) {
- AuthenticationLinkModel model = new AuthenticationLinkModel(authLinkEntity.getAuthProvider(), authLinkEntity.getAuthUserId());
- result.add(model);
- }
- return result;
}
@Override
- public void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
+ public void setAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
UserEntity userEntity = ((UserAdapter)user).getUser();
AuthenticationLinkEntity authLinkEntity = new AuthenticationLinkEntity();
authLinkEntity.setAuthProvider(authenticationLink.getAuthProvider());
authLinkEntity.setAuthUserId(authenticationLink.getAuthUserId());
+ userEntity.setAuthenticationLink(authLinkEntity);
- getMongoStore().pushItemToList(userEntity, "authenticationLinks", authLinkEntity, true, invocationContext);
+ getMongoStore().updateEntity(userEntity, invocationContext);
}
protected void updateRealm() {
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java
index f0539df323..96b87919eb 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java
@@ -33,7 +33,7 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
private List requiredActions;
private List credentials = new ArrayList();
private List socialLinks;
- private List authenticationLinks;
+ private AuthenticationLinkEntity authenticationLink;
@MongoField
public String getLoginName() {
@@ -163,11 +163,11 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
}
@MongoField
- public List getAuthenticationLinks() {
- return authenticationLinks;
+ public AuthenticationLinkEntity getAuthenticationLink() {
+ return authenticationLink;
}
- public void setAuthenticationLinks(List authenticationLinks) {
- this.authenticationLinks = authenticationLinks;
+ public void setAuthenticationLink(AuthenticationLinkEntity authenticationLink) {
+ this.authenticationLink = authenticationLink;
}
}
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java
index 3f57de3434..e365d1e134 100644
--- a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java
@@ -95,10 +95,10 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
Assert.assertEquals("john@email.org", john2.getEmail());
// Verify link exists
- Set authLinks = realm2.getAuthenticationLinks(john2);
- Assert.assertEquals(1, authLinks.size());
- AuthenticationLinkModel authLink = authLinks.iterator().next();
+ AuthenticationLinkModel authLink = realm2.getAuthenticationLink(john2);
+ Assert.assertNotNull(authLink);
Assert.assertEquals(authLink.getAuthProvider(), AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL);
+ Assert.assertEquals(authLink.getAuthUserId(), realm1.getUser("john").getId());
} finally {
ResteasyProviderFactory.clearContextData();
}
@@ -110,14 +110,19 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
// Add externalModel authenticationProvider into realm2 and point to realm1
setupAuthenticationProviders();
+ // Add john to realm2 and set authentication link
+ UserModel john = realm2.addUser("john");
+ john.setEnabled(true);
+ realm2.setAuthenticationLink(john, new AuthenticationLinkModel(AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, realm1.getUser("john").getId()));
+
try {
// this is needed for externalModel provider
ResteasyProviderFactory.pushContext(KeycloakSession.class, identitySession);
- // Change credential via realm2 and validate that they are changed in both realms
+ // Change credential via realm2 and validate that they are changed also in realm1
AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm2);
try {
- authProviderManager.updatePassword("john", "password-updated");
+ Assert.assertTrue(authProviderManager.updatePassword(john, "password-updated"));
} catch (AuthenticationProviderException ape) {
ape.printStackTrace();
Assert.fail("Error not expected");
@@ -132,14 +137,14 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
// Change credential and validate that password is updated just for realm2
try {
- authProviderManager.updatePassword("john", "password-updated2");
+ Assert.assertFalse(authProviderManager.updatePassword(john, "password-updated2"));
} catch (AuthenticationProviderException ape) {
ape.printStackTrace();
Assert.fail("Error not expected");
}
formData = createFormData("john", "password-updated2");
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm1, formData));
- Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm2, formData));
+ Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm2, formData));
// Allow passwordUpdate propagation again
@@ -148,7 +153,7 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
// Set passwordPolicy for realm1 and verify that password update fail
realm1.setPasswordPolicy(new PasswordPolicy("length(8)"));
try {
- authProviderManager.updatePassword("john", "passw");
+ authProviderManager.updatePassword(john, "passw");
Assert.fail("Update not expected to pass");
} catch (AuthenticationProviderException ape) {
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
index 96157fe762..1711d22d49 100644
--- a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
@@ -95,9 +95,8 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
Assert.assertEquals("john@email.org", john.getEmail());
// Verify link exists
- Set authLinks = realm.getAuthenticationLinks(john);
- Assert.assertEquals(1, authLinks.size());
- AuthenticationLinkModel authLink = authLinks.iterator().next();
+ AuthenticationLinkModel authLink = realm.getAuthenticationLink(john);
+ Assert.assertNotNull(authLink);
Assert.assertEquals(authLink.getAuthProvider(), AuthProviderConstants.PROVIDER_NAME_PICKETLINK);
} finally {
ResteasyProviderFactory.clearContextData();
@@ -114,6 +113,7 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
// Add some user and password to realm
UserModel realmUser = realm.addUser("realmUser");
+ realmUser.setEnabled(true);
UserCredentialModel credential = new UserCredentialModel();
credential.setType(CredentialRepresentation.PASSWORD);
credential.setValue("pass");
@@ -135,6 +135,11 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
realmUser.setEnabled(false);
formData = AuthProvidersExternalModelTest.createFormData("realmUser", "pass");
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.ACCOUNT_DISABLED, am.authenticateForm(realm, formData));
+
+ // Successful authentication
+ realmUser.setEnabled(true);
+ formData = AuthProvidersExternalModelTest.createFormData("realmUser", "pass");
+ Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm, formData));
} finally {
ResteasyProviderFactory.clearContextData();
}
@@ -149,26 +154,34 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
// this is needed for ldap provider
ResteasyProviderFactory.pushContext(KeycloakRegistry.class, new KeycloakRegistry());
+ LdapTestUtils.setLdapPassword(realm, "john", "password");
+
+ // First authenticate successfully to sync john into realm
+ MultivaluedMap formData = AuthProvidersExternalModelTest.createFormData("john", "password");
+ Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm, formData));
+
// Change credential and validate that user can authenticate
AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm);
+
+ UserModel john = realm.getUser("john");
try {
- authProviderManager.updatePassword("john", "password-updated");
+ Assert.assertTrue(authProviderManager.updatePassword(john, "password-updated"));
} catch (AuthenticationProviderException ape) {
ape.printStackTrace();
Assert.fail("Error not expected");
}
- MultivaluedMap formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated");
+ formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated");
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm, formData));
// Password updated just in LDAP, so validating directly in realm should fail
- Assert.assertFalse(realm.validatePassword(realm.getUser("john"), "password-updated"));
+ Assert.assertFalse(realm.validatePassword(john, "password-updated"));
// Switch to not allow updating passwords in ldap
AuthProvidersExternalModelTest.setPasswordUpdateForProvider(false, AuthProviderConstants.PROVIDER_NAME_PICKETLINK, realm);
// Change credential and validate that password is not updated
try {
- authProviderManager.updatePassword("john", "password-updated2");
+ Assert.assertFalse(authProviderManager.updatePassword(john, "password-updated2"));
} catch (AuthenticationProviderException ape) {
ape.printStackTrace();
Assert.fail("Error not expected");
diff --git a/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
index ce4ca5363d..fb8ebaabf9 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
@@ -208,24 +208,9 @@ public class ImportTest extends AbstractModelTest {
Assert.assertTrue(authProv3.isPasswordUpdateSupported());
// Test authentication linking
- Set authLinks = realm.getAuthenticationLinks(socialUser);
- Assert.assertEquals(2, authLinks.size());
- boolean plFound = false;
- boolean extFound = false;
- for (AuthenticationLinkModel authLinkModel : authLinks) {
- if (AuthProviderConstants.PROVIDER_NAME_PICKETLINK.equals(authLinkModel.getAuthProvider())) {
- plFound = true;
- Assert.assertEquals(authLinkModel.getAuthUserId(), "myUser1");
- } else if (AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL.equals(authLinkModel.getAuthProvider())) {
- extFound = true;
- Assert.assertEquals(authLinkModel.getAuthUserId(), "myUser11");
- }
- }
- Assert.assertTrue(plFound && extFound);
-
- UserModel foundAuthUser = realm.getUserByAuthenticationLink(new AuthenticationLinkModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, "myUser1"));
- Assert.assertEquals(foundAuthUser.getLoginName(), socialUser.getLoginName());
- Assert.assertNull(realm.getUserByAuthenticationLink(new AuthenticationLinkModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, "not-existing")));
+ AuthenticationLinkModel authLink = realm.getAuthenticationLink(socialUser);
+ Assert.assertEquals(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, authLink.getAuthProvider());
+ Assert.assertEquals("myUser1", authLink.getAuthUserId());
commit();
diff --git a/model/tests/src/test/resources/testrealm.json b/model/tests/src/test/resources/testrealm.json
index b28087d7c8..56bf23ee71 100755
--- a/model/tests/src/test/resources/testrealm.json
+++ b/model/tests/src/test/resources/testrealm.json
@@ -75,7 +75,11 @@
},
{
"username": "mySocialUser",
- "enabled": true
+ "enabled": true,
+ "authenticationLink": {
+ "authProvider": "picketlink",
+ "authUserId": "myUser1"
+ }
}
],
"socialMappings": [
@@ -100,21 +104,6 @@
]
}
],
- "authenticationMappings": [
- {
- "username": "mySocialUser",
- "authenticationLinks": [
- {
- "authProvider": "picketlink",
- "authUserId": "myUser1"
- },
- {
- "authProvider": "externalModel",
- "authUserId": "myUser11"
- }
- ]
- }
- ],
"applications": [
{
"name": "Application",
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index fe86e5b603..ff6c61207c 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -16,8 +16,7 @@ import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.spi.authentication.AuthProviderStatus;
-import org.keycloak.spi.authentication.AuthResult;
-import org.keycloak.spi.authentication.AuthenticatedUser;
+import org.keycloak.spi.authentication.AuthUser;
import org.keycloak.spi.authentication.AuthenticationProviderManager;
import org.keycloak.util.Time;
@@ -188,6 +187,25 @@ public class AuthenticationManager {
}
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
+ if (user == null) {
+ AuthUser authUser = AuthenticationProviderManager.getManager(realm).getUser(username);
+ if (authUser != null) {
+ // Create new user and link him with authentication provider
+ user = realm.addUser(authUser.getUsername());
+ user.setEnabled(true);
+ user.setFirstName(authUser.getFirstName());
+ user.setLastName(authUser.getLastName());
+ user.setEmail(authUser.getEmail());
+ realm.setAuthenticationLink(user, new AuthenticationLinkModel(authUser.getProviderName(), authUser.getId()));
+ } else {
+ logger.warn("User " + username + " not found");
+ return AuthenticationStatus.INVALID_USER;
+ }
+ }
+
+ if (!checkEnabled(user)) {
+ return AuthenticationStatus.ACCOUNT_DISABLED;
+ }
Set types = new HashSet();
@@ -202,20 +220,13 @@ public class AuthenticationManager {
return AuthenticationStatus.MISSING_PASSWORD;
}
- if (user == null && types.contains(CredentialRepresentation.TOTP)) {
- logger.warn("User doesn't exists and TOTP is required for the realm");
- return AuthenticationStatus.INVALID_USER;
- }
-
- if (user != null && user.isTotp()) {
+ if (user.isTotp()) {
String token = formData.getFirst(CredentialRepresentation.TOTP);
if (token == null) {
logger.warn("TOTP token not provided");
return AuthenticationStatus.MISSING_TOTP;
}
- if (!checkEnabled(user)) {
- return AuthenticationStatus.ACCOUNT_DISABLED;
- }
+
logger.debug("validating TOTP");
if (!realm.validateTOTP(user, password, token)) {
return AuthenticationStatus.INVALID_CREDENTIALS;
@@ -223,58 +234,12 @@ public class AuthenticationManager {
} else {
logger.debug("validating password for user: " + username);
- AuthResult authResult = AuthenticationProviderManager.getManager(realm).validatePassword(username, password);
- if (authResult.getAuthProviderStatus() == AuthProviderStatus.INVALID_CREDENTIALS) {
+ AuthProviderStatus authStatus = AuthenticationProviderManager.getManager(realm).validatePassword(user, password);
+ if (authStatus == AuthProviderStatus.INVALID_CREDENTIALS) {
logger.debug("invalid password for user: " + username);
return AuthenticationStatus.INVALID_CREDENTIALS;
- } else if (authResult.getAuthProviderStatus() == AuthProviderStatus.USER_NOT_FOUND) {
- logger.debug("User " + username + " not found in any Authentication provider");
- return AuthenticationStatus.INVALID_USER;
- }
-
- if (authResult.getAuthenticatedUser() != null) {
- AuthenticatedUser authUser = authResult.getAuthenticatedUser();
- AuthenticationLinkModel authLink = new AuthenticationLinkModel(authResult.getProviderName(), authUser.getId());
- user = realm.getUserByAuthenticationLink(authLink);
- if (user == null) {
- user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
- if (user != null) {
- // Case when we already have user with the same username like authenticated, but he is not yet linked to current provider.
- // TODO: Revisit if it's ok to link if we allow to change username. Maybe ask user?
- // TODO: Update of existing account?
- realm.addAuthenticationLink(user, authLink);
- logger.info("User " + authUser.getUsername() + " successfully authenticated and linked with provider " + authResult.getProviderName());
- } else {
- // Create new user, which has been successfully authenticated and link him with authentication provider
- user = realm.addUser(authUser.getUsername());
- user.setEnabled(true);
- user.setFirstName(authUser.getFirstName());
- user.setLastName(authUser.getLastName());
- user.setEmail(authUser.getEmail());
-
- realm.addAuthenticationLink(user, authLink);
- logger.info("User " + username + " successfully authenticated and created based on provider " + authResult.getProviderName());
- }
- } else {
- // Existing and linked user has been authenticated TODO: Update of existing account?
- }
-
- // Authenticated username could be different from the "form" username. In this case, we will change it
- if (!username.equals(user.getLoginName())) {
- formData.putSingle(FORM_USERNAME, user.getLoginName());
- logger.debug("Existing user " + user.getLoginName() + " successfully authenticated");
- }
-
- } else {
- // Authentication provider didn't send AuthenticatedUser. Using already retrieved user based on username from "form"
- if (user == null) {
- logger.warn("User '" + username + "' successfully authenticated, but he doesn't exists and don't know how to create him");
- return AuthenticationStatus.INVALID_USER;
- }
- }
-
- if (!checkEnabled(user)) {
- return AuthenticationStatus.ACCOUNT_DISABLED;
+ } else if (authStatus == AuthProviderStatus.FAILED) {
+ return AuthenticationStatus.FAILED;
}
}
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 5b4c735ae6..d9d40d2926 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -21,7 +21,6 @@ import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.AuthenticationLinkRepresentation;
-import org.keycloak.representations.idm.AuthenticationMappingRepresentation;
import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.OAuthClientRepresentation;
@@ -388,15 +387,6 @@ public class RealmManager {
}
}
}
- if (rep.getAuthenticationMappings() != null) {
- for (AuthenticationMappingRepresentation authMapping : rep.getAuthenticationMappings()) {
- UserModel user = userMap.get(authMapping.getUsername());
- for (AuthenticationLinkRepresentation link : authMapping.getAuthenticationLinks()) {
- AuthenticationLinkModel mappingModel = new AuthenticationLinkModel(link.getAuthProvider(), link.getAuthUserId());
- newRealm.addAuthenticationLink(user, mappingModel);
- }
- }
- }
if (rep.getSmtpServer() != null) {
newRealm.setSmtpConfig(new HashMap(rep.getSmtpServer()));
@@ -478,6 +468,11 @@ public class RealmManager {
newRealm.updateCredential(user, credential);
}
}
+ if (userRep.getAuthenticationLink() != null) {
+ AuthenticationLinkRepresentation link = userRep.getAuthenticationLink();
+ AuthenticationLinkModel authLink = new AuthenticationLinkModel(link.getAuthProvider(), link.getAuthUserId());
+ newRealm.setAuthenticationLink(user, authLink);
+ }
return user;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 4ac9a08cfd..b937674b58 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -264,13 +264,15 @@ public class AccountService {
AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm);
if (Validation.isEmpty(password)) {
return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
- // TODO: This may not work in some cases. For example if ldap username is "foo" but actual loginName of user is "bar", which could theoretically happen...
- } else if (authProviderManager.validatePassword(user.getLoginName(), password).getAuthProviderStatus() != AuthProviderStatus.SUCCESS) {
+ } else if (authProviderManager.validatePassword(user, password) != AuthProviderStatus.SUCCESS) {
return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
}
try {
- authProviderManager.updatePassword(user.getLoginName(), passwordNew);
+ boolean passwordUpdateSuccess = authProviderManager.updatePassword(user, passwordNew);
+ if (!passwordUpdateSuccess) {
+ return account.setError("Password update failed").createResponse(AccountPages.PASSWORD);
+ }
} catch (AuthenticationProviderException ape) {
return account.setError(ape.getMessage()).createResponse(AccountPages.PASSWORD);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
index c6b0a6ca85..d77ba7e6ee 100755
--- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -200,7 +200,10 @@ public class RequiredActionsService {
}
try {
- AuthenticationProviderManager.getManager(realm).updatePassword(user.getLoginName(), passwordNew);
+ boolean updateSuccessful = AuthenticationProviderManager.getManager(realm).updatePassword(user, passwordNew);
+ if (!updateSuccessful) {
+ return loginForms.setError("Password update failed").createResponse(RequiredAction.UPDATE_PASSWORD);
+ }
} catch (AuthenticationProviderException ape) {
return loginForms.setError(ape.getMessage()).createResponse(RequiredAction.UPDATE_PASSWORD);
}
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 00fbbb400d..7a91471153 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -397,12 +397,21 @@ public class TokenService {
UserCredentialModel credentials = new UserCredentialModel();
credentials.setType(CredentialRepresentation.PASSWORD);
credentials.setValue(formData.getFirst("password"));
+
+ boolean passwordUpdateSuccessful;
+ String passwordUpdateError = null;
try {
- AuthenticationProviderManager.getManager(realm).updatePassword(username, formData.getFirst("password"));
+ passwordUpdateSuccessful = AuthenticationProviderManager.getManager(realm).updatePassword(user, formData.getFirst("password"));
+ passwordUpdateError = "Password update failed";
} catch (AuthenticationProviderException ape) {
- // User already registered, but force him to update password
+ passwordUpdateSuccessful = false;
+ passwordUpdateError = ape.getMessage();
+ }
+
+ // User already registered, but force him to update password
+ if (!passwordUpdateSuccessful) {
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
- return Flows.forms(realm, request, uriInfo).setError(ape.getMessage()).createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
+ return Flows.forms(realm, request, uriInfo).setError(passwordUpdateError).createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
}
}
diff --git a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/AbstractModelAuthenticationProvider.java b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/AbstractModelAuthenticationProvider.java
index 58bf42390f..66166ab0e0 100644
--- a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/AbstractModelAuthenticationProvider.java
+++ b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/AbstractModelAuthenticationProvider.java
@@ -9,8 +9,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.spi.authentication.AuthProviderStatus;
-import org.keycloak.spi.authentication.AuthResult;
-import org.keycloak.spi.authentication.AuthenticatedUser;
+import org.keycloak.spi.authentication.AuthUser;
import org.keycloak.spi.authentication.AuthenticationProvider;
import org.keycloak.spi.authentication.AuthenticationProviderException;
@@ -24,22 +23,19 @@ public abstract class AbstractModelAuthenticationProvider implements Authenticat
private static final Logger logger = Logger.getLogger(AbstractModelAuthenticationProvider.class);
@Override
- public AuthResult validatePassword(RealmModel currentRealm, Map config, String username, String password) throws AuthenticationProviderException {
+ public AuthUser getUser(RealmModel currentRealm, Map config, String username) throws AuthenticationProviderException {
RealmModel realm = getRealm(currentRealm, config);
+ UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
+ return user == null ? null : createAuthenticatedUserInstance(user);
+ }
+ @Override
+ public AuthProviderStatus validatePassword(RealmModel currentRealm, Map config, String username, String password) throws AuthenticationProviderException {
+ RealmModel realm = getRealm(currentRealm, config);
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
- if (user == null) {
- return new AuthResult(AuthProviderStatus.USER_NOT_FOUND);
- }
-
boolean result = realm.validatePassword(user, password);
- if (!result) {
- return new AuthResult(AuthProviderStatus.INVALID_CREDENTIALS);
- }
-
- AuthenticatedUser authUser = createAuthenticatedUserInstance(user);
- return new AuthResult(AuthProviderStatus.SUCCESS).setProviderName(getName()).setUser(authUser);
+ return result ? AuthProviderStatus.SUCCESS : AuthProviderStatus.INVALID_CREDENTIALS;
}
@Override
@@ -54,7 +50,7 @@ public abstract class AbstractModelAuthenticationProvider implements Authenticat
UserModel user = realm.getUser(username);
if (user == null) {
- logger.debugf("User '%s' doesn't exists. Skip password update", username);
+ logger.warnf("User '%s' doesn't exists. Skip password update", username);
return false;
}
@@ -68,5 +64,9 @@ public abstract class AbstractModelAuthenticationProvider implements Authenticat
protected abstract RealmModel getRealm(RealmModel currentRealm, Map config) throws AuthenticationProviderException;
- protected abstract AuthenticatedUser createAuthenticatedUserInstance(UserModel user);
+ protected AuthUser createAuthenticatedUserInstance(UserModel user) {
+ return new AuthUser(user.getId(), user.getLoginName(), getName())
+ .setName(user.getFirstName(), user.getLastName())
+ .setEmail(user.getEmail());
+ }
}
diff --git a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ExternalModelAuthenticationProvider.java b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ExternalModelAuthenticationProvider.java
index a24a4952d7..a4d129bdc3 100644
--- a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ExternalModelAuthenticationProvider.java
+++ b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ExternalModelAuthenticationProvider.java
@@ -7,7 +7,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.spi.authentication.AuthProviderConstants;
-import org.keycloak.spi.authentication.AuthenticatedUser;
+import org.keycloak.spi.authentication.AuthUser;
import org.keycloak.spi.authentication.AuthenticationProviderException;
/**
@@ -40,11 +40,4 @@ public class ExternalModelAuthenticationProvider extends AbstractModelAuthentica
}
return realm;
}
-
- @Override
- protected AuthenticatedUser createAuthenticatedUserInstance(UserModel user) {
- return new AuthenticatedUser(user.getId(), user.getLoginName())
- .setName(user.getFirstName(), user.getLastName())
- .setEmail(user.getEmail());
- }
}
diff --git a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ModelAuthenticationProvider.java b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ModelAuthenticationProvider.java
index 516948920a..29ab43aafe 100644
--- a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ModelAuthenticationProvider.java
+++ b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ModelAuthenticationProvider.java
@@ -5,7 +5,7 @@ import java.util.Map;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.spi.authentication.AuthProviderConstants;
-import org.keycloak.spi.authentication.AuthenticatedUser;
+import org.keycloak.spi.authentication.AuthUser;
/**
* AbstractModelAuthenticationProvider, which uses current realm to call operations on
@@ -23,10 +23,4 @@ public class ModelAuthenticationProvider extends AbstractModelAuthenticationProv
protected RealmModel getRealm(RealmModel currentRealm, Map config) {
return currentRealm;
}
-
- @Override
- protected AuthenticatedUser createAuthenticatedUserInstance(UserModel user) {
- // We don't want AuthenticatedUser instance. Auto-registration won't never happen with this provider
- return null;
- }
}
diff --git a/spi/authentication-picketlink/src/main/java/org/keycloak/spi/authentication/picketlink/PicketlinkAuthenticationProvider.java b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/authentication/picketlink/PicketlinkAuthenticationProvider.java
index 9d87e769d7..ffc1ab6b37 100644
--- a/spi/authentication-picketlink/src/main/java/org/keycloak/spi/authentication/picketlink/PicketlinkAuthenticationProvider.java
+++ b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/authentication/picketlink/PicketlinkAuthenticationProvider.java
@@ -6,9 +6,8 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.spi.authentication.AuthProviderStatus;
-import org.keycloak.spi.authentication.AuthResult;
import org.keycloak.spi.authentication.AuthProviderConstants;
-import org.keycloak.spi.authentication.AuthenticatedUser;
+import org.keycloak.spi.authentication.AuthUser;
import org.keycloak.spi.authentication.AuthenticationProvider;
import org.keycloak.spi.authentication.AuthenticationProviderException;
import org.keycloak.spi.picketlink.PartitionManagerProvider;
@@ -36,28 +35,27 @@ public class PicketlinkAuthenticationProvider implements AuthenticationProvider
}
@Override
- public AuthResult validatePassword(RealmModel realm, Map configuration, String username, String password) throws AuthenticationProviderException {
+ public AuthUser getUser(RealmModel realm, Map configuration, String username) throws AuthenticationProviderException {
IdentityManager identityManager = getIdentityManager(realm);
-
User picketlinkUser = BasicModel.getUser(identityManager, username);
- if (picketlinkUser == null) {
- return new AuthResult(AuthProviderStatus.USER_NOT_FOUND);
- }
+ return picketlinkUser == null ? null : new AuthUser(picketlinkUser.getId(), picketlinkUser.getLoginName(), getName())
+ .setName(picketlinkUser.getFirstName(), picketlinkUser.getLastName())
+ .setEmail(picketlinkUser.getEmail())
+ .setProviderName(getName());
+ }
+
+ @Override
+ public AuthProviderStatus validatePassword(RealmModel realm, Map configuration, String username, String password) throws AuthenticationProviderException {
+ IdentityManager identityManager = getIdentityManager(realm);
UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
credential.setUsername(username);
credential.setPassword(new Password(password.toCharArray()));
identityManager.validateCredentials(credential);
if (credential.getStatus() == Credentials.Status.VALID) {
- AuthResult result = new AuthResult(AuthProviderStatus.SUCCESS);
-
- AuthenticatedUser authenticatedUser = new AuthenticatedUser(picketlinkUser.getId(), picketlinkUser.getLoginName())
- .setName(picketlinkUser.getFirstName(), picketlinkUser.getLastName())
- .setEmail(picketlinkUser.getEmail());
- result.setUser(authenticatedUser).setProviderName(getName());
- return result;
+ return AuthProviderStatus.SUCCESS;
} else {
- return new AuthResult(AuthProviderStatus.INVALID_CREDENTIALS);
+ return AuthProviderStatus.INVALID_CREDENTIALS;
}
}
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderStatus.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderStatus.java
index 56d05d0543..c782dfc707 100644
--- a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderStatus.java
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderStatus.java
@@ -7,6 +7,6 @@ package org.keycloak.spi.authentication;
*/
public enum AuthProviderStatus {
- SUCCESS, INVALID_CREDENTIALS, USER_NOT_FOUND
+ SUCCESS, INVALID_CREDENTIALS, FAILED
}
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthResult.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthResult.java
deleted file mode 100644
index fd52d679aa..0000000000
--- a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthResult.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.keycloak.spi.authentication;
-
-/**
- * @author Marek Posolda
- */
-public class AuthResult {
-
- // Status of authentication
- private final AuthProviderStatus authProviderStatus;
-
- // Provider, which authenticated user
- private String providerName;
-
- // filled usually only in case of successful authentication and just with some Authentication providers
- private AuthenticatedUser authenticatedUser;
-
- public AuthResult(AuthProviderStatus authProviderStatus) {
- this.authProviderStatus = authProviderStatus;
- }
-
- public AuthResult setProviderName(String providerName) {
- this.providerName = providerName;
- return this;
- }
-
- public AuthResult setUser(AuthenticatedUser user) {
- this.authenticatedUser = user;
- return this;
- }
-
- public AuthProviderStatus getAuthProviderStatus() {
- return authProviderStatus;
- }
-
- public String getProviderName() {
- return providerName;
- }
-
- public AuthenticatedUser getAuthenticatedUser() {
- return authenticatedUser;
- }
-
-
-}
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticatedUser.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthUser.java
similarity index 64%
rename from spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticatedUser.java
rename to spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthUser.java
index 760f99ce2f..e0e6f76ca6 100644
--- a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticatedUser.java
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthUser.java
@@ -3,7 +3,7 @@ package org.keycloak.spi.authentication;
/**
* @author Marek Posolda
*/
-public class AuthenticatedUser {
+public class AuthUser {
private String id;
private String username;
@@ -11,16 +11,19 @@ public class AuthenticatedUser {
private String lastName;
private String email;
- public AuthenticatedUser(String id, String username) {
+ private String providerName;
+
+ public AuthUser(String id, String username, String providerName) {
this.id = id;
this.username = username;
+ this.providerName = providerName;
}
public String getId() {
return id;
}
- public AuthenticatedUser setId(String id) {
+ public AuthUser setId(String id) {
this.id = id;
return this;
}
@@ -29,7 +32,7 @@ public class AuthenticatedUser {
return username;
}
- public AuthenticatedUser setUsername(String username) {
+ public AuthUser setUsername(String username) {
this.username = username;
return this;
}
@@ -38,7 +41,7 @@ public class AuthenticatedUser {
return firstName;
}
- public AuthenticatedUser setName(String name) {
+ public AuthUser setName(String name) {
int i = name.lastIndexOf(' ');
if (i != -1) {
firstName = name.substring(0, i);
@@ -50,7 +53,7 @@ public class AuthenticatedUser {
return this;
}
- public AuthenticatedUser setName(String firstName, String lastName) {
+ public AuthUser setName(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
return this;
@@ -64,8 +67,17 @@ public class AuthenticatedUser {
return email;
}
- public AuthenticatedUser setEmail(String email) {
+ public AuthUser setEmail(String email) {
this.email = email;
return this;
}
+
+ public String getProviderName() {
+ return providerName;
+ }
+
+ public AuthUser setProviderName(String providerName) {
+ this.providerName = providerName;
+ return this;
+ }
}
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java
index 1ab1836ef0..014d432ba7 100644
--- a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java
@@ -11,6 +11,17 @@ public interface AuthenticationProvider {
String getName();
+ /**
+ * Get user by given username or email. Return user instance or null if user doesn't exists in this authentication provider
+ *
+ * @param realm
+ * @param configuration
+ * @param username or email
+ * @return found user or null if user with given username doesn't exists
+ * @throws AuthenticationProviderException
+ */
+ AuthUser getUser(RealmModel realm, Map configuration, String username) throws AuthenticationProviderException;
+
/**
* Standard Authentication flow
*
@@ -18,7 +29,7 @@ public interface AuthenticationProvider {
* @param password
* @return result of authentication, which might eventually encapsulate info about authenticated user and provider which successfully authenticated
*/
- AuthResult validatePassword(RealmModel realm, Map configuration, String username, String password) throws AuthenticationProviderException;
+ AuthProviderStatus validatePassword(RealmModel realm, Map configuration, String username, String password) throws AuthenticationProviderException;
/**
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderManager.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderManager.java
index 8c6a6b8ad0..c6a59a1a8f 100644
--- a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderManager.java
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderManager.java
@@ -1,14 +1,15 @@
package org.keycloak.spi.authentication;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jboss.logging.Logger;
+import org.keycloak.models.AuthenticationLinkModel;
import org.keycloak.models.AuthenticationProviderModel;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
import org.keycloak.util.ProviderLoader;
/**
@@ -46,77 +47,107 @@ public class AuthenticationProviderManager {
this.delegates = delegates;
}
- public AuthResult validatePassword(String username, String password) {
- List configuredProviders = getConfiguredProviders(realm);
- boolean userExists = false;
-
- for (AuthenticationProviderModel authProviderConfig : configuredProviders) {
- String providerName = authProviderConfig.getProviderName();
-
- AuthenticationProvider delegate = getDelegate(providerName);
+ public AuthUser getUser(String username) {
+ List authProviderModels = getConfiguredProviderModels(realm);
+ for (AuthenticationProviderModel providerModel : authProviderModels) {
+ AuthenticationProvider delegate = getProvider(providerModel.getProviderName());
if (delegate == null) {
continue;
}
try {
- AuthResult currentResult = delegate.validatePassword(realm, authProviderConfig.getConfig(), username, password);
- logger.debugf("Authentication provider '%s' finished with '%s' for authentication of '%s'", delegate.getName(), currentResult.getAuthProviderStatus().toString(), username);
-
- if (currentResult.getAuthProviderStatus() == AuthProviderStatus.SUCCESS) {
- return currentResult;
- } else if (currentResult.getAuthProviderStatus() == AuthProviderStatus.INVALID_CREDENTIALS) {
- userExists = true;
+ AuthUser authUser = delegate.getUser(realm, providerModel.getConfig(), username);
+ if (authUser != null) {
+ logger.debugf("User '%s' found with provider '%s'", username, providerModel.getProviderName());
+ return authUser;
}
} catch (AuthenticationProviderException ape) {
logger.warn(ape.getMessage(), ape);
}
}
- AuthProviderStatus status = userExists ? AuthProviderStatus.INVALID_CREDENTIALS : AuthProviderStatus.USER_NOT_FOUND;
- logger.debugf("Not able to authenticate '%s' with any authentication provider. Status: '%s'", username, status.toString());
-
- return new AuthResult(status);
+ logger.debugf("User '%s' not found with any provider", username);
+ return null;
}
- public void updatePassword(String username, String password) throws AuthenticationProviderException {
- List configuredProviders = getConfiguredProviders(realm);
+ public AuthProviderStatus validatePassword(UserModel user, String password) {
+ AuthenticationLinkModel authLink = realm.getAuthenticationLink(user);
+ if (authLink == null) {
+ authLink = new AuthenticationLinkModel(AuthenticationProviderModel.DEFAULT_PROVIDER.getProviderName(), user.getId());
+ }
- for (AuthenticationProviderModel authProviderConfig : configuredProviders) {
+ String providerName = authLink.getAuthProvider();
- // Update just those, which support password update
- if (authProviderConfig.isPasswordUpdateSupported()) {
- String providerName = authProviderConfig.getProviderName();
- AuthenticationProvider delegate = getDelegate(providerName);
- if (delegate == null) {
- continue;
- }
+ AuthenticationProviderModel providerModel = getConfiguredProviderModel(realm, providerName);
+ AuthenticationProvider delegate = getProvider(providerName);
+ if (delegate == null || providerModel == null) {
+ return AuthProviderStatus.FAILED;
+ }
- try {
- if (delegate.updateCredential(realm, authProviderConfig.getConfig(), username, password)) {
- logger.debugf("Updated password in authentication provider '%s' for user '%s'", delegate.getName(), username);
- } else {
- logger.debugf("Password not updated in authentication provider '%s' for user '%s'", delegate.getName(), username);
- }
- } catch (AuthenticationProviderException ape) {
- // Rethrow it to upper layer
- logger.warn("Failed to update password: " + ape.getMessage());
- throw ape;
- }
- } else {
- logger.debugf("Skip password update for authentication provider '%s' for user '%s'", authProviderConfig.getProviderName(), username);
- }
+ try {
+ checkCorrectAuthLink(delegate, providerModel, authLink, user.getLoginName());
+
+ AuthProviderStatus currentResult = delegate.validatePassword(realm, providerModel.getConfig(), user.getLoginName(), password);
+ logger.debugf("Authentication provider '%s' finished with '%s' for authentication of '%s'", delegate.getName(), currentResult.toString(), user.getLoginName());
+ return currentResult;
+ } catch (AuthenticationProviderException ape) {
+ logger.warn(ape.getMessage(), ape);
+ return AuthProviderStatus.FAILED;
}
}
- private AuthenticationProvider getDelegate(String providerName) {
+ public boolean updatePassword(UserModel user, String password) throws AuthenticationProviderException {
+ AuthenticationLinkModel authLink = realm.getAuthenticationLink(user);
+ if (authLink == null) {
+ authLink = new AuthenticationLinkModel(AuthenticationProviderModel.DEFAULT_PROVIDER.getProviderName(), user.getId());
+ }
+
+ String providerName = authLink.getAuthProvider();
+
+ AuthenticationProviderModel providerModel = getConfiguredProviderModel(realm, providerName);
+ if (providerModel == null) {
+ return false;
+ }
+
+ String username = user.getLoginName();
+
+ // Update just those, which support password update
+ if (providerModel.isPasswordUpdateSupported()) {
+ try {
+ AuthenticationProvider delegate = getProvider(providerName);
+ if (delegate == null) {
+ return false;
+ }
+
+ checkCorrectAuthLink(delegate, providerModel, authLink, username);
+
+ if (delegate.updateCredential(realm,providerModel.getConfig(), user.getLoginName(), password)) {
+ logger.debugf("Updated password in authentication provider '%s' for user '%s'", providerName, username);
+ return true;
+ } else {
+ logger.warnf("Password not updated in authentication provider '%s' for user '%s'", providerName, username);
+ return false;
+ }
+ } catch (AuthenticationProviderException ape) {
+ // Rethrow it to upper layer
+ logger.warn("Failed to update password: " + ape.getMessage());
+ throw ape;
+ }
+ } else {
+ logger.warnf("Skip password update for authentication provider '%s' for user '%s'", providerName, username);
+ return false;
+ }
+ }
+
+ private AuthenticationProvider getProvider(String providerName) {
AuthenticationProvider delegate = delegates.get(providerName);
if (delegate == null) {
- logger.warnf("Configured provider with name '%s' not found", providerName);
+ logger.warnf("Provider '%s' not available on classpath", providerName);
}
return delegate;
}
- private List getConfiguredProviders(RealmModel realm) {
+ private List getConfiguredProviderModels(RealmModel realm) {
List configuredProviders = realm.getAuthenticationProviders();
// Use model based authentication of current realm by default
@@ -127,4 +158,27 @@ public class AuthenticationProviderManager {
return configuredProviders;
}
+
+ private AuthenticationProviderModel getConfiguredProviderModel(RealmModel realm, String providerName) {
+ List providers = getConfiguredProviderModels(realm);
+ for (AuthenticationProviderModel provider : providers) {
+ if (providerName.equals(provider.getProviderName())) {
+ return provider;
+ }
+ }
+
+ logger.warnf("Provider '%s' not configured in realm", providerName);
+ return null;
+ }
+
+ // Check if ID of linked AuthUser is same as expected ID from authenticationLink . It should catch the case when for example user "john" was deleted in LDAP
+ // and then user "john" has been created again, but it's actually different user with different ID
+ private void checkCorrectAuthLink(AuthenticationProvider authProvider, AuthenticationProviderModel providerModel,
+ AuthenticationLinkModel authLinkModel, String username) throws AuthenticationProviderException {
+ AuthUser authUser = authProvider.getUser(realm, providerModel.getConfig(), username);
+ String userExternalId = authUser.getId();
+ if (!userExternalId.equals(authLinkModel.getAuthUserId())) {
+ throw new AuthenticationProviderException("ID did not match! ID from provider: " + userExternalId + ", ID from authentication link: " + authLinkModel.getAuthUserId());
+ }
+ }
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
index c34e77e9cd..37cde85958 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
@@ -47,7 +47,7 @@ public class AuthProvidersIntegrationTest {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
addUser(appRealm, "mary", "mary@test.com", "password-app");
- addUser(adminstrationRealm, "mary", "mary@admin.com", "password-admin");
+ addUser(adminstrationRealm, "mary-admin", "mary@admin.com", "password-admin");
AuthenticationProviderModel modelProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, false, Collections.EMPTY_MAP);
AuthenticationProviderModel picketlinkProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, true, Collections.EMPTY_MAP);
@@ -116,7 +116,7 @@ public class AuthProvidersIntegrationTest {
@Test
public void loginExternalModel() {
loginPage.open();
- loginPage.login("mary", "password-admin");
+ loginPage.login("mary-admin", "password-admin");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
@@ -148,18 +148,18 @@ public class AuthProvidersIntegrationTest {
try {
changePasswordPage.open();
- loginPage.login("mary", "password-admin");
+ loginPage.login("mary-admin", "password-admin");
// Can't update to "pass" due to passwordPolicy
changePasswordPage.changePassword("password-admin", "pass", "pass");
Assert.assertEquals("Invalid password: minimum length 6", profilePage.getError());
- changePasswordPage.changePassword("password-app", "password-updated", "password-updated");
+ changePasswordPage.changePassword("password-admin", "password-updated", "password-updated");
Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
changePasswordPage.logout();
loginPage.open();
- loginPage.login("mary", "password-updated");
+ loginPage.login("mary-admin", "password-updated");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
} finally {