keycloak-uncached
Changes
federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java 1(+1 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapper2WaySyncTest.java 259(+259 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperSyncTest.java 320(+320 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperTest.java 368(+368 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPMultipleAttributesTest.java 249(+249 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java 51(+26 -25)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPRoleMappingsTest.java 362(+362 -0)
Details
diff --git a/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java b/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java
index 6c5fbb2..cd41c5a 100644
--- a/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java
+++ b/federation/ldap2/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java
@@ -157,6 +157,7 @@ public class UserAttributeLDAPStorageMapper extends AbstractLDAPStorageMapper {
// throw ModelDuplicateException if there is different user in model with same email
protected void checkDuplicateEmail(String userModelAttrName, String email, RealmModel realm, KeycloakSession session, UserModel user) {
+ if (email == null) return;
if (UserModel.EMAIL.equalsIgnoreCase(userModelAttrName)) {
// lowercase before search
email = KeycloakModelUtils.toLowerCaseSafe(email);
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 2b2179c..8252f6b 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -28,6 +28,7 @@ import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
@@ -39,6 +40,7 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.cache.CachedRealmModel;
import org.keycloak.models.cache.infinispan.entities.CachedRealm;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.UserStorageProvider;
import java.security.Key;
import java.security.PrivateKey;
@@ -62,10 +64,12 @@ public class RealmAdapter implements CachedRealmModel {
protected RealmCacheSession cacheSession;
protected RealmModel updated;
protected RealmCache cache;
+ protected KeycloakSession session;
- public RealmAdapter(CachedRealm cached, RealmCacheSession cacheSession) {
+ public RealmAdapter(KeycloakSession session, CachedRealm cached, RealmCacheSession cacheSession) {
this.cached = cached;
this.cacheSession = cacheSession;
+ this.session = session;
}
@Override
@@ -1333,12 +1337,28 @@ public class RealmAdapter implements CachedRealmModel {
@Override
public ComponentModel addComponentModel(ComponentModel model) {
getDelegateForUpdate();
+ evictUsers(model);
return updated.addComponentModel(model);
}
+ public void evictUsers(ComponentModel model) {
+ String parentId = model.getParentId();
+ evictUsers(parentId);
+ }
+
+ public void evictUsers(String parentId) {
+ if (parentId != null && !parentId.equals(getId())) {
+ ComponentModel parent = getComponent(parentId);
+ if (parent != null && UserStorageProvider.class.getName().equals(parent.getProviderType())) {
+ session.getUserCache().evict(this);
+ }
+ }
+ }
+
@Override
public void updateComponent(ComponentModel component) {
getDelegateForUpdate();
+ evictUsers(component);
updated.updateComponent(component);
}
@@ -1346,6 +1366,7 @@ public class RealmAdapter implements CachedRealmModel {
@Override
public void removeComponent(ComponentModel component) {
getDelegateForUpdate();
+ evictUsers(component);
updated.removeComponent(component);
}
@@ -1353,6 +1374,7 @@ public class RealmAdapter implements CachedRealmModel {
@Override
public void removeComponents(String parentId) {
getDelegateForUpdate();
+ evictUsers(parentId);
updated.removeComponents(parentId);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
index 565fd48..9321f47 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
@@ -389,7 +389,7 @@ public class RealmCacheSession implements CacheRealmProvider {
} else if (managedRealms.containsKey(id)) {
return managedRealms.get(id);
}
- RealmAdapter adapter = new RealmAdapter(cached, this);
+ RealmAdapter adapter = new RealmAdapter(session, cached, this);
if (wasCached) {
CachedRealmModel.RealmCachedEvent event = new CachedRealmModel.RealmCachedEvent() {
@Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index 6030a66..bc49440 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -64,6 +64,11 @@ public class UserAdapter implements CachedUserModel {
}
@Override
+ public boolean isMarkedForEviction() {
+ return updated != null;
+ }
+
+ @Override
public void invalidate() {
getDelegateForUpdate();
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
index 0bb71c0..b51d911 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
@@ -115,7 +115,10 @@ public class UserCacheSession implements UserCache {
}
}
-
+ @Override
+ public void evict(RealmModel realm) {
+ realmInvalidations.add(realm.getId());
+ }
protected void runInvalidations() {
for (String realmId : realmInvalidations) {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index b8670dc..6de22a9 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -2046,7 +2046,10 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
}
c.setName(model.getName());
c.setParentId(model.getParentId());
- if (model.getParentId() == null) c.setParentId(this.getId());
+ if (model.getParentId() == null) {
+ c.setParentId(this.getId());
+ model.setParentId(this.getId());
+ }
c.setProviderType(model.getProviderType());
c.setProviderId(model.getProviderId());
c.setSubType(model.getSubType());
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 2fa7561..205f6b3 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -1964,7 +1964,10 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
updateComponentEntity(entity, model);
model.setId(entity.getId());
- if (model.getParentId() == null) entity.setParentId(this.getId());
+ if (model.getParentId() == null) {
+ entity.setParentId(this.getId());
+ model.setParentId(this.getId());
+ }
realm.getComponentEntities().add(entity);
updateRealm();
ComponentUtil.notifyCreated(session, this, model);
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
index 67431a9..8434d9d 100644
--- a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
@@ -35,6 +35,8 @@ public interface CachedUserModel extends UserModel {
*/
UserModel getDelegateForUpdate();
+ boolean isMarkedForEviction();
+
/**
* Invalidate the cache for this model
*
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java
index f309079..260b0be 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java
@@ -32,5 +32,12 @@ public interface UserCache extends UserProvider {
* @param user
*/
void evict(RealmModel realm, UserModel user);
+
+ /**
+ * Evict users of a specific realm
+ *
+ * @param realm
+ */
+ void evict(RealmModel realm);
void clear();
}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index a962300..b57d2b1 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -52,6 +52,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.CertificateRepresentation;
+import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.transaction.JtaTransactionManagerLookup;
import javax.crypto.spec.SecretKeySpec;
@@ -372,6 +373,29 @@ public final class KeycloakModelUtils {
}
return null;
}
+
+ public static UserStorageProviderModel findUserStorageProviderByName(String displayName, RealmModel realm) {
+ if (displayName == null) {
+ return null;
+ }
+
+ for (UserStorageProviderModel fedProvider : realm.getUserStorageProviders()) {
+ if (displayName.equals(fedProvider.getName())) {
+ return fedProvider;
+ }
+ }
+ return null;
+ }
+
+ public static UserStorageProviderModel findUserStorageProviderById(String fedProviderId, RealmModel realm) {
+ for (UserStorageProviderModel fedProvider : realm.getUserStorageProviders()) {
+ if (fedProviderId.equals(fedProvider.getId())) {
+ return fedProvider;
+ }
+ }
+ return null;
+ }
+
public static ComponentModel createComponentModel(String name, String parentId, String providerId, String providerType, String... config) {
ComponentModel mapperModel = new ComponentModel();
mapperModel.setParentId(parentId);
diff --git a/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java b/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java
index f71f84a..fcd1e18 100644
--- a/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java
+++ b/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java
@@ -43,8 +43,9 @@ public class OTPCredentialProvider implements CredentialProvider, CredentialInpu
protected KeycloakSession session;
protected List<CredentialModel> getCachedCredentials(UserModel user, String type) {
- if (!(user instanceof CachedUserModel)) return Collections.EMPTY_LIST;
+ if (!(user instanceof CachedUserModel)) return null;
CachedUserModel cached = (CachedUserModel)user;
+ if (cached.isMarkedForEviction()) return null;
List<CredentialModel> rtn = (List<CredentialModel>)cached.getCachedWith().get(OTPCredentialProvider.class.getName() + "." + type);
if (rtn == null) return Collections.EMPTY_LIST;
return rtn;
@@ -186,8 +187,9 @@ public class OTPCredentialProvider implements CredentialProvider, CredentialInpu
}
protected boolean configuredForTOTP(RealmModel realm, UserModel user) {
- return !getCachedCredentials(user, CredentialModel.TOTP).isEmpty()
- || !getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP).isEmpty();
+ List<CredentialModel> cachedCredentials = getCachedCredentials(user, CredentialModel.TOTP);
+ if (cachedCredentials == null) return !getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP).isEmpty();
+ return !cachedCredentials.isEmpty();
}
public static boolean validOTP(RealmModel realm, String token, String secret) {
diff --git a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
index d0558ab..84cd0f0 100644
--- a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
+++ b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
@@ -59,7 +59,7 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia
public CredentialModel getPassword(RealmModel realm, UserModel user) {
List<CredentialModel> passwords = null;
- if (user instanceof CachedUserModel) {
+ if (user instanceof CachedUserModel && !((CachedUserModel)user).isMarkedForEviction()) {
CachedUserModel cached = (CachedUserModel)user;
passwords = (List<CredentialModel>)cached.getCachedWith().get(PASSWORD_CACHE_KEY);
diff --git a/services/src/main/java/org/keycloak/storage/UserStorageManager.java b/services/src/main/java/org/keycloak/storage/UserStorageManager.java
index 8394bbb..e3eeaec 100755
--- a/services/src/main/java/org/keycloak/storage/UserStorageManager.java
+++ b/services/src/main/java/org/keycloak/storage/UserStorageManager.java
@@ -138,6 +138,12 @@ public class UserStorageManager implements UserProvider, OnUserCache {
if (getFederatedStorage() != null) getFederatedStorage().preRemove(realm, user);
StorageId storageId = new StorageId(user.getId());
if (storageId.getProviderId() == null) {
+ if (user.getFederationLink() != null) {
+ UserStorageProvider provider = getStorageProvider(session, realm, user.getFederationLink());
+ if (provider != null && provider instanceof UserRegistrationProvider) {
+ ((UserRegistrationProvider)provider).removeUser(realm, user);
+ }
+ }
return localStorage().removeUser(realm, user);
}
UserRegistrationProvider registry = (UserRegistrationProvider)getStorageProvider(session, realm, storageId.getProviderId());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapper2WaySyncTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapper2WaySyncTest.java
new file mode 100755
index 0000000..7534a93
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapper2WaySyncTest.java
@@ -0,0 +1,259 @@
+/*
+ * 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;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
+import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
+import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory;
+import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.storage.user.SynchronizationResult;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.LDAPRule;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPGroupMapper2WaySyncTest {
+
+ @ClassRule
+ public static LDAPRule ldapRule = new LDAPRule();
+
+ private static ComponentModel ldapModel = null;
+ private static String descriptionAttrName = null;
+
+ @Rule
+ public KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
+ ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true");
+ ldapConfig.putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.EditMode.WRITABLE.toString());
+ ldapConfig.putSingle(LDAPConstants.BATCH_SIZE_FOR_SYNC, "4"); // Issues with pagination on ApacheDS
+ 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);
+
+ 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 into Keycloak
+ removeAllModelGroups(appRealm);
+
+ GroupModel group1 = appRealm.createGroup("group1");
+ appRealm.moveGroup(group1, null);
+ group1.setSingleAttribute(descriptionAttrName, "group1 - description1");
+
+ GroupModel group11 = appRealm.createGroup("group11");
+ appRealm.moveGroup(group11, group1);
+
+ GroupModel group12 = appRealm.createGroup("group12");
+ appRealm.moveGroup(group12, group1);
+ group12.setSingleAttribute(descriptionAttrName, "group12 - description12");
+
+ GroupModel group2 = appRealm.createGroup("group2");
+ appRealm.moveGroup(group2, null);
+ }
+ });
+
+
+ @Test
+ public void test01_syncNoPreserveGroupInheritance() throws Exception {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel realm = session.realms().getRealmByName("test");
+ ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper");
+ LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+
+ // Update group mapper to skip preserve inheritance and check it will pass now
+ LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false");
+ realm.updateComponent(mapperModel);
+
+ // Sync from Keycloak into LDAP
+ SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromKeycloakToFederationProvider(mapperModel, ldapProvider, session, realm);
+ LDAPTestUtils.assertSyncEquals(syncResult, 4, 0, 0, 0);
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel realm = session.realms().getRealmByName("test");
+
+ // Delete all KC groups now
+ removeAllModelGroups(realm);
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group1"));
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group11"));
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group2"));
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+
+
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel realm = session.realms().getRealmByName("test");
+ ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper");
+ LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+
+ // Sync from LDAP back into Keycloak
+ SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm);
+ LDAPTestUtils.assertSyncEquals(syncResult, 4, 0, 0, 0);
+
+ // Assert groups are imported to keycloak. All are at top level
+ GroupModel kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1");
+ GroupModel kcGroup11 = KeycloakModelUtils.findGroupByPath(realm, "/group11");
+ GroupModel kcGroup12 = KeycloakModelUtils.findGroupByPath(realm, "/group12");
+ GroupModel kcGroup2 = KeycloakModelUtils.findGroupByPath(realm, "/group2");
+
+ Assert.assertEquals(0, kcGroup1.getSubGroups().size());
+
+ Assert.assertEquals("group1 - description1", kcGroup1.getFirstAttribute(descriptionAttrName));
+ Assert.assertNull(kcGroup11.getFirstAttribute(descriptionAttrName));
+ Assert.assertEquals("group12 - description12", kcGroup12.getFirstAttribute(descriptionAttrName));
+ Assert.assertNull(kcGroup2.getFirstAttribute(descriptionAttrName));
+
+ // test drop non-existing works
+ testDropNonExisting(session, realm, mapperModel, ldapProvider);
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+ }
+
+ @Test
+ public void test02_syncWithGroupInheritance() throws Exception {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel realm = session.realms().getRealmByName("test");
+ ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper");
+ LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+
+ // Update group mapper to skip preserve inheritance and check it will pass now
+ LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "true");
+ realm.updateComponent(mapperModel);
+
+ // Sync from Keycloak into LDAP
+ SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromKeycloakToFederationProvider(mapperModel, ldapProvider, session, realm);
+ LDAPTestUtils.assertSyncEquals(syncResult, 4, 0, 0, 0);
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel realm = session.realms().getRealmByName("test");
+
+ // Delete all KC groups now
+ removeAllModelGroups(realm);
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group1"));
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group11"));
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group2"));
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+
+
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel realm = session.realms().getRealmByName("test");
+ ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper");
+ LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+
+ // Sync from LDAP back into Keycloak
+ SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm);
+ LDAPTestUtils.assertSyncEquals(syncResult, 4, 0, 0, 0);
+
+ // Assert groups are imported to keycloak. All are at top level
+ GroupModel kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1");
+ GroupModel kcGroup11 = KeycloakModelUtils.findGroupByPath(realm, "/group1/group11");
+ GroupModel kcGroup12 = KeycloakModelUtils.findGroupByPath(realm, "/group1/group12");
+ GroupModel kcGroup2 = KeycloakModelUtils.findGroupByPath(realm, "/group2");
+
+ Assert.assertEquals(2, kcGroup1.getSubGroups().size());
+
+ Assert.assertEquals("group1 - description1", kcGroup1.getFirstAttribute(descriptionAttrName));
+ Assert.assertNull(kcGroup11.getFirstAttribute(descriptionAttrName));
+ Assert.assertEquals("group12 - description12", kcGroup12.getFirstAttribute(descriptionAttrName));
+ Assert.assertNull(kcGroup2.getFirstAttribute(descriptionAttrName));
+
+ // test drop non-existing works
+ testDropNonExisting(session, realm, mapperModel, ldapProvider);
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+ }
+
+
+ private static void removeAllModelGroups(RealmModel appRealm) {
+ for (GroupModel group : appRealm.getTopLevelGroups()) {
+ appRealm.removeGroup(group);
+ }
+ }
+
+ private void testDropNonExisting(KeycloakSession session, RealmModel realm, ComponentModel mapperModel, LDAPStorageProvider ldapProvider) {
+ // Put some group directly to LDAP
+ LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "group3");
+
+ // Sync and assert our group is still in LDAP
+ SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromKeycloakToFederationProvider(mapperModel, ldapProvider, session, realm);
+ LDAPTestUtils.assertSyncEquals(syncResult, 0, 4, 0, 0);
+ Assert.assertNotNull(LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, realm).loadLDAPGroupByName("group3"));
+
+ // Change config to drop non-existing groups
+ LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.DROP_NON_EXISTING_GROUPS_DURING_SYNC, "true");
+ realm.updateComponent(mapperModel);
+
+ // Sync and assert group removed from LDAP
+ syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromKeycloakToFederationProvider(mapperModel, ldapProvider, session, realm);
+ LDAPTestUtils.assertSyncEquals(syncResult, 0, 4, 1, 0);
+ Assert.assertNull(LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, realm).loadLDAPGroupByName("group3"));
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperSyncTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperSyncTest.java
new file mode 100755
index 0000000..6632124
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperSyncTest.java
@@ -0,0 +1,320 @@
+/*
+ * 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;
+
+import org.junit.Assert;
+import org.junit.Before;
+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.storage.UserStorageProviderModel;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
+import org.keycloak.storage.ldap.LDAPUtils;
+import org.keycloak.storage.ldap.idm.model.LDAPObject;
+import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
+import org.keycloak.storage.ldap.mappers.membership.MembershipType;
+import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapper;
+import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory;
+import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig;
+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.user.SynchronizationResult;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.LDAPRule;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPGroupMapperSyncTest {
+
+ private static LDAPRule ldapRule = new LDAPRule();
+
+ private static ComponentModel ldapModel = null;
+ private static String descriptionAttrName = null;
+
+ private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
+ ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true");
+ ldapConfig.putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.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);
+
+ 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, group1, group11, false);
+ LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, group1, group12, true);
+ }
+ });
+
+ @ClassRule
+ public static TestRule chain = RuleChain
+ .outerRule(ldapRule)
+ .around(keycloakRule);
+
+ @Before
+ public void before() {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel realm = session.realms().getRealmByName("test");
+ List<GroupModel> kcGroups = realm.getTopLevelGroups();
+ for (GroupModel kcGroup : kcGroups) {
+ realm.removeGroup(kcGroup);
+ }
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+ }
+
+ @Test
+ public void test01_syncNoPreserveGroupInheritance() throws Exception {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel realm = session.realms().getRealmByName("test");
+ ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper");
+ LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, realm);
+
+ // Add recursive group mapping to LDAP. Check that sync with preserve group inheritance will fail
+ LDAPObject group1 = groupMapper.loadLDAPGroupByName("group1");
+ LDAPObject group12 = groupMapper.loadLDAPGroupByName("group12");
+ LDAPUtils.addMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, group12, group1, true);
+
+ try {
+ new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm);
+ Assert.fail("Not expected group sync to pass");
+ } catch (ModelException expected) {
+ Assert.assertTrue(expected.getMessage().contains("Recursion detected"));
+ }
+
+ // Update group mapper to skip preserve inheritance and check it will pass now
+ LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false");
+ realm.updateComponent(mapperModel);
+
+ new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm);
+
+ // Assert groups are imported to keycloak. All are at top level
+ GroupModel kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1");
+ GroupModel kcGroup11 = KeycloakModelUtils.findGroupByPath(realm, "/group11");
+ GroupModel kcGroup12 = KeycloakModelUtils.findGroupByPath(realm, "/group12");
+
+ Assert.assertEquals(0, kcGroup1.getSubGroups().size());
+
+ Assert.assertEquals("group1 - description", kcGroup1.getFirstAttribute(descriptionAttrName));
+ Assert.assertNull(kcGroup11.getFirstAttribute(descriptionAttrName));
+ Assert.assertEquals("group12 - description", kcGroup12.getFirstAttribute(descriptionAttrName));
+
+ // Cleanup - remove recursive mapping in LDAP
+ LDAPUtils.deleteMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, group12, group1, true);
+
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+ @Test
+ public void test02_syncWithGroupInheritance() throws Exception {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel realm = session.realms().getRealmByName("test");
+ ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper");
+ LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, realm);
+
+ // Sync groups with inheritance
+ SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm);
+ LDAPTestUtils.assertSyncEquals(syncResult, 3, 0, 0, 0);
+
+ // Assert groups are imported to keycloak including their inheritance from LDAP
+ GroupModel kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1");
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group11"));
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group12"));
+ GroupModel kcGroup11 = KeycloakModelUtils.findGroupByPath(realm, "/group1/group11");
+ GroupModel kcGroup12 = KeycloakModelUtils.findGroupByPath(realm, "/group1/group12");
+
+ Assert.assertEquals(2, kcGroup1.getSubGroups().size());
+
+ Assert.assertEquals("group1 - description", kcGroup1.getFirstAttribute(descriptionAttrName));
+ Assert.assertNull(kcGroup11.getFirstAttribute(descriptionAttrName));
+ Assert.assertEquals("group12 - description", kcGroup12.getFirstAttribute(descriptionAttrName));
+
+ // Update description attributes in LDAP
+ LDAPObject group1 = groupMapper.loadLDAPGroupByName("group1");
+ group1.setSingleAttribute(descriptionAttrName, "group1 - changed description");
+ ldapProvider.getLdapIdentityStore().update(group1);
+
+ LDAPObject group12 = groupMapper.loadLDAPGroupByName("group12");
+ group12.setAttribute(descriptionAttrName, null);
+ ldapProvider.getLdapIdentityStore().update(group12);
+
+ // Sync and assert groups updated
+ syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm);
+ LDAPTestUtils.assertSyncEquals(syncResult, 0, 3, 0, 0);
+
+ // Assert attributes changed in keycloak
+ kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1");
+ kcGroup12 = KeycloakModelUtils.findGroupByPath(realm, "/group1/group12");
+ Assert.assertEquals("group1 - changed description", kcGroup1.getFirstAttribute(descriptionAttrName));
+ Assert.assertNull(kcGroup12.getFirstAttribute(descriptionAttrName));
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+ @Test
+ public void test03_syncWithDropNonExistingGroups() throws Exception {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel realm = session.realms().getRealmByName("test");
+ ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper");
+ LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+
+ // Sync groups with inheritance
+ SynchronizationResult syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm);
+ LDAPTestUtils.assertSyncEquals(syncResult, 3, 0, 0, 0);
+
+ // Assert groups are imported to keycloak including their inheritance from LDAP
+ GroupModel kcGroup1 = KeycloakModelUtils.findGroupByPath(realm, "/group1");
+ Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group11"));
+ Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group12"));
+
+ Assert.assertEquals(2, kcGroup1.getSubGroups().size());
+
+ // Create some new groups in keycloak
+ GroupModel model1 = realm.createGroup("model1");
+ realm.moveGroup(model1, null);
+ GroupModel model2 = realm.createGroup("model2");
+ kcGroup1.addChild(model2);
+
+ // Sync groups again from LDAP. Nothing deleted
+ syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm);
+ LDAPTestUtils.assertSyncEquals(syncResult, 0, 3, 0, 0);
+
+ Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group11"));
+ Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group12"));
+ Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/model1"));
+ Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/model2"));
+
+ // Update group mapper to drop non-existing groups during sync
+ LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.DROP_NON_EXISTING_GROUPS_DURING_SYNC, "true");
+ realm.updateComponent(mapperModel);
+
+ // Sync groups again from LDAP. Assert LDAP non-existing groups deleted
+ syncResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapProvider, session, realm);
+ Assert.assertEquals(3, syncResult.getUpdated());
+ Assert.assertTrue(syncResult.getRemoved() == 2);
+
+ // Sync and assert groups updated
+ Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group11"));
+ Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group12"));
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/model1"));
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/model2"));
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+
+
+ @Test
+ public void test04_syncNoPreserveGroupInheritanceWithLazySync() throws Exception {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel realm = session.realms().getRealmByName("test");
+ ComponentModel mapperModel = LDAPTestUtils.getComponentByName(realm,ldapModel, "groupsMapper");
+ LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, realm);
+
+ // Update group mapper to skip preserve inheritance
+ LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false");
+ realm.updateComponent(mapperModel);
+
+ // Add user to LDAP and put him as member of group11
+ LDAPTestUtils.removeAllLDAPUsers(ldapProvider, realm);
+ LDAPObject johnLdap = LDAPTestUtils.addLDAPUser(ldapProvider, realm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
+ LDAPTestUtils.updateLDAPPassword(ldapProvider, johnLdap, "Password1");
+ groupMapper.addGroupMappingInLDAP("group11", johnLdap);
+
+ // Assert groups not yet imported to Keycloak DB
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group1"));
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group11"));
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group12"));
+
+ // Load user from LDAP to Keycloak DB
+ UserModel john = session.users().getUserByUsername("johnkeycloak", realm);
+ Set<GroupModel> johnGroups = john.getGroups();
+
+ // Assert just those groups, which john was memberOf exists because they were lazily created
+ GroupModel group1 = KeycloakModelUtils.findGroupByPath(realm, "/group1");
+ GroupModel group11 = KeycloakModelUtils.findGroupByPath(realm, "/group11");
+ GroupModel group12 = KeycloakModelUtils.findGroupByPath(realm, "/group12");
+ Assert.assertNull(group1);
+ Assert.assertNotNull(group11);
+ Assert.assertNull(group12);
+
+ Assert.assertEquals(1, johnGroups.size());
+ Assert.assertTrue(johnGroups.contains(group11));
+
+ // Delete group mapping
+ john.leaveGroup(group11);
+
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperTest.java
new file mode 100755
index 0000000..c15a304
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperTest.java
@@ -0,0 +1,368 @@
+/*
+ * 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;
+
+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.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.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.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.LDAPRule;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPGroupMapperTest {
+
+ private static LDAPRule ldapRule = new LDAPRule();
+
+ private static ComponentModel ldapModel = null;
+ private static String descriptionAttrName = 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");
+ 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, LDAPStorageProviderFactory.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);
+
+ 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, group1, group11, false);
+ LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, group1, group12, true);
+
+ // Sync LDAP groups to Keycloak DB
+ ComponentModel mapperModel = LDAPTestUtils.getComponentByName(appRealm,ldapModel, "groupsMapper");
+ new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(mapperModel, ldapFedProvider, session, 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");
+
+ }
+ });
+
+ @ClassRule
+ public static TestRule chain = RuleChain
+ .outerRule(ldapRule)
+ .around(keycloakRule);
+
+ @Test
+ public void test01_ldapOnlyGroupMappings() {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel appRealm = session.realms().getRealmByName("test");
+
+ ComponentModel mapperModel = LDAPTestUtils.getComponentByName(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);
+
+ // 1 - Grant some groups in LDAP
+
+ // This group should already exists as it was imported from LDAP
+ GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
+ john.joinGroup(group1);
+
+ // This group should already exists as it was imported from LDAP
+ GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
+ mary.joinGroup(group11);
+
+ // This group should already exists as it was imported from LDAP
+ GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
+ john.joinGroup(group12);
+ mary.joinGroup(group12);
+
+ // 2 - Check that group mappings are not in local Keycloak DB (They are in LDAP).
+
+ UserModel johnDb = session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm);
+ Set<GroupModel> johnDbGroups = johnDb.getGroups();
+ Assert.assertEquals(0, johnDbGroups.size());
+
+ // 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(group12);
+
+ johnGroups = john.getGroups();
+ Assert.assertEquals(0, johnGroups.size());
+
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+ @Test
+ public void test02_readOnlyGroupMappings() {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ System.out.println("starting test02_readOnlyGroupMappings");
+ RealmModel appRealm = session.realms().getRealmByName("test");
+
+ ComponentModel mapperModel = LDAPTestUtils.getComponentByName(appRealm,ldapModel, "groupsMapper");
+ LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.READ_ONLY.toString());
+ appRealm.updateComponent(mapperModel);
+
+ UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+
+ GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
+ GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
+ GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
+
+ // Add some group mappings directly into LDAP
+ LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm);
+
+ LDAPObject maryLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "marykeycloak");
+ groupMapper.addGroupMappingInLDAP("group1", maryLdap);
+ groupMapper.addGroupMappingInLDAP("group11", maryLdap);
+
+ // Add some group mapping to model
+ mary.joinGroup(group12);
+
+ // Assert that mary has both LDAP and DB mapped groups
+ Set<GroupModel> maryGroups = mary.getGroups();
+ Assert.assertEquals(3, maryGroups.size());
+ Assert.assertTrue(maryGroups.contains(group1));
+ Assert.assertTrue(maryGroups.contains(group11));
+ Assert.assertTrue(maryGroups.contains(group12));
+
+ // Assert that access through DB will have just DB mapped groups
+ System.out.println("******");
+ UserModel maryDB = session.userLocalStorage().getUserByUsername("marykeycloak", appRealm);
+ Set<GroupModel> maryDBGroups = maryDB.getGroups();
+ Assert.assertFalse(maryDBGroups.contains(group1));
+ Assert.assertFalse(maryDBGroups.contains(group11));
+ Assert.assertTrue(maryDBGroups.contains(group12));
+
+ // 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("marykeycloak", group1Members.get(0).getUsername());
+ Assert.assertEquals(1, group11Members.size());
+ Assert.assertEquals("marykeycloak", group11Members.get(0).getUsername());
+ Assert.assertEquals(1, group12Members.size());
+ Assert.assertEquals("marykeycloak", group12Members.get(0).getUsername());
+
+ mary.leaveGroup(group12);
+ try {
+ mary.leaveGroup(group1);
+ Assert.fail("It wasn't expected to successfully delete LDAP group mappings in READ_ONLY mode");
+ } catch (ModelException expected) {
+ }
+
+ // Delete role mappings directly in LDAP
+ deleteGroupMappingsInLDAP(groupMapper, maryLdap, "group1");
+ deleteGroupMappingsInLDAP(groupMapper, maryLdap, "group11");
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+ @Test
+ public void test03_importGroupMappings() {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel appRealm = session.realms().getRealmByName("test");
+
+ ComponentModel mapperModel = LDAPTestUtils.getComponentByName(appRealm,ldapModel, "groupsMapper");
+ LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.IMPORT.toString());
+ appRealm.updateComponent(mapperModel);
+
+ // Add some group mappings directly in LDAP
+ LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm);
+
+ LDAPObject robLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "robkeycloak");
+ groupMapper.addGroupMappingInLDAP("group11", robLdap);
+ groupMapper.addGroupMappingInLDAP("group12", robLdap);
+
+ // Get user and check that he has requested groupa from LDAP
+ UserModel rob = session.users().getUserByUsername("robkeycloak", appRealm);
+ Set<GroupModel> robGroups = rob.getGroups();
+
+ GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
+ GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
+ GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
+
+ Assert.assertFalse(robGroups.contains(group1));
+ Assert.assertTrue(robGroups.contains(group11));
+ Assert.assertTrue(robGroups.contains(group12));
+
+ // Delete some group mappings in LDAP and check that it doesn't have any effect and user still has groups
+ deleteGroupMappingsInLDAP(groupMapper, robLdap, "group11");
+ deleteGroupMappingsInLDAP(groupMapper, robLdap, "group12");
+ robGroups = rob.getGroups();
+ Assert.assertTrue(robGroups.contains(group11));
+ Assert.assertTrue(robGroups.contains(group12));
+
+ // Delete group mappings through model and verifies that user doesn't have them anymore
+ rob.leaveGroup(group11);
+ rob.leaveGroup(group12);
+ robGroups = rob.getGroups();
+ Assert.assertEquals(0, robGroups.size());
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+
+ // KEYCLOAK-2682
+ @Test
+ public void test04_groupReferencingNonExistentMember() {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ // Ignoring this test on ActiveDirectory as it's not allowed to have LDAP group referencing nonexistent member. KEYCLOAK-2682 was related to OpenLDAP TODO: Better solution than programmatic...
+ LDAPConfig config = LDAPTestUtils.getLdapProvider(session, ldapModel).getLdapIdentityStore().getConfig();
+ if (config.isActiveDirectory()) {
+ return;
+ }
+
+ RealmModel appRealm = session.realms().getRealmByName("test");
+
+ ComponentModel mapperModel = LDAPTestUtils.getComponentByName(appRealm,ldapModel, "groupsMapper");
+ LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.LDAP_ONLY.toString());
+ appRealm.updateComponent(mapperModel);
+
+ // 1 - Add some group to LDAP for testing
+ LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm);
+ LDAPObject group2 = LDAPTestUtils.createLDAPGroup(session, appRealm, ldapModel, "group2", descriptionAttrName, "group2 - description");
+
+ // 2 - Add one existing user rob to LDAP group
+ LDAPObject jamesLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "jameskeycloak");
+ LDAPUtils.addMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, group2, jamesLdap, false);
+
+ // 3 - Add non-existing user to LDAP group
+ LDAPDn nonExistentDn = LDAPDn.fromString(ldapProvider.getLdapIdentityStore().getConfig().getUsersDn());
+ nonExistentDn.addFirst(jamesLdap.getRdnAttributeName(), "nonexistent");
+ LDAPObject nonExistentLdapUser = new LDAPObject();
+ nonExistentLdapUser.setDn(nonExistentDn);
+ LDAPUtils.addMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, group2, nonExistentLdapUser, true);
+
+ // 4 - Check group members. Just existing user rob should be present
+ groupMapper.syncDataFromFederationProviderToKeycloak();
+ GroupModel kcGroup2 = KeycloakModelUtils.findGroupByPath(appRealm, "/group2");
+ List<UserModel> groupUsers = session.users().getGroupMembers(appRealm, kcGroup2, 0, 5);
+ Assert.assertEquals(1, groupUsers.size());
+ UserModel rob = groupUsers.get(0);
+ Assert.assertEquals("jameskeycloak", rob.getUsername());
+
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+ 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/LDAPMultipleAttributesTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPMultipleAttributesTest.java
new file mode 100755
index 0000000..c054e08
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPMultipleAttributesTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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;
+
+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.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
+import org.keycloak.services.managers.RealmManager;
+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.testsuite.OAuthClient;
+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 javax.ws.rs.core.UriBuilder;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPMultipleAttributesTest {
+
+ protected String APP_SERVER_BASE_URL = "http://localhost:8081";
+ protected String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(APP_SERVER_BASE_URL + "/auth")).build("test").toString();
+
+ 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) {
+ MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
+ ldapConfig.putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.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);
+ ldapModel = appRealm.addComponentModel(model);
+
+ LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
+ LDAPTestUtils.addUserAttributeMapper(appRealm, ldapModel, "streetMapper", "street", LDAPConstants.STREET);
+
+ // Remove current users and add default users
+ LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
+
+ LDAPObject james = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", null, "88441");
+ LDAPTestUtils.updateLDAPPassword(ldapFedProvider, james, "Password1");
+
+ // User for testing duplicating surname and postalCode
+ LDAPObject bruce = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "bwilson", "Bruce", "Wilson", "bwilson@keycloak.org", "Elm 5", "88441", "77332");
+ bruce.setAttribute("sn", new LinkedHashSet<>(Arrays.asList("Wilson", "Schneider")));
+ ldapFedProvider.getLdapIdentityStore().update(bruce);
+ LDAPTestUtils.updateLDAPPassword(ldapFedProvider, bruce, "Password1");
+
+ // Create ldap-portal client
+ ClientModel ldapClient = KeycloakModelUtils.createClient(appRealm, "ldap-portal");
+ ldapClient.addRedirectUri("/ldap-portal");
+ ldapClient.addRedirectUri("/ldap-portal/*");
+ ldapClient.setManagementUrl("/ldap-portal");
+ ldapClient.addProtocolMapper(UserAttributeMapper.createClaimMapper("postalCode", "postal_code", "postal_code", "String", true, "", true, true, true));
+ ldapClient.addProtocolMapper(UserAttributeMapper.createClaimMapper("street", "street", "street", "String", true, "", true, true, false));
+ ldapClient.addScopeMapping(appRealm.getRole("user"));
+ ldapClient.setSecret("password");
+
+ // Deploy ldap-portal client
+ URL url = getClass().getResource("/ldap/ldap-app-keycloak.json");
+ keycloakRule.createApplicationDeployment()
+ .name("ldap-portal").contextPath("/ldap-portal")
+ .servletClass(LDAPExampleServlet.class).adapterConfigPath(url.getPath())
+ .role("user").deployApplication();
+ }
+ });
+
+ @ClassRule
+ public static TestRule chain = RuleChain
+ .outerRule(ldapRule)
+ .around(keycloakRule);
+
+ @Rule
+ public WebRule webRule = new WebRule(this);
+
+ @WebResource
+ protected WebDriver driver;
+
+ @WebResource
+ protected OAuthClient oauth;
+
+ @WebResource
+ protected LoginPage loginPage;
+
+ @Test
+ public void testModel() {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel appRealm = session.realms().getRealmByName("test");
+
+ LDAPTestUtils.assertUserImported(session.users(), appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", "88441");
+
+ UserModel user = session.users().getUserByUsername("bwilson", appRealm);
+ Assert.assertEquals("bwilson@keycloak.org", user.getEmail());
+ Assert.assertEquals("Bruce", user.getFirstName());
+
+ // There are 2 lastnames in ldif
+ Assert.assertTrue("Wilson".equals(user.getLastName()) || "Schneider".equals(user.getLastName()));
+
+ // Actually there are 2 postalCodes
+ List<String> postalCodes = user.getAttribute("postal_code");
+ assertPostalCodes(postalCodes, "88441", "77332");
+ List<String> tmp = new LinkedList<>();
+ tmp.addAll(postalCodes);
+ postalCodes = tmp;
+ postalCodes.remove("77332");
+ user.setAttribute("postal_code", postalCodes);
+
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel appRealm = session.realms().getRealmByName("test");
+ UserModel user = session.users().getUserByUsername("bwilson", appRealm);
+ List<String> postalCodes = user.getAttribute("postal_code");
+ assertPostalCodes(postalCodes, "88441");
+ List<String> tmp = new LinkedList<>();
+ tmp.addAll(postalCodes);
+ postalCodes = tmp;
+ postalCodes.add("77332");
+ user.setAttribute("postal_code", postalCodes);
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel appRealm = session.realms().getRealmByName("test");
+ UserModel user = session.users().getUserByUsername("bwilson", appRealm);
+ assertPostalCodes(user.getAttribute("postal_code"), "88441", "77332");
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+ }
+
+ private void assertPostalCodes(List<String> postalCodes, String... expectedPostalCodes) {
+ if (expectedPostalCodes == null && postalCodes.isEmpty()) {
+ return;
+ }
+
+
+ Assert.assertEquals(expectedPostalCodes.length, postalCodes.size());
+ for (String expected : expectedPostalCodes) {
+ if (!postalCodes.contains(expected)) {
+ Assert.fail("postalCode '" + expected + "' not in postalCodes: " + postalCodes);
+ }
+ }
+ }
+
+ @Test
+ public void ldapPortalEndToEndTest() {
+ // Login as bwilson
+ driver.navigate().to(APP_SERVER_BASE_URL + "/ldap-portal");
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+ loginPage.login("bwilson", "Password1");
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(APP_SERVER_BASE_URL + "/ldap-portal"));
+ String pageSource = driver.getPageSource();
+ System.out.println(pageSource);
+ Assert.assertTrue(pageSource.contains("bwilson") && pageSource.contains("Bruce"));
+ Assert.assertTrue(pageSource.contains("street") && pageSource.contains("Elm 5"));
+ Assert.assertTrue(pageSource.contains("postal_code") && pageSource.contains("88441") && pageSource.contains("77332"));
+
+ // Logout
+ String logoutUri = OIDCLoginProtocolService.logoutUrl(UriBuilder.fromUri(APP_SERVER_BASE_URL + "/auth"))
+ .queryParam(OAuth2Constants.REDIRECT_URI, APP_SERVER_BASE_URL + "/ldap-portal").build("test").toString();
+ driver.navigate().to(logoutUri);
+
+ // Login as jbrown
+ driver.navigate().to(APP_SERVER_BASE_URL + "/ldap-portal");
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+ loginPage.login("jbrown", "Password1");
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(APP_SERVER_BASE_URL + "/ldap-portal"));
+ pageSource = driver.getPageSource();
+ System.out.println(pageSource);
+ Assert.assertTrue(pageSource.contains("jbrown") && pageSource.contains("James Brown"));
+ Assert.assertFalse(pageSource.contains("street"));
+ Assert.assertTrue(pageSource.contains("postal_code") && pageSource.contains("88441"));
+ Assert.assertFalse(pageSource.contains("77332"));
+
+ // Logout
+ driver.navigate().to(logoutUri);
+ }
+
+
+
+}
+
+
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java
index 85f7812..13735e6 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java
@@ -79,16 +79,6 @@ public class LDAPProvidersIntegrationTest {
private static ComponentModel ldapModel = null;
- private static MultivaluedHashMap<String, String> getLdapRuleConfig() {
- MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
- Map<String,String> ldapConfig = ldapRule.getConfig();
- for (Map.Entry<String, String> entry : ldapConfig.entrySet()) {
- config.add(entry.getKey(), entry.getValue());
-
- }
- return config;
-
- }
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@@ -96,7 +86,7 @@ public class LDAPProvidersIntegrationTest {
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "marykeycloak", "mary@test.com", "password-app");
- MultivaluedHashMap<String,String> ldapConfig = getLdapRuleConfig();
+ MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true");
ldapConfig.putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.EditMode.WRITABLE.toString());
UserStorageProviderModel model = new UserStorageProviderModel();
@@ -441,7 +431,7 @@ public class LDAPProvidersIntegrationTest {
}
}
- @Test
+ //@Test // don't think we should support this, bburke
public void testDirectLDAPUpdate() {
KeycloakSession session = keycloakRule.startSession();
@@ -728,6 +718,17 @@ public class LDAPProvidersIntegrationTest {
@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");
@@ -756,26 +757,26 @@ public class LDAPProvidersIntegrationTest {
LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username4", "John4", "Doel4", "user4@email.org", null, "124");
// Users are not at local store at this moment
- Assert.assertNull(session.userStorage().getUserByUsername("username1", appRealm));
- Assert.assertNull(session.userStorage().getUserByUsername("username2", appRealm));
- Assert.assertNull(session.userStorage().getUserByUsername("username3", appRealm));
- Assert.assertNull(session.userStorage().getUserByUsername("username4", appRealm));
+ Assert.assertNull(session.userLocalStorage().getUserByUsername("username1", appRealm));
+ Assert.assertNull(session.userLocalStorage().getUserByUsername("username2", appRealm));
+ Assert.assertNull(session.userLocalStorage().getUserByUsername("username3", appRealm));
+ Assert.assertNull(session.userLocalStorage().getUserByUsername("username4", appRealm));
// search by username
session.users().searchForUser("username1", appRealm);
- LDAPTestUtils.assertUserImported(session.userStorage(), appRealm, "username1", "John1", "Doel1", "user1@email.org", "121");
+ LDAPTestUtils.assertUserImported(session.userLocalStorage(), appRealm, "username1", "John1", "Doel1", "user1@email.org", "121");
// search by email
session.users().searchForUser("user2@email.org", appRealm);
- LDAPTestUtils.assertUserImported(session.userStorage(), appRealm, "username2", "John2", "Doel2", "user2@email.org", "122");
+ LDAPTestUtils.assertUserImported(session.userLocalStorage(), appRealm, "username2", "John2", "Doel2", "user2@email.org", "122");
// search by lastName
session.users().searchForUser("Doel3", appRealm);
- LDAPTestUtils.assertUserImported(session.userStorage(), appRealm, "username3", "John3", "Doel3", "user3@email.org", "123");
+ LDAPTestUtils.assertUserImported(session.userLocalStorage(), appRealm, "username3", "John3", "Doel3", "user3@email.org", "123");
// search by firstName + lastName
session.users().searchForUser("John4 Doel4", appRealm);
- LDAPTestUtils.assertUserImported(session.userStorage(), appRealm, "username4", "John4", "Doel4", "user4@email.org", "124");
+ LDAPTestUtils.assertUserImported(session.userLocalStorage(), appRealm, "username4", "John4", "Doel4", "user4@email.org", "124");
} finally {
keycloakRule.stopSession(session, true);
}
@@ -803,15 +804,15 @@ public class LDAPProvidersIntegrationTest {
LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username7", "John7", "Doel7", "user7@email.org", null, "127");
// search by email
- session.users().searchForUser("user5@email.org", appRealm);
- LDAPTestUtils.assertUserImported(session.userStorage(), appRealm, "username5", "John5", "Doel5", "user5@email.org", "125");
+ List<UserModel> list = session.users().searchForUser("user5@email.org", appRealm);
+ LDAPTestUtils.assertUserImported(session.userLocalStorage(), appRealm, "username5", "John5", "Doel5", "user5@email.org", "125");
session.users().searchForUser("John6 Doel6", appRealm);
- LDAPTestUtils.assertUserImported(session.userStorage(), appRealm, "username6", "John6", "Doel6", "user6@email.org", "126");
+ LDAPTestUtils.assertUserImported(session.userLocalStorage(), appRealm, "username6", "John6", "Doel6", "user6@email.org", "126");
session.users().searchForUser("user7@email.org", appRealm);
session.users().searchForUser("John7 Doel7", appRealm);
- Assert.assertNull(session.userStorage().getUserByUsername("username7", appRealm));
+ Assert.assertNull(session.userLocalStorage().getUserByUsername("username7", appRealm));
// Remove custom filter
ldapModel.getConfig().remove(LDAPConstants.CUSTOM_USER_SEARCH_FILTER);
@@ -850,7 +851,7 @@ public class LDAPProvidersIntegrationTest {
Assert.assertTrue(session.users().removeUser(appRealm, user));
// Assert user not available locally, but will be reimported from LDAP once searched
- Assert.assertNull(session.userStorage().getUserByUsername("johnkeycloak", appRealm));
+ Assert.assertNull(session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm));
Assert.assertNotNull(session.users().getUserByUsername("johnkeycloak", appRealm));
} finally {
keycloakRule.stopSession(session, false);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPRoleMappingsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPRoleMappingsTest.java
new file mode 100644
index 0000000..19c2b6b
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPRoleMappingsTest.java
@@ -0,0 +1,362 @@
+/*
+ * 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;
+
+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.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.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.ldap.mappers.membership.role.RoleLDAPStorageMapper;
+import org.keycloak.testsuite.OAuthClient;
+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.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPRoleMappingsTest {
+
+ 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, LDAPStorageProviderFactory.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);
+
+ 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 test01_ldapOnlyRoleMappings() {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ 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);
+
+ // 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);
+
+ // 2 - Check that role mappings are not in local Keycloak DB (They are in LDAP).
+
+ UserModel johnDb = session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm);
+ Set<RoleModel> johnDbRoles = johnDb.getRoleMappings();
+ Assert.assertFalse(johnDbRoles.contains(realmRole1));
+ Assert.assertFalse(johnDbRoles.contains(realmRole2));
+ Assert.assertFalse(johnDbRoles.contains(realmRole3));
+ Assert.assertFalse(johnDbRoles.contains(financeRole1));
+ Assert.assertTrue(johnDbRoles.contains(manageAccountRole));
+
+ // 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);
+ john.deleteRoleMapping(manageAccountRole);
+
+ johnRoles = john.getRoleMappings();
+ Assert.assertFalse(johnRoles.contains(realmRole1));
+ Assert.assertFalse(johnRoles.contains(realmRole2));
+ Assert.assertFalse(johnRoles.contains(realmRole3));
+ Assert.assertFalse(johnRoles.contains(financeRole1));
+ Assert.assertFalse(johnRoles.contains(manageAccountRole));
+
+ // Cleanup
+ mary.deleteRoleMapping(realmRole2);
+ mary.deleteRoleMapping(realmRole3);
+ john.grantRole(manageAccountRole);
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+ @Test
+ public void test02_readOnlyRoleMappings() {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel appRealm = session.realms().getRealmByName("test");
+
+ LDAPTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.READ_ONLY);
+
+ UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+
+ RoleModel realmRole1 = appRealm.getRole("realmRole1");
+ RoleModel realmRole2 = appRealm.getRole("realmRole2");
+ RoleModel realmRole3 = appRealm.getRole("realmRole3");
+ if (realmRole3 == null) {
+ realmRole3 = appRealm.addRole("realmRole3");
+ }
+
+ // Add some role mappings directly into LDAP
+ ComponentModel roleMapperModel = LDAPTestUtils.getComponentByName(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);
+
+ // Add some role to model
+ mary.grantRole(realmRole3);
+
+ // Assert that mary has both LDAP and DB mapped roles
+ Set<RoleModel> maryRoles = mary.getRealmRoleMappings();
+ Assert.assertTrue(maryRoles.contains(realmRole1));
+ Assert.assertTrue(maryRoles.contains(realmRole2));
+ Assert.assertTrue(maryRoles.contains(realmRole3));
+
+ // Assert that access through DB will have just DB mapped role
+ UserModel maryDB = session.userLocalStorage().getUserByUsername("marykeycloak", appRealm);
+ Set<RoleModel> maryDBRoles = maryDB.getRealmRoleMappings();
+ Assert.assertFalse(maryDBRoles.contains(realmRole1));
+ Assert.assertFalse(maryDBRoles.contains(realmRole2));
+ Assert.assertTrue(maryDBRoles.contains(realmRole3));
+
+ mary.deleteRoleMapping(realmRole3);
+ try {
+ mary.deleteRoleMapping(realmRole1);
+ Assert.fail("It wasn't expected to successfully delete LDAP role mappings in READ_ONLY mode");
+ } catch (ModelException expected) {
+ }
+
+ // Delete role mappings directly in LDAP
+ deleteRoleMappingsInLDAP(roleMapper, maryLdap, "realmRole1");
+ deleteRoleMappingsInLDAP(roleMapper, maryLdap, "realmRole2");
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel appRealm = session.realms().getRealmByName("test");
+ UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
+
+ // Assert role mappings is not available
+ Set<RoleModel> maryRoles = mary.getRealmRoleMappings();
+ Assert.assertFalse(maryRoles.contains(appRealm.getRole("realmRole1")));
+ Assert.assertFalse(maryRoles.contains(appRealm.getRole("realmRole2")));
+ Assert.assertFalse(maryRoles.contains(appRealm.getRole("realmRole3")));
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+ @Test
+ public void test03_importRoleMappings() {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel appRealm = session.realms().getRealmByName("test");
+
+ LDAPTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.IMPORT);
+
+ // Add some role mappings directly in LDAP
+ ComponentModel roleMapperModel = LDAPTestUtils.getComponentByName(appRealm, ldapModel, "realmRolesMapper");
+ LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ RoleLDAPStorageMapper roleMapper = LDAPTestUtils.getRoleMapper(roleMapperModel, ldapProvider, appRealm);
+
+ LDAPObject robLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "robkeycloak");
+ roleMapper.addRoleMappingInLDAP("realmRole1", robLdap);
+ roleMapper.addRoleMappingInLDAP("realmRole2", robLdap);
+
+ // Get user and check that he has requested roles from LDAP
+ UserModel rob = session.users().getUserByUsername("robkeycloak", appRealm);
+ RoleModel realmRole1 = appRealm.getRole("realmRole1");
+ RoleModel realmRole2 = appRealm.getRole("realmRole2");
+ RoleModel realmRole3 = appRealm.getRole("realmRole3");
+ if (realmRole3 == null) {
+ realmRole3 = appRealm.addRole("realmRole3");
+ }
+ Set<RoleModel> robRoles = rob.getRealmRoleMappings();
+ Assert.assertTrue(robRoles.contains(realmRole1));
+ Assert.assertTrue(robRoles.contains(realmRole2));
+ Assert.assertFalse(robRoles.contains(realmRole3));
+
+ // Add some role mappings in model and check that user has it
+ rob.grantRole(realmRole3);
+ robRoles = rob.getRealmRoleMappings();
+ Assert.assertTrue(robRoles.contains(realmRole3));
+
+ // Delete some role mappings in LDAP and check that it doesn't have any effect and user still has role
+ deleteRoleMappingsInLDAP(roleMapper, robLdap, "realmRole1");
+ deleteRoleMappingsInLDAP(roleMapper, robLdap, "realmRole2");
+ robRoles = rob.getRealmRoleMappings();
+ Assert.assertTrue(robRoles.contains(realmRole1));
+ Assert.assertTrue(robRoles.contains(realmRole2));
+
+ // Delete role mappings through model and verifies that user doesn't have them anymore
+ rob.deleteRoleMapping(realmRole1);
+ rob.deleteRoleMapping(realmRole2);
+ rob.deleteRoleMapping(realmRole3);
+ robRoles = rob.getRealmRoleMappings();
+ Assert.assertFalse(robRoles.contains(realmRole1));
+ Assert.assertFalse(robRoles.contains(realmRole2));
+ Assert.assertFalse(robRoles.contains(realmRole3));
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+ private void deleteRoleMappingsInLDAP(RoleLDAPStorageMapper roleMapper, LDAPObject ldapUser, String roleName) {
+ LDAPObject ldapRole1 = roleMapper.loadLDAPRoleByName(roleName);
+ roleMapper.deleteRoleMappingInLDAP(ldapUser, ldapRole1);
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPSyncTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPSyncTest.java
new file mode 100755
index 0000000..c1d55c1
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPSyncTest.java
@@ -0,0 +1,378 @@
+/*
+ * 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;
+
+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.common.util.Time;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.services.managers.UserStorageSyncManager;
+import org.keycloak.storage.ldap.idm.model.LDAPObject;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
+import org.keycloak.storage.user.SynchronizationResult;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.LDAPRule;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPSyncTest {
+
+ private static LDAPRule ldapRule = new LDAPRule();
+
+ private static UserStorageProviderModel ldapModel = null;
+
+ private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ // Other tests may left Time offset uncleared, which could cause issues
+ Time.setOffset(0);
+
+ MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
+ ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "false");
+ ldapConfig.putSingle(LDAPConstants.EDIT_MODE, LDAPStorageProviderFactory.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);
+
+ ldapModel = new UserStorageProviderModel(appRealm.addComponentModel(model));
+
+
+ LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
+
+ // Delete all LDAP users and add 5 new users for testing
+ LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
+
+ for (int i=1 ; i<=5 ; i++) {
+ LDAPObject ldapUser = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", null, "12" + i);
+ LDAPTestUtils.updateLDAPPassword(ldapFedProvider, ldapUser, "Password1");
+ }
+ }
+ });
+
+ @ClassRule
+ public static TestRule chain = RuleChain
+ .outerRule(ldapRule)
+ .around(keycloakRule);
+
+// @Test
+// public void test01runit() throws Exception {
+// Thread.sleep(10000000);
+// }
+
+ @Test
+ public void test01LDAPSync() {
+ UserStorageSyncManager usersSyncManager = new UserStorageSyncManager();
+
+ // wait a bit
+ sleep(ldapRule.getSleepTime());
+
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+ SynchronizationResult syncResult = usersSyncManager.syncAllUsers(sessionFactory, "test", ldapModel);
+ LDAPTestUtils.assertSyncEquals(syncResult, 5, 0, 0, 0);
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+ UserProvider userProvider = session.userLocalStorage();
+ // Assert users imported
+ LDAPTestUtils.assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org", "121");
+ LDAPTestUtils.assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org", "122");
+ LDAPTestUtils.assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org", "123");
+ LDAPTestUtils.assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org", "124");
+ LDAPTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org", "125");
+
+ // Assert lastSync time updated
+ Assert.assertTrue(ldapModel.getLastSync() > 0);
+ for (UserStorageProviderModel persistentFedModel : testRealm.getUserStorageProviders()) {
+ if (LDAPStorageProviderFactory.PROVIDER_NAME.equals(persistentFedModel.getProviderId())) {
+ Assert.assertTrue(persistentFedModel.getLastSync() > 0);
+ } else {
+ // Dummy provider has still 0
+ Assert.assertEquals(0, persistentFedModel.getLastSync());
+ }
+ }
+
+ // wait a bit
+ sleep(ldapRule.getSleepTime());
+
+ // Add user to LDAP and update 'user5' in LDAP
+ LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ LDAPTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", null, "126");
+ LDAPObject ldapUser5 = ldapFedProvider.loadLDAPUserByUsername(testRealm, "user5");
+ // NOTE: Changing LDAP attributes directly here
+ ldapUser5.setSingleAttribute(LDAPConstants.EMAIL, "user5Updated@email.org");
+ ldapUser5.setSingleAttribute(LDAPConstants.POSTAL_CODE, "521");
+ ldapFedProvider.getLdapIdentityStore().update(ldapUser5);
+
+ // Assert still old users in local provider
+ LDAPTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org", "125");
+ Assert.assertNull(userProvider.getUserByUsername("user6", testRealm));
+
+ // Trigger partial sync
+ KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+ SynchronizationResult syncResult = usersSyncManager.syncChangedUsers(sessionFactory, "test", ldapModel);
+ LDAPTestUtils.assertSyncEquals(syncResult, 1, 1, 0, 0);
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+ UserProvider userProvider = session.userLocalStorage();
+ // Assert users updated in local provider
+ LDAPTestUtils.assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5updated@email.org", "521");
+ LDAPTestUtils.assertUserImported(userProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126");
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+ @Test
+ public void test02duplicateUsernameAndEmailSync() {
+ LDAPObject duplicatedLdapUser;
+
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+
+ LDAPTestUtils.addLocalUser(session, testRealm, "user7", "user7@email.org", "password");
+ LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+
+ // Add user to LDAP with duplicated username "user7"
+ duplicatedLdapUser = LDAPTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user7", "User7FN", "User7LN", "user7-something@email.org", null, "126");
+
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+
+ // Assert syncing from LDAP fails due to duplicated username
+ SynchronizationResult result = new UserStorageSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel);
+ Assert.assertEquals(1, result.getFailed());
+
+ // Remove "user7" from LDAP
+ LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ ldapFedProvider.getLdapIdentityStore().remove(duplicatedLdapUser);
+
+ // Add user to LDAP with duplicated email "user7@email.org"
+ duplicatedLdapUser = LDAPTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user7-something", "User7FNN", "User7LNL", "user7@email.org", null, "126");
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+
+ // Assert syncing from LDAP fails due to duplicated email
+ SynchronizationResult result = new UserStorageSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel);
+ Assert.assertEquals(1, result.getFailed());
+ Assert.assertNull(session.userLocalStorage().getUserByUsername("user7-something", testRealm));
+
+ // Update LDAP user to avoid duplicated email
+ duplicatedLdapUser.setSingleAttribute(LDAPConstants.EMAIL, "user7-changed@email.org");
+ LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ ldapFedProvider.getLdapIdentityStore().update(duplicatedLdapUser);
+
+ // Assert user successfully synced now
+ result = new UserStorageSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel);
+ Assert.assertEquals(0, result.getFailed());
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+
+ // Assert user imported in another transaction
+ session = keycloakRule.startSession();
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+ LDAPTestUtils.assertUserImported(session.userLocalStorage(), testRealm, "user7-something", "User7FNN", "User7LNL", "user7-changed@email.org", "126");
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
+ // KEYCLOAK-1571
+ @Test
+ public void test03SameUUIDAndUsernameSync() {
+ KeycloakSession session = keycloakRule.startSession();
+ String origUuidAttrName;
+
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+
+ // Remove all users from model
+ for (UserModel user : session.userLocalStorage().getUsers(testRealm, true)) {
+ session.userLocalStorage().removeUser(testRealm, user);
+ }
+
+ UserStorageProviderModel providerModel = KeycloakModelUtils.findUserStorageProviderByName(ldapModel.getName(), testRealm);
+
+ // Change name of UUID attribute to same like usernameAttribute
+ LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ String uidAttrName = ldapFedProvider.getLdapIdentityStore().getConfig().getUsernameLdapAttribute();
+ origUuidAttrName = providerModel.getConfig().getFirst(LDAPConstants.UUID_LDAP_ATTRIBUTE);
+ providerModel.getConfig().putSingle(LDAPConstants.UUID_LDAP_ATTRIBUTE, uidAttrName);
+
+ // Need to change this due to ApacheDS pagination bug (For other LDAP servers, pagination works fine) TODO: Remove once ApacheDS upgraded and pagination is fixed
+ providerModel.getConfig().putSingle(LDAPConstants.BATCH_SIZE_FOR_SYNC, "10");
+ testRealm.updateComponent(providerModel);
+
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+ UserStorageProviderModel providerModel = KeycloakModelUtils.findUserStorageProviderByName(ldapModel.getName(), testRealm);
+
+ KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+ SynchronizationResult syncResult = new UserStorageSyncManager().syncAllUsers(sessionFactory, "test", providerModel);
+ Assert.assertEquals(0, syncResult.getFailed());
+
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+
+ // Assert users imported with correct LDAP_ID
+ LDAPTestUtils.assertUserImported(session.users(), testRealm, "user1", "User1FN", "User1LN", "user1@email.org", "121");
+ LDAPTestUtils.assertUserImported(session.users(), testRealm, "user2", "User2FN", "User2LN", "user2@email.org", "122");
+ UserModel user1 = session.users().getUserByUsername("user1", testRealm);
+ Assert.assertEquals("user1", user1.getFirstAttribute(LDAPConstants.LDAP_ID));
+
+ // Revert config changes
+ UserStorageProviderModel providerModel = KeycloakModelUtils.findUserStorageProviderByName(ldapModel.getName(), testRealm);
+ providerModel.getConfig().putSingle(LDAPConstants.UUID_LDAP_ATTRIBUTE, origUuidAttrName);
+ testRealm.updateComponent(providerModel);
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+ }
+
+ // KEYCLOAK-1728
+ @Test
+ public void test04MissingLDAPUsernameSync() {
+ KeycloakSession session = keycloakRule.startSession();
+ String origUsernameAttrName;
+
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+
+ // Remove all users from model
+ for (UserModel user : session.userLocalStorage().getUsers(testRealm, true)) {
+ System.out.println("trying to delete user: " + user.getUsername());
+ session.getUserCache().evict(testRealm, user);
+ session.userLocalStorage().removeUser(testRealm, user);
+ }
+
+ UserStorageProviderModel providerModel = KeycloakModelUtils.findUserStorageProviderByName(ldapModel.getName(), testRealm);
+
+ // Add street mapper and add some user including street
+ ComponentModel streetMapper = LDAPTestUtils.addUserAttributeMapper(testRealm, ldapModel, "streetMapper", "street", LDAPConstants.STREET);
+ LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ LDAPObject streetUser = LDAPTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user8", "User8FN", "User8LN", "user8@email.org", "user8street", "126");
+
+ // Change name of username attribute name to street
+ origUsernameAttrName = providerModel.getConfig().getFirst(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
+ providerModel.getConfig().putSingle(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, "street");
+
+ // Need to change this due to ApacheDS pagination bug (For other LDAP servers, pagination works fine) TODO: Remove once ApacheDS upgraded and pagination is fixed
+ providerModel.getConfig().putSingle(LDAPConstants.BATCH_SIZE_FOR_SYNC, "10");
+ testRealm.updateComponent(providerModel);
+
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+
+ // Just user8 synced. All others failed to sync
+ session = keycloakRule.startSession();
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+ UserStorageProviderModel providerModel = KeycloakModelUtils.findUserStorageProviderByName(ldapModel.getName(), testRealm);
+
+ KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+ SynchronizationResult syncResult = new UserStorageSyncManager().syncAllUsers(sessionFactory, "test", providerModel);
+ Assert.assertEquals(1, syncResult.getAdded());
+ Assert.assertTrue(syncResult.getFailed() > 0);
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+
+ session = keycloakRule.startSession();
+ try {
+ RealmModel testRealm = session.realms().getRealm("test");
+
+ // Revert config changes
+ UserStorageProviderModel providerModel = KeycloakModelUtils.findUserStorageProviderByName(ldapModel.getName(), testRealm);
+ providerModel.getConfig().putSingle(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, origUsernameAttrName);
+ testRealm.updateComponent(providerModel);
+ ComponentModel streetMapper = LDAPTestUtils.getComponentByName(testRealm, providerModel, "streetMapper");
+ testRealm.removeComponent(streetMapper);
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+ }
+
+ private void sleep(int time) {
+ try {
+ Thread.sleep(time);
+ } catch (InterruptedException ie) {
+ throw new RuntimeException(ie);
+ }
+ }
+}
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 f4bf182..4b612f2 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
@@ -18,6 +18,7 @@
package org.keycloak.testsuite.federation.storage.ldap;
import org.junit.Assert;
+import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
@@ -46,6 +47,7 @@ import org.keycloak.storage.ldap.mappers.membership.role.RoleLDAPStorageMapper;
import org.keycloak.storage.ldap.mappers.membership.role.RoleLDAPStorageMapperFactory;
import org.keycloak.storage.ldap.mappers.membership.role.RoleMapperConfig;
import org.keycloak.storage.user.SynchronizationResult;
+import org.keycloak.testsuite.rule.LDAPRule;
import java.util.Arrays;
import java.util.Collections;
@@ -58,9 +60,20 @@ import java.util.Set;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPTestUtils {
+ public static MultivaluedHashMap<String, String> getLdapRuleConfig(LDAPRule ldapRule) {
+ MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+ Map<String,String> ldapConfig = ldapRule.getConfig();
+ for (Map.Entry<String, String> entry : ldapConfig.entrySet()) {
+ config.add(entry.getKey(), entry.getValue());
+
+ }
+ return config;
+
+ }
+
public static UserModel addLocalUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
- UserModel user = session.userStorage().addUser(realm, username);
+ UserModel user = session.userLocalStorage().addUser(realm, username);
user.setEmail(email);
user.setEnabled(true);