keycloak-memoizeit

LDAP No-Import

2/4/2017 1:29:34 PM

Details

diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java
index 7e112b1..e4f3ee9 100755
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java
@@ -34,6 +34,7 @@ import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.ModelException;
 import org.keycloak.models.ModelReadOnlyException;
+import org.keycloak.models.utils.ReadOnlyUserModelDelegate;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
@@ -43,6 +44,8 @@ import org.keycloak.models.cache.UserCache;
 import org.keycloak.models.credential.PasswordUserCredentialModel;
 import org.keycloak.storage.StorageId;
 import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.storage.adapter.InMemoryUserAdapter;
 import org.keycloak.storage.ldap.idm.model.LDAPObject;
 import org.keycloak.storage.ldap.idm.query.Condition;
 import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
@@ -86,7 +89,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
 
     protected LDAPStorageProviderFactory factory;
     protected KeycloakSession session;
-    protected ComponentModel model;
+    protected UserStorageProviderModel model;
     protected LDAPIdentityStore ldapIdentityStore;
     protected EditMode editMode;
     protected LDAPProviderKerberosConfig kerberosConfig;
@@ -94,12 +97,16 @@ public class LDAPStorageProvider implements UserStorageProvider,
     protected LDAPStorageMapperManager mapperManager;
     protected LDAPStorageUserManager userManager;
 
