keycloak-aplcache

Changes

picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPAgentIgnoreCredentialHandler.java 27(+0 -27)

Details

diff --git a/authentication/authentication-picketlink/src/main/java/org/keycloak/authentication/picketlink/PicketlinkAuthenticationProvider.java b/authentication/authentication-picketlink/src/main/java/org/keycloak/authentication/picketlink/PicketlinkAuthenticationProvider.java
index ce69bd3..615e2c7 100755
--- a/authentication/authentication-picketlink/src/main/java/org/keycloak/authentication/picketlink/PicketlinkAuthenticationProvider.java
+++ b/authentication/authentication-picketlink/src/main/java/org/keycloak/authentication/picketlink/PicketlinkAuthenticationProvider.java
@@ -67,6 +67,10 @@ public class PicketlinkAuthenticationProvider implements AuthenticationProvider 
         try {
             User picketlinkUser = new User(username);
             identityManager.add(picketlinkUser);
+
+            // Hack needed due to ActiveDirectory bug in Picketlink TODO: Remove once https://issues.jboss.org/browse/PLINK-485 fixed and updated in keycloak master
+            picketlinkUser = BasicModel.getUser(identityManager, picketlinkUser.getLoginName());
+
             return picketlinkUser.getId();
         } catch (IdentityManagementException ie) {
             throw convertIDMException(ie);
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
index 9b95be0..fc10a08 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
@@ -902,6 +902,12 @@ module.controller('RealmSMTPSettingsCtrl', function($scope, Current, Realm, real
 module.controller('RealmLdapSettingsCtrl', function($scope, $location, Notifications, Realm, realm) {
     console.log('RealmLdapSettingsCtrl');
 
+    $scope.ldapVendors = [
+        { "id": "ad", "name": "Active Directory" },
+        { "id": "rhds", "name": "Red Hat Directory Server" },
+        { "id": "other", "name": "Other" }
+    ];
+
     $scope.realm = realm;
 
     var oldCopy = angular.copy($scope.realm);
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-ldap.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-ldap.html
index e717ef5..6eb9fdb 100644
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-ldap.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-ldap.html
@@ -14,6 +14,17 @@
             <fieldset>
                 <legend><span class="text">Required Settings</span></legend>
                 <div class="form-group clearfix">
+                    <label class="col-sm-2 control-label" for="vendor">Vendor <span class="required">*</span></label>
+                    <div class="col-sm-4">
+                        <div class="select-kc">
+                            <select id="vendor"
+                                    ng-model="realm.ldapServer.vendor"
+                                    ng-options="vendor.id as vendor.name for vendor in ldapVendors">
+                            </select>
+                        </div>
+                    </div>
+                </div>
+                <div class="form-group clearfix">
                     <label class="col-sm-2 control-label" for="ldapConnectionUrl">Connection URL <span class="required">*</span></label>
                     <div class="col-sm-4">
                         <input class="form-control" id="ldapConnectionUrl" type="text" ng-model="realm.ldapServer.connectionUrl" placeholder="LDAP connection URL" required>
diff --git a/model/tests/src/main/java/org/keycloak/model/test/LDAPEmbeddedServer.java b/model/tests/src/main/java/org/keycloak/model/test/LDAPEmbeddedServer.java
index 0598c42..70c149f 100644
--- a/model/tests/src/main/java/org/keycloak/model/test/LDAPEmbeddedServer.java
+++ b/model/tests/src/main/java/org/keycloak/model/test/LDAPEmbeddedServer.java
@@ -49,6 +49,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
     protected boolean startEmbeddedLdapLerver = true;
     protected String bindDn = "uid=admin,ou=system";
     protected String bindCredential = "secret";
+    protected String vendor;
 
     public static String IDM_TEST_LDAP_CONNECTION_URL = "idm.test.ldap.connection.url";
     public static String IDM_TEST_LDAP_BASE_DN = "idm.test.ldap.base.dn";
@@ -59,6 +60,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
     public static String IDM_TEST_LDAP_START_EMBEDDED_LDAP_SERVER = "idm.test.ldap.start.embedded.ldap.server";
     public static String IDM_TEST_LDAP_BIND_DN = "idm.test.ldap.bind.dn";
     public static String IDM_TEST_LDAP_BIND_CREDENTIAL = "idm.test.ldap.bind.credential";
+    public static String IDM_TEST_LDAP_VENDOR = "idm.test.ldap.vendor";
 
 
     public LDAPEmbeddedServer() {
@@ -85,6 +87,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
         startEmbeddedLdapLerver = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_START_EMBEDDED_LDAP_SERVER, "true"));
         bindDn = p.getProperty(IDM_TEST_LDAP_BIND_DN, bindDn);
         bindCredential = p.getProperty(IDM_TEST_LDAP_BIND_CREDENTIAL, bindCredential);
+        vendor = p.getProperty(IDM_TEST_LDAP_VENDOR);
     }
 
     @Override
@@ -132,6 +135,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
         ldapConfig.put(LdapConstants.BIND_DN, getBindDn());
         ldapConfig.put(LdapConstants.BIND_CREDENTIAL, getBindCredential());
         ldapConfig.put(LdapConstants.USER_DN_SUFFIX, getUserDnSuffix());
+        ldapConfig.put(LdapConstants.VENDOR, getVendor());
         realm.setLdapServerConfig(ldapConfig);
     }
 
@@ -202,6 +206,10 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
         return bindCredential;
     }
 
+    public String getVendor() {
+        return vendor;
+    }
+
     @Override
     public void importLDIF(String fileName) throws Exception {
         // import LDIF only in case we are running against embedded LDAP server
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 108fd5f..155b235 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
@@ -75,23 +75,23 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
 
     @Test
     public void testLdapAuthentication() {
-        MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("john", "password");
+        MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("johnkeycloak", "password");
 
         // Set password of user in LDAP
-        LdapTestUtils.setLdapPassword(providerSession, realm, "john", "password");
+        LdapTestUtils.setLdapPassword(providerSession, realm, "johnkeycloak", "password");
 
         // Verify that user doesn't exists in realm2 and can't authenticate here
         Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_USER, am.authenticateForm(null, realm, formData));
-        Assert.assertNull(realm.getUser("john"));
+        Assert.assertNull(realm.getUser("johnkeycloak"));
 
         // Add ldap authenticationProvider
         setupAuthenticationProviders();
 
         // Authenticate john and verify that now he exists in realm
         Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(null, realm, formData));
-        UserModel john = realm.getUser("john");
+        UserModel john = realm.getUser("johnkeycloak");
         Assert.assertNotNull(john);
-        Assert.assertEquals("john", john.getLoginName());
+        Assert.assertEquals("johnkeycloak", john.getLoginName());
         Assert.assertEquals("John", john.getFirstName());
         Assert.assertEquals("Doe", john.getLastName());
         Assert.assertEquals("john@email.org", john.getEmail());
@@ -119,7 +119,7 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
         Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_USER, am.authenticateForm(null, realm, formData));
 
         // User exists in ldap
