keycloak-aplcache

Merge pull request #318 from mposolda/ldap Basic support

4/7/2014 5:35:35 AM

Details

diff --git a/forms/account-api/src/main/java/org/keycloak/account/Account.java b/forms/account-api/src/main/java/org/keycloak/account/Account.java
index f92b161..5a62fec 100644
--- a/forms/account-api/src/main/java/org/keycloak/account/Account.java
+++ b/forms/account-api/src/main/java/org/keycloak/account/Account.java
@@ -31,5 +31,5 @@ public interface Account {
 
     Account setEvents(List<Event> events);
 
-    Account setFeatures(boolean social, boolean audit);
+    Account setFeatures(boolean social, boolean audit, boolean passwordUpdateSupported);
 }
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
index 53f43d4..9b6f0d8 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
@@ -44,6 +44,7 @@ public class FreeMarkerAccount implements Account {
     private List<Event> events;
     private boolean social;
     private boolean audit;
+    private boolean passwordUpdateSupported;
 
     public static enum MessageType {SUCCESS, WARNING, ERROR}
 
@@ -95,7 +96,7 @@ public class FreeMarkerAccount implements Account {
 
         attributes.put("url", new UrlBean(realm, theme, baseUri));
 
-        attributes.put("features", new FeaturesBean(social, audit));
+        attributes.put("features", new FeaturesBean(social, audit, passwordUpdateSupported));
 
         switch (page) {
             case ACCOUNT:
@@ -172,9 +173,10 @@ public class FreeMarkerAccount implements Account {
     }
 
     @Override
-    public Account setFeatures(boolean social, boolean audit) {
+    public Account setFeatures(boolean social, boolean audit, boolean passwordUpdateSupported) {
         this.social = social;
         this.audit = audit;
+        this.passwordUpdateSupported = passwordUpdateSupported;
         return this;
     }
 }
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/FeaturesBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/FeaturesBean.java
index 6f8158b..06e99eb 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/FeaturesBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/FeaturesBean.java
@@ -7,10 +7,12 @@ public class FeaturesBean {
 
     private final boolean social;
     private final boolean log;
+    private final boolean passwordUpdateSupported;
 
-    public FeaturesBean(boolean social, boolean log) {
+    public FeaturesBean(boolean social, boolean log, boolean passwordUpdateSupported) {
         this.social = social;
         this.log = log;
+        this.passwordUpdateSupported = passwordUpdateSupported;
     }
 
     public boolean isSocial() {
@@ -21,4 +23,7 @@ public class FeaturesBean {
         return log;
     }
 
+    public boolean isPasswordUpdateSupported() {
+        return passwordUpdateSupported;
+    }
 }
diff --git a/forms/common-themes/src/main/resources/theme/account/base/template.ftl b/forms/common-themes/src/main/resources/theme/account/base/template.ftl
index 6a39817..883d8fb 100644
--- a/forms/common-themes/src/main/resources/theme/account/base/template.ftl
+++ b/forms/common-themes/src/main/resources/theme/account/base/template.ftl
@@ -40,7 +40,7 @@
         <div class="bs-sidebar col-sm-3  ng-scope">
             <ul>
                 <li class="<#if active=='account'>active</#if>"><a href="${url.accountUrl}">Account</a></li>
-                <li class="<#if active=='password'>active</#if>"><a href="${url.passwordUrl}">Password</a></li>
+                <#if features.passwordUpdateSupported><li class="<#if active=='password'>active</#if>"><a href="${url.passwordUrl}">Password</a></li></#if>
                 <li class="<#if active=='totp'>active</#if>"><a href="${url.totpUrl}">Authenticator</a></li>
                 <#if features.social><li class="<#if active=='social'>active</#if>"><a href="${url.socialUrl}">Social</a></li></#if>
                 <#if features.log><li class="<#if active=='log'>active</#if>"><a href="${url.logUrl}">Log</a></li></#if>
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/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index a789660..bdc97fe 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -35,6 +35,8 @@ import org.keycloak.audit.Events;
 import org.keycloak.jaxrs.JaxrsOAuthClient;
 import org.keycloak.models.AccountRoles;
 import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.RealmModel;
@@ -143,12 +145,21 @@ public class AccountService {
     public void init() {
         auditProvider = providers.getProvider(AuditProvider.class);
 
-        account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setFeatures(realm.isSocial(), auditProvider != null);
+        account = AccountLoader.load().createAccount(uriInfo).setRealm(realm);
 
+        boolean passwordUpdateSupported = false;
         auth = authManager.authenticate(realm, headers);
         if (auth != null) {
             account.setUser(auth.getUser());
+
+            AuthenticationLinkModel authLinkModel = realm.getAuthenticationLink(auth.getUser());
+            if (authLinkModel != null) {
+                AuthenticationProviderModel authProviderModel = AuthenticationProviderManager.getConfiguredProviderModel(realm, authLinkModel.getAuthProvider());
+                passwordUpdateSupported = authProviderModel.isPasswordUpdateSupported();
+            }
         }
+
+        account.setFeatures(realm.isSocial(), auditProvider != null, passwordUpdateSupported);
     }
 
     public static UriBuilder accountServiceBaseUrl(UriInfo uriInfo) {
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 4111806..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
@@ -14,6 +14,7 @@ import org.keycloak.spi.authentication.AuthenticationProvider;
 import org.keycloak.spi.authentication.AuthenticationProviderException;
 import org.keycloak.spi.picketlink.PartitionManagerProvider;
 import org.keycloak.util.ProviderLoader;
+import org.picketlink.idm.IdentityManagementException;
 import org.picketlink.idm.IdentityManager;
 import org.picketlink.idm.PartitionManager;
 import org.picketlink.idm.credential.Credentials;
@@ -44,25 +45,47 @@ public class PicketlinkAuthenticationProvider implements AuthenticationProvider 
     @Override
     public AuthUser getUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException {
         IdentityManager identityManager = getIdentityManager(realm);
-        User picketlinkUser = BasicModel.getUser(identityManager, username);
-        return picketlinkUser == null ? null : new AuthUser(picketlinkUser.getId(), picketlinkUser.getLoginName(), getName())
-                .setName(picketlinkUser.getFirstName(), picketlinkUser.getLastName())
-                .setEmail(picketlinkUser.getEmail())
-                .setProviderName(getName());
+
+        try {
+            User picketlinkUser = BasicModel.getUser(identityManager, username);
+            return picketlinkUser == null ? null : new AuthUser(picketlinkUser.getId(), picketlinkUser.getLoginName(), getName())
+                    .setName(picketlinkUser.getFirstName(), picketlinkUser.getLastName())
+                    .setEmail(picketlinkUser.getEmail())
+                    .setProviderName(getName());
+        } catch (IdentityManagementException ie) {
+            throw convertIDMException(ie);
+        }
+    }
+
+    @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);
 
-        UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
-        credential.setUsername(username);
-        credential.setPassword(new Password(password.toCharArray()));
-        identityManager.validateCredentials(credential);
-        if (credential.getStatus() == Credentials.Status.VALID) {
-            return AuthProviderStatus.SUCCESS;
-        } else {
-            return AuthProviderStatus.INVALID_CREDENTIALS;
+        try {
+            UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
+            credential.setUsername(username);
+            credential.setPassword(new Password(password.toCharArray()));
+            identityManager.validateCredentials(credential);
+            if (credential.getStatus() == Credentials.Status.VALID) {
+                return AuthProviderStatus.SUCCESS;
+            } else {
+                return AuthProviderStatus.INVALID_CREDENTIALS;
+            }
+        } catch (IdentityManagementException ie) {
+            throw convertIDMException(ie);
         }
     }
 
@@ -70,14 +93,18 @@ public class PicketlinkAuthenticationProvider implements AuthenticationProvider 
     public boolean updateCredential(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
         IdentityManager identityManager = getIdentityManager(realm);
 
-        User picketlinkUser = BasicModel.getUser(identityManager, username);
-        if (picketlinkUser == null) {
-            logger.debugf("User '%s' doesn't exists. Skip password update", username);
-            return false;
-        }
+        try {
+            User picketlinkUser = BasicModel.getUser(identityManager, username);
+            if (picketlinkUser == null) {
+                logger.debugf("User '%s' doesn't exists. Skip password update", username);
+                return false;
+            }
 
-        identityManager.updateCredential(picketlinkUser, new Password(password.toCharArray()));
-        return true;
+            identityManager.updateCredential(picketlinkUser, new Password(password.toCharArray()));
+            return true;
+        } catch (IdentityManagementException ie) {
+            throw convertIDMException(ie);
+        }
     }
 
     public IdentityManager getIdentityManager(RealmModel realm) throws AuthenticationProviderException {
@@ -103,4 +130,14 @@ public class PicketlinkAuthenticationProvider implements AuthenticationProvider 
         }
         return identityManager;
     }
+
+    private AuthenticationProviderException convertIDMException(IdentityManagementException ie) {
+        Throwable realCause = ie;
+        while (realCause.getCause() != null) {
+            realCause = realCause.getCause();
+        }
+
+        // Use the message from the realCause
+        return new AuthenticationProviderException(realCause.getMessage(), ie);
+    }
 }
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..823a14b 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();
@@ -147,7 +182,7 @@ public class AuthenticationProviderManager {
         return delegate;
     }
 
-    private List<AuthenticationProviderModel> getConfiguredProviderModels(RealmModel realm) {
+    private static List<AuthenticationProviderModel> getConfiguredProviderModels(RealmModel realm) {
         List<AuthenticationProviderModel> configuredProviders = realm.getAuthenticationProviders();
 
         // Use model based authentication of current realm by default
@@ -159,7 +194,7 @@ public class AuthenticationProviderManager {
         return configuredProviders;
     }
 
-    private AuthenticationProviderModel getConfiguredProviderModel(RealmModel realm, String providerName) {
+    public static AuthenticationProviderModel getConfiguredProviderModel(RealmModel realm, String providerName) {
         List<AuthenticationProviderModel> providers = getConfiguredProviderModels(realm);
         for (AuthenticationProviderModel provider : providers) {
             if (providerName.equals(provider.getProviderName())) {
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..7bf2e3a 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
@@ -111,6 +115,9 @@ public class AuthProvidersIntegrationTest {
 
         Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
         Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        profilePage.open();
+        Assert.assertFalse(profilePage.isPasswordUpdateSupported());
     }
 
     @Test
@@ -120,6 +127,9 @@ public class AuthProvidersIntegrationTest {
 
         Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
         Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        profilePage.open();
+        Assert.assertTrue(profilePage.isPasswordUpdateSupported());
     }
 
     @Test
@@ -131,6 +141,7 @@ public class AuthProvidersIntegrationTest {
         Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
 
         profilePage.open();
+        Assert.assertTrue(profilePage.isPasswordUpdateSupported());
         Assert.assertEquals("John", profilePage.getFirstName());
         Assert.assertEquals("Doe", profilePage.getLastName());
         Assert.assertEquals("john@email.org", profilePage.getEmail());
@@ -191,4 +202,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/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
index 582c112..04f1c60 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
@@ -96,4 +96,8 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
     public String getError() {
         return errorMessage.getText();
     }
+
+    public boolean isPasswordUpdateSupported() {
+        return driver.getPageSource().contains(PATH + "/password");
+    }
 }
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