+    // these exist to make sure that we only hit ldap once per transaction
+    //protected Map<String, UserModel> noImportSessionCache = new HashMap<>();
+
+
     protected final Set<String> supportedCredentialTypes = new HashSet<>();
 
     public LDAPStorageProvider(LDAPStorageProviderFactory factory, KeycloakSession session, ComponentModel model, LDAPIdentityStore ldapIdentityStore) {
         this.factory = factory;
         this.session = session;
-        this.model = model;
+        this.model = new UserStorageProviderModel(model);
         this.ldapIdentityStore = ldapIdentityStore;
         this.kerberosConfig = new LDAPProviderKerberosConfig(model);
         this.editMode = ldapIdentityStore.getConfig().getEditMode();
@@ -163,7 +170,11 @@ public class LDAPStorageProvider implements UserStorageProvider,
 
         switch (editMode) {
             case READ_ONLY:
-                proxied = new ReadonlyLDAPUserModelDelegate(local, this);
+                if (model.isImportEnabled()) {
+                    proxied = new ReadonlyLDAPUserModelDelegate(local, this);
+                } else {
+                    proxied = new ReadOnlyUserModelDelegate(local);
+                }
                 break;
             case WRITABLE:
                 proxied = new WritableLDAPUserModelDelegate(local, this, ldapObject);
@@ -217,8 +228,14 @@ public class LDAPStorageProvider implements UserStorageProvider,
         if (!synchronizeRegistrations()) {
             return null;
         }
-        UserModel user = session.userLocalStorage().addUser(realm, username);
-        user.setFederationLink(model.getId());
+        UserModel user = null;
+        if (model.isImportEnabled()) {
+            user = session.userLocalStorage().addUser(realm, username);
+            user.setFederationLink(model.getId());
+        } else {
+            user = new InMemoryUserAdapter(session, realm, new StorageId(model.getId(), username).getId());
+            user.setUsername(username);
+        }
         LDAPObject ldapUser = LDAPUtils.addUserToLDAP(this, realm, user);
         LDAPUtils.checkUuid(ldapUser, ldapIdentityStore.getConfig());
         user.setSingleAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
@@ -248,6 +265,9 @@ public class LDAPStorageProvider implements UserStorageProvider,
 
     @Override
     public UserModel getUserById(String id, RealmModel realm) {
+        UserModel alreadyLoadedInSession = userManager.getManagedProxiedUser(id);
+        if (alreadyLoadedInSession != null) return alreadyLoadedInSession;
+
         StorageId storageId = new StorageId(id);
         return getUserByUsername(storageId.getExternalId(), realm);
     }
@@ -341,7 +361,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
             UserModel kcUser = session.users().getUserByUsername(username, realm);
             if (kcUser == null) {
                 logger.warnf("User '%s' referenced by membership wasn't found in LDAP", username);
-            } else if (!model.getId().equals(kcUser.getFederationLink())) {
+            } else if (model.isImportEnabled() && !model.getId().equals(kcUser.getFederationLink())) {
                 logger.warnf("Incorrect federation provider of user '%s'", kcUser.getUsername());
             } else {
                 result.add(kcUser);
@@ -434,7 +454,14 @@ public class LDAPStorageProvider implements UserStorageProvider,
         String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
         LDAPUtils.checkUuid(ldapUser, ldapIdentityStore.getConfig());
 
-        UserModel imported = session.userLocalStorage().addUser(realm, ldapUsername);
+        UserModel imported = null;
+        if (model.isImportEnabled()) {
+            imported = session.userLocalStorage().addUser(realm, ldapUsername);
+        } else {
+            InMemoryUserAdapter adapter = new InMemoryUserAdapter(session, realm, new StorageId(model.getId(), ldapUsername).getId());
+            adapter.addDefaults();
+            imported = adapter;
+        }
         imported.setEnabled(true);
 
         List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
@@ -448,13 +475,15 @@ public class LDAPStorageProvider implements UserStorageProvider,
         }
 
         String userDN = ldapUser.getDn().toString();
-        imported.setFederationLink(model.getId());
+        if (model.isImportEnabled()) imported.setFederationLink(model.getId());
         imported.setSingleAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
         imported.setSingleAttribute(LDAPConstants.LDAP_ENTRY_DN, userDN);
 
+
         logger.debugf("Imported new user from LDAP to Keycloak DB. Username: [%s], Email: [%s], LDAP_ID: [%s], LDAP Entry DN: [%s]", imported.getUsername(), imported.getEmail(),
                 ldapUser.getUuid(), userDN);
-        return proxy(realm, imported, ldapUser);
+        UserModel proxy = proxy(realm, imported, ldapUser);
+        return proxy;
     }
 
     protected LDAPObject queryByEmail(RealmModel realm, String email) {
diff --git a/server-spi/src/main/java/org/keycloak/storage/ReadOnlyException.java b/server-spi/src/main/java/org/keycloak/storage/ReadOnlyException.java
index 3404370..d43fb4d 100644
--- a/server-spi/src/main/java/org/keycloak/storage/ReadOnlyException.java
+++ b/server-spi/src/main/java/org/keycloak/storage/ReadOnlyException.java
@@ -23,6 +23,9 @@ package org.keycloak.storage;
  * @version $Revision: 1 $
  */
 public class ReadOnlyException extends RuntimeException {
+    public ReadOnlyException() {
+    }
+
     public ReadOnlyException(String message) {
         super(message);
     }
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ReadOnlyUserModelDelegate.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ReadOnlyUserModelDelegate.java
new file mode 100644
index 0000000..bb4777b
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ReadOnlyUserModelDelegate.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2016 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.models.utils;
+
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.ModelReadOnlyException;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.UserModelDelegate;
+import org.keycloak.storage.ReadOnlyException;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ReadOnlyUserModelDelegate extends UserModelDelegate {
+    public ReadOnlyUserModelDelegate(UserModel delegate) {
+        super(delegate);
+    }
+
+    @Override
+    public void setUsername(String username) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void setSingleAttribute(String name, String value) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void setAttribute(String name, List<String> values) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void addRequiredAction(String action) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void removeRequiredAction(String action) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void addRequiredAction(RequiredAction action) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void removeRequiredAction(RequiredAction action) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void setFirstName(String firstName) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void setLastName(String lastName) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void setEmail(String email) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void setEmailVerified(boolean verified) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void deleteRoleMapping(RoleModel role) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void setFederationLink(String link) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void setServiceAccountClientLink(String clientInternalId) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void setCreatedTimestamp(Long timestamp) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void joinGroup(GroupModel group) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void leaveGroup(GroupModel group) {
+        throw new ModelReadOnlyException();
+    }
+
+    @Override
+    public void grantRole(RoleModel role) {
+        throw new ModelReadOnlyException();
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java b/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java
new file mode 100644
index 0000000..9025972
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2016 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.storage.adapter;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.common.util.Time;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.DefaultRoles;
+import org.keycloak.models.utils.RoleUtils;
+import org.keycloak.storage.ReadOnlyException;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class InMemoryUserAdapter implements UserModel {
+    private String username;
+    private Long createdTimestamp = Time.currentTimeMillis();
+    private String firstName;
+    private String lastName;
+    private String email;
+    private boolean emailVerified;
+    private boolean enabled;
+
+    private String realmId;
+
+    private Set<String> roleIds = new HashSet<>();
+    private Set<String> groupIds = new HashSet<>();
+
+    private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
+    private Set<String> requiredActions = new HashSet<>();
+    private String federationLink;
+    private String serviceAccountClientLink;
+
+    private KeycloakSession session;
+    private RealmModel realm;
+    private String id;
+    private boolean readonly;
+
+    public InMemoryUserAdapter(KeycloakSession session, RealmModel realm, String id) {
+        this.session = session;
+        this.realm = realm;
+        this.id = id;
+
+
+    }
+
+    public void addDefaults() {
+        DefaultRoles.addDefaultRoles(realm, this);
+
+        for (GroupModel g : realm.getDefaultGroups()) {
+            joinGroup(g);
+        }
+
+    }
+
+    public void setReadonly(boolean flag) {
+        readonly = flag;
+    }
+
+    protected void checkReadonly() {
+        if (readonly) throw new ReadOnlyException("In memory user model is not writable");
+    }
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public String getUsername() {
+        return username;
+    }
+
+    @Override
+    public void setUsername(String username) {
+        checkReadonly();
+        this.username = username.toLowerCase();
+
+    }
+
+    @Override
+    public Long getCreatedTimestamp() {
+        return createdTimestamp;
+    }
+
+    @Override
+    public void setCreatedTimestamp(Long timestamp) {
+        checkReadonly();
+        this.createdTimestamp = timestamp;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        checkReadonly();
+        this.enabled = enabled;
+
+    }
+
+    @Override
+    public void setSingleAttribute(String name, String value) {
+        checkReadonly();
+        attributes.putSingle(name, value);
+
+    }
+
+    @Override
+    public void setAttribute(String name, List<String> values) {
+        checkReadonly();
+        attributes.put(name, values);
+
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+        checkReadonly();
+        attributes.remove(name);
+
+    }
+
+    @Override
+    public String getFirstAttribute(String name) {
+        return attributes.getFirst(name);
+    }
+
+    @Override
+    public List<String> getAttribute(String name) {
+        List<String> value = attributes.get(name);
+        if (value == null) {
+            return new LinkedList<>();
+        }
+        return value;
+    }
+
+    @Override
+    public Map<String, List<String>> getAttributes() {
+        return attributes;
+    }
+
+    @Override
+    public Set<String> getRequiredActions() {
+        return requiredActions;
+    }
+
+    @Override
+    public void addRequiredAction(String action) {
+        checkReadonly();
+        requiredActions.add(action);
+
+    }
+
+    @Override
+    public void removeRequiredAction(String action) {
+        checkReadonly();
+        requiredActions.remove(action);
+
+    }
+
+    @Override
+    public void addRequiredAction(RequiredAction action) {
+        checkReadonly();
+        requiredActions.add(action.name());
+
+    }
+
+    @Override
+    public void removeRequiredAction(RequiredAction action) {
+        checkReadonly();
+        requiredActions.remove(action.name());
+    }
+
+    @Override
+    public String getFirstName() {
+        return firstName;
+    }
+
+    @Override
+    public void setFirstName(String firstName) {
+        checkReadonly();
+        this.firstName = firstName;
+
+    }
+
+    @Override
+    public String getLastName() {
+        return lastName;
+    }
+
+    @Override
+    public void setLastName(String lastName) {
+        checkReadonly();
+        this.lastName = lastName;
+
+    }
+
+    @Override
+    public String getEmail() {
+        return email;
+    }
+
+    @Override
+    public void setEmail(String email) {
+        checkReadonly();
+        if (email != null) email = email.toLowerCase();
+        this.email = email;
+
+    }
+
+    @Override
+    public boolean isEmailVerified() {
+        return emailVerified;
+    }
+
+    @Override
+    public void setEmailVerified(boolean verified) {
+        checkReadonly();
+        this.emailVerified = verified;
+
+    }
+
+    @Override
+    public Set<GroupModel> getGroups() {
+        if (groupIds.size() == 0) return Collections.EMPTY_SET;
+        Set<GroupModel> groups = new HashSet<>();
+        for (String id : groupIds) {
+            groups.add(realm.getGroupById(id));
+        }
+        return groups;
+    }
+
+    @Override
+    public void joinGroup(GroupModel group) {
+        checkReadonly();
+        groupIds.add(group.getId());
+
+    }
+
+    @Override
+    public void leaveGroup(GroupModel group) {
+        checkReadonly();
+        groupIds.remove(group.getId());
+
+    }
+
+    @Override
+    public boolean isMemberOf(GroupModel group) {
+        if (groupIds == null) return false;
+        if (groupIds.contains(group.getId())) return true;
+        Set<GroupModel> groups = getGroups();
+        return RoleUtils.isMember(groups, group);
+    }
+
+    @Override
+    public String getFederationLink() {
+        return federationLink;
+    }
+
+    @Override
+    public void setFederationLink(String link) {
+        checkReadonly();
+        this.federationLink = link;
+
+    }
+
+    @Override
+    public String getServiceAccountClientLink() {
+        return serviceAccountClientLink;
+    }
+
+    @Override
+    public void setServiceAccountClientLink(String clientInternalId) {
+        checkReadonly();
+        this.serviceAccountClientLink = clientInternalId;
+
+    }
+
+    @Override
+    public Set<RoleModel> getRealmRoleMappings() {
+        Set<RoleModel> allRoles = getRoleMappings();
+
+        // Filter to retrieve just realm roles
+        Set<RoleModel> realmRoles = new HashSet<RoleModel>();
+        for (RoleModel role : allRoles) {
+            if (role.getContainer() instanceof RealmModel) {
+                realmRoles.add(role);
+            }
+        }
+        return realmRoles;
+    }
+
+    @Override
+    public Set<RoleModel> getClientRoleMappings(ClientModel app) {
+        Set<RoleModel> result = new HashSet<RoleModel>();
+        Set<RoleModel> roles = getRoleMappings();
+
+        for (RoleModel role : roles) {
+            if (app.equals(role.getContainer())) {
+                result.add(role);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public boolean hasRole(RoleModel role) {
+        Set<RoleModel> roles = getRoleMappings();
+        return RoleUtils.hasRole(roles, role)
+                || RoleUtils.hasRoleFromGroup(getGroups(), role, true);
+    }
+
+    @Override
+    public void grantRole(RoleModel role) {
+        roleIds.add(role.getId());
+
+    }
+
+    @Override
+    public Set<RoleModel> getRoleMappings() {
+        if (roleIds.size() == 0) return Collections.EMPTY_SET;
+        Set<RoleModel> roles = new HashSet<>();
+        for (String id : roleIds) {
+            roles.add(realm.getRoleById(id));
+        }
+        return roles;
+    }
+
+    @Override
+    public void deleteRoleMapping(RoleModel role) {
+        roleIds.remove(role.getId());
+
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || !(o instanceof UserModel)) return false;
+
+        UserModel that = (UserModel) o;
+        return that.getId().equals(getId());
+    }
+
+    @Override
+    public int hashCode() {
+        return getId().hashCode();
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java
index 941d2e9..49d81e7 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java
@@ -150,6 +150,14 @@ public class LDAPTestUtils {
         Assert.assertEquals(expectedPostalCode, user.getFirstAttribute("postal_code"));
     }
 
+    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"));
+    }
+
 
     // CRUD model mappers
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPGroupMapperNoImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPGroupMapperNoImportTest.java
new file mode 100755
index 0000000..6cf6435
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPGroupMapperNoImportTest.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2016 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.storage.ldap.noimport;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runners.MethodSorters;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.services.managers.RealmManager;
+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.LDAPStorageProviderFactory;
+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.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory;
+import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig;
+import org.keycloak.testsuite.federation.storage.ldap.LDAPTestUtils;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.LDAPRule;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPGroupMapperNoImportTest {
+
+    private static LDAPRule ldapRule = new LDAPRule();
+
+    private static ComponentModel ldapModel = null;
+    private static String descriptionAttrName = null;
+
+
+    static class GroupTestKeycloakSetup extends KeycloakRule.KeycloakSetup {
+
+        private final LDAPRule ldapRule;
+
+        ComponentModel ldapModel = null;
+        String descriptionAttrName = null;
+
+
+        public GroupTestKeycloakSetup(LDAPRule ldapRule) {
+            this.ldapRule = ldapRule;
+        }
+
+
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
+            LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "john", "john@test.com", "password-app");
+
+            MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
+            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(false);
+
+            ldapModel = appRealm.addComponentModel(model);
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            descriptionAttrName = ldapFedProvider.getLdapIdentityStore().getConfig().isActiveDirectory() ? "displayName" : "description";
+
+            // Add group mapper
+            LDAPTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName);
+
+            // Remove all LDAP groups
+            LDAPTestUtils.removeAllLDAPGroups(session, appRealm, ldapModel, "groupsMapper");
+
+            // Add some groups for testing
+            LDAPObject group1 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group1", descriptionAttrName, "group1 - description");
+            LDAPObject group11 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11");
+            LDAPObject group12 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group12", descriptionAttrName, "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);
+
+            // Sync LDAP groups to Keycloak DB
+            ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "groupsMapper");
+            new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(appRealm);
+
+            // Delete all LDAP users
+            LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
+
+            // Add some LDAP users for testing
+            LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1");
+
+            LDAPObject mary = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, mary, "Password1");
+
+            LDAPObject rob = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, rob, "Password1");
+
+            LDAPObject james = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jameskeycloak", "James", "Brown", "james@email.org", null, "8910");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, james, "Password1");
+
+            postSetup(appRealm, ldapFedProvider);
+        }
+
+
+        void postSetup(RealmModel appRealm, LDAPStorageProvider ldapProvider) {
+            LDAPGroupMapperNoImportTest.ldapModel = this.ldapModel;
+            LDAPGroupMapperNoImportTest.descriptionAttrName = this.descriptionAttrName;
+        }
+
+    }
+
+
+    private static KeycloakRule keycloakRule = new KeycloakRule(new GroupTestKeycloakSetup(ldapRule));
+
+    @ClassRule
+    public static TestRule chain = RuleChain
+            .outerRule(ldapRule)
+            .around(keycloakRule);
+
+    @Test
+    public void test01ReadGroupMappings() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = session.realms().getRealmByName("test");
+            ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "groupsMapper");
+            LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.LDAP_ONLY.toString());
+            appRealm.updateComponent(mapperModel);
+
+            LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm);
+
+            LDAPObject maryLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "marykeycloak");
+            groupMapper.addGroupMappingInLDAP(appRealm, "group1", maryLdap);
+            groupMapper.addGroupMappingInLDAP(appRealm, "group11", maryLdap);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+        session = keycloakRule.startSession();
+        try {
+            session.userCache().clear();
+            System.out.println("starting test01ReadGroupMappings");
+            RealmModel appRealm = session.realms().getRealmByName("test");
+
+            GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
+            Assert.assertNotNull(group1);
+            GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
+            Assert.assertNotNull(group11);
+            GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
+            Assert.assertNotNull(group12);
+
+
+            UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+            // make sure we are in no-import mode
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("marykeycloak", appRealm));
+            // Assert that mary has both LDAP and DB mapped groups
+            Set<GroupModel> maryGroups = mary.getGroups();
+            Assert.assertEquals(2, maryGroups.size());
+            Assert.assertTrue(maryGroups.contains(group1));
+            Assert.assertTrue(maryGroups.contains(group11));
+
+            // Check through userProvider
+            List<UserModel> group1Members = session.users().getGroupMembers(appRealm, group1, 0, 10);
+            List<UserModel> group11Members = session.users().getGroupMembers(appRealm, group11, 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());
+
+            // Delete role mappings directly in LDAP
+            LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm,ldapModel, "groupsMapper");
+            GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm);
+            LDAPObject maryLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "marykeycloak");
+            deleteGroupMappingsInLDAP(groupMapper, maryLdap, "group1");
+            deleteGroupMappingsInLDAP(groupMapper, maryLdap, "group11");
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+    }
+
+    @Test
+    public void test02WriteGroupMappings() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = session.realms().getRealmByName("test");
+
+            ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "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);
+
+            // make sure we are in no-import mode
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm));
+            Assert.assertNull(session.userLocalStorage().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);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            session.userCache().clear();  // clear cache to make sure we're reloading from LDAP.
+            RealmModel appRealm = session.realms().getRealmByName("test");
+            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);
+
+            // make sure we are in no-import mode
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm));
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("marykeycloak", appRealm));
+
+            // 3 - Check that group mappings are in LDAP and hence available through federation
+
+            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(group11);
+            mary.leaveGroup(group12);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            session.userCache().clear();  // clear cache to make sure we're reloading from LDAP.
+            RealmModel appRealm = session.realms().getRealmByName("test");
+
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+
+            // make sure we are in no-import mode
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm));
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("marykeycloak", appRealm));
+            session.userCache().clear();  // clear cache to make sure we're reloading from LDAP.
+            Set<GroupModel> johnGroups = john.getGroups();
+            Assert.assertEquals(0, johnGroups.size());
+            Set<GroupModel> maryGroups = mary.getGroups();
+            Assert.assertEquals(0, maryGroups.size());
+
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+    }
+
+
+    private void deleteGroupMappingsInLDAP(GroupLDAPStorageMapper groupMapper, LDAPObject ldapUser, String groupName) {
+        LDAPObject ldapGroup = groupMapper.loadLDAPGroupByName(groupName);
+        groupMapper.deleteGroupMappingInLDAP(ldapUser, ldapGroup);
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPProvidersIntegrationNoImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPProvidersIntegrationNoImportTest.java
new file mode 100755
index 0000000..957bfe5
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPProvidersIntegrationNoImportTest.java
@@ -0,0 +1,849 @@
+/*
+ * Copyright 2016 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.storage.ldap.noimport;
+
+import org.jboss.logging.Logger;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runners.MethodSorters;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.ModelReadOnlyException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.services.managers.RealmManager;
+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.LDAPStorageProviderFactory;
+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.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.OAuthClient;
+import org.keycloak.testsuite.federation.storage.ldap.LDAPTestUtils;
+import org.keycloak.testsuite.pages.AccountPasswordPage;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.RegisterPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.LDAPRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPProvidersIntegrationNoImportTest {
+
+    private static final Logger log = Logger.getLogger(LDAPProvidersIntegrationNoImportTest.class);
+
+    private static LDAPRule ldapRule = new LDAPRule();
+
+    private static ComponentModel ldapModel = null;
+
+
+    private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "marykeycloak", "mary@test.com", "password-app");
+
+            MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
+            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(false);
+
+            ldapModel = appRealm.addComponentModel(model);
+            LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
+
+            // Delete all LDAP users and add some new for testing
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
+
+            LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1");
+
+            LDAPObject existing = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678");
+
+            appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
+        }
+    });
+
+    @ClassRule
+    public static TestRule chain = RuleChain
+            .outerRule(ldapRule)
+            .around(keycloakRule);
+
+    @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;
+
+//    @Test
+//    @Ignore
+//    public void runit() throws Exception {
+//        Thread.sleep(10000000);
+//
+//    }
+
+    /**
+     * KEYCLOAK-3986
+     *
+     */
+    @Test
+    public void testSyncRegistrationOff() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmManager manager = new RealmManager(session);
+            RealmModel appRealm = manager.getRealm("test");
+            UserStorageProviderModel newModel = new UserStorageProviderModel(ldapModel);
+            newModel.getConfig().putSingle(LDAPConstants.SYNC_REGISTRATIONS, "false");
+            appRealm.updateComponent(newModel);
+            UserModel newUser1 = session.users().addUser(appRealm, "newUser1");
+            Assert.assertTrue(StorageId.isLocalStorage(newUser1));
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+
+
+    }
+
+    @Test
+    public void caseInSensitiveImport() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmManager manager = new RealmManager(session);
+            RealmModel appRealm = manager.getRealm("test");
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            LDAPObject jbrown2 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "JBrown2", "John", "Brown2", "jbrown2@email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, jbrown2, "Password1");
+            LDAPObject jbrown3 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown3", "John", "Brown3", "JBrown3@email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, jbrown3, "Password1");
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        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() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmManager manager = new RealmManager(session);
+            RealmModel appRealm = manager.getRealm("test");
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            LDAPObject jbrown4 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "JBrown4", "John", "Brown4", "jbrown4@email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, jbrown4, "Password1");
+            LDAPObject jbrown5 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown5", "John", "Brown5", "JBrown5@Email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, jbrown5, "Password1");
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            RealmManager manager = new RealmManager(session);
+            RealmModel appRealm = manager.getRealm("test");
+
+            // search by username
+            List<UserModel> users = session.users().searchForUser("JBROwn4", appRealm);
+            Assert.assertEquals(1, users.size());
+            UserModel user4 = users.get(0);
+            Assert.assertEquals("jbrown4", user4.getUsername());
+            Assert.assertEquals("jbrown4@email.org", user4.getEmail());
+
+            // search by email
+            users = session.users().searchForUser("JBROwn5@eMAil.org", appRealm);
+            Assert.assertEquals(1, users.size());
+            UserModel user5 = users.get(0);
+            Assert.assertEquals("jbrown5", user5.getUsername());
+            Assert.assertEquals("jbrown5@email.org", user5.getEmail());
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+    }
+
+    @Test
+    public void deleteFederationLink() {
+        loginLdap();
+        {
+            KeycloakSession session = keycloakRule.startSession();
+            try {
+                RealmManager manager = new RealmManager(session);
+
+                RealmModel appRealm = manager.getRealm("test");
+                appRealm.removeComponent(ldapModel);
+                Assert.assertEquals(0, appRealm.getComponents(appRealm.getId(), UserStorageProvider.class.getName()).size());
+            } finally {
+                keycloakRule.stopSession(session, true);
+            }
+        }
+        loginPage.open();
+        loginPage.login("johnkeycloak", "Password1");
+        loginPage.assertCurrent();
+
+        Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        {
+            KeycloakSession session = keycloakRule.startSession();
+            try {
+                RealmManager manager = new RealmManager(session);
+
+                RealmModel appRealm = manager.getRealm("test");
+                ldapModel.setId(null);
+                ldapModel = appRealm.addComponentModel(ldapModel);
+                LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
+            } finally {
+                keycloakRule.stopSession(session, true);
+            }
+        }
+        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");
+        assertEquals(200, response.getStatusCode());
+        AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
+
+        response = oauth.doGrantAccessTokenRequest("password", "johnkeycloak", "");
+        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());
+    }
+
+    @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());
+
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = session.realms().getRealmByName("test");
+            UserModel user = session.users().getUserByUsername("registerUserSuccess2", appRealm);
+            Assert.assertNotNull(user);
+            Assert.assertFalse(StorageId.isLocalStorage(user));
+            Assert.assertEquals(StorageId.providerId(user.getId()), ldapModel.getId());
+            Assert.assertEquals("registerusersuccess2", user.getUsername());
+            Assert.assertEquals("firstName", user.getFirstName());
+            Assert.assertEquals("lastName", user.getLastName());
+            Assert.assertTrue(user.isEnabled());
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+    }
+
+    @Test
+    public void testCaseSensitiveAttributeName() {
+        KeycloakSession session = keycloakRule.startSession();
+
+        try {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+
+            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);
+
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+    }
+
+    @Test
+    public void testCommaInUsername() {
+        KeycloakSession session = keycloakRule.startSession();
+        boolean skip = false;
+
+        try {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+
+            // Workaround as comma is not allowed in sAMAccountName on active directory. So we will skip the test for this configuration
+            LDAPConfig config = ldapFedProvider.getLdapIdentityStore().getConfig();
+            if (config.isActiveDirectory() && config.getUsernameLdapAttribute().equals(LDAPConstants.SAM_ACCOUNT_NAME)) {
+                skip = true;
+            }
+
+            if (!skip) {
+                LDAPObject johnComma = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "john,comma", "John", "Comma", "johncomma@email.org", null, "12387");
+                LDAPTestUtils.updateLDAPPassword(ldapFedProvider, johnComma, "Password1");
+
+                LDAPObject johnPlus = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "john+plus,comma", "John", "Plus", "johnplus@email.org", null, "12387");
+                LDAPTestUtils.updateLDAPPassword(ldapFedProvider, johnPlus, "Password1");
+            }
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+
+        if (!skip) {
+            // Try to import the user with comma in username into Keycloak
+            loginSuccessAndLogout("john,comma", "Password1");
+            loginSuccessAndLogout("john+plus,comma", "Password1");
+        }
+    }
+
+    //@Test // don't think we should support this, bburke
+    public void testDirectLDAPUpdate() {
+        KeycloakSession session = keycloakRule.startSession();
+
+        try {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            LDAPObject johnDirect = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johndirect", "John", "Direct", "johndirect@email.org", null, "12399");
+
+            // 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("12399", postalCode);
+
+            // Directly update user in LDAP
+            johnDirect.setSingleAttribute(LDAPConstants.POSTAL_CODE, "12400");
+            johnDirect.setSingleAttribute(LDAPConstants.SN, "DirectLDAPUpdated");
+            ldapFedProvider.getLdapIdentityStore().update(johnDirect);
+
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+            UserModel user = session.users().getUserByUsername("johndirect", appRealm);
+
+            // Verify that postalCode is still the same as we read it's value from Keycloak DB
+            user = session.users().getUserByUsername("johndirect", appRealm);
+            String postalCode = user.getFirstAttribute("postal_code");
+            Assert.assertEquals("12399", postalCode);
+
+            // Check user.getAttributes()
+            postalCode = user.getAttributes().get("postal_code").get(0);
+            Assert.assertEquals("12399", postalCode);
+
+            // LastName is new as lastName mapper will read the value from LDAP
+            String lastName = user.getLastName();
+            Assert.assertEquals("DirectLDAPUpdated", lastName);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+
+            // Update postalCode mapper to always read the value from LDAP
+            ComponentModel zipMapper = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "zipCodeMapper");
+            zipMapper.getConfig().putSingle(UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "true");
+            appRealm.updateComponent(zipMapper);
+
+            // Update lastName mapper to read the value from Keycloak DB
+            ComponentModel lastNameMapper = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "last name");
+            lastNameMapper.getConfig().putSingle(UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
+            appRealm.updateComponent(lastNameMapper);
+
+            // Verify that postalCode is read from LDAP now
+            UserModel user = session.users().getUserByUsername("johndirect", appRealm);
+            String postalCode = user.getFirstAttribute("postal_code");
+            Assert.assertEquals("12400", postalCode);
+
+            // Check user.getAttributes()
+            postalCode = user.getAttributes().get("postal_code").get(0);
+            Assert.assertEquals("12400", postalCode);
+
+            Assert.assertFalse(user.getAttributes().containsKey(UserModel.LAST_NAME));
+
+            // lastName is read from Keycloak DB now
+            String lastName = user.getLastName();
+            Assert.assertEquals("Direct", lastName);
+
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+    }
+
+
+    // TODO: Rather separate test for fullNameMapper to better test all the possibilities
+    @Test
+    public void testFullNameMapper() {
+        KeycloakSession session = keycloakRule.startSession();
+        ComponentModel firstNameMapper = null;
+
+        try {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+
+            // 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)
+            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)
+            firstNameMapper =  LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "first name");
+            String ldapFirstNameAttributeName = firstNameMapper.getConfig().getFirst(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE);
+            appRealm.removeComponent(firstNameMapper);
+
+            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);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+
+            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
+            LDAPTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
+
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+
+        // Assert changing user in Keycloak will change him in LDAP too...
+        session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+
+            UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
+            fullnameUser.setFirstName("James2");
+            fullnameUser.setLastName("Dee2");
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+
+        // Assert changed user available in Keycloak
+        session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+
+            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
+            LDAPTestUtils.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, ldapModel, "full name");
+            appRealm.removeComponent(fullNameMapperModel);
+
+            firstNameMapper.setId(null);
+            appRealm.addComponentModel(firstNameMapper);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+    }
+
+    @Test
+    public void testHardcodedRoleMapper() {
+        KeycloakSession session = keycloakRule.startSession();
+        ComponentModel firstNameMapper = null;
+
+        try {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+            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", ldapModel.getId(), HardcodedLDAPRoleStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
+                    HardcodedLDAPRoleStorageMapper.ROLE, "hardcoded-role");
+            appRealm.addComponentModel(hardcodedMapperModel);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+            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, ldapModel, "hardcoded role");
+            appRealm.removeComponent(hardcodedMapperModel);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+    }
+
+    @Test
+    public void testImportExistingUserFromLDAP() throws Exception {
+        // Add LDAP user with same email like existing model user
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+                LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary1", "Kelly1", "mary1@email.org", null, "123");
+                LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "mary-duplicatemail", "Mary2", "Kelly2", "mary@test.com", null, "123");
+                LDAPObject marynoemail = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marynoemail", "Mary1", "Kelly1", null, null, "123");
+                LDAPTestUtils.updateLDAPPassword(ldapFedProvider, 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() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = session.realms().getRealmByName("test");
+
+            UserStorageProviderModel model = new UserStorageProviderModel(ldapModel);
+            model.getConfig().putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.READ_ONLY.toString());
+            appRealm.updateComponent(model);
+            UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
+            Assert.assertNotNull(user);
+            try {
+                user.setEmail("error@error.com");
+                Assert.fail("should fail");
+            } catch (ModelReadOnlyException e) {
+
+            }
+            try {
+                user.setLastName("Berk");
+                Assert.fail("should fail");
+            } catch (ModelReadOnlyException e) {
+
+            }
+            try {
+                user.setFirstName("Bilbo");
+                Assert.fail("should fail");
+            } catch (ModelReadOnlyException e) {
+
+            }
+            try {
+                UserCredentialModel cred = UserCredentialModel.password("PoopyPoop1", true);
+                session.userCredentialManager().updateCredential(appRealm, user, cred);
+                Assert.fail("should fail");
+            } catch (ModelReadOnlyException e) {
+
+            }
+
+            Assert.assertTrue(session.users().removeUser(appRealm, user));
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = session.realms().getRealmByName("test");
+            Assert.assertEquals(UserStorageProvider.EditMode.WRITABLE.toString(), appRealm.getComponent(ldapModel.getId()).getConfig().getFirst(LDAPConstants.EDIT_MODE));
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+    }
+
+    @Test
+    public void testRemoveFederatedUser() {
+        /*
+        {
+            KeycloakSession session = keycloakRule.startSession();
+            RealmModel appRealm = session.realms().getRealmByName("test");
+            UserModel user = session.users().getUserByUsername("registerUserSuccess2", appRealm);
+            keycloakRule.stopSession(session, true);
+            if (user == null) {
+                registerUserLdapSuccess();
+            }
+        }
+        */
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = session.realms().getRealmByName("test");
+            UserModel user = session.users().getUserByUsername("registerUserSuccess2", appRealm);
+            Assert.assertNotNull(user);
+            Assert.assertFalse(StorageId.isLocalStorage(user));
+            Assert.assertEquals(StorageId.providerId(user.getId()), ldapModel.getId());
+
+            Assert.assertTrue(session.users().removeUser(appRealm, user));
+            Assert.assertNull(session.users().getUserByUsername("registerUserSuccess2", appRealm));
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+    }
+
+    @Test
+    public void testSearch() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = session.realms().getRealmByName("test");
+            LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+
+            LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username1", "John1", "Doel1", "user1@email.org", null, "121");
+            LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username2", "John2", "Doel2", "user2@email.org", null, "122");
+            LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username3", "John3", "Doel3", "user3@email.org", null, "123");
+            LDAPTestUtils.addLDAPUser(ldapProvider, 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));
+
+            UserModel user;
+            // search by username
+            user = session.users().searchForUser("username1", appRealm).get(0);
+            LDAPTestUtils.assertLoaded(user, "username1", "John1", "Doel1", "user1@email.org", "121");
+
+            // search by email
+            user = session.users().searchForUser("user2@email.org", appRealm).get(0);
+            LDAPTestUtils.assertLoaded(user, "username2", "John2", "Doel2", "user2@email.org", "122");
+
+            // search by lastName
+            user = session.users().searchForUser("Doel3", appRealm).get(0);
+            LDAPTestUtils.assertLoaded(user, "username3", "John3", "Doel3", "user3@email.org", "123");
+
+            // search by firstName + lastName
+            user = session.users().searchForUser("John4 Doel4", appRealm).get(0);
+            LDAPTestUtils.assertLoaded(user, "username4", "John4", "Doel4", "user4@email.org", "124");
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+    }
+
+    @Test
+    public void testSearchWithCustomLDAPFilter() {
+        // Add custom filter for searching users
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = session.realms().getRealmByName("test");
+            ldapModel.getConfig().putSingle(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(|(mail=user5@email.org)(mail=user6@email.org))");
+            appRealm.updateComponent(ldapModel);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            RealmModel appRealm = session.realms().getRealmByName("test");
+
+            LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username5", "John5", "Doel5", "user5@email.org", null, "125");
+            LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username6", "John6", "Doel6", "user6@email.org", null, "126");
+            LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username7", "John7", "Doel7", "user7@email.org", null, "127");
+
+            UserModel user;
+            // search by email
+            user = session.users().searchForUser("user5@email.org", appRealm).get(0);
+            LDAPTestUtils.assertLoaded(user, "username5", "John5", "Doel5", "user5@email.org", "125");
+
+            user = session.users().searchForUser("John6 Doel6", appRealm).get(0);
+            LDAPTestUtils.assertLoaded(user, "username6", "John6", "Doel6", "user6@email.org", "126");
+
+            Assert.assertTrue(session.users().searchForUser("user7@email.org", appRealm).isEmpty());
+
+            // Remove custom filter
+            ldapModel.getConfig().remove(LDAPConstants.CUSTOM_USER_SEARCH_FILTER);
+            appRealm.updateComponent(ldapModel);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPRoleMappingsNoImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPRoleMappingsNoImportTest.java
new file mode 100644
index 0000000..f0d31b3
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/noimport/LDAPRoleMappingsNoImportTest.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2016 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.storage.ldap.noimport;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runners.MethodSorters;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.AccountRoles;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.RealmManager;
+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.idm.model.LDAPObject;
+import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
+import org.keycloak.storage.ldap.mappers.membership.role.RoleLDAPStorageMapper;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.federation.storage.ldap.LDAPTestUtils;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.LDAPRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPRoleMappingsNoImportTest {
+
+    private static LDAPRule ldapRule = new LDAPRule();
+
+    private static ComponentModel ldapModel = null;
+
+    private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
+
+            MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
+            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(false);
+
+            ldapModel = appRealm.addComponentModel(model);
+
+            // Delete all LDAP users
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
+
+            // Add sample application
+            ClientModel finance = appRealm.addClient("finance");
+
+            // Delete all LDAP roles
+            LDAPTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY);
+            LDAPTestUtils.removeAllLDAPRoles(manager.getSession(), appRealm, ldapModel, "realmRolesMapper");
+            LDAPTestUtils.removeAllLDAPRoles(manager.getSession(), appRealm, ldapModel, "financeRolesMapper");
+
+            // Add some users for testing
+            LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1");
+
+            LDAPObject mary = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, mary, "Password1");
+
+            LDAPObject rob = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910");
+            LDAPTestUtils.updateLDAPPassword(ldapFedProvider, rob, "Password1");
+
+            // Add some roles for testing
+            LDAPTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "realmRolesMapper", "realmRole1");
+            LDAPTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "realmRolesMapper", "realmRole2");
+            LDAPTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "financeRolesMapper", "financeRole1");
+
+            // Sync LDAP roles to Keycloak DB
+            LDAPTestUtils.syncRolesFromLDAP(appRealm, ldapFedProvider, ldapModel);
+        }
+    });
+
+    @ClassRule
+    public static TestRule chain = RuleChain
+            .outerRule(ldapRule)
+            .around(keycloakRule);
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @WebResource
+    protected OAuthClient oauth;
+
+    @WebResource
+    protected WebDriver driver;
+
+    @WebResource
+    protected AppPage appPage;
+
+    @WebResource
+    protected LoginPage loginPage;
+
+    @Test
+    public void test01ReadMappings() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmModel appRealm = session.realms().getRealmByName("test");
+            LDAPTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY);
+
+            ComponentModel roleMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "realmRolesMapper");
+            LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            RoleLDAPStorageMapper roleMapper = LDAPTestUtils.getRoleMapper(roleMapperModel, ldapProvider, appRealm);
+
+
+            LDAPObject maryLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "marykeycloak");
+            roleMapper.addRoleMappingInLDAP("realmRole1", maryLdap);
+            roleMapper.addRoleMappingInLDAP("realmRole2", maryLdap);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+        session = keycloakRule.startSession();
+        try {
+            session.userCache().clear();
+            RealmModel appRealm = session.realms().getRealmByName("test");
+
+            UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+            // make sure we are in no-import mode!
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("marykeycloak", appRealm));
+
+            // This role should already exists as it was imported from LDAP
+            RoleModel realmRole1 = appRealm.getRole("realmRole1");
+
+            // This role should already exists as it was imported from LDAP
+            RoleModel realmRole2 = appRealm.getRole("realmRole2");
+
+            Set<RoleModel> maryRoles = mary.getRealmRoleMappings();
+            Assert.assertTrue(maryRoles.contains(realmRole1));
+            Assert.assertTrue(maryRoles.contains(realmRole2));
+
+            // Add some role mappings directly into LDAP
+            ComponentModel roleMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "realmRolesMapper");
+            LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            RoleLDAPStorageMapper roleMapper = LDAPTestUtils.getRoleMapper(roleMapperModel, ldapProvider, appRealm);
+
+            LDAPObject maryLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "marykeycloak");
+            deleteRoleMappingsInLDAP(roleMapper, maryLdap, "realmRole1");
+            deleteRoleMappingsInLDAP(roleMapper, maryLdap, "realmRole2");
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+        session = keycloakRule.startSession();
+        try {
+            session.userCache().clear();
+            RealmModel appRealm = session.realms().getRealmByName("test");
+
+            UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+            // This role should already exists as it was imported from LDAP
+            RoleModel realmRole1 = appRealm.getRole("realmRole1");
+
+            // This role should already exists as it was imported from LDAP
+            RoleModel realmRole2 = appRealm.getRole("realmRole2");
+
+            Set<RoleModel> maryRoles = mary.getRealmRoleMappings();
+            Assert.assertFalse(maryRoles.contains(realmRole1));
+            Assert.assertFalse(maryRoles.contains(realmRole2));
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+    }
+
+    @Test
+    public void test02WriteMappings() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            session.userCache().clear();
+            RealmModel appRealm = session.realms().getRealmByName("test");
+
+            LDAPTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY);
+
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+
+            // make sure we are in no-import mode
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm));
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("marykeycloak", appRealm));
+
+            // 1 - Grant some roles in LDAP
+
+            // This role should already exists as it was imported from LDAP
+            RoleModel realmRole1 = appRealm.getRole("realmRole1");
+            john.grantRole(realmRole1);
+
+            // This role should already exists as it was imported from LDAP
+            RoleModel realmRole2 = appRealm.getRole("realmRole2");
+            mary.grantRole(realmRole2);
+
+            // This role may already exists from previous test (was imported from LDAP), but may not
+            RoleModel realmRole3 = appRealm.getRole("realmRole3");
+            if (realmRole3 == null) {
+                realmRole3 = appRealm.addRole("realmRole3");
+            }
+
+            john.grantRole(realmRole3);
+            mary.grantRole(realmRole3);
+
+            ClientModel accountApp = appRealm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
+            ClientModel financeApp = appRealm.getClientByClientId("finance");
+
+            RoleModel manageAccountRole = accountApp.getRole(AccountRoles.MANAGE_ACCOUNT);
+            RoleModel financeRole1 = financeApp.getRole("financeRole1");
+            john.grantRole(financeRole1);
+            session.userCache().clear();
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            session.userCache().clear();
+            RealmModel appRealm = session.realms().getRealmByName("test");
+            UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
+            UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+
+            // make sure we are in no-import mode
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm));
+            Assert.assertNull(session.userLocalStorage().getUserByUsername("marykeycloak", appRealm));
+
+            RoleModel realmRole1 = appRealm.getRole("realmRole1");
+            RoleModel realmRole2 = appRealm.getRole("realmRole2");
+            RoleModel realmRole3 = appRealm.getRole("realmRole3");
+            ClientModel accountApp = appRealm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
+            ClientModel financeApp = appRealm.getClientByClientId("finance");
+            RoleModel manageAccountRole = accountApp.getRole(AccountRoles.MANAGE_ACCOUNT);
+            RoleModel financeRole1 = financeApp.getRole("financeRole1");
+
+            // 3 - Check that role mappings are in LDAP and hence available through federation
+
+            Set<RoleModel> johnRoles = john.getRoleMappings();
+            Assert.assertTrue(johnRoles.contains(realmRole1));
+            Assert.assertFalse(johnRoles.contains(realmRole2));
+            Assert.assertTrue(johnRoles.contains(realmRole3));
+            Assert.assertTrue(johnRoles.contains(financeRole1));
+            Assert.assertTrue(johnRoles.contains(manageAccountRole));
+
+            Set<RoleModel> johnRealmRoles = john.getRealmRoleMappings();
+            Assert.assertEquals(2, johnRealmRoles.size());
+            Assert.assertTrue(johnRealmRoles.contains(realmRole1));
+            Assert.assertTrue(johnRealmRoles.contains(realmRole3));
+
+            // account roles are not mapped in LDAP. Those are in Keycloak DB
+            Set<RoleModel> johnAccountRoles = john.getClientRoleMappings(accountApp);
+            Assert.assertTrue(johnAccountRoles.contains(manageAccountRole));
+
+            Set<RoleModel> johnFinanceRoles = john.getClientRoleMappings(financeApp);
+            Assert.assertEquals(1, johnFinanceRoles.size());
+            Assert.assertTrue(johnFinanceRoles.contains(financeRole1));
+
+            // 4 - Delete some role mappings and check they are deleted
+
+            john.deleteRoleMapping(realmRole3);
+            john.deleteRoleMapping(realmRole1);
+            john.deleteRoleMapping(financeRole1);
+
+            johnRoles = john.getRoleMappings();
+            Assert.assertFalse(johnRoles.contains(realmRole1));
+            Assert.assertFalse(johnRoles.contains(realmRole2));
+            Assert.assertFalse(johnRoles.contains(realmRole3));
+            Assert.assertFalse(johnRoles.contains(financeRole1));
+
+            // Cleanup
+            mary.deleteRoleMapping(realmRole2);
+            mary.deleteRoleMapping(realmRole3);
+            session.userCache().clear();
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+    }
+
+
+    private void deleteRoleMappingsInLDAP(RoleLDAPStorageMapper roleMapper, LDAPObject ldapUser, String roleName) {
+        LDAPObject ldapRole1 = roleMapper.loadLDAPRoleByName(roleName);
+        roleMapper.deleteRoleMappingInLDAP(ldapUser, ldapRole1);
+    }
+}