-        formData = AuthProvidersExternalModelTest.createFormData("john", "invalid");
+        formData = AuthProvidersExternalModelTest.createFormData("johnkeycloak", "invalid");
         Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(null, realm, formData));
 
         // User exists in realm
@@ -142,23 +142,23 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
         // Add ldap
         setupAuthenticationProviders();
 
-        LdapTestUtils.setLdapPassword(providerSession, realm, "john", "password");
+        LdapTestUtils.setLdapPassword(providerSession, realm, "johnkeycloak", "password");
 
         // First authenticate successfully to sync john into realm
-        MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("john", "password");
+        MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("johnkeycloak", "password");
         Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(null, realm, formData));
 
         // Change credential and validate that user can authenticate
         AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm, providerSession);
 
-        UserModel john = realm.getUser("john");
+        UserModel john = realm.getUser("johnkeycloak");
         try {
             Assert.assertTrue(authProviderManager.updatePassword(john, "password-updated"));
         } catch (AuthenticationProviderException ape) {
             ape.printStackTrace();
             Assert.fail("Error not expected");
         }
-        formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated");
+        formData = AuthProvidersExternalModelTest.createFormData("johnkeycloak", "password-updated");
         Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(null, realm, formData));
 
         // Password updated just in LDAP, so validating directly in realm should fail
@@ -174,7 +174,7 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
             ape.printStackTrace();
             Assert.fail("Error not expected");
         }
