keycloak-aplcache

Details

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 8bb9e53..2d942d9 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -231,6 +231,7 @@ public class AuthenticationManager {
                 user.setLastName(authUser.getLastName());
                 user.setEmail(authUser.getEmail());
                 realm.setAuthenticationLink(user, new AuthenticationLinkModel(authUser.getProviderName(), authUser.getId()));
+                logger.info("User " + authUser.getUsername() + " created and linked with provider " + authUser.getProviderName());
             } else {
                 logger.warn("User " + username + " not found");
                 return AuthenticationStatus.INVALID_USER;
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 b326216..a18e141 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -383,13 +383,15 @@ public class TokenService {
             return Flows.forms(realm, request, uriInfo).setError(error).setFormData(formData).createRegistration();
         }
 
-        UserModel user = realm.getUser(username);
-        if (user != null) {
+        AuthenticationProviderManager authenticationProviderManager = AuthenticationProviderManager.getManager(realm);
+
+        // Validate that user with this username doesn't exist in realm or any authentication provider
+        if (realm.getUser(username) != null || authenticationProviderManager.getUser(username) != null) {
             audit.error(Errors.USERNAME_IN_USE);
             return Flows.forms(realm, request, uriInfo).setError(Messages.USERNAME_EXISTS).setFormData(formData).createRegistration();
         }
 
-        user = realm.addUser(username);
+        UserModel user = realm.addUser(username);
         user.setEnabled(true);
         user.setFirstName(formData.getFirst("firstName"));
         user.setLastName(formData.getFirst("lastName"));
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 66166ab..d69c428 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
@@ -30,6 +30,14 @@ public abstract class AbstractModelAuthenticationProvider implements Authenticat
     }
 
     @Override
+    public String registerUser(RealmModel currentRealm, Map<String, String> config, String username) throws AuthenticationProviderException {
+        RealmModel realm = getRealm(currentRealm, config);
+        UserModel user = currentRealm.addUser(username);
+        user.setEnabled(true);
+        return user.getId();
+    }
+
+    @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);
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 94b3aab..8344425 100755
--- 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
@@ -58,6 +58,19 @@ public class PicketlinkAuthenticationProvider implements AuthenticationProvider 
     }
 
     @Override
