keycloak-aplcache
Changes
core/src/main/java/org/keycloak/representations/idm/AuthenticationMappingRepresentation.java 39(+0 -39)
spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/AbstractModelAuthenticationProvider.java 30(+15 -15)
spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ExternalModelAuthenticationProvider.java 9(+1 -8)
spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ModelAuthenticationProvider.java 8(+1 -7)
spi/authentication-picketlink/src/main/java/org/keycloak/spi/authentication/picketlink/PicketlinkAuthenticationProvider.java 28(+13 -15)
spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java 13(+12 -1)
spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderManager.java 138(+96 -42)
Details
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 fc72ef0..a4278d2 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<String, List<UserRoleMappingRepresentation>> applicationRoleMappings;
protected Map<String, List<ScopeMappingRepresentation>> applicationScopeMappings;
protected List<SocialMappingRepresentation> socialMappings;
- protected List<AuthenticationMappingRepresentation> authenticationMappings;
protected List<ApplicationRepresentation> applications;
protected List<OAuthClientRepresentation> oauthClients;
protected Map<String, String> socialProviders;
@@ -181,18 +180,6 @@ public class RealmRepresentation {
return mapping;
}
- public List<AuthenticationMappingRepresentation> getAuthenticationMappings() {
- return authenticationMappings;
- }
-
- public AuthenticationMappingRepresentation authenticationMapping(String username) {
- AuthenticationMappingRepresentation mapping = new AuthenticationMappingRepresentation();
- mapping.setUsername(username);
- if (authenticationMappings == null) authenticationMappings = new ArrayList<AuthenticationMappingRepresentation>();
- authenticationMappings.add(mapping);
- return mapping;
- }
-
public Set<String> 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 5fc55f0..43aa368 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<String, String> attributes;
protected List<CredentialRepresentation> credentials;
protected List<String> 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<String, String> 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 827f628..1fd0843 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<AuthenticationLinkModel> 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/entities/AuthenticationLinkEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java
index 0eecfba..e6ae8d4 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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-@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 ab25a64..9d8435d 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<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
+ @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/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 08e6f39..c0483af 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<UserEntity> query = em.createNamedQuery("findUserByAuthLinkAndRealm", UserEntity.class);
- query.setParameter("realm", realm);
- query.setParameter("authProvider", authenticationLink.getAuthProvider());
- query.setParameter("authUserId", authenticationLink.getAuthUserId());
- List<UserEntity> 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);
- }
- }
-
- @Override
- public Set<AuthenticationLinkModel> getAuthenticationLinks(UserModel user) {
- TypedQuery<AuthenticationLinkEntity> query = em.createNamedQuery("findAuthLinkByUser", AuthenticationLinkEntity.class);
- query.setParameter("user", ((UserAdapter) user).getUser());
- List<AuthenticationLinkEntity> results = query.getResultList();
- Set<AuthenticationLinkModel> set = new HashSet<AuthenticationLinkModel>();
- for (AuthenticationLinkEntity entity : results) {
- set.add(new AuthenticationLinkModel(entity.getAuthProvider(), entity.getAuthUserId()));
- }
- return set;
+ 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 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/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 fb9619a..e5f6696 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<RealmEntity> 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<AuthenticationLinkModel> getAuthenticationLinks(UserModel user) {
+ public AuthenticationLinkModel getAuthenticationLink(UserModel user) {
UserEntity userEntity = ((UserAdapter)user).getUser();
- List<AuthenticationLinkEntity> linkEntities = userEntity.getAuthenticationLinks();
+ AuthenticationLinkEntity authLinkEntity = userEntity.getAuthenticationLink();
- if (linkEntities == null) {
- return Collections.EMPTY_SET;
- }
-
- Set<AuthenticationLinkModel> result = new HashSet<AuthenticationLinkModel>();
- for (AuthenticationLinkEntity authLinkEntity : linkEntities) {
- AuthenticationLinkModel model = new AuthenticationLinkModel(authLinkEntity.getAuthProvider(), authLinkEntity.getAuthUserId());
- result.add(model);
+ if (authLinkEntity == null) {
+ return null;
+ } else {
+ return new AuthenticationLinkModel(authLinkEntity.getAuthProvider(), authLinkEntity.getAuthUserId());
}
- 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 f0539df..96b8791 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<UserModel.RequiredAction> requiredActions;
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
private List<SocialLinkEntity> socialLinks;
- private List<AuthenticationLinkEntity> authenticationLinks;
+ private AuthenticationLinkEntity authenticationLink;
@MongoField
public String getLoginName() {
@@ -163,11 +163,11 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
}
@MongoField
- public List<AuthenticationLinkEntity> getAuthenticationLinks() {
- return authenticationLinks;
+ public AuthenticationLinkEntity getAuthenticationLink() {
+ return authenticationLink;
}
- public void setAuthenticationLinks(List<AuthenticationLinkEntity> 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 3f57de3..e365d1e 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<AuthenticationLinkModel> 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 96157fe..1711d22 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<AuthenticationLinkModel> 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<String, String> 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<String, String> 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 ce4ca53..fb8ebaa 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<AuthenticationLinkModel> 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 b28087d..56bf23e 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 fe86e5b..ff6c612 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<String> types = new HashSet<String>();
@@ -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 5b4c735..d9d40d2 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 4ac9a08..b937674 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 c6b0a6c..d77ba7e 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 00fbbb4..7a91471 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 58bf423..66166ab 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<String, String> config, String username, String password) throws AuthenticationProviderException {
+ public AuthUser getUser(RealmModel currentRealm, Map<String, String> config, String username) throws AuthenticationProviderException {
RealmModel realm = getRealm(currentRealm, config);
-
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
+ return user == null ? null : createAuthenticatedUserInstance(user);
+ }
- if (user == null) {
- return new AuthResult(AuthProviderStatus.USER_NOT_FOUND);
- }
+ @Override
+ public AuthProviderStatus validatePassword(RealmModel currentRealm, Map<String, String> config, String username, String password) throws AuthenticationProviderException {
+ RealmModel realm = getRealm(currentRealm, config);
+ UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
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<String, String> 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 a24a495..a4d129b 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 5169489..29ab43a 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<String, String> 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 9d87e76..ffc1ab6 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<String, String> configuration, String username, String password) throws AuthenticationProviderException {
+ public AuthUser getUser(RealmModel realm, Map<String, String> 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<String, String> 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/AuthenticationProvider.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java
index 1ab1836..014d432 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
@@ -12,13 +12,24 @@ 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<String, String> configuration, String username) throws AuthenticationProviderException;
+
+ /**
* Standard Authentication flow
*
* @param username
* @param password
* @return result of authentication, which might eventually encapsulate info about authenticated user and provider which successfully authenticated
*/
- AuthResult validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException;
+ AuthProviderStatus validatePassword(RealmModel realm, Map<String, String> 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 8c6a6b8..c6a59a1 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<AuthenticationProviderModel> configuredProviders = getConfiguredProviders(realm);
- boolean userExists = false;
-
- for (AuthenticationProviderModel authProviderConfig : configuredProviders) {
- String providerName = authProviderConfig.getProviderName();
-
- AuthenticationProvider delegate = getDelegate(providerName);
+ public AuthUser getUser(String username) {
+ List<AuthenticationProviderModel> 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());
+ logger.debugf("User '%s' not found with any provider", username);
+ return null;
+ }
+
+ public AuthProviderStatus validatePassword(UserModel user, String password) {
+ AuthenticationLinkModel authLink = realm.getAuthenticationLink(user);
+ if (authLink == null) {
+ authLink = new AuthenticationLinkModel(AuthenticationProviderModel.DEFAULT_PROVIDER.getProviderName(), user.getId());
+ }
+
+ String providerName = authLink.getAuthProvider();
- return new AuthResult(status);
+ AuthenticationProviderModel providerModel = getConfiguredProviderModel(realm, providerName);
+ AuthenticationProvider delegate = getProvider(providerName);
+ if (delegate == null || providerModel == null) {
+ return AuthProviderStatus.FAILED;
+ }
+
+ 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;
+ }
}
- public void updatePassword(String username, String password) throws AuthenticationProviderException {
- List<AuthenticationProviderModel> configuredProviders = getConfiguredProviders(realm);
+ 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();
- for (AuthenticationProviderModel authProviderConfig : configuredProviders) {
+ AuthenticationProviderModel providerModel = getConfiguredProviderModel(realm, providerName);
+ if (providerModel == null) {
+ return false;
+ }
+
+ String username = user.getLoginName();
- // Update just those, which support password update
- if (authProviderConfig.isPasswordUpdateSupported()) {
- String providerName = authProviderConfig.getProviderName();
- AuthenticationProvider delegate = getDelegate(providerName);
+ // Update just those, which support password update
+ if (providerModel.isPasswordUpdateSupported()) {
+ try {
+ AuthenticationProvider delegate = getProvider(providerName);
if (delegate == null) {
- continue;
+ return false;
}
- 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;
+ 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;
}
- } else {
- logger.debugf("Skip password update for authentication provider '%s' for user '%s'", authProviderConfig.getProviderName(), username);
+ } 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 getDelegate(String providerName) {
+ 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<AuthenticationProviderModel> getConfiguredProviders(RealmModel realm) {
+ private List<AuthenticationProviderModel> getConfiguredProviderModels(RealmModel realm) {
List<AuthenticationProviderModel> 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<AuthenticationProviderModel> 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/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 56d05d0..c782dfc 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/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
index c34e77e..37cde85 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 {