-        formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated2");
+        formData = AuthProvidersExternalModelTest.createFormData("johnkeycloak", "password-updated2");
         Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(null, realm, formData));
     }
 
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 bd10ac1..4264ff7 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
@@ -201,10 +201,11 @@ public class ImportTest extends AbstractModelTest {
 
         // Test ldap config
         Map<String, String> ldapConfig = realm.getLdapServerConfig();
-        Assert.assertTrue(ldapConfig.size() == 5);
+        Assert.assertTrue(ldapConfig.size() == 6);
         Assert.assertEquals("ldap://localhost:10389", ldapConfig.get("connectionUrl"));
         Assert.assertEquals("dc=keycloak,dc=org", ldapConfig.get("baseDn"));
         Assert.assertEquals("ou=People,dc=keycloak,dc=org", ldapConfig.get("userDnSuffix"));
+        Assert.assertEquals("other", ldapConfig.get("vendor"));
 
         // Test authentication providers
         List<AuthenticationProviderModel> authProviderModels = realm.getAuthenticationProviders();
diff --git a/model/tests/src/test/resources/ldap/users.ldif b/model/tests/src/test/resources/ldap/users.ldif
index 8ba3a98..cd6bac8 100644
--- a/model/tests/src/test/resources/ldap/users.ldif
+++ b/model/tests/src/test/resources/ldap/users.ldif
@@ -9,12 +9,12 @@ objectclass: top
 objectclass: organizationalUnit
 ou: People
 
-dn: uid=john,ou=People,dc=keycloak,dc=org
+dn: uid=johnkeycloak,ou=People,dc=keycloak,dc=org
 objectclass: top
 objectclass: uidObject
 objectclass: person
 objectclass: inetOrgPerson
-uid: john
+uid: johnkeycloak
 cn: John
 sn: Doe
 mail: john@email.org
diff --git a/model/tests/src/test/resources/testrealm.json b/model/tests/src/test/resources/testrealm.json
index 56bf23e..4ab4ebf 100755
--- a/model/tests/src/test/resources/testrealm.json
+++ b/model/tests/src/test/resources/testrealm.json
@@ -17,7 +17,8 @@
         "baseDn": "dc=keycloak,dc=org",
         "userDnSuffix": "ou=People,dc=keycloak,dc=org",
         "bindDn": "uid=admin,ou=system",
-        "bindCredential": "secret"
+        "bindCredential": "secret",
+        "vendor": "other"
     },
     "socialProviders": {
         "google.key": "abc",
diff --git a/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/AbstractIdentityManagerProvider.java b/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/AbstractIdentityManagerProvider.java
new file mode 100644
index 0000000..3ee589e
--- /dev/null
+++ b/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/AbstractIdentityManagerProvider.java
@@ -0,0 +1,32 @@
+package org.keycloak.picketlink;
+
+import org.keycloak.models.RealmModel;
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.PartitionManager;
+
+/**
+ * Per-request IdentityManager caching . Not thread-safe
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AbstractIdentityManagerProvider implements IdentityManagerProvider {
+
+    private IdentityManager identityManager;
+
+    @Override
+    public IdentityManager getIdentityManager(RealmModel realm) {
+        if (identityManager == null) {
+            PartitionManager partitionManager = getPartitionManager(realm);
+            identityManager = partitionManager.createIdentityManager();
+        }
+
+        return identityManager;
+    }
+
+    protected abstract PartitionManager getPartitionManager(RealmModel realm);
+
+    @Override
+    public void close() {
+        identityManager = null;
+    }
+}
diff --git a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LdapConstants.java b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LdapConstants.java
index d7fbd12..4eb3454 100644
--- a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LdapConstants.java
+++ b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LdapConstants.java
@@ -5,6 +5,11 @@ package org.keycloak.picketlink.idm;
  */
 public class LdapConstants {
 
+    public static final String VENDOR = "vendor";
+    public static final String VENDOR_RHDS = "rhds";
+    public static final String VENDOR_ACTIVE_DIRECTORY = "ad";
+    public static final String VENDOR_OTHER = "other";
+
     public static final String CONNECTION_URL = "connectionUrl";
     public static final String BASE_DN = "baseDn";
     public static final String USER_DN_SUFFIX = "userDnSuffix";
diff --git a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java
new file mode 100644
index 0000000..62c64f3
--- /dev/null
+++ b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java
@@ -0,0 +1,93 @@
+package org.keycloak.picketlink.idm;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Date;
+
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.credential.Password;
+import org.picketlink.idm.ldap.internal.LDAPIdentityStore;
+import org.picketlink.idm.ldap.internal.LDAPOperationManager;
+import org.picketlink.idm.ldap.internal.LDAPPlainTextPasswordCredentialHandler;
+import org.picketlink.idm.model.Account;
+import org.picketlink.idm.model.AttributedType;
+import org.picketlink.idm.model.basic.User;
+import org.picketlink.idm.spi.IdentityContext;
+
+import static org.picketlink.idm.IDMLog.CREDENTIAL_LOGGER;
+import static org.picketlink.idm.model.basic.BasicModel.getUser;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPKeycloakCredentialHandler extends LDAPPlainTextPasswordCredentialHandler {
+
+    private static Method GET_BINDING_DN_METHOD;
+    private static Method GET_OPERATION_MANAGER_METHOD;
+
+    static {
+        GET_BINDING_DN_METHOD = getMethodOnLDAPStore("getBindingDN", AttributedType.class);
+        GET_OPERATION_MANAGER_METHOD = getMethodOnLDAPStore("getOperationManager");
+    }
+
+    // Used just in ActiveDirectory to put account into "enabled" state (aka userAccountControl=512, see http://support.microsoft.com/kb/305144/en ) after password update. If value is -1, it's ignored
+    private String userAccountControlAfterPasswordUpdate;
+
+    @Override
+    public void setup(LDAPIdentityStore store) {
+        if (store.getConfig().isActiveDirectory() || Boolean.getBoolean("keycloak.ldap.ad.skipUserAccountControlAfterPasswordUpdate")) {
+            String userAccountControlProp = System.getProperty("keycloak.ldap.ad.userAccountControlAfterPasswordUpdate");
+            this.userAccountControlAfterPasswordUpdate = userAccountControlProp!=null ? userAccountControlProp : "512";
+            CREDENTIAL_LOGGER.info("Will use userAccountControl=" + userAccountControlAfterPasswordUpdate + " after password update of user in Active Directory");
+        }
+    }
+
+    // Overridden as in Keycloak, we don't have Agents
+    @Override
+    protected Account getAccount(IdentityContext context, String loginName) {
+        IdentityManager identityManager = getIdentityManager(context);
+
+        if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+            CREDENTIAL_LOGGER.debugf("Trying to find account [%s] using default account type [%s]", loginName, User.class);
+        }
+
+        return getUser(identityManager, loginName);
+    }
+
+    @Override
+    public void update(IdentityContext context, Account account, Password password, LDAPIdentityStore store, Date effectiveDate, Date expiryDate) {
+        super.update(context, account, password, store, effectiveDate, expiryDate);
+
+        if (userAccountControlAfterPasswordUpdate != null) {
+            ModificationItem[] mods = new ModificationItem[1];
+            BasicAttribute mod0 = new BasicAttribute("userAccountControl", userAccountControlAfterPasswordUpdate);
+            mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
+
+            try {
+                String bindingDN = (String) GET_BINDING_DN_METHOD.invoke(store, account);
+                LDAPOperationManager operationManager = (LDAPOperationManager) GET_OPERATION_MANAGER_METHOD.invoke(store);
+                operationManager.modifyAttribute(bindingDN, mod0);
+            } catch (IllegalAccessException iae) {
+                throw new RuntimeException(iae);
+            } catch (InvocationTargetException ite) {
+                Throwable cause = ite.getTargetException() != null ? ite.getTargetException() : ite;
+                throw new RuntimeException(cause);
+            }
+        }
+    }
+
+    // Hack as methods are protected on LDAPIdentityStore :/
+    private static Method getMethodOnLDAPStore(String methodName, Class... classes) {
+        try {
+            Method m = LDAPIdentityStore.class.getDeclaredMethod(methodName, classes);
+            m.setAccessible(true);
+            return m;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/PartitionManagerRegistry.java b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/PartitionManagerRegistry.java
index 261fdb3..6204aa7 100644
--- a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/PartitionManagerRegistry.java
+++ b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/PartitionManagerRegistry.java
@@ -6,15 +6,15 @@ import java.util.concurrent.ConcurrentHashMap;
 
 import org.jboss.logging.Logger;
 import org.keycloak.models.RealmModel;
-import org.keycloak.picketlink.idm.LDAPAgentIgnoreCredentialHandler;
+import org.keycloak.picketlink.idm.LDAPKeycloakCredentialHandler;
 import org.keycloak.picketlink.idm.LdapConstants;
 import org.picketlink.idm.PartitionManager;
 import org.picketlink.idm.config.IdentityConfigurationBuilder;
+import org.picketlink.idm.config.LDAPIdentityStoreConfiguration;
 import org.picketlink.idm.internal.DefaultPartitionManager;
 import org.picketlink.idm.model.basic.User;
 
 import static org.picketlink.common.constants.LDAPConstants.CN;
-import static org.picketlink.common.constants.LDAPConstants.CREATE_TIMESTAMP;
 import static org.picketlink.common.constants.LDAPConstants.EMAIL;
 import static org.picketlink.common.constants.LDAPConstants.SN;
 import static org.picketlink.common.constants.LDAPConstants.UID;
@@ -39,8 +39,8 @@ public class PartitionManagerRegistry {
 
         // Ldap config might have changed for the realm. In this case, we must re-initialize
         if (context == null || !ldapConfig.equals(context.config)) {
-            logger.infof("Creating new partition manager for the realm: %s, LDAP Connection URL: %s, LDAP Base DN: %s", realm.getId(),
-                    ldapConfig.get(LdapConstants.CONNECTION_URL), ldapConfig.get(LdapConstants.BASE_DN));
+            logger.infof("Creating new partition manager for the realm: %s, LDAP Connection URL: %s, LDAP Base DN: %s, LDAP Vendor: %s", realm.getId(),
+                    ldapConfig.get(LdapConstants.CONNECTION_URL), ldapConfig.get(LdapConstants.BASE_DN), ldapConfig.get(LdapConstants.VENDOR));
             PartitionManager manager = createPartitionManager(ldapConfig);
             context = new PartitionManagerContext(ldapConfig, manager);
             partitionManagers.put(realm.getId(), context);
@@ -66,26 +66,43 @@ public class PartitionManagerRegistry {
         checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", "plain");
         checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", "off");
 
+        String vendor = ldapConfig.get(LdapConstants.VENDOR);
+
+        // RHDS is using "nsuniqueid" as unique identifier instead of "entryUUID"
+        if (vendor != null && vendor.equals(LdapConstants.VENDOR_RHDS)) {
+            checkSystemProperty(LDAPIdentityStoreConfiguration.ENTRY_IDENTIFIER_ATTRIBUTE_NAME, "nsuniqueid");
+        }
+
+        boolean activeDirectory = vendor != null && vendor.equals(LdapConstants.VENDOR_ACTIVE_DIRECTORY);
+
+        // Try to compute properties based on LDAP server type, but still allow to override them through System properties TODO: Should allow better way than overriding from System properties. Perhaps init from XML?
+        String ldapLoginName = getNameOfLDAPAttribute("keycloak.ldap.idm.loginName", UID, CN, activeDirectory);
+        String ldapFirstName = getNameOfLDAPAttribute("keycloak.ldap.idm.firstName", CN, "givenName", activeDirectory);
+        String ldapLastName = getNameOfLDAPAttribute("keycloak.ldap.idm.lastName", SN, SN, activeDirectory);
+        String ldapEmail =  getNameOfLDAPAttribute("keycloak.ldap.idm.email", EMAIL, EMAIL, activeDirectory);
+
+        logger.infof("LDAP Attributes mapping: loginName: %s, firstName: %s, lastName: %s, email: %s", ldapLoginName, ldapFirstName, ldapLastName, ldapEmail);
+
         // Use same mapping for User and Agent for now
         builder
             .named("SIMPLE_LDAP_STORE_CONFIG")
                 .stores()
                     .ldap()
                         .connectionProperties(connectionProps)
-                        .addCredentialHandler(LDAPAgentIgnoreCredentialHandler.class)
+                        .addCredentialHandler(LDAPKeycloakCredentialHandler.class)
                         .baseDN(ldapConfig.get(LdapConstants.BASE_DN))
                         .bindDN(ldapConfig.get(LdapConstants.BIND_DN))
                         .bindCredential(ldapConfig.get(LdapConstants.BIND_CREDENTIAL))
                         .url(ldapConfig.get(LdapConstants.CONNECTION_URL))
+                        .activeDirectory(activeDirectory)
                         .supportAllFeatures()
                         .mapping(User.class)
                             .baseDN(ldapConfig.get(LdapConstants.USER_DN_SUFFIX))
                             .objectClasses("inetOrgPerson", "organizationalPerson")
-                            .attribute("loginName", UID, true)
-                            .attribute("firstName", CN)
-                            .attribute("lastName", SN)
-                            .attribute("email", EMAIL)
-                            .readOnlyAttribute("createdDate", CREATE_TIMESTAMP);
+                            .attribute("loginName", ldapLoginName, true)
+                            .attribute("firstName", ldapFirstName)
+                            .attribute("lastName", ldapLastName)
+                            .attribute("email", ldapEmail);
 
         return new DefaultPartitionManager(builder.buildAll());
     }
@@ -96,6 +113,16 @@ public class PartitionManagerRegistry {
         }
     }
 
+    private String getNameOfLDAPAttribute(String systemPropertyName, String defaultAttrName, String defaultAttrNameInActiveDirectory, boolean activeDirectory) {
+        // System property has biggest priority if available
+        String sysProperty = System.getProperty(systemPropertyName);
+        if (sysProperty != null) {
+            return sysProperty;
+        }
+
+        return activeDirectory ? defaultAttrNameInActiveDirectory : defaultAttrName;
+    }
+
     private class PartitionManagerContext {
 
         private PartitionManagerContext(Map<String,String> config, PartitionManager manager) {
diff --git a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/RealmIdentityManagerProvider.java b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/RealmIdentityManagerProvider.java
index 3c00489..849aa98 100644
--- a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/RealmIdentityManagerProvider.java
+++ b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/RealmIdentityManagerProvider.java
@@ -1,6 +1,7 @@
 package org.keycloak.picketlink.realm;
 
 import org.keycloak.models.RealmModel;
+import org.keycloak.picketlink.AbstractIdentityManagerProvider;
 import org.keycloak.picketlink.IdentityManagerProvider;
 import org.picketlink.idm.IdentityManager;
 import org.picketlink.idm.PartitionManager;
@@ -8,7 +9,7 @@ import org.picketlink.idm.PartitionManager;
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
-public class RealmIdentityManagerProvider implements IdentityManagerProvider {
+public class RealmIdentityManagerProvider extends AbstractIdentityManagerProvider {
 
     private final PartitionManagerRegistry partitionManagerRegistry;
 
@@ -17,12 +18,7 @@ public class RealmIdentityManagerProvider implements IdentityManagerProvider {
     }
 
     @Override
-    public IdentityManager getIdentityManager(RealmModel realm) {
-        PartitionManager partitionManager = partitionManagerRegistry.getPartitionManager(realm);
-        return partitionManager.createIdentityManager();
-    }
-
-    @Override
-    public void close() {
+    protected PartitionManager getPartitionManager(RealmModel realm) {
+        return partitionManagerRegistry.getPartitionManager(realm);
     }
 }
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 1ad65e4..18021da 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
@@ -62,7 +62,7 @@ public class AuthProvidersIntegrationTest {
 
             // Configure LDAP
             ldapRule.getEmbeddedServer().setupLdapInRealm(appRealm);
-            LdapTestUtils.setLdapPassword(providerSession, appRealm, "john", "password");
+            LdapTestUtils.setLdapPassword(providerSession, appRealm, "johnkeycloak", "password");
         }
     });
 
@@ -135,7 +135,7 @@ public class AuthProvidersIntegrationTest {
     @Test
     public void loginLdap() {
         loginPage.open();
-        loginPage.login("john", "password");
+        loginPage.login("johnkeycloak", "password");
 
         Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
         Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
@@ -186,20 +186,19 @@ public class AuthProvidersIntegrationTest {
     @Test
     public void passwordChangeLdap() throws Exception {
         changePasswordPage.open();
-        loginPage.login("john", "password");
+        loginPage.login("johnkeycloak", "password");
         changePasswordPage.changePassword("password", "new-password", "new-password");
 
         Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
 
         changePasswordPage.logout();
 
-//        TODO: Disabled until https://issues.jboss.org/browse/PLINK-384 is released and updated
-//        loginPage.open();
-//        loginPage.login("john", "password");
-//        Assert.assertEquals("Invalid username or password.", loginPage.getError());
+        loginPage.open();
+        loginPage.login("johnkeycloak", "password");
+        Assert.assertEquals("Invalid username or password.", loginPage.getError());
 
         loginPage.open();
-        loginPage.login("john", "new-password");
+        loginPage.login("johnkeycloak", "new-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 0debe0b..386a9d6 100644
--- a/testsuite/integration/src/test/resources/ldap/users.ldif
+++ b/testsuite/integration/src/test/resources/ldap/users.ldif
@@ -19,12 +19,12 @@ objectclass: top
 objectclass: organizationalUnit
 ou: Groups
 
-dn: uid=john,ou=People,dc=keycloak,dc=org
+dn: uid=johnkeycloak,ou=People,dc=keycloak,dc=org
 objectclass: top
 objectclass: uidObject
 objectclass: person
 objectclass: inetOrgPerson
-uid: john
+uid: johnkeycloak
 cn: John
 sn: Doe
 mail: john@email.org