+    public String registerUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException {
+        IdentityManager identityManager = getIdentityManager(realm);
+
+        try {
+            User picketlinkUser = new User(username);
+            identityManager.add(picketlinkUser);
+            return picketlinkUser.getId();
+        } catch (IdentityManagementException ie) {
+            throw convertIDMException(ie);
+        }
+    }
+
+    @Override
     public AuthProviderStatus validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
         IdentityManager identityManager = getIdentityManager(realm);
 
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 550da90..898bc1c 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
@@ -31,6 +31,17 @@ public interface AuthenticationProvider {
     AuthUser getUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException;
 
     /**
+     * Try to register user with this authentication provider
+     *
+     * @param realm
+     * @param configuration
+     * @param username
+     * @return ID of newly created user (For example ID from LDAP)
+     * @throws AuthenticationProviderException if user creation couldn't happen
+     */
+    String registerUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException;
+
+    /**
      * Standard Authentication flow
      *
      * @param username
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 3319cff..4c540a8 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
@@ -73,7 +73,11 @@ public class AuthenticationProviderManager {
     public AuthProviderStatus validatePassword(UserModel user, String password) {
         AuthenticationLinkModel authLink = realm.getAuthenticationLink(user);
         if (authLink == null) {
-            authLink = new AuthenticationLinkModel(AuthenticationProviderModel.DEFAULT_PROVIDER.getProviderName(), user.getId());
+            // User not yet linked with any authenticationProvider. Find provider with biggest priority where he is and link
+            AuthUser authUser = getUser(user.getLoginName());
+            authLink = new AuthenticationLinkModel(authUser.getProviderName(), authUser.getId());
+            realm.setAuthenticationLink(user, authLink);
+            logger.infof("User '%s' linked with provider '%s'", authUser.getUsername(), authUser.getProviderName());
         }
 
         String providerName = authLink.getAuthProvider();
@@ -99,7 +103,38 @@ public class AuthenticationProviderManager {
     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());
+            // Find provider with biggest priority where password update is supported. Then register user here and link him
+            List<AuthenticationProviderModel> configuredProviders = getConfiguredProviderModels(realm);
+            for (AuthenticationProviderModel providerModel : configuredProviders) {
+                if (providerModel.isPasswordUpdateSupported()) {
+                    AuthenticationProvider delegate = getProvider(providerModel.getProviderName());
+                    if (delegate != null) {
+                        AuthUser authUser = delegate.getUser(realm, providerModel.getConfig(), user.getLoginName());
+                        if (authUser != null) {
+                            // Linking existing user supported just for "model" provider. In other cases throw exception
+                            if (providerModel.getProviderName().equals(AuthenticationProviderModel.DEFAULT_PROVIDER.getProviderName())) {
+                                authLink = new AuthenticationLinkModel(providerModel.getProviderName(), authUser.getId());
+                                realm.setAuthenticationLink(user, authLink);
+                                logger.infof("User '%s' linked with provider '%s'", authUser.getUsername(), authUser.getProviderName());
+                            } else {
+                                throw new AuthenticationProviderException("User " + authUser.getUsername() + " exists in provider "
+                                        + authUser.getProviderName() + " but is not linked with model user");
+                            }
+                        } else {
+                            String userIdInProvider = delegate.registerUser(realm, providerModel.getConfig(), user.getLoginName());
+                            authLink = new AuthenticationLinkModel(providerModel.getProviderName(), userIdInProvider);
+                            realm.setAuthenticationLink(user, authLink);
+                            logger.infof("User '%s' registered in provider '%s' and linked", user.getLoginName(), providerModel.getProviderName());
+                        }
+                        break;
+                    }
+                }
+            }
+
+            if (authLink == null) {
+                logger.warnf("No providers found where password update is supported for user '%s'", user.getLoginName());
+                return false;
+            }
         }
 
         String providerName = authLink.getAuthProvider();
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 37cde85..6a22cd7 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
@@ -23,6 +23,7 @@ import org.keycloak.testsuite.pages.AccountPasswordPage;
 import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.RegisterPage;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.LDAPRule;
 import org.keycloak.testsuite.rule.WebResource;
@@ -83,6 +84,9 @@ public class AuthProvidersIntegrationTest {
     protected AppPage appPage;
 
     @WebResource
+    protected RegisterPage registerPage;
+
+    @WebResource
     protected LoginPage loginPage;
 
     @WebResource
@@ -191,4 +195,26 @@ public class AuthProvidersIntegrationTest {
         loginPage.login("john", "new-password");
         Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
     }
+
+    @Test
+    public void registerExistingLdapUser() {
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        registerPage.register("firstName", "lastName", "email", "existing", "password", "password");
+
+        registerPage.assertCurrent();
+        Assert.assertEquals("Username already exists", registerPage.getError());
+    }
+
+    @Test
+    public void registerUserLdapSuccess() {
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        registerPage.register("firstName", "lastName", "email", "registerUserSuccess", "password", "password");
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+    }
 }
diff --git a/testsuite/integration/src/test/resources/ldap/users.ldif b/testsuite/integration/src/test/resources/ldap/users.ldif
index 76295d3..0debe0b 100644
--- a/testsuite/integration/src/test/resources/ldap/users.ldif
+++ b/testsuite/integration/src/test/resources/ldap/users.ldif
@@ -28,3 +28,13 @@ uid: john
 cn: John
 sn: Doe
 mail: john@email.org
+
+dn: uid=existing,ou=People,dc=keycloak,dc=org
+objectclass: top
+objectclass: uidObject
+objectclass: person
+objectclass: inetOrgPerson
+uid: existing
+cn: Existing
+sn: Foo
+mail: existing@email.org