keycloak-aplcache

Changes

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestConfiguration.java 151(+0 -151)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPExampleServlet.java 63(+0 -63)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperTest.java 579(+0 -579)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPMSADFullNameTest.java 371(+0 -371)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPMSADMapperTest.java 176(+0 -176)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPMultipleAttributesTest.java 270(+0 -270)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java 1294(+0 -1294)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPSyncTest.java 381(+0 -381)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPGroupMapperNoImportTest.java 378(+0 -378)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPMultipleAttributesNoImportTest.java 112(+0 -112)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPProvidersIntegrationNoImportTest.java 989(+0 -989)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java 140(+0 -140)

testsuite/integration-deprecated/src/test/resources/ldap/ldap-app-keycloak.json 10(+0 -10)

testsuite/integration-deprecated/src/test/resources/ldap/ldap-connection.properties 26(+0 -26)

testsuite/integration-deprecated/src/test/resources/ldap/users.ldif 25(+0 -25)

Details

diff --git a/misc/Testsuite.md b/misc/Testsuite.md
index ad7f61d..df7bfc6 100644
--- a/misc/Testsuite.md
+++ b/misc/Testsuite.md
@@ -16,6 +16,10 @@ By default the testsuite uses an embedded H2 database to test with other databas
 Test utils
 ==========
 
+All the utils can be executed from the directory testsuite/utils:
+
+    cd testsuite/utils
+
 Keycloak server
 ---------------
 
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestLDAPResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestLDAPResource.java
new file mode 100644
index 0000000..bae7cbe
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestLDAPResource.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.rest.resource;
+
+import java.util.Map;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
+import org.keycloak.storage.ldap.LDAPUtils;
+import org.keycloak.storage.ldap.idm.model.LDAPObject;
+import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
+import org.keycloak.storage.ldap.mappers.membership.MembershipType;
+import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory;
+import org.keycloak.testsuite.util.LDAPTestUtils;
+import org.keycloak.utils.MediaType;
+
+import static org.keycloak.testsuite.util.LDAPTestUtils.getGroupDescriptionLDAPAttrName;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TestLDAPResource {
+
+    private final KeycloakSession session;
+    private final RealmModel realm;
+
+    public TestLDAPResource(KeycloakSession session, RealmModel realm) {
+        this.session = session;
+        this.realm = realm;
+    }
+
+
+    /**
+     * @param ldapCfg configuration of LDAP provider
+     * @param importEnabled specify if LDAP provider will have import enabled
+     * @return ID of newly created provider
+     */
+    @POST
+    @Path("/create-ldap-provider")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public String createLDAPProvider(Map<String,String> ldapCfg, @QueryParam("import") boolean importEnabled) {
+        MultivaluedHashMap<String, String> ldapConfig = toComponentConfig(ldapCfg);
+        ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true");
+        ldapConfig.putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.WRITABLE.toString());
+
+        UserStorageProviderModel model = new UserStorageProviderModel();
+        model.setLastSync(0);
+        model.setChangedSyncPeriod(-1);
+        model.setFullSyncPeriod(-1);
+        model.setName("test-ldap");
+        model.setPriority(0);
+        model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME);
+        model.setConfig(ldapConfig);
+
+        model.setImportEnabled(importEnabled);
+
+        model.setCachePolicy(UserStorageProviderModel.CachePolicy.MAX_LIFESPAN);
+        model.setMaxLifespan(600000); // Lifetime is 10 minutes
+
+        ComponentModel ldapModel = realm.addComponentModel(model);
+        return ldapModel.getId();
+    }
+
+
+    private static MultivaluedHashMap<String, String> toComponentConfig(Map<String, String> ldapConfig) {
+        MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+        for (Map.Entry<String, String> entry : ldapConfig.entrySet()) {
+            config.add(entry.getKey(), entry.getValue());
+
+        }
+        return config;
+    }
+
+
+    /**
+     * Prepare groups LDAP tests. Creates some LDAP mappers as well as some built-in GRoups and users in LDAP
+     */
+    @POST
+    @Path("/configure-groups")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void prepareGroupsLDAPTest() {
+        LDAPTestUtils.addLocalUser(session, realm, "mary", "mary@test.com", "password-app");
+        LDAPTestUtils.addLocalUser(session, realm, "john", "john@test.com", "password-app");
+
+        ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(session, realm);
+        LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+        String descriptionAttrName = getGroupDescriptionLDAPAttrName(ldapFedProvider);
+
+        // Add group mapper
+        LDAPTestUtils.addOrUpdateGroupMapper(realm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName);
+
+        // Remove all LDAP groups
+        LDAPTestUtils.removeAllLDAPGroups(session, realm, ldapModel, "groupsMapper");
+
+        // Add some groups for testing
+        LDAPObject group1 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "group1", descriptionAttrName, "group1 - description");
+        LDAPObject group11 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "group11");
+        LDAPObject group12 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "group12", descriptionAttrName, "group12 - description");
+
+        LDAPObject defaultGroup1 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "defaultGroup1", descriptionAttrName, "Default Group1 - description");
+        LDAPObject defaultGroup11 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "defaultGroup11");
+        LDAPObject defaultGroup12 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "defaultGroup12", descriptionAttrName, "Default Group12 - description");
+
+        LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group11, false);
+        LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group12, true);
+
+        LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", defaultGroup1, defaultGroup11, false);
+        LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", defaultGroup1, defaultGroup12, true);
+
+        // Sync LDAP groups to Keycloak DB
+        ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(realm, ldapModel, "groupsMapper");
+        new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(realm);
+
+        realm.addDefaultGroup(KeycloakModelUtils.findGroupByPath(realm, "/defaultGroup1/defaultGroup11"));
+        realm.addDefaultGroup(KeycloakModelUtils.findGroupByPath(realm, "/defaultGroup1/defaultGroup12"));
+
+        // Delete all LDAP users
+        LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, realm);
+
+        // Add some LDAP users for testing
+        LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, realm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
+        LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1");
+
+        LDAPObject mary = LDAPTestUtils.addLDAPUser(ldapFedProvider, realm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678");
+        LDAPTestUtils.updateLDAPPassword(ldapFedProvider, mary, "Password1");
+
+        LDAPObject rob = LDAPTestUtils.addLDAPUser(ldapFedProvider, realm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910");
+        LDAPTestUtils.updateLDAPPassword(ldapFedProvider, rob, "Password1");
+
+        LDAPObject james = LDAPTestUtils.addLDAPUser(ldapFedProvider, realm, "jameskeycloak", "James", "Brown", "james@email.org", null, "8910");
+        LDAPTestUtils.updateLDAPPassword(ldapFedProvider, james, "Password1");
+    }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
index 1e5b9ab..7ea49d2 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
@@ -20,6 +20,7 @@ package org.keycloak.testsuite.rest;
 import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.spi.BadRequestException;
 import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.common.util.Time;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.events.Event;
@@ -35,6 +36,7 @@ import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RealmProvider;
 import org.keycloak.models.UserCredentialModel;
@@ -54,6 +56,8 @@ import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.resource.RealmResourceProvider;
 import org.keycloak.services.scheduled.ClearExpiredUserSessions;
 import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
 import org.keycloak.testsuite.components.TestProvider;
 import org.keycloak.testsuite.components.TestProviderFactory;
 import org.keycloak.testsuite.events.EventsListenerProvider;
@@ -63,6 +67,7 @@ import org.keycloak.testsuite.forms.PassThroughClientAuthenticator;
 import org.keycloak.testsuite.rest.representation.AuthenticatorState;
 import org.keycloak.testsuite.rest.resource.TestCacheResource;
 import org.keycloak.testsuite.rest.resource.TestJavascriptResource;
+import org.keycloak.testsuite.rest.resource.TestLDAPResource;
 import org.keycloak.testsuite.rest.resource.TestingExportImportResource;
 import org.keycloak.testsuite.runonserver.ModuleUtil;
 import org.keycloak.testsuite.runonserver.FetchOnServer;
@@ -572,6 +577,13 @@ public class TestingResourceProvider implements RealmResourceProvider {
     }
 
 
+    @Path("/ldap/{realm}")
+    public TestLDAPResource ldap(@PathParam("realm") final String realmName) {
+        RealmModel realm = session.realms().getRealmByName(realmName);
+        return new TestLDAPResource(session, realm);
+    }
+
+
     @Override
     public void close() {
     }
@@ -682,6 +694,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
         return reps;
     }
 
+
     @GET
     @Path("/identity-config")
     @Produces(MediaType.APPLICATION_JSON)
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml
index 467601a..9d818c3 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml
@@ -32,6 +32,7 @@
         <module name="org.keycloak.keycloak-services"/>
         <module name="org.keycloak.keycloak-model-infinispan"/>
         <module name="org.keycloak.keycloak-model-jpa"/>
+        <module name="org.keycloak.keycloak-ldap-federation"/>
         <module name="org.infinispan"/>
         <module name="org.infinispan.client.hotrod"/>
         <module name="org.jboss.logging"/>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java
index 09c280b..92c71fb 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java
@@ -97,7 +97,7 @@ public class KeycloakTestingClient {
         public <T> T fetch(FetchOnServer function, Class<T> clazz) throws RunOnServerException {
             try {
                 String s = fetchString(function);
-                return JsonSerialization.readValue(s, clazz);
+                return s==null ? null : JsonSerialization.readValue(s, clazz);
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingLDAPResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingLDAPResource.java
new file mode 100644
index 0000000..4eb45a1
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingLDAPResource.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.client.resources;
+
+import java.util.Map;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+
+import org.keycloak.utils.MediaType;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface TestingLDAPResource {
+
+
+    /**
+     * @param ldapCfg configuration of LDAP provider
+     * @param importEnabled specify if LDAP provider will have import enabled
+     * @return ID of newly created provider
+     */
+    @POST
+    @Path("/create-ldap-provider")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    String createLDAPProvider(Map<String,String> ldapCfg, @QueryParam("import") boolean importEnabled);
+
+
+    /**
+     * Prepare groups LDAP tests. Creates some LDAP mappers as well as some built-in GRoups and users in LDAP
+     */
+    @POST
+    @Path("/configure-groups")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    void prepareGroupsLDAPTest();
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
index 04daf8d..a217ec1 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
@@ -204,6 +204,9 @@ public interface TestingResource {
     @Path("/cache/{cache}")
     TestingCacheResource cache(@PathParam("cache") String cacheName);
 
+    @Path("/ldap/{realm}")
+    TestingLDAPResource ldap(@PathParam("realm") final String realmName);
+
     @POST
     @Path("/update-pass-through-auth-state")
     @Produces(MediaType.APPLICATION_JSON)
@@ -251,6 +254,7 @@ public interface TestingResource {
     @Produces(MediaType.APPLICATION_JSON)
     Map<String, TestProvider.DetailsRepresentation> getTestComponentDetails();
 
+
     @GET
     @Path("/identity-config")
     @Produces(MediaType.APPLICATION_JSON)
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LDAPRule.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LDAPRule.java
index 7aef684..1b9a4c6 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LDAPRule.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LDAPRule.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.testsuite.util;
 
+import org.junit.Assume;
 import org.junit.rules.ExternalResource;
 import org.keycloak.models.LDAPConstants;
 import org.keycloak.util.ldap.LDAPEmbeddedServer;
@@ -39,14 +40,23 @@ public class LDAPRule extends ExternalResource {
 
     private static final String PROPERTY_CERTIFICATE_PASSWORD = "certificatePassword";
 
-    protected LDAPTestConfiguration ldapTestConfiguration;
-    protected LDAPEmbeddedServer ldapEmbeddedServer;
+    LDAPTestConfiguration ldapTestConfiguration;
+    private LDAPEmbeddedServer ldapEmbeddedServer;
+    private LDAPAssume assume;
+
+    public LDAPRule assumeTrue(LDAPAssume assume) {
+        this.assume = assume;
+        return this;
+    }
+
 
     @Override
     protected void before() throws Throwable {
         String connectionPropsLocation = getConnectionPropertiesLocation();
         ldapTestConfiguration = LDAPTestConfiguration.readConfiguration(connectionPropsLocation);
 
+        Assume.assumeTrue("Assumption in LDAPRule is false. Skiping the test", assume==null || assume.assumeTrue(ldapTestConfiguration));
+
         if (ldapTestConfiguration.isStartEmbeddedLdapServer()) {
             ldapEmbeddedServer = createServer();
             ldapEmbeddedServer.init();
@@ -75,8 +85,6 @@ public class LDAPRule extends ExternalResource {
         Properties defaultProperties = new Properties();
         defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_DSF, LDAPEmbeddedServer.DSF_INMEMORY);
         defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_LDIF_FILE, "classpath:ldap/users.ldif");
-        defaultProperties.setProperty(LDAPConstants.CONNECTION_URL, "ldaps://localhost:10636");
-        defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_BIND_PORT, "10636");
         defaultProperties.setProperty(PROPERTY_ENABLE_SSL, "true");
         defaultProperties.setProperty(PROPERTY_CERTIFICATE_PASSWORD, "secret");
         defaultProperties.setProperty(PROPERTY_KEYSTORE_FILE, this.getClass().getClassLoader().getResource(LDAPRule.PRIVATE_KEY).getFile());
@@ -91,4 +99,12 @@ public class LDAPRule extends ExternalResource {
     public int getSleepTime() {
         return ldapTestConfiguration.getSleepTime();
     }
+
+
+    /** Allows to run particular LDAP test just under specific conditions (eg. some test running just on Active Directory) **/
+    public interface LDAPAssume {
+
+        boolean assumeTrue(LDAPTestConfiguration ldapConfig);
+
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/AbstractLDAPTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/AbstractLDAPTest.java
new file mode 100644
index 0000000..8b7edd7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/AbstractLDAPTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.ldap;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+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.OAuthGrantPage;
+import org.keycloak.testsuite.pages.RegisterPage;
+import org.keycloak.testsuite.util.LDAPRule;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AbstractLDAPTest extends AbstractTestRealmKeycloakTest {
+
+    static final String TEST_REALM_NAME = "test";
+
+    protected static String ldapModelId;
+
+    @Page
+    protected AppPage appPage;
+
+    @Page
+    protected LoginPage loginPage;
+
+    @Page
+    protected RegisterPage registerPage;
+
+    @Page
+    protected AccountPasswordPage changePasswordPage;
+
+    @Page
+    protected AccountUpdateProfilePage profilePage;
+
+    @Page
+    protected OAuthGrantPage grantPage;
+
+
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+
+
+    @Override
+    public void importTestRealms() {
+        super.importTestRealms();
+        log.infof("Test realms imported");
+
+        createLDAPProvider();
+
+        afterImportTestRealm();
+    }
+
+
+    protected void createLDAPProvider() {
+        Map<String, String> cfg = getLDAPRule().getConfig();
+        ldapModelId = testingClient.testing().ldap(TEST_REALM_NAME).createLDAPProvider(cfg, isImportEnabled());
+        log.infof("LDAP Provider created");
+    }
+
+
+    protected boolean isImportEnabled() {
+        return true;
+    }
+
+    /**
+     * Executed once per class. It is executed after the test realm is imported
+     */
+    protected abstract void afterImportTestRealm();
+
+    protected abstract LDAPRule getLDAPRule();
+
+
+    protected ComponentRepresentation findMapperRepByName(String name) {
+        List<ComponentRepresentation> mappers = testRealm().components().query(ldapModelId, LDAPStorageMapper.class.getName());
+        for (ComponentRepresentation mapper : mappers) {
+            if (mapper.getName().equals(name)) {
+                return mapper;
+            }
+        }
+        return null;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperTest.java
new file mode 100755
index 0000000..beddc3a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperTest.java
@@ -0,0 +1,554 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.ldap;
+
+import java.util.List;
+import java.util.Set;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.storage.ldap.LDAPConfig;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.LDAPUtils;
+import org.keycloak.storage.ldap.idm.model.LDAPDn;
+import org.keycloak.storage.ldap.idm.model.LDAPObject;
+import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
+import org.keycloak.storage.ldap.mappers.membership.MembershipType;
+import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapper;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+import org.keycloak.testsuite.util.LDAPRule;
+import org.keycloak.testsuite.util.LDAPTestUtils;
+
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
+import static org.keycloak.testsuite.util.LDAPTestUtils.getGroupDescriptionLDAPAttrName;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPGroupMapperTest extends AbstractLDAPTest {
+
+    @ClassRule
+    public static LDAPRule ldapRule = new LDAPRule();
+
+    @Deployment
+    @TargetsContainer(AUTH_SERVER_CURRENT)
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class, AbstractLDAPTest.class)
+                .addPackages(true,
+                        "org.keycloak.testsuite",
+                        "org.keycloak.testsuite.federation.ldap");
+    }
+
+
+    @Override
+    protected LDAPRule getLDAPRule() {
+        return ldapRule;
+    }
+
+    @Override
+    protected void afterImportTestRealm() {
+        testingClient.testing().ldap(TEST_REALM_NAME).prepareGroupsLDAPTest();
+    }
+
+
+
+    @Test
+    public void test01_ldapOnlyGroupMappings() {
+        test01_ldapOnlyGroupMappings(true);
+    }
+
+
+    protected void test01_ldapOnlyGroupMappings(boolean importEnabled) {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "groupsMapper");
+            LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.LDAP_ONLY.toString());
+            appRealm.updateComponent(mapperModel);
+
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+
+            // 1 - Grant some groups in LDAP
+
+            // This group should already exists as it was imported from LDAP
+            GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
+            john.joinGroup(group1);
+
+            // This group should already exists as it was imported from LDAP
+            GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
+            mary.joinGroup(group11);
+
+            // This group should already exists as it was imported from LDAP
+            GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
+            john.joinGroup(group12);
+            mary.joinGroup(group12);
+        });
+
+
+        // 2 - Check that group mappings are not in local Keycloak DB (They are in LDAP).
+        if (importEnabled) {
+            testingClient.server().run(session -> {
+                LDAPTestContext ctx = LDAPTestContext.init(session);
+                RealmModel appRealm = ctx.getRealm();
+
+                UserModel johnDb = session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm);
+                Set<GroupModel> johnDbGroups = johnDb.getGroups();
+                Assert.assertEquals(2, johnDbGroups.size());
+            });
+        }
+
+
+        // 3 - Check that group mappings are in LDAP and hence available through federation
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
+            GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
+            GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+
+            Set<GroupModel> johnGroups = john.getGroups();
+            Assert.assertEquals(2, johnGroups.size());
+            Assert.assertTrue(johnGroups.contains(group1));
+            Assert.assertFalse(johnGroups.contains(group11));
+            Assert.assertTrue(johnGroups.contains(group12));
+
+            // 4 - Check through userProvider
+            List<UserModel> group1Members = session.users().getGroupMembers(appRealm, group1, 0, 10);
+            List<UserModel> group11Members = session.users().getGroupMembers(appRealm, group11, 0, 10);
+            List<UserModel> group12Members = session.users().getGroupMembers(appRealm, group12, 0, 10);
+
+            Assert.assertEquals(1, group1Members.size());
+            Assert.assertEquals("johnkeycloak", group1Members.get(0).getUsername());
+            Assert.assertEquals(1, group11Members.size());
+            Assert.assertEquals("marykeycloak", group11Members.get(0).getUsername());
+            Assert.assertEquals(2, group12Members.size());
+
+            // 4 - Delete some group mappings and check they are deleted
+
+            john.leaveGroup(group1);
+            john.leaveGroup(group12);
+
+            mary.leaveGroup(group1);
+            mary.leaveGroup(group12);
+
+            johnGroups = john.getGroups();
+            Assert.assertEquals(0, johnGroups.size());
+        });
+    }
+
+    @Test
+    public void test02_readOnlyGroupMappings() {
+        test02_readOnlyGroupMappings(true);
+    }
+
+
+
+    protected void test02_readOnlyGroupMappings(boolean importEnabled) {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "groupsMapper");
+            LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.READ_ONLY.toString());
+            appRealm.updateComponent(mapperModel);
+
+            UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+
+            GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
+            GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
+            GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
+
+            // Add some group mappings directly into LDAP
+            GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ctx.getLdapProvider(), appRealm);
+
+            LDAPObject maryLdap = ctx.getLdapProvider().loadLDAPUserByUsername(appRealm, "marykeycloak");
+            groupMapper.addGroupMappingInLDAP(appRealm, group1, maryLdap);
+            groupMapper.addGroupMappingInLDAP(appRealm, group11, maryLdap);
+
+            // Add some group mapping to model
+            mary.joinGroup(group12);
+
+            // Assert that mary has both LDAP and DB mapped groups
+            Set<GroupModel> maryGroups = mary.getGroups();
+            Assert.assertEquals(5, maryGroups.size());
+            Assert.assertTrue(maryGroups.contains(group1));
+            Assert.assertTrue(maryGroups.contains(group11));
+            Assert.assertTrue(maryGroups.contains(group12));
+        });
+
+        // Assert that access through DB will have just DB mapped groups
+        if (importEnabled) {
+            testingClient.server().run(session -> {
+                LDAPTestContext ctx = LDAPTestContext.init(session);
+                RealmModel appRealm = ctx.getRealm();
+
+                GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
+                GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
+                GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
+
+                UserModel maryDB = session.userLocalStorage().getUserByUsername("marykeycloak", appRealm);
+
+                Set<GroupModel> maryDBGroups = maryDB.getGroups();
+                Assert.assertFalse(maryDBGroups.contains(group1));
+                Assert.assertFalse(maryDBGroups.contains(group11));
+                Assert.assertTrue(maryDBGroups.contains(group12));
+
+                // Test the group mapping available for group12
+                List<UserModel> group12Members = session.users().getGroupMembers(appRealm, group12, 0, 10);
+                Assert.assertEquals(1, group12Members.size());
+                Assert.assertEquals("marykeycloak", group12Members.get(0).getUsername());
+
+                UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+                mary.leaveGroup(group12);
+            });
+        } else {
+            testingClient.server().run(session -> {
+                LDAPTestContext ctx = LDAPTestContext.init(session);
+                RealmModel appRealm = ctx.getRealm();
+
+                GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
+
+                // Test the group mapping NOT available for group12
+                List<UserModel> group12Members = session.users().getGroupMembers(appRealm, group12, 0, 10);
+                Assert.assertEquals(0, group12Members.size());
+            });
+        }
+
+
+        // Check through userProvider
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
+            GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
+            GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+
+            ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "groupsMapper");
+            GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ctx.getLdapProvider(), appRealm);
+            LDAPObject maryLdap = ctx.getLdapProvider().loadLDAPUserByUsername(appRealm, "marykeycloak");
+
+            List<UserModel> group1Members = session.users().getGroupMembers(appRealm, group1, 0, 10);
+            List<UserModel> group11Members = session.users().getGroupMembers(appRealm, group11, 0, 10);
+            List<UserModel> group12Members = session.users().getGroupMembers(appRealm, group12, 0, 10);
+            Assert.assertEquals(1, group1Members.size());
+            Assert.assertEquals("marykeycloak", group1Members.get(0).getUsername());
+            Assert.assertEquals(1, group11Members.size());
+            Assert.assertEquals("marykeycloak", group11Members.get(0).getUsername());
+
+            try {
+                mary.leaveGroup(group1);
+                Assert.fail("It wasn't expected to successfully delete LDAP group mappings in READ_ONLY mode");
+            } catch (ModelException expected) {
+            }
+
+            // Delete group mappings directly in LDAP
+            LDAPObject ldapGroup = groupMapper.loadLDAPGroupByName("group1");
+            groupMapper.deleteGroupMappingInLDAP(maryLdap, ldapGroup);
+
+            ldapGroup = groupMapper.loadLDAPGroupByName("group11");
+            groupMapper.deleteGroupMappingInLDAP(maryLdap, ldapGroup);
+        });
+    }
+
+
+    @Test
+    public void test03_importGroupMappings() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "groupsMapper");
+            LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.IMPORT.toString());
+            appRealm.updateComponent(mapperModel);
+
+            // Add some group mappings directly in LDAP
+            LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel());
+            GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm);
+
+            GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
+            GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
+            GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
+
+            LDAPObject robLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "robkeycloak");
+            groupMapper.addGroupMappingInLDAP(appRealm, group11, robLdap);
+            groupMapper.addGroupMappingInLDAP(appRealm, group12, robLdap);
+
+            // Get user and check that he has requested groups from LDAP
+            UserModel rob = session.users().getUserByUsername("robkeycloak", appRealm);
+            Set<GroupModel> robGroups = rob.getGroups();
+
+            Assert.assertFalse(robGroups.contains(group1));
+            Assert.assertTrue(robGroups.contains(group11));
+            Assert.assertTrue(robGroups.contains(group12));
+
+            // Delete some group mappings in LDAP and check that it doesn't have any effect and user still has groups
+            LDAPObject ldapGroup = groupMapper.loadLDAPGroupByName("group11");
+            groupMapper.deleteGroupMappingInLDAP(robLdap, ldapGroup);
+
+            ldapGroup = groupMapper.loadLDAPGroupByName("group12");
+            groupMapper.deleteGroupMappingInLDAP(robLdap, ldapGroup);
+
+            robGroups = rob.getGroups();
+            Assert.assertTrue(robGroups.contains(group11));
+            Assert.assertTrue(robGroups.contains(group12));
+
+            // Delete group mappings through model and verifies that user doesn't have them anymore
+            rob.leaveGroup(group11);
+            rob.leaveGroup(group12);
+            robGroups = rob.getGroups();
+            Assert.assertEquals(2, robGroups.size());
+        });
+    }
+
+
+    // KEYCLOAK-2682
+    @Test
+    public void test04_groupReferencingNonExistentMember() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            // Ignoring this test on ActiveDirectory as it's not allowed to have LDAP group referencing nonexistent member. KEYCLOAK-2682 was related to OpenLDAP TODO: Better solution than programmatic...
+            LDAPConfig config = ctx.getLdapProvider().getLdapIdentityStore().getConfig();
+            if (config.isActiveDirectory()) {
+                return;
+            }
+
+            ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "groupsMapper");
+            LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.LDAP_ONLY.toString());
+            appRealm.updateComponent(mapperModel);
+
+            String descriptionAttrName = getGroupDescriptionLDAPAttrName(ctx.getLdapProvider());
+
+            // 1 - Add some group to LDAP for testing
+            LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel());
+            GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm);
+            LDAPObject group2 = LDAPTestUtils.createLDAPGroup(session, appRealm, ctx.getLdapModel(), "group2", descriptionAttrName, "group2 - description");
+
+            // 2 - Add one existing user rob to LDAP group
+            LDAPObject jamesLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "jameskeycloak");
+            LDAPUtils.addMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group2, jamesLdap, false);
+
+            // 3 - Add non-existing user to LDAP group
+            LDAPDn nonExistentDn = LDAPDn.fromString(ldapProvider.getLdapIdentityStore().getConfig().getUsersDn());
+            nonExistentDn.addFirst(jamesLdap.getRdnAttributeName(), "nonexistent");
+            LDAPObject nonExistentLdapUser = new LDAPObject();
+            nonExistentLdapUser.setDn(nonExistentDn);
+            LDAPUtils.addMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group2, nonExistentLdapUser, true);
+
+            // 4 - Check group members. Just existing user rob should be present
+            groupMapper.syncDataFromFederationProviderToKeycloak(appRealm);
+            GroupModel kcGroup2 = KeycloakModelUtils.findGroupByPath(appRealm, "/group2");
+            List<UserModel> groupUsers = session.users().getGroupMembers(appRealm, kcGroup2, 0, 5);
+            Assert.assertEquals(1, groupUsers.size());
+            UserModel rob = groupUsers.get(0);
+            Assert.assertEquals("jameskeycloak", rob.getUsername());
+
+        });
+    }
+
+
+    // KEYCLOAK-5848
+    // Test GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE with custom 'Member-Of LDAP Attribute'. As a workaround, we are testing this with custom attribute "street"
+    // just because it's available on all the LDAP servers
+    @Test
+    public void test05_getGroupsFromUserMemberOfStrategyTest() throws Exception {
+        ComponentRepresentation groupMapperRep = findMapperRepByName("groupsMapper");
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            // Create street attribute mapper
+            LDAPTestUtils.addUserAttributeMapper(appRealm, ctx.getLdapModel(), "streetMapper", "street", LDAPConstants.STREET);
+
+            // Find DN of "group1"
+            ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "groupsMapper");
+            GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ctx.getLdapProvider(), appRealm);
+            LDAPObject ldapGroup = groupMapper.loadLDAPGroupByName("group1");
+            String ldapGroupDN = ldapGroup.getDn().toString();
+
+            // Create new user in LDAP. Add him some "street" referencing existing LDAP Group
+            LDAPObject carlos = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "carloskeycloak", "Carlos", "Doel", "carlos.doel@email.org", ldapGroupDN, "1234");
+            LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), carlos, "Password1");
+
+            // Update group mapper
+            LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel,
+                    GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, GroupMapperConfig.GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE,
+                    GroupMapperConfig.MEMBEROF_LDAP_ATTRIBUTE, LDAPConstants.STREET);
+            appRealm.updateComponent(mapperModel);
+        });
+
+        ComponentRepresentation streetMapperRep = findMapperRepByName("streetMapper");
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            // Get user in Keycloak. Ensure that he is member of requested group
+            UserModel carlos = session.users().getUserByUsername("carloskeycloak", appRealm);
+            Set<GroupModel> carlosGroups = carlos.getGroups();
+
+            GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
+            GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
+            GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
+
+            Assert.assertTrue(carlosGroups.contains(group1));
+            Assert.assertFalse(carlosGroups.contains(group11));
+            Assert.assertFalse(carlosGroups.contains(group12));
+
+            Assert.assertEquals(1, carlosGroups.size());
+        });
+
+        // Revert mappers
+        testRealm().components().component(streetMapperRep.getId()).remove();
+        groupMapperRep.getConfig().putSingle(GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE);
+        testRealm().components().component(groupMapperRep.getId()).update(groupMapperRep);
+    }
+
+
+    // KEYCLOAK-5017
+    @Test
+    public void test06_addingUserToNewKeycloakGroup() throws Exception {
+        // Add some groups to Keycloak
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            GroupModel group3 = appRealm.createGroup("group3");
+            session.realms().addTopLevelGroup(appRealm, group3);
+            GroupModel group31 = appRealm.createGroup("group31");
+            group3.addChild(group31);
+            GroupModel group32 = appRealm.createGroup("group32");
+            group3.addChild(group32);
+
+            GroupModel group4 = appRealm.createGroup("group4");
+            session.realms().addTopLevelGroup(appRealm, group4);
+
+            GroupModel group14 = appRealm.createGroup("group14");
+            GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
+            group1.addChild(group14);
+
+        });
+
+        // Add user to some newly created KC groups
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+
+            GroupModel group4 =  KeycloakModelUtils.findGroupByPath(appRealm, "/group4");
+            john.joinGroup(group4);
+
+            GroupModel group31 = KeycloakModelUtils.findGroupByPath(appRealm, "/group3/group31");
+            GroupModel group32 = KeycloakModelUtils.findGroupByPath(appRealm, "/group3/group32");
+
+            john.joinGroup(group31);
+            john.joinGroup(group32);
+
+            GroupModel group14 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group14");
+            john.joinGroup(group14);
+        });
+
+        // Check user group memberships
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+
+            GroupModel group14 =  KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group14");
+            GroupModel group3 =  KeycloakModelUtils.findGroupByPath(appRealm, "/group3");
+            GroupModel group31 = KeycloakModelUtils.findGroupByPath(appRealm, "/group3/group31");
+            GroupModel group32 = KeycloakModelUtils.findGroupByPath(appRealm, "/group3/group32");
+            GroupModel group4 =  KeycloakModelUtils.findGroupByPath(appRealm, "/group4");
+
+            Set<GroupModel> groups = john.getGroups();
+            Assert.assertTrue(groups.contains(group14));
+            Assert.assertFalse(groups.contains(group3));
+            Assert.assertTrue(groups.contains(group31));
+            Assert.assertTrue(groups.contains(group32));
+            Assert.assertTrue(groups.contains(group4));
+        });
+    }
+
+
+    @Test
+    public void test07_newUserDefaultGroupsImportModeTest() throws Exception {
+
+        // Check user group memberships
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "groupsMapper");
+            LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.IMPORT.toString());
+            appRealm.updateComponent(mapperModel);
+
+            UserModel david = session.users().addUser(appRealm, "davidkeycloak");
+
+            GroupModel defaultGroup11 =  KeycloakModelUtils.findGroupByPath(appRealm, "/defaultGroup1/defaultGroup11");
+            Assert.assertNotNull(defaultGroup11);
+
+            GroupModel defaultGroup12 =  KeycloakModelUtils.findGroupByPath(appRealm, "/defaultGroup1/defaultGroup12");
+            Assert.assertNotNull(defaultGroup12);
+
+            GroupModel group31 = KeycloakModelUtils.findGroupByPath(appRealm, "/group3/group31");
+            Assert.assertNotNull(group31);
+            GroupModel group32 = KeycloakModelUtils.findGroupByPath(appRealm, "/group3/group32");
+            Assert.assertNotNull(group32);
+            GroupModel group4 =  KeycloakModelUtils.findGroupByPath(appRealm, "/group4");
+            Assert.assertNotNull(group4);
+
+            Set<GroupModel> groups = david.getGroups();
+            Assert.assertTrue(groups.contains(defaultGroup11));
+            Assert.assertTrue(groups.contains(defaultGroup12));
+            Assert.assertFalse(groups.contains(group31));
+            Assert.assertFalse(groups.contains(group32));
+            Assert.assertFalse(groups.contains(group4));
+
+        });
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPMSADFullNameTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPMSADFullNameTest.java
new file mode 100644
index 0000000..8daf9da
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPMSADFullNameTest.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.ldap;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper;
+import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory;
+import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+import org.keycloak.testsuite.util.LDAPRule;
+import org.keycloak.testsuite.util.LDAPTestConfiguration;
+import org.keycloak.testsuite.util.LDAPTestUtils;
+
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
+
+/**
+ * Test for the MSAD setup with usernameAttribute=sAMAccountName, rdnAttribute=cn and fullNameMapper mapped to cn
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPMSADFullNameTest extends AbstractLDAPTest {
+
+    // Run this test just on MSAD and just when sAMAccountName is mapped to username
+    @ClassRule
+    public static LDAPRule ldapRule = new LDAPRule()
+            .assumeTrue((LDAPTestConfiguration ldapConfig) -> {
+
+                String vendor = ldapConfig.getLDAPConfig().get(LDAPConstants.VENDOR);
+                if (!vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY)) {
+                    return false;
+                }
+
+                String usernameAttr = ldapConfig.getLDAPConfig().get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
+                return usernameAttr.equalsIgnoreCase(LDAPConstants.SAM_ACCOUNT_NAME);
+
+            });
+
+
+
+    @Deployment
+    @TargetsContainer(AUTH_SERVER_CURRENT)
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class, AbstractLDAPTest.class)
+                .addPackages(true,
+                        "org.keycloak.testsuite",
+                        "org.keycloak.testsuite.federation.ldap");
+    }
+
+    @Override
+    protected LDAPRule getLDAPRule() {
+        return ldapRule;
+    }
+
+
+    @Override
+    protected void afterImportTestRealm() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+            UserStorageProviderModel ldapModel = ctx.getLdapModel();
+
+            LDAPTestUtils.addLocalUser(session, appRealm, "marykeycloak", "mary@test.com", "password-app");
+
+            LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
+
+            // Delete all LDAP users and add some new for testing
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
+
+            // Remove the mapper for "username-cn" and create new mapper for fullName
+            ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "username-cn");
+            Assert.assertNotNull(mapperModel);
+            appRealm.removeComponent(mapperModel);
+
+            mapperModel = KeycloakModelUtils.createComponentModel("fullNameWritable", ldapModel.getId(), FullNameLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
+                    FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN,
+                    FullNameLDAPStorageMapper.READ_ONLY, "false",
+                    FullNameLDAPStorageMapper.WRITE_ONLY, "true");
+            appRealm.addComponentModel(mapperModel);
+
+            appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
+        });
+    }
+
+
+
+//    @Test
+//    public void test01Sleep() throws Exception {
+//        Thread.sleep(1000000);
+//    }
+
+    @Test
+    public void test01_addUserWithoutFullName() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel john = session.users().addUser(appRealm, "johnkeycloak");
+            john.setEmail("johnkeycloak@email.cz");
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            Assert.assertNotNull(john.getFederationLink());
+            assertDnStartsWith(session, ctx, john, "cn=johnkeycloak");
+
+            session.users().removeUser(appRealm, john);
+        });
+    }
+
+
+    @Test
+    public void test02_registerUserWithFullName() {
+
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        registerPage.register("Johny", "Anthony", "johnyanth@check.cz", "johnkeycloak", "Password1", "Password1");
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            assertUser(session, ctx, john, "johnkeycloak", "Johny", "Anthony", true, "cn=Johny Anthony");
+
+            session.users().removeUser(appRealm, john);
+        });
+    }
+
+
+    @Test
+    public void test03_addUserWithFirstNameOnly() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel john = session.users().addUser(appRealm, "johnkeycloak");
+            john.setEmail("johnkeycloak@email.cz");
+            john.setFirstName("Johnyyy");
+            john.setEnabled(true);
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            assertUser(session, ctx, john, "johnkeycloak", "Johnyyy", "", true, "cn=Johnyyy");
+
+            session.users().removeUser(appRealm, john);
+        });
+    }
+
+
+    @Test
+    public void test04_addUserWithLastNameOnly() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel john = session.users().addUser(appRealm, "johnkeycloak");
+            john.setEmail("johnkeycloak@email.cz");
+            john.setLastName("Anthonyy");
+            john.setEnabled(true);
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            assertUser(session, ctx, john, "johnkeycloak", "", "Anthonyy", true, "cn=Anthonyy");
+
+            session.users().removeUser(appRealm, john);
+        });
+    }
+
+
+    @Test
+    public void test05_registerUserWithFullNameSpecialChars() {
+
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        registerPage.register("Jož,o", "Baříč", "johnyanth@check.cz", "johnkeycloak", "Password1", "Password1");
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            assertUser(session, ctx, john, "johnkeycloak", "Jož,o", "Baříč", true, "cn=Jož\\,o Baříč");
+
+            session.users().removeUser(appRealm, john);
+        });
+    }
+
+
+    @Test
+    public void test06_conflicts() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel john = session.users().addUser(appRealm, "existingkc");
+            john.setFirstName("John");
+            john.setLastName("Existing");
+            john.setEnabled(true);
+
+            UserModel john2 = session.users().addUser(appRealm, "existingkc1");
+            john2.setEnabled(true);
+        });
+
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        registerPage.register("John", "Existing", "johnyanth@check.cz", "existingkc", "Password1", "Password1");
+        Assert.assertEquals("Username already exists.", registerPage.getError());
+
+        registerPage.register("John", "Existing", "johnyanth@check.cz", "existingkc2", "Password1", "Password1");
+        appPage.logout();
+
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+        registerPage.register("John", "Existing", "johnyanth2@check.cz", "existingkc3", "Password1", "Password1");
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel existingKc = session.users().getUserByUsername("existingkc", appRealm);
+            assertUser(session, ctx, existingKc, "existingkc", "John", "Existing", true, "cn=John Existing");
+
+            UserModel existingKc1 = session.users().getUserByUsername("existingkc1", appRealm);
+            assertUser(session, ctx, existingKc1, "existingkc1", "", "", true, "cn=existingkc1");
+
+            UserModel existingKc2 = session.users().getUserByUsername("existingkc2", appRealm);
+            assertUser(session, ctx, existingKc2, "existingkc2", "John", "Existing", true, "cn=John Existing0");
+
+            UserModel existingKc3 = session.users().getUserByUsername("existingkc3", appRealm);
+            assertUser(session, ctx, existingKc3, "existingkc3", "John", "Existing", true, "cn=John Existing1");
+
+            session.users().removeUser(appRealm, existingKc);
+            session.users().removeUser(appRealm, existingKc1);
+            session.users().removeUser(appRealm, existingKc2);
+            session.users().removeUser(appRealm, existingKc3);
+        });
+    }
+
+
+    private static void assertUser(KeycloakSession session, LDAPTestContext ctx, UserModel user, String expectedUsername, String expectedFirstName, String expectedLastName, boolean expectedEnabled, String expectedDn) {
+        Assert.assertNotNull(user);
+        Assert.assertNotNull(user.getFederationLink());
+        Assert.assertEquals(user.getFederationLink(), ctx.getLdapModel().getId());
+        Assert.assertEquals(expectedUsername, user.getUsername());
+        Assert.assertEquals(expectedFirstName, user.getFirstName());
+        Assert.assertEquals(expectedLastName, user.getLastName());
+        Assert.assertEquals(expectedEnabled, user.isEnabled());
+        assertDnStartsWith(session, ctx, user, expectedDn);
+    }
+
+
+    private static void assertDnStartsWith(KeycloakSession session, LDAPTestContext ctx, UserModel user, String expectedRDn) {
+        String usersDn = ctx.getLdapProvider().getLdapIdentityStore().getConfig().getUsersDn();
+        String userDN = user.getFirstAttribute(LDAPConstants.LDAP_ENTRY_DN);
+        Assert.assertTrue(userDN.equalsIgnoreCase(expectedRDn + "," + usersDn));
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPMSADMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPMSADMapperTest.java
new file mode 100644
index 0000000..36f4450
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPMSADMapperTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.ldap;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.idm.model.LDAPObject;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+import org.keycloak.testsuite.util.LDAPRule;
+import org.keycloak.testsuite.util.LDAPTestConfiguration;
+import org.keycloak.testsuite.util.LDAPTestUtils;
+
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPMSADMapperTest extends AbstractLDAPTest {
+
+    // Run this test just on MSAD
+    @ClassRule
+    public static LDAPRule ldapRule = new LDAPRule()
+            .assumeTrue((LDAPTestConfiguration ldapConfig) -> {
+
+                // TODO: This is skipped as it requires that MSAD server is set to not allow weak passwords (There needs to be pwdProperties=1 set on MSAD side).
+                // TODO: Currently we can't rely on it. See KEYCLOAK-4276
+                return false;
+                // return LDAPConstants.VENDOR_ACTIVE_DIRECTORY.equals(vendor);
+
+            });
+
+
+
+    @Deployment
+    @TargetsContainer(AUTH_SERVER_CURRENT)
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class, AbstractLDAPTest.class)
+                .addPackages(true,
+                        "org.keycloak.testsuite",
+                        "org.keycloak.testsuite.federation.ldap");
+    }
+
+    @Override
+    protected LDAPRule getLDAPRule() {
+        return ldapRule;
+    }
+
+
+    @Override
+    protected void afterImportTestRealm() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            LDAPTestUtils.addLocalUser(session, appRealm, "marykeycloak", "mary@test.com", "password-app");
+
+            LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ctx.getLdapModel());
+
+            // Delete all LDAP users and add some new for testing
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel());
+            LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
+
+            LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1");
+
+            appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
+        });
+    }
+
+
+
+//    @Rule
+//    public WebRule webRule = new WebRule(this);
+//
+//    @WebResource
+//    protected OAuthClient oauth;
+//
+//    @WebResource
+//    protected WebDriver driver;
+//
+//    @WebResource
+//    protected AppPage appPage;
+//
+//    @WebResource
+//    protected RegisterPage registerPage;
+//
+//    @WebResource
+//    protected LoginPage loginPage;
+//
+//    @WebResource
+//    protected AccountUpdateProfilePage profilePage;
+//
+//    @WebResource
+//    protected AccountPasswordPage changePasswordPage;
+//
+
+    @Page
+    protected LoginPasswordUpdatePage passwordUpdatePage;
+
+
+    @Test
+    public void test01RegisterUserWithWeakPasswordFirst() {
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        // Weak password. This will fail to update password to MSAD due to password policy.
+        registerPage.register("firstName", "lastName", "email2@check.cz", "registerUserSuccess2", "password", "password");
+
+        // Another weak password
+        passwordUpdatePage.assertCurrent();
+        passwordUpdatePage.changePassword("pass", "pass");
+        Assert.assertEquals("Invalid password: new password doesn't match password policies.", passwordUpdatePage.getError());
+
+        // Strong password. Successfully update password and being redirected to the app
+        passwordUpdatePage.changePassword("Password1", "Password1");
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel user = session.users().getUserByUsername("registerUserSuccess2", appRealm);
+            Assert.assertNotNull(user);
+            Assert.assertNotNull(user.getFederationLink());
+            Assert.assertEquals(user.getFederationLink(), ctx.getLdapModel().getId());
+            Assert.assertEquals("registerusersuccess2", user.getUsername());
+            Assert.assertEquals("firstName", user.getFirstName());
+            Assert.assertEquals("lastName", user.getLastName());
+            Assert.assertTrue(user.isEnabled());
+            Assert.assertEquals(0, user.getRequiredActions().size());
+        });
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPMultipleAttributesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPMultipleAttributesTest.java
new file mode 100755
index 0000000..a465799
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPMultipleAttributesTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.ldap;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
+import org.keycloak.representations.IDToken;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.idm.model.LDAPObject;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+import org.keycloak.testsuite.util.LDAPRule;
+import org.keycloak.testsuite.util.LDAPTestConfiguration;
+import org.keycloak.testsuite.util.LDAPTestUtils;
+import org.keycloak.testsuite.util.OAuthClient;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPMultipleAttributesTest extends AbstractLDAPTest {
+
+
+    // Skip this test on MSAD due to lack of supported user multivalued attributes
+    @ClassRule
+    public static LDAPRule ldapRule = new LDAPRule()
+            .assumeTrue((LDAPTestConfiguration ldapConfig) -> {
+
+                String vendor = ldapConfig.getLDAPConfig().get(LDAPConstants.VENDOR);
+                return !LDAPConstants.VENDOR_ACTIVE_DIRECTORY.equals(vendor);
+
+            });
+
+
+    @Deployment
+    @TargetsContainer(AUTH_SERVER_CURRENT)
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class, AbstractLDAPTest.class)
+                .addPackages(true,
+                        "org.keycloak.testsuite",
+                        "org.keycloak.testsuite.federation.ldap");
+    }
+
+    @Override
+    protected LDAPRule getLDAPRule() {
+        return ldapRule;
+    }
+
+
+    @Override
+    protected void afterImportTestRealm() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ctx.getLdapModel());
+            LDAPTestUtils.addUserAttributeMapper(appRealm, ctx.getLdapModel(), "streetMapper", "street", LDAPConstants.STREET);
+
+            // Remove current users and add default users
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel());
+            LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
+
+            LDAPObject james = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", null, "88441");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, james, "Password1");
+
+            // User for testing duplicating surname and postalCode
+            LDAPObject bruce = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "bwilson", "Bruce", "Wilson", "bwilson@keycloak.org", "Elm 5", "88441", "77332");
+            bruce.setAttribute("sn", new LinkedHashSet<>(Arrays.asList("Wilson", "Schneider")));
+            ldapFedProvider.getLdapIdentityStore().update(bruce);
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, bruce, "Password1");
+
+            // Create ldap-portal client
+            ClientModel ldapClient = KeycloakModelUtils.createClient(appRealm, "ldap-portal");
+            ldapClient.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+            ldapClient.addRedirectUri("/ldap-portal");
+            ldapClient.addRedirectUri("/ldap-portal/*");
+            ldapClient.setManagementUrl("/ldap-portal");
+            ldapClient.addProtocolMapper(UserAttributeMapper.createClaimMapper("postalCode", "postal_code", "postal_code", "String", true, true, true));
+            ldapClient.addProtocolMapper(UserAttributeMapper.createClaimMapper("street", "street", "street", "String", true, true, false));
+            ldapClient.addScopeMapping(appRealm.getRole("user"));
+            ldapClient.setSecret("password");
+        });
+    }
+
+
+    @Test
+    public void testUserImport() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            session.userCache().clear();
+            RealmModel appRealm = ctx.getRealm();
+
+            // Test user imported in local storage now
+            UserModel user = session.users().getUserByUsername("jbrown", appRealm);
+            Assert.assertNotNull(session.userLocalStorage().getUserById(user.getId(), appRealm));
+            LDAPTestAsserts.assertUserImported(session.userLocalStorage(), appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", "88441");
+        });
+    }
+
+
+    @Test
+    public void testModel() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            session.userCache().clear();
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel user = session.users().getUserByUsername("bwilson", appRealm);
+            Assert.assertEquals("bwilson@keycloak.org", user.getEmail());
+            Assert.assertEquals("Bruce", user.getFirstName());
+
+            // There are 2 lastnames in ldif
+            Assert.assertTrue("Wilson".equals(user.getLastName()) || "Schneider".equals(user.getLastName()));
+
+            // Actually there are 2 postalCodes
+            List<String> postalCodes = user.getAttribute("postal_code");
+            assertPostalCodes(postalCodes, "88441", "77332");
+            List<String> tmp = new LinkedList<>();
+            tmp.addAll(postalCodes);
+            postalCodes = tmp;
+            postalCodes.remove("77332");
+            user.setAttribute("postal_code", postalCodes);
+
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel user = session.users().getUserByUsername("bwilson", appRealm);
+            List<String> postalCodes = user.getAttribute("postal_code");
+            assertPostalCodes(postalCodes, "88441");
+            List<String> tmp = new LinkedList<>();
+            tmp.addAll(postalCodes);
+            postalCodes = tmp;
+            postalCodes.add("77332");
+            user.setAttribute("postal_code", postalCodes);
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel user = session.users().getUserByUsername("bwilson", appRealm);
+            assertPostalCodes(user.getAttribute("postal_code"), "88441", "77332");
+        });
+    }
+
+    private static void assertPostalCodes(List<String> postalCodes, String... expectedPostalCodes) {
+        if (expectedPostalCodes == null && postalCodes.isEmpty()) {
+            return;
+        }
+
+
+        Assert.assertEquals(expectedPostalCodes.length, postalCodes.size());
+        for (String expected : expectedPostalCodes) {
+            if (!postalCodes.contains(expected)) {
+                Assert.fail("postalCode '" + expected + "' not in postalCodes: " + postalCodes);
+            }
+        }
+    }
+
+    @Test
+    public void ldapPortalEndToEndTest() {
+        // Login as bwilson
+        oauth.clientId("ldap-portal");
+        oauth.redirectUri("/ldap-portal");
+
+        loginPage.open();
+        loginPage.login("bwilson", "Password1");
+
+        String code = new OAuthClient.AuthorizationEndpointResponse(oauth).getCode();
+        OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+
+        Assert.assertEquals(200, response.getStatusCode());
+        IDToken idToken = oauth.verifyIDToken(response.getIdToken());
+
+        Assert.assertEquals("Bruce Wilson", idToken.getName());
+        Assert.assertEquals("Elm 5", idToken.getOtherClaims().get("street"));
+        Collection postalCodes = (Collection) idToken.getOtherClaims().get("postal_code");
+        Assert.assertEquals(2, postalCodes.size());
+        Assert.assertTrue(postalCodes.contains("88441"));
+        Assert.assertTrue(postalCodes.contains("77332"));
+
+        oauth.doLogout(response.getRefreshToken(), "password");
+
+        // Login as jbrown
+        loginPage.open();
+        loginPage.login("jbrown", "Password1");
+
+        code = new OAuthClient.AuthorizationEndpointResponse(oauth).getCode();
+        response = oauth.doAccessTokenRequest(code, "password");
+
+        org.keycloak.testsuite.Assert.assertEquals(200, response.getStatusCode());
+        idToken = oauth.verifyIDToken(response.getIdToken());
+
+        Assert.assertEquals("James Brown", idToken.getName());
+        Assert.assertNull(idToken.getOtherClaims().get("street"));
+        postalCodes = (Collection) idToken.getOtherClaims().get("postal_code");
+        Assert.assertEquals(1, postalCodes.size());
+        Assert.assertTrue(postalCodes.contains("88441"));
+        Assert.assertFalse(postalCodes.contains("77332"));
+
+        oauth.doLogout(response.getRefreshToken(), "password");
+    }
+
+
+
+}
+
+
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersIntegrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersIntegrationTest.java
new file mode 100755
index 0000000..bac2dc1
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersIntegrationTest.java
@@ -0,0 +1,1089 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.ldap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.naming.AuthenticationException;
+import javax.ws.rs.core.Response;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CachedUserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.storage.ReadOnlyException;
+import org.keycloak.storage.StorageId;
+import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.storage.ldap.LDAPConfig;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.idm.model.LDAPObject;
+import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper;
+import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory;
+import org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapper;
+import org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapperFactory;
+import org.keycloak.storage.ldap.mappers.HardcodedLDAPGroupStorageMapper;
+import org.keycloak.storage.ldap.mappers.HardcodedLDAPGroupStorageMapperFactory;
+import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapper;
+import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapperFactory;
+import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
+import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapper;
+import org.keycloak.testsuite.AbstractAuthTest;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+import org.keycloak.testsuite.util.LDAPRule;
+import org.keycloak.testsuite.util.LDAPTestUtils;
+import org.keycloak.testsuite.util.OAuthClient;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPProvidersIntegrationTest extends AbstractLDAPTest {
+
+    @ClassRule
+    public static LDAPRule ldapRule = new LDAPRule();
+
+    @Deployment
+    @TargetsContainer(AUTH_SERVER_CURRENT)
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class, AbstractLDAPTest.class)
+                .addPackages(true,
+                        "org.keycloak.testsuite",
+                        "org.keycloak.testsuite.federation.ldap");
+    }
+
+
+    @Override
+    protected LDAPRule getLDAPRule() {
+        return ldapRule;
+    }
+
+    @Override
+    protected void afterImportTestRealm() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            LDAPTestUtils.addLocalUser(session, appRealm, "marykeycloak", "mary@test.com", "password-app");
+
+            LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ctx.getLdapModel());
+
+            // Delete all LDAP users and add some new for testing
+            LDAPTestUtils.removeAllLDAPUsers(ctx.getLdapProvider(), appRealm);
+
+            LDAPObject john = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john, "Password1");
+
+            LDAPObject existing = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678");
+
+            appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
+
+        });
+    }
+
+
+
+//    @Test
+//    @Ignore
+//    public void runit() throws Exception {
+//        Thread.sleep(10000000);
+//
+//    }
+
+    /**
+     * KEYCLOAK-3986
+     *
+     */
+    @Test
+    public void testSyncRegistrationOff() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            ctx.getLdapModel().put(LDAPConstants.SYNC_REGISTRATIONS, "false");
+            ctx.getRealm().updateComponent(ctx.getLdapModel());
+        });
+
+        UserRepresentation newUser1 = AbstractAuthTest.createUserRepresentation("newUser1", "newUser1@email.cz", null, null, true);
+        Response resp = testRealm().users().create(newUser1);
+        String userId = ApiUtil.getCreatedId(resp);
+        resp.close();
+
+        testRealm().users().get(userId).toRepresentation();
+        Assert.assertTrue(StorageId.isLocalStorage(userId));
+        Assert.assertNull(newUser1.getFederationLink());
+
+        // Revert
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            ctx.getLdapModel().getConfig().putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true");
+            ctx.getRealm().updateComponent(ctx.getLdapModel());
+        });
+    }
+
+
+    @Test
+    public void testRemoveImportedUsers() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            UserModel user = session.users().getUserByUsername("johnkeycloak", ctx.getRealm());
+            Assert.assertEquals(ctx.getLdapModel().getId(), user.getFederationLink());
+        });
+
+        adminClient.realm("test").userStorage().removeImportedUsers(ldapModelId);
+
+        testingClient.server().run(session -> {
+            RealmManager manager = new RealmManager(session);
+            RealmModel appRealm = manager.getRealm("test");
+            UserModel user = session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm);
+            Assert.assertNull(user);
+        });
+    }
+
+    // test name prefixed with zz to make sure it runs last as we are unlinking imported users
+    @Test
+    public void zzTestUnlinkUsers() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            UserModel user = session.users().getUserByUsername("johnkeycloak", ctx.getRealm());
+            Assert.assertEquals(ctx.getLdapModel().getId(), user.getFederationLink());
+        });
+
+        adminClient.realm("test").userStorage().unlink(ldapModelId);
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            UserModel user = session.users().getUserByUsername("johnkeycloak", ctx.getRealm());
+            Assert.assertNotNull(user);
+            Assert.assertNull(user.getFederationLink());
+        });
+    }
+
+    @Test
+    public void caseInSensitiveImport() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            LDAPObject jbrown2 = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), ctx.getRealm(), "JBrown2", "John", "Brown2", "jbrown2@email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), jbrown2, "Password1");
+            LDAPObject jbrown3 = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), ctx.getRealm(), "jbrown3", "John", "Brown3", "JBrown3@email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), jbrown3, "Password1");
+        });
+
+        loginSuccessAndLogout("jbrown2", "Password1");
+        loginSuccessAndLogout("JBrown2", "Password1");
+        loginSuccessAndLogout("jbrown2@email.org", "Password1");
+        loginSuccessAndLogout("JBrown2@email.org", "Password1");
+
+        loginSuccessAndLogout("jbrown3", "Password1");
+        loginSuccessAndLogout("JBrown3", "Password1");
+        loginSuccessAndLogout("jbrown3@email.org", "Password1");
+        loginSuccessAndLogout("JBrown3@email.org", "Password1");
+    }
+
+    private void loginSuccessAndLogout(String username, String password) {
+        loginPage.open();
+        loginPage.login(username, password);
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+        oauth.openLogout();
+    }
+
+    @Test
+    public void caseInsensitiveSearch() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            LDAPObject jbrown4 = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), ctx.getRealm(), "JBrown4", "John", "Brown4", "jbrown4@email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), jbrown4, "Password1");
+            LDAPObject jbrown5 = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), ctx.getRealm(), "jbrown5", "John", "Brown5", "JBrown5@Email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), jbrown5, "Password1");
+        });
+
+        // search by username
+        List<UserRepresentation> users = testRealm().users().search("JBROwn4", 0, 10);
+        UserRepresentation user4 = users.get(0);
+        Assert.assertEquals("jbrown4", user4.getUsername());
+        Assert.assertEquals("jbrown4@email.org", user4.getEmail());
+
+        // search by email
+        users = testRealm().users().search("JBROwn5@eMAil.org", 0, 10);
+        Assert.assertEquals(1, users.size());
+        UserRepresentation user5 = users.get(0);
+        Assert.assertEquals("jbrown5", user5.getUsername());
+        Assert.assertEquals("jbrown5@email.org", user5.getEmail());
+    }
+
+    @Test
+    public void deleteFederationLink() throws Exception {
+        // KEYCLOAK-4789: Login in client, which requires consent
+        oauth.clientId("third-party");
+        loginPage.open();
+        loginPage.login("johnkeycloak", "Password1");
+
+        grantPage.assertCurrent();
+        grantPage.accept();
+
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        ComponentRepresentation ldapRep = testRealm().components().component(ldapModelId).toRepresentation();
+        testRealm().components().component(ldapModelId).remove();
+
+        // User not available once LDAP provider was removed
+        loginPage.open();
+        loginPage.login("johnkeycloak", "Password1");
+        loginPage.assertCurrent();
+
+        Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        // Re-add LDAP provider
+        Map<String, String> cfg = getLDAPRule().getConfig();
+        ldapModelId = testingClient.testing().ldap(TEST_REALM_NAME).createLDAPProvider(cfg, isImportEnabled());
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            LDAPTestUtils.addZipCodeLDAPMapper(ctx.getRealm(), ctx.getLdapModel());
+        });
+
+        oauth.clientId("test-app");
+
+        loginLdap();
+
+    }
+
+    @Test
+    public void loginClassic() {
+        loginPage.open();
+        loginPage.login("marykeycloak", "password-app");
+
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+    }
+
+    @Test
+    public void loginLdap() {
+        loginPage.open();
+        loginPage.login("johnkeycloak", "Password1");
+
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        profilePage.open();
+        Assert.assertEquals("John", profilePage.getFirstName());
+        Assert.assertEquals("Doe", profilePage.getLastName());
+        Assert.assertEquals("john@email.org", profilePage.getEmail());
+    }
+
+    @Test
+    public void loginLdapWithDirectGrant() throws Exception {
+        OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "johnkeycloak", "Password1");
+        Assert.assertEquals(200, response.getStatusCode());
+        AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
+
+        response = oauth.doGrantAccessTokenRequest("password", "johnkeycloak", "");
+        Assert.assertEquals(401, response.getStatusCode());
+    }
+
+    @Test
+    public void loginLdapWithEmail() {
+        loginPage.open();
+        loginPage.login("john@email.org", "Password1");
+
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+    }
+
+    @Test
+    public void loginLdapWithoutPassword() {
+        loginPage.open();
+        loginPage.login("john@email.org", "");
+        Assert.assertEquals("Invalid username or password.", loginPage.getError());
+    }
+
+    @Test
+    public void passwordChangeLdap() throws Exception {
+        changePasswordPage.open();
+        loginPage.login("johnkeycloak", "Password1");
+        changePasswordPage.changePassword("Password1", "New-password1", "New-password1");
+
+        Assert.assertEquals("Your password has been updated.", profilePage.getSuccess());
+
+        changePasswordPage.logout();
+
+        loginPage.open();
+        loginPage.login("johnkeycloak", "Bad-password1");
+        Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        loginPage.open();
+        loginPage.login("johnkeycloak", "New-password1");
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        // Change password back to previous value
+        changePasswordPage.open();
+        changePasswordPage.changePassword("New-password1", "Password1", "Password1");
+        Assert.assertEquals("Your password has been updated.", profilePage.getSuccess());
+    }
+
+    @Test
+    public void registerExistingLdapUser() {
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        // check existing username
+        registerPage.register("firstName", "lastName", "email@mail.cz", "existing", "Password1", "Password1");
+        registerPage.assertCurrent();
+        Assert.assertEquals("Username already exists.", registerPage.getError());
+
+        // Check existing email
+        registerPage.register("firstName", "lastName", "existing@email.org", "nonExisting", "Password1", "Password1");
+        registerPage.assertCurrent();
+        Assert.assertEquals("Email already exists.", registerPage.getError());
+    }
+
+
+
+    //
+    // KEYCLOAK-4533
+    //
+    @Test
+    public void testLDAPUserDeletionImport() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            LDAPConfig config = ctx.getLdapProvider().getLdapIdentityStore().getConfig();
+
+            // Make sure mary is gone
+            LDAPTestUtils.removeLDAPUserByUsername(ctx.getLdapProvider(), ctx.getRealm(), config, "maryjane");
+
+            // Create the user in LDAP and register him
+
+            LDAPObject mary = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), ctx.getRealm(), "maryjane", "mary", "yram", "mj@testing.redhat.cz", null, "12398");
+            LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), mary, "Password1");
+
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            LDAPConfig config = ctx.getLdapProvider().getLdapIdentityStore().getConfig();
+
+            // Delete LDAP User
+            LDAPTestUtils.removeLDAPUserByUsername(ctx.getLdapProvider(), ctx.getRealm(), config, "maryjane");
+
+            // Make sure the deletion took place.
+            List<UserModel> deletedUsers = session.users().searchForUser("mary yram", ctx.getRealm());
+            Assert.assertTrue(deletedUsers.isEmpty());
+
+        });
+    }
+
+
+    @Test
+    public void registerUserLdapSuccess() {
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        registerPage.register("firstName", "lastName", "email2@check.cz", "registerUserSuccess2", "Password1", "Password1");
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        UserRepresentation user = ApiUtil.findUserByUsername(testRealm(),"registerUserSuccess2");
+        Assert.assertNotNull(user);
+        assertFederatedUserLink(user);
+        Assert.assertEquals("registerusersuccess2", user.getUsername());
+        Assert.assertEquals("firstName", user.getFirstName());
+        Assert.assertEquals("lastName", user.getLastName());
+        Assert.assertTrue(user.isEnabled());
+    }
+
+
+    protected void assertFederatedUserLink(UserRepresentation user) {
+        Assert.assertTrue(StorageId.isLocalStorage(user.getId()));
+        Assert.assertNotNull(user.getFederationLink());
+        Assert.assertEquals(user.getFederationLink(), ldapModelId);
+    }
+
+
+    @Test
+    public void testCaseSensitiveAttributeName() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(session, appRealm);
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            LDAPObject johnZip = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnzip", "John", "Zip", "johnzip@email.org", null, "12398");
+
+            // Remove default zipcode mapper and add the mapper for "POstalCode" to test case sensitivity
+            ComponentModel currentZipMapper = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "zipCodeMapper");
+            appRealm.removeComponent(currentZipMapper);
+            LDAPTestUtils.addUserAttributeMapper(appRealm, ldapModel, "zipCodeMapper-cs", "postal_code", "POstalCode");
+
+            // Fetch user from LDAP and check that postalCode is filled
+            UserModel user = session.users().getUserByUsername("johnzip", appRealm);
+            String postalCode = user.getFirstAttribute("postal_code");
+            Assert.assertEquals("12398", postalCode);
+
+        });
+    }
+
+    @Test
+    public void testCommaInUsername() {
+        Boolean skipTest = testingClient.server().fetch(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            boolean skip = false;
+
+            // Workaround as comma is not allowed in sAMAccountName on active directory. So we will skip the test for this configuration
+            LDAPConfig config = ctx.getLdapProvider().getLdapIdentityStore().getConfig();
+            if (config.isActiveDirectory() && config.getUsernameLdapAttribute().equals(LDAPConstants.SAM_ACCOUNT_NAME)) {
+                skip = true;
+            }
+
+            if (!skip) {
+                LDAPObject johnComma = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), ctx.getRealm(), "john,comma", "John", "Comma", "johncomma@email.org", null, "12387");
+                LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), johnComma, "Password1");
+
+                LDAPObject johnPlus = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), ctx.getRealm(), "john+plus,comma", "John", "Plus", "johnplus@email.org", null, "12387");
+                LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), johnPlus, "Password1");
+            }
+
+            return skip;
+
+        }, Boolean.class);
+
+        if (!skipTest) {
+            // Try to import the user with comma in username into Keycloak
+            loginSuccessAndLogout("john,comma", "Password1");
+            loginSuccessAndLogout("john+plus,comma", "Password1");
+        }
+    }
+
+
+    // TODO: Rather separate test class for fullNameMapper to better test all the possibilities
+    @Test
+    public void testFullNameMapper() {
+
+        ComponentRepresentation firstNameMapperRep = testingClient.server().fetch(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            // assert that user "fullnameUser" is not in local DB
+            Assert.assertNull(session.users().getUserByUsername("fullname", appRealm));
+
+            // Add the user with some fullName into LDAP directly. Ensure that fullName is saved into "cn" attribute in LDAP (currently mapped to model firstName)
+            ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(session, appRealm);
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "fullname", "James Dee", "Dee", "fullname@email.org", null, "4578");
+
+            // add fullname mapper to the provider and remove "firstNameMapper". For this test, we will simply map full name to the LDAP attribute, which was before firstName ( "givenName" on active directory, "cn" on other LDAP servers)
+            ComponentModel firstNameMapper =  LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "first name");
+            String ldapFirstNameAttributeName = firstNameMapper.getConfig().getFirst(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE);
+            appRealm.removeComponent(firstNameMapper);
+
+            ComponentRepresentation firstNameMapperRepp = ModelToRepresentation.toRepresentation(session, firstNameMapper, true);
+
+            ComponentModel fullNameMapperModel = KeycloakModelUtils.createComponentModel("full name", ldapModel.getId(), FullNameLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
+                    FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE, ldapFirstNameAttributeName,
+                    FullNameLDAPStorageMapper.READ_ONLY, "false");
+            appRealm.addComponentModel(fullNameMapperModel);
+
+            return firstNameMapperRepp;
+        }, ComponentRepresentation.class);
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
+            LDAPTestAsserts.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
+        });
+
+        // Assert user will be changed in LDAP too
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
+            fullnameUser.setFirstName("James2");
+            fullnameUser.setLastName("Dee2");
+        });
+
+
+        // Assert changed user available in Keycloak
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
+            LDAPTestAsserts.assertUserImported(session.users(), appRealm, "fullname", "James2", "Dee2", "fullname@email.org", "4578");
+
+            // Remove "fullnameUser" to assert he is removed from LDAP. Revert mappers to previous state
+            UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
+            session.users().removeUser(appRealm, fullnameUser);
+
+            // Revert mappers
+            ComponentModel fullNameMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "full name");
+            appRealm.removeComponent(fullNameMapperModel);
+        });
+
+        firstNameMapperRep.setId(null);
+        Response response = testRealm().components().add(firstNameMapperRep);
+        Assert.assertEquals(201, response.getStatus());
+        response.close();
+    }
+
+
+    @Test
+    public void testHardcodedAttributeMapperTest() throws Exception {
+        // Create hardcoded mapper for "description"
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            ComponentModel hardcodedMapperModel = KeycloakModelUtils.createComponentModel("hardcodedAttr-description", ctx.getLdapModel().getId(), HardcodedLDAPAttributeMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
+                    HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_NAME, "description",
+                    HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_VALUE, "some-${RANDOM}");
+            ctx.getRealm().addComponentModel(hardcodedMapperModel);
+        });
+
+        // Register new user
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        registerPage.register("firstName", "lastName", "email34@check.cz", "register123", "Password1", "Password1");
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            // See that user don't yet have any description
+            UserModel user = LDAPTestAsserts.assertUserImported(session.users(), appRealm, "register123", "firstName", "lastName", "email34@check.cz", null);
+            Assert.assertNull(user.getFirstAttribute("desc"));
+            Assert.assertNull(user.getFirstAttribute("description"));
+
+            // Remove hardcoded mapper for "description" and create regular userAttribute mapper for description
+            ComponentModel hardcodedMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "hardcodedAttr-description");
+            appRealm.removeComponent(hardcodedMapperModel);
+
+            ComponentModel userAttrMapper = LDAPTestUtils.addUserAttributeMapper(appRealm, ctx.getLdapModel(), "desc-attribute-mapper", "desc", "description");
+            userAttrMapper.put(UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "true");
+            appRealm.updateComponent(userAttrMapper);
+        });
+
+        // Check that user has description on him now
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            session.userCache().evict(appRealm, session.users().getUserByUsername("register123", appRealm));
+
+            // See that user don't yet have any description
+            UserModel user = session.users().getUserByUsername("register123", appRealm);
+            Assert.assertNull(user.getFirstAttribute("description"));
+            Assert.assertNotNull(user.getFirstAttribute("desc"));
+            String desc = user.getFirstAttribute("desc");
+            Assert.assertTrue(desc.startsWith("some-"));
+            Assert.assertEquals(35, desc.length());
+
+            // Remove mapper for "description"
+            ComponentModel userAttrMapper = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "desc-attribute-mapper");
+            appRealm.removeComponent(userAttrMapper);
+        });
+    }
+
+
+    @Test
+    public void testHardcodedRoleMapper() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+            RoleModel hardcodedRole = appRealm.addRole("hardcoded-role");
+
+            // assert that user "johnkeycloak" doesn't have hardcoded role
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            Assert.assertFalse(john.hasRole(hardcodedRole));
+
+            ComponentModel hardcodedMapperModel = KeycloakModelUtils.createComponentModel("hardcoded role", ctx.getLdapModel().getId(),
+                    HardcodedLDAPRoleStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
+                    HardcodedLDAPRoleStorageMapper.ROLE, "hardcoded-role");
+            appRealm.addComponentModel(hardcodedMapperModel);
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            RoleModel hardcodedRole = appRealm.getRole("hardcoded-role");
+
+            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            Assert.assertTrue(john.hasRole(hardcodedRole));
+
+            // Can't remove user from hardcoded role
+            try {
+                john.deleteRoleMapping(hardcodedRole);
+                Assert.fail("Didn't expected to remove role mapping");
+            } catch (ModelException expected) {
+            }
+
+            // Revert mappers
+            ComponentModel hardcodedMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "hardcoded role");
+            appRealm.removeComponent(hardcodedMapperModel);
+        });
+    }
+
+    @Test
+    public void testHardcodedGroupMapper() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            GroupModel hardcodedGroup = appRealm.createGroup("hardcoded-group", "hardcoded-group");
+
+            // assert that user "johnkeycloak" doesn't have hardcoded group
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            Assert.assertFalse(john.isMemberOf(hardcodedGroup));
+
+            ComponentModel hardcodedMapperModel = KeycloakModelUtils.createComponentModel("hardcoded group",
+                    ctx.getLdapModel().getId(), HardcodedLDAPGroupStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
+                    HardcodedLDAPGroupStorageMapper.GROUP, "hardcoded-group");
+            appRealm.addComponentModel(hardcodedMapperModel);
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            GroupModel hardcodedGroup = appRealm.getGroupById("hardcoded-group");
+
+            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            Assert.assertTrue(john.isMemberOf(hardcodedGroup));
+
+            // Can't remove user from hardcoded role
+            try {
+                john.leaveGroup(hardcodedGroup);
+                Assert.fail("Didn't expected to leave group");
+            } catch (ModelException expected) {
+            }
+
+            // Revert mappers
+            ComponentModel hardcodedMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "hardcoded group");
+            appRealm.removeComponent(hardcodedMapperModel);
+        });
+    }
+
+    @Test
+    public void testImportExistingUserFromLDAP() throws Exception {
+        // Add LDAP user with same email like existing model user
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "marykeycloak", "Mary1", "Kelly1", "mary1@email.org", null, "123");
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "mary-duplicatemail", "Mary2", "Kelly2", "mary@test.com", null, "123");
+            LDAPObject marynoemail = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "marynoemail", "Mary1", "Kelly1", null, null, "123");
+            LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), marynoemail, "Password1");
+        });
+
+
+        // Try to import the duplicated LDAP user into Keycloak
+        loginPage.open();
+        loginPage.login("mary-duplicatemail", "password");
+        Assert.assertEquals("Email already exists.", loginPage.getError());
+
+        loginPage.login("mary1@email.org", "password");
+        Assert.assertEquals("Username already exists.", loginPage.getError());
+
+        loginSuccessAndLogout("marynoemail", "Password1");
+    }
+
+    @Test
+    public void testReadonly() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            ctx.getLdapModel().getConfig().putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.READ_ONLY.toString());
+            appRealm.updateComponent(ctx.getLdapModel());
+        });
+
+        UserRepresentation userRep = ApiUtil.findUserByUsername(testRealm(), "johnkeycloak");
+        assertFederatedUserLink(userRep);
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
+            Assert.assertNotNull(user);
+            try {
+                user.setEmail("error@error.com");
+                Assert.fail("should fail");
+            } catch (ReadOnlyException e) {
+
+            }
+            try {
+                user.setLastName("Berk");
+                Assert.fail("should fail");
+            } catch (ReadOnlyException e) {
+
+            }
+            try {
+                user.setFirstName("Bilbo");
+                Assert.fail("should fail");
+            } catch (ReadOnlyException e) {
+
+            }
+            try {
+                UserCredentialModel cred = UserCredentialModel.password("PoopyPoop1", true);
+                session.userCredentialManager().updateCredential(appRealm, user, cred);
+                Assert.fail("should fail");
+            } catch (ReadOnlyException e) {
+
+            }
+
+            Assert.assertTrue(session.users().removeUser(appRealm, user));
+        });
+
+        // Revert
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            ctx.getLdapModel().put(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.WRITABLE.toString());
+            appRealm.updateComponent(ctx.getLdapModel());
+
+            Assert.assertEquals(UserStorageProvider.EditMode.WRITABLE.toString(),
+                    appRealm.getComponent(ctx.getLdapModel().getId()).getConfig().getFirst(LDAPConstants.EDIT_MODE));
+        });
+    }
+
+    @Test
+    public void testRemoveFederatedUser() {
+        UserRepresentation user = ApiUtil.findUserByUsername(testRealm(), "registerusersuccess2");
+
+        // Case when this test was executed "alone" (User "registerusersuccess2" is registered inside registerUserLdapSuccess)
+        if (user == null) {
+            registerUserLdapSuccess();
+            user = ApiUtil.findUserByUsername(testRealm(), "registerusersuccess2");
+        }
+
+        assertFederatedUserLink(user);
+        testRealm().users().get(user.getId()).remove();
+        user = ApiUtil.findUserByUsername(testRealm(), "registerusersuccess2");
+        Assert.assertNull(user);
+    }
+
+    @Test
+    public void testSearch() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username1", "John1", "Doel1", "user1@email.org", null, "121");
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username2", "John2", "Doel2", "user2@email.org", null, "122");
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username3", "John3", "Doel3", "user3@email.org", null, "123");
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username4", "John4", "Doel4", "user4@email.org", null, "124");
+
+            // Users are not at local store at this moment
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("username1", appRealm));
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("username2", appRealm));
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("username3", appRealm));
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("username4", appRealm));
+
+            // search by username
+            session.users().searchForUser("username1", appRealm);
+            LDAPTestAsserts.assertUserImported(session.userLocalStorage(), appRealm, "username1", "John1", "Doel1", "user1@email.org", "121");
+
+            // search by email
+            session.users().searchForUser("user2@email.org", appRealm);
+            LDAPTestAsserts.assertUserImported(session.userLocalStorage(), appRealm, "username2", "John2", "Doel2", "user2@email.org", "122");
+
+            // search by lastName
+            session.users().searchForUser("Doel3", appRealm);
+            LDAPTestAsserts.assertUserImported(session.userLocalStorage(), appRealm, "username3", "John3", "Doel3", "user3@email.org", "123");
+
+            // search by firstName + lastName
+            session.users().searchForUser("John4 Doel4", appRealm);
+            LDAPTestAsserts.assertUserImported(session.userLocalStorage(), appRealm, "username4", "John4", "Doel4", "user4@email.org", "124");
+        });
+    }
+
+    @Test
+    public void testSearchWithCustomLDAPFilter() {
+        // Add custom filter for searching users
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            ctx.getLdapModel().getConfig().putSingle(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(|(mail=user5@email.org)(mail=user6@email.org))");
+            appRealm.updateComponent(ctx.getLdapModel());
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username5", "John5", "Doel5", "user5@email.org", null, "125");
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username6", "John6", "Doel6", "user6@email.org", null, "126");
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username7", "John7", "Doel7", "user7@email.org", null, "127");
+
+            // search by email
+            List<UserModel> list = session.users().searchForUser("user5@email.org", appRealm);
+            LDAPTestAsserts.assertUserImported(session.userLocalStorage(), appRealm, "username5", "John5", "Doel5", "user5@email.org", "125");
+
+            session.users().searchForUser("John6 Doel6", appRealm);
+            LDAPTestAsserts.assertUserImported(session.userLocalStorage(), appRealm, "username6", "John6", "Doel6", "user6@email.org", "126");
+
+            session.users().searchForUser("user7@email.org", appRealm);
+            session.users().searchForUser("John7 Doel7", appRealm);
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("username7", appRealm));
+
+            // Remove custom filter
+            ctx.getLdapModel().getConfig().remove(LDAPConstants.CUSTOM_USER_SEARCH_FILTER);
+            appRealm.updateComponent(ctx.getLdapModel());
+        });
+    }
+
+    @Test
+    public void testUnsynced() throws Exception {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserStorageProviderModel model = new UserStorageProviderModel(ctx.getLdapModel());
+            model.getConfig().putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.UNSYNCED.toString());
+            appRealm.updateComponent(model);
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
+            Assert.assertNotNull(user);
+            Assert.assertNotNull(user.getFederationLink());
+            Assert.assertEquals(user.getFederationLink(), ctx.getLdapModel().getId());
+
+            UserCredentialModel cred = UserCredentialModel.password("Candycand1", true);
+            session.userCredentialManager().updateCredential(appRealm, user, cred);
+            CredentialModel userCredentialValueModel = session.userCredentialManager().getStoredCredentialsByType(appRealm, user, CredentialModel.PASSWORD).get(0);
+            Assert.assertEquals(UserCredentialModel.PASSWORD, userCredentialValueModel.getType());
+            Assert.assertTrue(session.userCredentialManager().isValid(appRealm, user, cred));
+
+            // LDAP password is still unchanged
+            try {
+                LDAPObject ldapUser = ctx.getLdapProvider().loadLDAPUserByUsername(appRealm, "johnkeycloak");
+                ctx.getLdapProvider().getLdapIdentityStore().validatePassword(ldapUser, "Password1");
+            } catch (AuthenticationException ex) {
+                throw new RuntimeException(ex);
+            }
+
+            // User is deleted just locally
+            Assert.assertTrue(session.users().removeUser(appRealm, user));
+
+            // Assert user not available locally, but will be reimported from LDAP once searched
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm));
+            Assert.assertNotNull(session.users().getUserByUsername("johnkeycloak", appRealm));
+        });
+
+        // Revert
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            ctx.getLdapModel().getConfig().putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.WRITABLE.toString());
+
+            appRealm.updateComponent(ctx.getLdapModel());
+
+            Assert.assertEquals(UserStorageProvider.EditMode.WRITABLE.toString(), appRealm.getComponent(ctx.getLdapModel().getId()).getConfig().getFirst(LDAPConstants.EDIT_MODE));
+        });
+    }
+
+
+    @Test
+    public void testSearchByAttributes() {
+        testingClient.server().run(session -> {
+            final String ATTRIBUTE = "postal_code";
+            final String ATTRIBUTE_VALUE = "80330340";
+
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username8", "John8", "Doel8", "user8@email.org", null, ATTRIBUTE_VALUE);
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username9", "John9", "Doel9", "user9@email.org", null, ATTRIBUTE_VALUE);
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username10", "John10", "Doel10", "user10@email.org", null, "1210");
+
+            // Users are not at local store at this moment
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("username8", appRealm));
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("username9", appRealm));
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("username10", appRealm));
+
+            // search for user by attribute
+            List<UserModel> users = ctx.getLdapProvider().searchForUserByUserAttribute(ATTRIBUTE, ATTRIBUTE_VALUE, appRealm);
+            assertEquals(2, users.size());
+            assertNotNull(users.get(0).getAttribute(ATTRIBUTE));
+            assertEquals(1, users.get(0).getAttribute(ATTRIBUTE).size());
+            assertEquals(ATTRIBUTE_VALUE, users.get(0).getAttribute(ATTRIBUTE).get(0));
+
+            assertNotNull(users.get(1).getAttribute(ATTRIBUTE));
+            assertEquals(1, users.get(1).getAttribute(ATTRIBUTE).size());
+            assertEquals(ATTRIBUTE_VALUE, users.get(1).getAttribute(ATTRIBUTE).get(0));
+
+	        // user are now imported to local store
+            LDAPTestAsserts.assertUserImported(session.userLocalStorage(), appRealm, "username8", "John8", "Doel8", "user8@email.org", ATTRIBUTE_VALUE);
+            LDAPTestAsserts.assertUserImported(session.userLocalStorage(), appRealm, "username9", "John9", "Doel9", "user9@email.org", ATTRIBUTE_VALUE);
+            // but the one not looked up is not
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("username10", appRealm));
+
+        });
+    }
+
+    @Test
+    public void testLDAPUserRefreshCache() {
+        testingClient.server().run(session -> {
+            session.userCache().clear();
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel());
+            LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "johndirect", "John", "Direct", "johndirect@email.org", null, "1234");
+
+            // Fetch user from LDAP and check that postalCode is filled
+            UserModel user = session.users().getUserByUsername("johndirect", appRealm);
+            String postalCode = user.getFirstAttribute("postal_code");
+            Assert.assertEquals("1234", postalCode);
+
+            LDAPTestUtils.removeLDAPUserByUsername(ldapProvider, appRealm, ldapProvider.getLdapIdentityStore().getConfig(), "johndirect");
+        });
+
+        setTimeOffset(60 * 5); // 5 minutes in future, user should be cached still
+
+        testingClient.server().run(session -> {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+            CachedUserModel user = (CachedUserModel) session.users().getUserByUsername("johndirect", appRealm);
+            String postalCode = user.getFirstAttribute("postal_code");
+            String email = user.getEmail();
+            Assert.assertEquals("1234", postalCode);
+            Assert.assertEquals("johndirect@email.org", email);
+        });
+
+        setTimeOffset(60 * 20); // 20 minutes into future, cache will be invalidated
+
+        testingClient.server().run(session -> {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+            UserModel user = session.users().getUserByUsername("johndirect", appRealm);
+            Assert.assertNull(user);
+        });
+
+        setTimeOffset(0);
+    }
+
+    @Test
+    public void testCacheUser() {
+        String userId = testingClient.server().fetch(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            ctx.getLdapModel().setCachePolicy(UserStorageProviderModel.CachePolicy.NO_CACHE);
+            ctx.getRealm().updateComponent(ctx.getLdapModel());
+
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), ctx.getRealm(), "testCacheUser", "John", "Cached", "johndirect@test.com", null, "1234");
+
+            // Fetch user from LDAP and check that postalCode is filled
+            UserModel testedUser = session.users().getUserByUsername("testCacheUser", ctx.getRealm());
+
+            String usserId = testedUser.getId();
+            Assert.assertNotNull(usserId);
+            Assert.assertFalse(usserId.isEmpty());
+
+            return usserId;
+        }, String.class);
+
+        testingClient.server().run(session -> {
+
+            RealmModel appRealm = session.realms().getRealmByName(TEST_REALM_NAME);
+            UserModel testedUser = session.users().getUserById(userId, appRealm);
+            Assert.assertFalse(testedUser instanceof CachedUserModel);
+        });
+
+        // restore default cache policy
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            ctx.getLdapModel().setCachePolicy(UserStorageProviderModel.CachePolicy.MAX_LIFESPAN);
+            ctx.getLdapModel().setMaxLifespan(600000); // Lifetime is 10 minutes
+            ctx.getRealm().updateComponent(ctx.getLdapModel());
+        });
+
+
+        testingClient.server().run(session -> {
+            RealmModel appRealm = session.realms().getRealmByName(TEST_REALM_NAME);
+            UserModel testedUser = session.users().getUserById(userId, appRealm);
+            Assert.assertTrue(testedUser instanceof CachedUserModel);
+        });
+
+        setTimeOffset(60 * 5); // 5 minutes in future, should be cached still
+        testingClient.server().run(session -> {
+            RealmModel appRealm = session.realms().getRealmByName(TEST_REALM_NAME);
+            UserModel testedUser = session.users().getUserById(userId, appRealm);
+            Assert.assertTrue(testedUser instanceof CachedUserModel);
+        });
+
+        setTimeOffset(60 * 10); // 10 minutes into future, cache will be invalidated
+        testingClient.server().run(session -> {
+            RealmModel appRealm = session.realms().getRealmByName(TEST_REALM_NAME);
+            UserModel testedUser = session.users().getUserByUsername("thor", appRealm);
+            Assert.assertFalse(testedUser instanceof CachedUserModel);
+        });
+
+        setTimeOffset(0);
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSyncTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSyncTest.java
new file mode 100755
index 0000000..c48507b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSyncTest.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.ldap;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.services.managers.UserStorageSyncManager;
+import org.keycloak.storage.ldap.idm.model.LDAPObject;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserProvider;
+import org.keycloak.models.cache.UserCache;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
+import org.keycloak.storage.user.SynchronizationResult;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+import org.keycloak.testsuite.util.LDAPRule;
+import org.keycloak.testsuite.util.LDAPTestUtils;
+import org.keycloak.testsuite.util.WaitUtils;
+
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPSyncTest extends AbstractLDAPTest {
+
+    @ClassRule
+    public static LDAPRule ldapRule = new LDAPRule();
+
+    @Deployment
+    @TargetsContainer(AUTH_SERVER_CURRENT)
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class, AbstractLDAPTest.class)
+                .addPackages(true,
+                        "org.keycloak.testsuite",
+                        "org.keycloak.testsuite.federation.ldap");
+    }
+
+
+    @Override
+    protected LDAPRule getLDAPRule() {
+        return ldapRule;
+    }
+
+    @Override
+    protected void afterImportTestRealm() {
+        // Don't sync registrations in this test
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(session, appRealm);
+            ldapModel.put(LDAPConstants.SYNC_REGISTRATIONS, "false");
+            appRealm.updateComponent(ldapModel);
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            LDAPTestUtils.addLocalUser(session, appRealm, "marykeycloak", "mary@test.com", "password-app");
+
+            ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(session, appRealm);
+
+            LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
+
+            // Delete all LDAP users and add 5 new users for testing
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
+
+            for (int i=1 ; i<=5 ; i++) {
+                LDAPObject ldapUser = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", null, "12" + i);
+                LDAPTestUtils.updateLDAPPassword(ldapFedProvider, ldapUser, "Password1");
+            }
+
+        });
+    }
+
+
+//    @Test
+//    public void test01runit() throws Exception {
+//        Thread.sleep(10000000);
+//    }
+
+    @Test
+    public void test01LDAPSync() {
+        // wait a bit
+        WaitUtils.pause(ldapRule.getSleepTime());
+
+        // Sync 5 users from LDAP
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            UserStorageSyncManager usersSyncManager = new UserStorageSyncManager();
+            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+            SynchronizationResult syncResult = usersSyncManager.syncAllUsers(sessionFactory, "test", ctx.getLdapModel());
+            LDAPTestAsserts.assertSyncEquals(syncResult, 5, 0, 0, 0);
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel testRealm = ctx.getRealm();
+            UserProvider userProvider = session.userLocalStorage();
+
+            // Assert users imported
+            LDAPTestAsserts.assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org", "121");
+            LDAPTestAsserts.assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org", "122");
+            LDAPTestAsserts.assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org", "123");
+            LDAPTestAsserts.assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org", "124");
+            LDAPTestAsserts.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org", "125");
+
+            // Assert lastSync time updated
+            Assert.assertTrue(ctx.getLdapModel().getLastSync() > 0);
+            for (UserStorageProviderModel persistentFedModel : testRealm.getUserStorageProviders()) {
+                if (LDAPStorageProviderFactory.PROVIDER_NAME.equals(persistentFedModel.getProviderId())) {
+                    Assert.assertTrue(persistentFedModel.getLastSync() > 0);
+                } else {
+                    // Dummy provider has still 0
+                    Assert.assertEquals(0, persistentFedModel.getLastSync());
+                }
+            }
+        });
+
+        // wait a bit
+        WaitUtils.pause(ldapRule.getSleepTime());
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel testRealm = ctx.getRealm();
+            UserProvider userProvider = session.userLocalStorage();
+            UserStorageSyncManager usersSyncManager = new UserStorageSyncManager();
+
+            // Add user to LDAP and update 'user5' in LDAP
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), testRealm, "user6", "User6FN", "User6LN", "user6@email.org", null, "126");
+            LDAPObject ldapUser5 = ctx.getLdapProvider().loadLDAPUserByUsername(testRealm, "user5");
+            // NOTE: Changing LDAP attributes directly here
+            ldapUser5.setSingleAttribute(LDAPConstants.EMAIL, "user5Updated@email.org");
+            ldapUser5.setSingleAttribute(LDAPConstants.POSTAL_CODE, "521");
+            ctx.getLdapProvider().getLdapIdentityStore().update(ldapUser5);
+
+            // Assert still old users in local provider
+            LDAPTestAsserts.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org", "125");
+            Assert.assertNull(userProvider.getUserByUsername("user6", testRealm));
+
+            // Trigger partial sync
+            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+            SynchronizationResult syncResult = usersSyncManager.syncChangedUsers(sessionFactory, "test", ctx.getLdapModel());
+            LDAPTestAsserts.assertSyncEquals(syncResult, 1, 1, 0, 0);
+        });
+
+        testingClient.server().run(session -> {
+            RealmModel testRealm = session.realms().getRealm("test");
+            UserProvider userProvider = session.userLocalStorage();
+            // Assert users updated in local provider
+            LDAPTestAsserts.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5updated@email.org", "521");
+            LDAPTestAsserts.assertUserImported(userProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126");
+        });
+    }
+
+
+    @Test
+    public void test02duplicateUsernameAndEmailSync() {
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            LDAPTestUtils.addLocalUser(session, ctx.getRealm(), "user7", "user7@email.org", "password");
+
+            // Add user to LDAP with duplicated username "user7"
+            LDAPObject duplicatedLdapUser = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), ctx.getRealm(), "user7", "User7FN", "User7LN", "user7-something@email.org", null, "126");
+
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            // Assert syncing from LDAP fails due to duplicated username
+            SynchronizationResult result = new UserStorageSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ctx.getLdapModel());
+            Assert.assertEquals(1, result.getFailed());
+
+            // Remove "user7" from LDAP
+            LDAPObject duplicatedLdapUser = ctx.getLdapProvider().loadLDAPUserByUsername(ctx.getRealm(), "user7");
+            ctx.getLdapProvider().getLdapIdentityStore().remove(duplicatedLdapUser);
+
+            // Add user to LDAP with duplicated email "user7@email.org"
+            duplicatedLdapUser = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), ctx.getRealm(), "user7-something", "User7FNN", "User7LNL", "user7@email.org", null, "126");
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            // Assert syncing from LDAP fails due to duplicated email
+            SynchronizationResult result = new UserStorageSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ctx.getLdapModel());
+            Assert.assertEquals(1, result.getFailed());
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("user7-something", ctx.getRealm()));
+
+            // Update LDAP user to avoid duplicated email
+            LDAPObject duplicatedLdapUser = ctx.getLdapProvider().loadLDAPUserByUsername(ctx.getRealm(), "user7-something");
+            duplicatedLdapUser.setSingleAttribute(LDAPConstants.EMAIL, "user7-changed@email.org");
+            ctx.getLdapProvider().getLdapIdentityStore().update(duplicatedLdapUser);
+
+            // Assert user successfully synced now
+            result = new UserStorageSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ctx.getLdapModel());
+            Assert.assertEquals(0, result.getFailed());
+        });
+
+        // Assert user was imported. Use another transaction for that
+        testingClient.server().run(session -> {
+            RealmModel testRealm = session.realms().getRealm("test");
+            LDAPTestAsserts.assertUserImported(session.userLocalStorage(), testRealm, "user7-something", "User7FNN", "User7LNL", "user7-changed@email.org", "126");
+        });
+    }
+
+
+    // KEYCLOAK-1571
+    @Test
+    public void test03SameUUIDAndUsernameSync() {
+        String origUuidAttrName = testingClient.server().fetch(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            // Remove all users from model
+            for (UserModel user : session.userLocalStorage().getUsers(ctx.getRealm(), true)) {
+                session.userLocalStorage().removeUser(ctx.getRealm(), user);
+            }
+
+            // Change name of UUID attribute to same like usernameAttribute
+            String uidAttrName = ctx.getLdapProvider().getLdapIdentityStore().getConfig().getUsernameLdapAttribute();
+            String origUuidAttrNamee = ctx.getLdapModel().get(LDAPConstants.UUID_LDAP_ATTRIBUTE);
+            ctx.getLdapModel().put(LDAPConstants.UUID_LDAP_ATTRIBUTE, uidAttrName);
+
+            // Need to change this due to ApacheDS pagination bug (For other LDAP servers, pagination works fine) TODO: Remove once ApacheDS upgraded and pagination is fixed
+            ctx.getLdapModel().put(LDAPConstants.BATCH_SIZE_FOR_SYNC, "10");
+            ctx.getRealm().updateComponent(ctx.getLdapModel());
+
+            return origUuidAttrNamee;
+
+        }, String.class);
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+            SynchronizationResult syncResult = new UserStorageSyncManager().syncAllUsers(sessionFactory, "test", ctx.getLdapModel());
+            Assert.assertEquals(0, syncResult.getFailed());
+
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            // Assert users imported with correct LDAP_ID
+            LDAPTestAsserts.assertUserImported(session.users(), ctx.getRealm(), "user1", "User1FN", "User1LN", "user1@email.org", "121");
+            LDAPTestAsserts.assertUserImported(session.users(), ctx.getRealm(), "user2", "User2FN", "User2LN", "user2@email.org", "122");
+            UserModel user1 = session.users().getUserByUsername("user1", ctx.getRealm());
+            Assert.assertEquals("user1", user1.getFirstAttribute(LDAPConstants.LDAP_ID));
+        });
+
+        // Revert config changes
+        ComponentRepresentation ldapRep = testRealm().components().component(ldapModelId).toRepresentation();
+        if (origUuidAttrName == null) {
+            ldapRep.getConfig().remove(LDAPConstants.UUID_LDAP_ATTRIBUTE);
+        } else {
+            ldapRep.getConfig().putSingle(LDAPConstants.UUID_LDAP_ATTRIBUTE, origUuidAttrName);
+        }
+        testRealm().components().component(ldapModelId).update(ldapRep);
+    }
+
+
+    // KEYCLOAK-1728
+    @Test
+    public void test04MissingLDAPUsernameSync() {
+        String origUsernameAttrName = testingClient.server().fetch(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            // Remove all users from model
+            for (UserModel user : session.userLocalStorage().getUsers(ctx.getRealm(), true)) {
+                System.out.println("trying to delete user: " + user.getUsername());
+                UserCache userCache = session.userCache();
+                if (userCache != null) {
+                    userCache.evict(ctx.getRealm(), user);
+                }
+                session.userLocalStorage().removeUser(ctx.getRealm(), user);
+            }
+
+            // Add street mapper and add some user including street
+            ComponentModel streetMapper = LDAPTestUtils.addUserAttributeMapper(ctx.getRealm(), ctx.getLdapModel(), "streetMapper", "street", LDAPConstants.STREET);
+            LDAPObject streetUser = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), ctx.getRealm(), "user8", "User8FN", "User8LN", "user8@email.org", "user8street", "126");
+
+            // Change name of username attribute name to street
+            String origUsernameAttrNamee = ctx.getLdapModel().get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
+            ctx.getLdapModel().getConfig().putSingle(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, "street");
+
+            // Need to change this due to ApacheDS pagination bug (For other LDAP servers, pagination works fine) TODO: Remove once ApacheDS upgraded and pagination is fixed
+            ctx.getLdapModel().put(LDAPConstants.BATCH_SIZE_FOR_SYNC, "10");
+            ctx.getRealm().updateComponent(ctx.getLdapModel());
+
+            return origUsernameAttrNamee;
+
+        }, String.class);
+
+        // Just user8 synced. All others failed to sync
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+            SynchronizationResult syncResult = new UserStorageSyncManager().syncAllUsers(sessionFactory, "test", ctx.getLdapModel());
+            Assert.assertEquals(1, syncResult.getAdded());
+            Assert.assertTrue(syncResult.getFailed() > 0);
+        });
+
+        // Revert config changes
+        ComponentRepresentation ldapRep = testRealm().components().component(ldapModelId).toRepresentation();
+        if (origUsernameAttrName == null) {
+            ldapRep.getConfig().remove(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
+        } else {
+            ldapRep.getConfig().putSingle(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, origUsernameAttrName);
+        }
+        testRealm().components().component(ldapModelId).update(ldapRep);
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+
+            // Revert config changes
+            ComponentModel streetMapper = LDAPTestUtils.getSubcomponentByName(ctx.getRealm(), ctx.getLdapModel(), "streetMapper");
+            ctx.getRealm().removeComponent(streetMapper);
+        });
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestAsserts.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestAsserts.java
new file mode 100644
index 0000000..5125079
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestAsserts.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.ldap;
+
+import org.junit.Assert;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserProvider;
+import org.keycloak.representations.idm.SynchronizationResultRepresentation;
+import org.keycloak.storage.user.SynchronizationResult;
+
+/**
+ * Common LDAP asserts
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPTestAsserts {
+
+    public static UserModel assertUserImported(UserProvider userProvider, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail, String expectedPostalCode) {
+        UserModel user = userProvider.getUserByUsername(username, realm);
+        assertLoaded(user, username, expectedFirstName, expectedLastName, expectedEmail, expectedPostalCode);
+        return user;
+    }
+
+
+    public static void assertLoaded(UserModel user, String username, String expectedFirstName, String expectedLastName, String expectedEmail, String expectedPostalCode) {
+        Assert.assertNotNull(user);
+        Assert.assertEquals(expectedFirstName, user.getFirstName());
+        Assert.assertEquals(expectedLastName, user.getLastName());
+        Assert.assertEquals(expectedEmail, user.getEmail());
+        Assert.assertEquals(expectedPostalCode, user.getFirstAttribute("postal_code"));
+    }
+
+
+    public static void assertSyncEquals(SynchronizationResult syncResult, int expectedAdded, int expectedUpdated, int expectedRemoved, int expectedFailed) {
+        Assert.assertEquals(expectedAdded, syncResult.getAdded());
+        Assert.assertEquals(expectedUpdated, syncResult.getUpdated());
+        Assert.assertEquals(expectedRemoved, syncResult.getRemoved());
+        Assert.assertEquals(expectedFailed, syncResult.getFailed());
+    }
+
+
+    public static void assertSyncEquals(SynchronizationResultRepresentation syncResult, int expectedAdded, int expectedUpdated, int expectedRemoved, int expectedFailed) {
+        Assert.assertEquals(expectedAdded, syncResult.getAdded());
+        Assert.assertEquals(expectedUpdated, syncResult.getUpdated());
+        Assert.assertEquals(expectedRemoved, syncResult.getRemoved());
+        Assert.assertEquals(expectedFailed, syncResult.getFailed());
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestContext.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestContext.java
new file mode 100644
index 0000000..7f08633
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPTestContext.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.ldap;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.testsuite.util.LDAPTestUtils;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPTestContext {
+
+    private final RealmModel realm;
+    private final UserStorageProviderModel ldapModel;
+    private final LDAPStorageProvider ldapProvider;
+
+    public static LDAPTestContext init(KeycloakSession session) {
+        RealmModel testRealm = session.realms().getRealm(AbstractLDAPTest.TEST_REALM_NAME);
+        ComponentModel ldapCompModel = LDAPTestUtils.getLdapProviderModel(session, testRealm);
+        UserStorageProviderModel ldapModel = new UserStorageProviderModel(ldapCompModel);
+        LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+        return new LDAPTestContext(testRealm, ldapModel, ldapProvider);
+    }
+
+    private LDAPTestContext(RealmModel realm, UserStorageProviderModel ldapModel, LDAPStorageProvider ldapProvider) {
+        this.realm = realm;
+        this.ldapModel = ldapModel;
+        this.ldapProvider = ldapProvider;
+    }
+
+
+    public RealmModel getRealm() {
+        return realm;
+    }
+
+    public UserStorageProviderModel getLdapModel() {
+        return ldapModel;
+    }
+
+    public LDAPStorageProvider getLdapProvider() {
+        return ldapProvider;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/noimport/LDAPGroupMapperNoImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/noimport/LDAPGroupMapperNoImportTest.java
new file mode 100755
index 0000000..e09726a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/noimport/LDAPGroupMapperNoImportTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.ldap.noimport;
+
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.testsuite.federation.ldap.LDAPGroupMapperTest;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPGroupMapperNoImportTest extends LDAPGroupMapperTest {
+
+
+    @Override
+    protected boolean isImportEnabled() {
+        return false;
+    }
+
+
+    @Test
+    @Override
+    public void test01_ldapOnlyGroupMappings() {
+        test01_ldapOnlyGroupMappings(false);
+    }
+
+    @Test
+    @Override
+    public void test02_readOnlyGroupMappings() {
+        test02_readOnlyGroupMappings(false);
+    }
+
+    @Test
+    @Override
+    @Ignore
+    public void test03_importGroupMappings() {
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/noimport/LDAPMultipleAttributesNoImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/noimport/LDAPMultipleAttributesNoImportTest.java
new file mode 100755
index 0000000..2505df8
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/noimport/LDAPMultipleAttributesNoImportTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.ldap.noimport;
+
+import org.junit.Assert;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.testsuite.federation.ldap.LDAPMultipleAttributesTest;
+import org.keycloak.testsuite.federation.ldap.LDAPTestAsserts;
+import org.keycloak.testsuite.federation.ldap.LDAPTestContext;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPMultipleAttributesNoImportTest extends LDAPMultipleAttributesTest {
+
+
+    @Override
+    protected boolean isImportEnabled() {
+        return false;
+    }
+
+
+    @Test
+    public void testUserImport() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            session.userCache().clear();
+            RealmModel appRealm = ctx.getRealm();
+
+            // Test user NOT imported in local storage now. He is available just through "session.users()"
+            UserModel user = session.users().getUserByUsername("jbrown", appRealm);
+            Assert.assertNotNull(user);
+            Assert.assertNull(session.userLocalStorage().getUserById(user.getId(), appRealm));
+            LDAPTestAsserts.assertUserImported(session.users(), appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", "88441");
+        });
+    }
+
+
+}
+
+
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/noimport/LDAPProvidersIntegrationNoImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/noimport/LDAPProvidersIntegrationNoImportTest.java
new file mode 100755
index 0000000..f105f05
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/noimport/LDAPProvidersIntegrationNoImportTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.federation.ldap.noimport;
+
+import javax.ws.rs.core.Response;
+
+import org.junit.Assert;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.storage.StorageId;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper;
+import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory;
+import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
+import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapper;
+import org.keycloak.testsuite.federation.ldap.LDAPProvidersIntegrationTest;
+import org.keycloak.testsuite.federation.ldap.LDAPTestAsserts;
+import org.keycloak.testsuite.federation.ldap.LDAPTestContext;
+import org.keycloak.testsuite.util.LDAPTestUtils;
+
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPProvidersIntegrationNoImportTest extends LDAPProvidersIntegrationTest {
+
+
+    @Override
+    protected boolean isImportEnabled() {
+        return false;
+    }
+
+
+    @Override
+    protected void assertFederatedUserLink(UserRepresentation user) {
+        StorageId storageId = new StorageId(user.getId());
+        Assert.assertFalse(storageId.isLocal());
+        Assert.assertEquals(ldapModelId, storageId.getProviderId());
+
+        // TODO: It should be possibly LDAP_ID (LDAP UUID) used as an externalId inside storageId...
+        Assert.assertEquals(storageId.getExternalId(), user.getUsername());
+        Assert.assertNull(user.getFederationLink());
+    }
+
+
+    // No sense to test this in no-import mode
+    @Test
+    @Ignore
+    @Override
+    public void testRemoveImportedUsers() {
+    }
+
+
+    @Test
+    @Override
+    public void testSearch() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username1", "John1", "Doel1", "user1@email.org", null, "121");
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username2", "John2", "Doel2", "user2@email.org", null, "122");
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username3", "John3", "Doel3", "user3@email.org", null, "123");
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username4", "John4", "Doel4", "user4@email.org", null, "124");
+
+            // search by username
+            UserModel user = session.users().searchForUser("username1", appRealm).get(0);
+            LDAPTestAsserts.assertLoaded(user, "username1", "John1", "Doel1", "user1@email.org", "121");
+
+            // search by email
+            user = session.users().searchForUser("user2@email.org", appRealm).get(0);
+            LDAPTestAsserts.assertLoaded(user, "username2", "John2", "Doel2", "user2@email.org", "122");
+
+            // search by lastName
+            user = session.users().searchForUser("Doel3", appRealm).get(0);
+            LDAPTestAsserts.assertLoaded(user, "username3", "John3", "Doel3", "user3@email.org", "123");
+
+            // search by firstName + lastName
+            user = session.users().searchForUser("John4 Doel4", appRealm).get(0);
+            LDAPTestAsserts.assertLoaded(user, "username4", "John4", "Doel4", "user4@email.org", "124");
+        });
+    }
+
+
+    // No need to test this in no-import mode. There won't be any users in localStorage after LDAP search
+    @Test
+    @Ignore
+    @Override
+    public void testSearchByAttributes() {
+    }
+
+
+    @Test
+    @Override
+    public void testSearchWithCustomLDAPFilter() {
+        // Add custom filter for searching users
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            ctx.getLdapModel().getConfig().putSingle(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(|(mail=user5@email.org)(mail=user6@email.org))");
+            appRealm.updateComponent(ctx.getLdapModel());
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username5", "John5", "Doel5", "user5@email.org", null, "125");
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username6", "John6", "Doel6", "user6@email.org", null, "126");
+            LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "username7", "John7", "Doel7", "user7@email.org", null, "127");
+
+            // search by email
+            UserModel user = session.users().searchForUser("user5@email.org", appRealm).get(0);
+            LDAPTestAsserts.assertLoaded(user, "username5", "John5", "Doel5", "user5@email.org", "125");
+
+            user = session.users().searchForUser("John6 Doel6", appRealm).get(0);
+            LDAPTestAsserts.assertLoaded(user, "username6", "John6", "Doel6", "user6@email.org", "126");
+
+            Assert.assertTrue(session.users().searchForUser("user7@email.org", appRealm).isEmpty());
+            Assert.assertTrue(session.users().searchForUser("John7 Doel7", appRealm).isEmpty());
+
+            // Remove custom filter
+            ctx.getLdapModel().getConfig().remove(LDAPConstants.CUSTOM_USER_SEARCH_FILTER);
+            appRealm.updateComponent(ctx.getLdapModel());
+        });
+    }
+
+
+    @Test
+    @Override
+    @Ignore // Unsynced mode doesn't have much sense in no-import
+    public void testUnsynced() throws Exception {
+    }
+
+
+    @Test
+    @Override
+    @Ignore // Unlinking users doesn't have much sense in no-import
+    public void zzTestUnlinkUsers() {
+    }
+
+
+    @Test
+    public void testFullNameMapperWriteOnly() {
+        ComponentRepresentation firstNameMapperRep = testingClient.server().fetch(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            // assert that user "fullnameUser" is not in local DB
+            Assert.assertNull(session.users().getUserByUsername("fullname", appRealm));
+
+            // Add the user with some fullName into LDAP directly. Ensure that fullName is saved into "cn" attribute in LDAP (currently mapped to model firstName)
+            ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(session, appRealm);
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "fullname", "James Dee", "Dee", "fullname@email.org", null, "4578");
+
+            // add fullname mapper to the provider and remove "firstNameMapper". For this test, we will simply map full name to the LDAP attribute, which was before firstName ( "givenName" on active directory, "cn" on other LDAP servers)
+            ComponentModel firstNameMapper =  LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "first name");
+            String ldapFirstNameAttributeName = firstNameMapper.getConfig().getFirst(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE);
+            appRealm.removeComponent(firstNameMapper);
+
+            ComponentRepresentation firstNameMapperRepp = ModelToRepresentation.toRepresentation(session, firstNameMapper, true);
+
+            ComponentModel fullNameMapperModel = KeycloakModelUtils.createComponentModel("full name", ldapModel.getId(), FullNameLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
+                    FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE, ldapFirstNameAttributeName,
+                    FullNameLDAPStorageMapper.READ_ONLY, "false");
+            appRealm.addComponentModel(fullNameMapperModel);
+
+            return firstNameMapperRepp;
+        }, ComponentRepresentation.class);
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
+            LDAPTestAsserts.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
+
+            // change mapper to writeOnly
+            ComponentModel fullNameMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "full name");
+            fullNameMapperModel.getConfig().putSingle(FullNameLDAPStorageMapper.WRITE_ONLY, "true");
+            appRealm.updateComponent(fullNameMapperModel);
+        });
+
+        // User will be changed in LDAP too
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
+            fullnameUser.setFirstName("James2");
+            fullnameUser.setLastName("Dee2");
+        });
+
+
+        // Assert changed user available in Keycloak, but his firstName is null (due the fullnameMapper is write-only and firstName mapper is removed)
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
+            LDAPTestAsserts.assertUserImported(session.users(), appRealm, "fullname", null, "Dee2", "fullname@email.org", "4578");
+
+            // Remove "fullnameUser" to assert he is removed from LDAP. Revert mappers to previous state
+            UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
+            session.users().removeUser(appRealm, fullnameUser);
+
+            // Revert mappers
+            ComponentModel fullNameMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "full name");
+            appRealm.removeComponent(fullNameMapperModel);
+        });
+
+        firstNameMapperRep.setId(null);
+        Response response = testRealm().components().add(firstNameMapperRep);
+        Assert.assertEquals(201, response.getStatus());
+        response.close();
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/ldap/users.ldif b/testsuite/integration-arquillian/tests/base/src/test/resources/ldap/users.ldif
index 176e19b..4df8b5e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/ldap/users.ldif
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/ldap/users.ldif
@@ -18,3 +18,8 @@ dn: ou=FinanceRoles,dc=keycloak,dc=org
 objectclass: top
 objectclass: organizationalUnit
 ou: FinanceRoles
+
+dn: ou=Groups,dc=keycloak,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: Groups
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/run-on-server-jboss-deployment-structure.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/run-on-server-jboss-deployment-structure.xml
index 5ffd9a1..034e0b6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/run-on-server-jboss-deployment-structure.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/run-on-server-jboss-deployment-structure.xml
@@ -10,6 +10,7 @@
             <module name="org.keycloak.keycloak-services"/>
             <module name="org.keycloak.keycloak-model-infinispan"/>
             <module name="org.keycloak.keycloak-model-jpa"/>
+            <module name="org.keycloak.keycloak-ldap-federation"/>
         </dependencies>
     </deployment>
 </jboss-deployment-structure>
\ No newline at end of file
diff --git a/testsuite/integration-deprecated/pom.xml b/testsuite/integration-deprecated/pom.xml
index 5f2e77d..20e7d18 100755
--- a/testsuite/integration-deprecated/pom.xml
+++ b/testsuite/integration-deprecated/pom.xml
@@ -249,12 +249,6 @@
             <artifactId>selenium-chrome-driver</artifactId>
         </dependency>
 
-        <!-- Apache DS -->
-        <dependency>
-            <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-util-embedded-ldap</artifactId>
-        </dependency>
-
         <dependency>
             <groupId>org.picketlink</groupId>
             <artifactId>picketlink-wildfly-common</artifactId>
@@ -351,36 +345,6 @@
                 </plugins>
             </build>
         </profile>
-        <profile>
-            <id>ldap</id>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.codehaus.mojo</groupId>
-                        <artifactId>exec-maven-plugin</artifactId>
-                        <configuration>
-                            <mainClass>org.keycloak.util.ldap.LDAPEmbeddedServer</mainClass>
-                            <classpathScope>test</classpathScope>
-                        </configuration>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-        <profile>
-            <id>kerberos</id>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.codehaus.mojo</groupId>
-                        <artifactId>exec-maven-plugin</artifactId>
-                        <configuration>
-                            <mainClass>org.keycloak.util.ldap.KerberosEmbeddedServer</mainClass>
-                            <classpathScope>test</classpathScope>
-                        </configuration>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
 
         <profile>
             <id>jpa</id>
diff --git a/testsuite/utils/pom.xml b/testsuite/utils/pom.xml
index 4c50317..706c0cd 100755
--- a/testsuite/utils/pom.xml
+++ b/testsuite/utils/pom.xml
@@ -324,5 +324,35 @@
                 </plugins>
             </build>
         </profile>
+        <profile>
+            <id>ldap</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.util.ldap.LDAPEmbeddedServer</mainClass>
+                            <classpathScope>test</classpathScope>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>kerberos</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.util.ldap.KerberosEmbeddedServer</mainClass>
+                            <classpathScope>test</classpathScope>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
     </profiles>
 </project>
diff --git a/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/LDAPEmbeddedServer.java b/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/LDAPEmbeddedServer.java
index 682fa9a..619dbf2 100644
--- a/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/LDAPEmbeddedServer.java
+++ b/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/LDAPEmbeddedServer.java
@@ -52,6 +52,7 @@ public class LDAPEmbeddedServer {
     public static final String PROPERTY_BASE_DN = "ldap.baseDN";
     public static final String PROPERTY_BIND_HOST = "ldap.host";
     public static final String PROPERTY_BIND_PORT = "ldap.port";
+    public static final String PROPERTY_BIND_LDAPS_PORT = "ldaps.port";
     public static final String PROPERTY_LDIF_FILE = "ldap.ldif";
     public static final String PROPERTY_SASL_PRINCIPAL = "ldap.saslPrincipal";
     public static final String PROPERTY_DSF = "ldap.dsf";
@@ -59,6 +60,7 @@ public class LDAPEmbeddedServer {
     private static final String DEFAULT_BASE_DN = "dc=keycloak,dc=org";
     private static final String DEFAULT_BIND_HOST = "localhost";
     private static final String DEFAULT_BIND_PORT = "10389";
+    private static final String DEFAULT_BIND_LDAPS_PORT = "10636";
     private static final String DEFAULT_LDIF_FILE = "classpath:ldap/default-users.ldif";
     private static final String PROPERTY_ENABLE_SSL = "enableSSL";
     private static final String PROPERTY_KEYSTORE_FILE = "keystoreFile";
@@ -73,6 +75,7 @@ public class LDAPEmbeddedServer {
     protected String baseDN;
     protected String bindHost;
     protected int bindPort;
+    protected int bindLdapsPort;
     protected String ldifFile;
     protected String ldapSaslPrincipal;
     protected String directoryServiceFactory;
@@ -117,6 +120,8 @@ public class LDAPEmbeddedServer {
         this.bindHost = readProperty(PROPERTY_BIND_HOST, DEFAULT_BIND_HOST);
         String bindPort = readProperty(PROPERTY_BIND_PORT, DEFAULT_BIND_PORT);
         this.bindPort = Integer.parseInt(bindPort);
+        String bindLdapsPort = readProperty(PROPERTY_BIND_LDAPS_PORT, DEFAULT_BIND_LDAPS_PORT);
+        this.bindLdapsPort = Integer.parseInt(bindLdapsPort);
         this.ldifFile = readProperty(PROPERTY_LDIF_FILE, DEFAULT_LDIF_FILE);
         this.ldapSaslPrincipal = readProperty(PROPERTY_SASL_PRINCIPAL, null);
         this.directoryServiceFactory = readProperty(PROPERTY_DSF, DEFAULT_DSF);
@@ -219,15 +224,15 @@ public class LDAPEmbeddedServer {
         ldapServer.setSearchBaseDn(this.baseDN);
 
         // Read the transports
-        Transport ldaps = new TcpTransport(this.bindHost, this.bindPort, 3, 50);
+        Transport ldap = new TcpTransport(this.bindHost, this.bindPort, 3, 50);
+        ldapServer.addTransports( ldap );
         if (enableSSL) {
+            Transport ldaps = new TcpTransport(this.bindHost, this.bindLdapsPort, 3, 50);
             ldaps.setEnableSSL(true);
             ldapServer.setKeystoreFile(keystoreFile);
             ldapServer.setCertificatePassword(certPassword);
-            Transport ldap = new TcpTransport(this.bindHost, 10389, 3, 50);
-            ldapServer.addTransports( ldap );
+            ldapServer.addTransports( ldaps );
         }
-        ldapServer.addTransports( ldaps );
 
         // Associate the DS to this LdapServer
         ldapServer.setDirectoryService( directoryService );