From d59a74c364e9cee1fdfaf2fc7de21c9ecb1e5c6f Mon Sep 17 00:00:00 2001 From: Martin Kanis Date: Tue, 11 Aug 2020 12:13:21 +0200 Subject: [PATCH] KEYCLOAK-15102 Complement methods for accessing groups with Stream variants --- .../group/GroupPolicyProviderFactory.java | 5 +- .../storage/ldap/LDAPStorageProvider.java | 5 +- .../HardcodedLDAPGroupStorageMapper.java | 9 +- .../group/GroupLDAPStorageMapper.java | 151 ++++++------------ .../role/RoleLDAPStorageMapper.java | 2 +- .../models/cache/infinispan/GroupAdapter.java | 15 +- .../models/cache/infinispan/RealmAdapter.java | 28 ++-- .../cache/infinispan/RealmCacheSession.java | 56 +++---- .../models/cache/infinispan/UserAdapter.java | 14 +- .../infinispan/entities/CachedGroup.java | 2 +- .../infinispan/entities/CachedRealm.java | 7 +- .../cache/infinispan/entities/CachedUser.java | 2 +- .../org/keycloak/models/jpa/GroupAdapter.java | 27 ++-- .../keycloak/models/jpa/JpaRealmProvider.java | 74 +++------ .../keycloak/models/jpa/JpaUserProvider.java | 6 +- .../org/keycloak/models/jpa/RealmAdapter.java | 26 ++- .../org/keycloak/models/jpa/UserAdapter.java | 25 ++- .../jpa/JpaUserFederatedStorageProvider.java | 16 +- .../policy/evaluation/DefaultEvaluation.java | 4 +- .../models/utils/KeycloakModelUtils.java | 70 ++++---- .../models/utils/ModelToRepresentation.java | 77 +++------ .../storage/adapter/InMemoryUserAdapter.java | 21 +-- .../java/org/keycloak/models/GroupModel.java | 17 +- .../org/keycloak/models/GroupProvider.java | 80 +++++++++- .../java/org/keycloak/models/RealmModel.java | 41 ++++- .../org/keycloak/models/RealmProvider.java | 20 ++- .../java/org/keycloak/models/UserModel.java | 30 ++-- .../org/keycloak/models/utils/RoleUtils.java | 47 +++++- .../models/utils/UserModelDelegate.java | 5 +- .../storage/adapter/AbstractUserAdapter.java | 19 ++- .../AbstractUserAdapterFederatedStorage.java | 19 ++- .../UserGroupMembershipFederatedStorage.java | 10 +- .../storage/user/UserQueryProvider.java | 12 +- .../exportimport/util/ExportUtils.java | 11 +- .../oidc/mappers/GroupMembershipMapper.java | 15 +- .../saml/mappers/GroupMembershipMapper.java | 18 +-- .../resources/admin/GroupResource.java | 23 ++- .../resources/admin/GroupsResource.java | 20 +-- .../resources/admin/RealmAdminResource.java | 10 +- .../admin/RoleContainerResource.java | 9 +- .../resources/admin/UserResource.java | 18 +-- .../admin/permissions/UserPermissions.java | 5 +- .../org/keycloak/utils/ServicesUtils.java | 7 + .../testsuite/federation/UserMapStorage.java | 8 +- .../ldap/LDAPGroupMapper2WaySyncTest.java | 8 +- .../ldap/LDAPGroupMapperSyncTest.java | 14 +- ...LDAPGroupMapperSyncWithGroupsPathTest.java | 8 +- .../federation/ldap/LDAPGroupMapperTest.java | 149 ++++++----------- .../federation/ldap/LDAPSpecialCharsTest.java | 6 +- .../FederatedStorageExportImportTest.java | 5 +- .../testsuite/util/cli/TestCacheUtils.java | 8 +- 51 files changed, 606 insertions(+), 678 deletions(-) diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java index 38180601c8..327fe260b4 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java @@ -24,6 +24,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; @@ -162,7 +163,7 @@ public class GroupPolicyProviderFactory implements PolicyProviderFactory topLevelGroups = authorization.getRealm().getTopLevelGroups(); + List topLevelGroups = authorization.getRealm().getTopLevelGroupsStream().collect(Collectors.toList()); for (GroupPolicyRepresentation.GroupDefinition definition : groups) { GroupModel group = null; @@ -184,7 +185,7 @@ public class GroupPolicyProviderFactory implements PolicyProviderFactory groupModel.getName().equals(part)).findFirst().orElseThrow(() -> new RuntimeException("Top level group with name [" + part + "] not found")); } else { - group = parent.getSubGroups().stream().filter(groupModel -> groupModel.getName().equals(part)).findFirst().orElseThrow(() -> new RuntimeException("Group with name [" + part + "] not found")); + group = parent.getSubGroupsStream().filter(groupModel -> groupModel.getName().equals(part)).findFirst().orElseThrow(() -> new RuntimeException("Group with name [" + part + "] not found")); parent = group; } } diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java index 0407583e9b..d50a36d895 100755 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java @@ -284,9 +284,8 @@ public class LDAPStorageProvider implements UserStorageProvider, UserModel proxy = proxy(realm, user, ldapUser, true); DefaultRoles.addDefaultRoles(realm, proxy); - for (GroupModel g : realm.getDefaultGroups()) { - proxy.joinGroup(g); - } + realm.getDefaultGroupsStream().forEach(proxy::joinGroup); + for (RequiredActionProviderModel r : realm.getRequiredActionProviders()) { if (r.isEnabled() && r.isDefaultAction()) { proxy.addRequiredAction(r.getAlias()); diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapper.java index 3bc038056b..4aa70c6cf5 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapper.java @@ -26,8 +26,7 @@ import org.keycloak.storage.ldap.LDAPStorageProvider; import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; -import java.util.HashSet; -import java.util.Set; +import java.util.stream.Stream; /** * @author Jean-Loup Maillet @@ -51,12 +50,12 @@ public class HardcodedLDAPGroupStorageMapper extends AbstractLDAPStorageMapper { return new UserModelDelegate(delegate) { @Override - public Set getGroups() { - Set groups = new HashSet(super.getGroups()); + public Stream getGroupsStream() { + Stream groups = super.getGroupsStream(); GroupModel group = getGroup(realm); if (group != null) { - groups.add(group); + return Stream.concat(groups, Stream.of(group)); } return groups; diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java index 9ac5f1cbcd..4a5004f478 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java @@ -18,7 +18,6 @@ package org.keycloak.storage.ldap.mappers.membership.group; import org.jboss.logging.Logger; -import org.keycloak.common.util.Time; import org.keycloak.component.ComponentModel; import org.keycloak.models.GroupModel; import org.keycloak.models.ModelException; @@ -44,7 +43,6 @@ import org.keycloak.storage.ldap.mappers.membership.MembershipType; import org.keycloak.storage.ldap.mappers.membership.UserRolesRetrieveStrategy; import org.keycloak.storage.user.SynchronizationResult; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -53,9 +51,12 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Marek Posolda @@ -269,7 +270,6 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements // List of group path groups known to the whole transaction Map transactionGroupPathGroups = getKcSubGroups(currentRealm, null) - .stream() .collect(Collectors.toMap(GroupModel::getName, Function.identity())); for (int i = 0; i < groupsPerTransaction && it.hasNext(); i++) { @@ -310,14 +310,8 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements String groupName = groupTreeEntry.getGroupName(); // Check if group already exists - GroupModel kcGroup = null; - Collection subgroups = getKcSubGroups(realm, kcParent); - for (GroupModel group : subgroups) { - if (group.getName().equals(groupName)) { - kcGroup = group; - break; - } - } + GroupModel kcGroup = getKcSubGroups(realm, kcParent) + .filter(g -> Objects.equals(g.getName(), groupName)).findFirst().orElse(null); if (kcGroup != null) { logger.debugf("Updated Keycloak group '%s' from LDAP", kcGroup.getName()); @@ -344,14 +338,13 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements private void dropNonExistingKcGroups(RealmModel realm, SynchronizationResult syncResult, Set visitedGroupIds) { // Remove keycloak groups, which doesn't exists in LDAP - List allGroups = getAllKcGroups(realm); - for (GroupModel kcGroup : allGroups) { - if (!visitedGroupIds.contains(kcGroup.getId())) { - logger.debugf("Removing Keycloak group '%s', which doesn't exist in LDAP", kcGroup.getName()); - realm.removeGroup(kcGroup); - syncResult.increaseRemoved(); - } - } + getAllKcGroups(realm) + .filter(kcGroup -> !visitedGroupIds.contains(kcGroup.getId())) + .forEach(kcGroup -> { + logger.debugf("Removing Keycloak group '%s', which doesn't exist in LDAP", kcGroup.getName()); + realm.removeGroup(kcGroup); + syncResult.increaseRemoved(); + }); } private void updateAttributesOfKCGroup(GroupModel kcGroup, LDAPObject ldapGroup) { @@ -374,14 +367,8 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements if (config.isPreserveGroupsInheritance()) { // Override if better effectivity or different algorithm is needed - List groups = getAllKcGroups(realm); - for (GroupModel group : groups) { - if (group.getName().equals(groupName)) { - return group; - } - } - - return null; + return getAllKcGroups(realm) + .filter(group -> Objects.equals(group.getName(), groupName)).findFirst().orElse(null); } else { // Without preserved inheritance, it's always at groups path return KeycloakModelUtils.findGroupByPath(realm, getKcGroupPathFromLDAPGroupName(groupName)); @@ -462,9 +449,8 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements Set ldapGroupNames = new HashSet<>(); // Create or update KC groups to LDAP including their attributes - for (GroupModel kcGroup : getKcSubGroups(realm, null)) { - processKeycloakGroupSyncToLDAP(kcGroup, ldapGroupsMap, ldapGroupNames, syncResult); - } + getKcSubGroups(realm, null) + .forEach(kcGroup -> processKeycloakGroupSyncToLDAP(kcGroup, ldapGroupsMap, ldapGroupNames, syncResult)); // If dropNonExisting, then drop all groups, which doesn't exist in KC from LDAP as well if (config.isDropNonExistingGroupsDuringSync()) { @@ -480,9 +466,8 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements // Finally process memberships, if (config.isPreserveGroupsInheritance()) { - for (GroupModel kcGroup : getKcSubGroups(realm, null)) { - processKeycloakGroupMembershipsSyncToLDAP(kcGroup, ldapGroupsMap); - } + getKcSubGroups(realm, null) + .forEach(kcGroup -> processKeycloakGroupMembershipsSyncToLDAP(kcGroup, ldapGroupsMap)); } return syncResult; @@ -497,9 +482,8 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements // extract group attributes to be updated to LDAP Map> supportedLdapAttributes = new HashMap<>(); for (String attrName : config.getGroupAttributes()) { - List kcAttrValues = kcGroup.getAttribute(attrName); - Set attrValues2 = (kcAttrValues == null || kcAttrValues.isEmpty()) ? null : new HashSet<>(kcAttrValues); - supportedLdapAttributes.put(attrName, attrValues2); + Set valueSet = kcGroup.getAttributeStream(attrName).collect(Collectors.toSet()); + supportedLdapAttributes.put(attrName, valueSet.isEmpty() ? null : valueSet); } LDAPObject ldapGroup = ldapGroupsMap.get(groupName); @@ -520,9 +504,8 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements ldapGroupNames.add(groupName); // process KC subgroups - for (GroupModel kcSubgroup : kcGroup.getSubGroups()) { - processKeycloakGroupSyncToLDAP(kcSubgroup, ldapGroupsMap, ldapGroupNames, syncResult); - } + kcGroup.getSubGroupsStream() + .forEach(kcSubgroup -> processKeycloakGroupSyncToLDAP(kcSubgroup, ldapGroupsMap, ldapGroupNames, syncResult)); } // Update memberships of group in LDAP based on subgroups from KC. Do it recursively @@ -533,7 +516,7 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements String membershipUserLdapAttrName = getMembershipUserLdapAttribute(); // Not applicable for groups, but needs to be here // Add LDAP subgroups, which are KC subgroups - Set kcSubgroups = kcGroup.getSubGroups(); + Set kcSubgroups = kcGroup.getSubGroupsStream().collect(Collectors.toSet()); for (GroupModel kcSubgroup : kcSubgroups) { LDAPObject ldapSubgroup = ldapGroupsMap.get(kcSubgroup.getName()); if (!toRemoveSubgroupsDNs.remove(ldapSubgroup.getDn())) { @@ -549,7 +532,7 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements LDAPUtils.deleteMember(ldapProvider, MembershipType.DN, config.getMembershipLdapAttribute(), membershipUserLdapAttrName, ldapGroup, fakeGroup); } - for (GroupModel kcSubgroup : kcGroup.getSubGroups()) { + for (GroupModel kcSubgroup : kcSubgroups) { processKeycloakGroupMembershipsSyncToLDAP(kcSubgroup, ldapGroupsMap); } } @@ -709,20 +692,18 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements @Override public boolean hasRole(RoleModel role) { - return super.hasRole(role) || RoleUtils.hasRoleFromGroup(getGroups(), role, true); + return super.hasRole(role) || RoleUtils.hasRoleFromGroup(getGroupsStream(), role, true); } @Override - public Set getGroups() { - Set ldapGroupMappings = getLDAPGroupMappingsConverted(); + public Stream getGroupsStream() { + Stream ldapGroupMappings = getLDAPGroupMappingsConverted(); if (config.getMode() == LDAPGroupMapperMode.LDAP_ONLY) { // Use just group mappings from LDAP return ldapGroupMappings; } else { // Merge mappings from both DB and LDAP - Set modelGroupMappings = super.getGroups(); - ldapGroupMappings.addAll(modelGroupMappings); - return ldapGroupMappings; + return Stream.concat(ldapGroupMappings, super.getGroupsStream()); } } @@ -770,28 +751,22 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements @Override public boolean isMemberOf(GroupModel group) { - Set ldapGroupMappings = getGroups(); - return ldapGroupMappings.contains(group); + return getGroupsStream().anyMatch(Predicate.isEqual(group)); } - protected Set getLDAPGroupMappingsConverted() { + protected Stream getLDAPGroupMappingsConverted() { if (cachedLDAPGroupMappings != null) { - return new HashSet<>(cachedLDAPGroupMappings); + return cachedLDAPGroupMappings.stream(); } List ldapGroups = getLDAPGroupMappings(ldapUser); - Set result = new HashSet<>(); - for (LDAPObject ldapGroup : ldapGroups) { - GroupModel kcGroup = findKcGroupOrSyncFromLDAP(realm, ldapGroup, this); - if (kcGroup != null) { - result.add(kcGroup); - } - } + cachedLDAPGroupMappings = ldapGroups.stream() + .map(ldapGroup -> findKcGroupOrSyncFromLDAP(realm, ldapGroup, this)) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); - cachedLDAPGroupMappings = new HashSet<>(result); - - return result; + return cachedLDAPGroupMappings.stream(); } } @@ -826,56 +801,34 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements /** * Provides a list of all KC sub groups from given parent group or from groups path. */ - protected Collection getKcSubGroups(RealmModel realm, GroupModel parentGroup) { + protected Stream getKcSubGroups(RealmModel realm, GroupModel parentGroup) { // If no parent group given then use groups path if (parentGroup == null) { parentGroup = getKcGroupsPathGroup(realm); } - return parentGroup == null ? realm.getTopLevelGroups() : parentGroup.getSubGroups(); + return parentGroup == null ? realm.getTopLevelGroupsStream() : parentGroup.getSubGroupsStream(); } /** - * Provides a list of all KC groups (with their sub groups) from groups path configured by the "Groups Path" configuration property. + * Provides a stream of all KC groups (with their sub groups) from groups path configured by the "Groups Path" configuration property. */ - protected List getAllKcGroups(RealmModel realm) { - Long start = null; - if (logger.isTraceEnabled()) { - logger.trace("Calling getAllKcGroups started"); - start = Time.currentTimeMillis(); - } - + protected Stream getAllKcGroups(RealmModel realm) { GroupModel topParentGroup = getKcGroupsPathGroup(realm); - List allGroups = realm.getGroups(); - List filtered = new ArrayList<>(); - for (GroupModel group : allGroups) { - if (topParentGroup == null) { - filtered.add(group); - } else { - // Check if group is descendant of the topParentGroup (which is group configured by "Groups Path") - boolean finished = false; - GroupModel parent = group.getParent(); - while (!finished) { - if (parent == null) { - finished = true; - } else { - if (parent.getId().equals(topParentGroup.getId())) { - filtered.add(group); - finished = true; - } else { - parent = parent.getParent(); - } - } + Stream allGroups = realm.getGroupsStream(); + if (topParentGroup == null) return allGroups; + + return allGroups.filter(group -> { + // Check if group is descendant of the topParentGroup (which is group configured by "Groups Path") + GroupModel parent = group.getParent(); + while (parent != null) { + if (parent.getId().equals(topParentGroup.getId())) { + return true; } + parent = parent.getParent(); } - } - - if (logger.isTraceEnabled()) { - long took = Time.currentTimeMillis() - start; - logger.tracef("Calling getAllKcGroups took %d ms. Count of groups %d", took, filtered.size()); - } - - return filtered; + return false; + }); } } diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapper.java index a3c23d733d..394fbc1247 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapper.java @@ -369,7 +369,7 @@ public class RoleLDAPStorageMapper extends AbstractLDAPStorageMapper implements public boolean hasRole(RoleModel role) { Set roles = getRoleMappings(); return RoleUtils.hasRole(roles, role) - || RoleUtils.hasRoleFromGroup(getGroups(), role, true); + || RoleUtils.hasRoleFromGroup(getGroupsStream(), role, true); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java index 38fceb569d..ba33407166 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Supplier; +import java.util.stream.Stream; /** * @author Bill Burke @@ -136,10 +137,10 @@ public class GroupAdapter implements GroupModel { } @Override - public List getAttribute(String name) { + public Stream getAttributeStream(String name) { List values = cached.getAttributes(modelSupplier).get(name); - if (values == null) return null; - return values; + if (values == null) return Stream.empty(); + return values.stream(); } @Override @@ -234,20 +235,20 @@ public class GroupAdapter implements GroupModel { } @Override - public Set getSubGroups() { - if (isUpdated()) return updated.getSubGroups(); + public Stream getSubGroupsStream() { + if (isUpdated()) return updated.getSubGroupsStream(); Set subGroups = new HashSet<>(); for (String id : cached.getSubGroups(modelSupplier)) { GroupModel subGroup = keycloakSession.groups().getGroupById(realm, id); if (subGroup == null) { // chance that role was removed, so just delegate to persistence and get user invalidated getDelegateForUpdate(); - return updated.getSubGroups(); + return updated.getSubGroupsStream(); } subGroups.add(subGroup); } - return subGroups; + return subGroups.stream(); } 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 51cffeefcb..151b645945 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 @@ -705,15 +705,9 @@ public class RealmAdapter implements CachedRealmModel { } @Override - public List getDefaultGroups() { - if (isUpdated()) return updated.getDefaultGroups(); - - List defaultGroups = new LinkedList<>(); - for (String id : cached.getDefaultGroups()) { - defaultGroups.add(cacheSession.getGroupById(this, id)); - } - return Collections.unmodifiableList(defaultGroups); - + public Stream getDefaultGroupsStream() { + if (isUpdated()) return updated.getDefaultGroupsStream(); + return cached.getDefaultGroups().stream().map(this::getGroupById); } @Override @@ -1420,8 +1414,8 @@ public class RealmAdapter implements CachedRealmModel { } @Override - public List getGroups() { - return cacheSession.getGroups(this); + public Stream getGroupsStream() { + return cacheSession.getGroupsStream(this); } @Override @@ -1435,18 +1429,18 @@ public class RealmAdapter implements CachedRealmModel { } @Override - public List getTopLevelGroups() { - return cacheSession.getTopLevelGroups(this); + public Stream getTopLevelGroupsStream() { + return cacheSession.getTopLevelGroupsStream(this); } @Override - public List getTopLevelGroups(Integer first, Integer max) { - return cacheSession.getTopLevelGroups(this, first, max); + public Stream getTopLevelGroupsStream(Integer first, Integer max) { + return cacheSession.getTopLevelGroupsStream(this, first, max); } @Override - public List searchForGroupByName(String search, Integer first, Integer max) { - return cacheSession.searchForGroupByName(this, search, first, max); + public Stream searchForGroupByNameStream(String search, Integer first, Integer max) { + return cacheSession.searchForGroupByNameStream(this, search, first, max); } @Override 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 df5ae972db..a0cdfc6931 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 @@ -866,11 +866,11 @@ public class RealmCacheSession implements CacheRealmProvider { } @Override - public List getGroups(RealmModel realm) { + public Stream getGroupsStream(RealmModel realm) { String cacheKey = getGroupsQueryCacheKey(realm.getId()); boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId()); if (queryDB) { - return getGroupDelegate().getGroups(realm); + return getGroupDelegate().getGroupsStream(realm); } GroupListQuery query = cache.get(cacheKey, GroupListQuery.class); @@ -880,28 +880,26 @@ public class RealmCacheSession implements CacheRealmProvider { if (query == null) { Long loaded = cache.getCurrentRevision(cacheKey); - List model = getGroupDelegate().getGroups(realm); - if (model == null) return null; + List model = getGroupDelegate().getGroupsStream(realm).collect(Collectors.toList()); + if (model.isEmpty()) return Stream.empty(); Set ids = new HashSet<>(); for (GroupModel client : model) ids.add(client.getId()); query = new GroupListQuery(loaded, cacheKey, realm, ids); logger.tracev("adding realm getGroups cache miss: realm {0} key {1}", realm.getName(), cacheKey); cache.addRevisioned(query, startupRevision); - return model; + return model.stream(); } List list = new LinkedList<>(); for (String id : query.getGroups()) { GroupModel group = session.groups().getGroupById(realm, id); if (group == null) { invalidations.add(cacheKey); - return getGroupDelegate().getGroups(realm); + return getGroupDelegate().getGroupsStream(realm); } list.add(group); } - list.sort(Comparator.comparing(GroupModel::getName)); - - return list; + return list.stream().sorted(Comparator.comparing(GroupModel::getName)); } @Override @@ -920,16 +918,16 @@ public class RealmCacheSession implements CacheRealmProvider { } @Override - public List getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults) { - return getGroupDelegate().getGroupsByRole(realm, role, firstResult, maxResults); + public Stream getGroupsByRoleStream(RealmModel realm, RoleModel role, int firstResult, int maxResults) { + return getGroupDelegate().getGroupsByRoleStream(realm, role, firstResult, maxResults); } @Override - public List getTopLevelGroups(RealmModel realm) { + public Stream getTopLevelGroupsStream(RealmModel realm) { String cacheKey = getTopGroupsQueryCacheKey(realm.getId()); boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId()); if (queryDB) { - return getGroupDelegate().getTopLevelGroups(realm); + return getGroupDelegate().getTopLevelGroupsStream(realm); } GroupListQuery query = cache.get(cacheKey, GroupListQuery.class); @@ -939,36 +937,34 @@ public class RealmCacheSession implements CacheRealmProvider { if (query == null) { Long loaded = cache.getCurrentRevision(cacheKey); - List model = getGroupDelegate().getTopLevelGroups(realm); - if (model == null) return null; + List model = getGroupDelegate().getTopLevelGroupsStream(realm).collect(Collectors.toList()); + if (model.isEmpty()) return Stream.empty(); Set ids = new HashSet<>(); for (GroupModel client : model) ids.add(client.getId()); query = new GroupListQuery(loaded, cacheKey, realm, ids); logger.tracev("adding realm getTopLevelGroups cache miss: realm {0} key {1}", realm.getName(), cacheKey); cache.addRevisioned(query, startupRevision); - return model; + return model.stream(); } List list = new LinkedList<>(); for (String id : query.getGroups()) { GroupModel group = session.groups().getGroupById(realm, id); if (group == null) { invalidations.add(cacheKey); - return getGroupDelegate().getTopLevelGroups(realm); + return getGroupDelegate().getTopLevelGroupsStream(realm); } list.add(group); } - list.sort(Comparator.comparing(GroupModel::getName)); - - return list; + return list.stream().sorted(Comparator.comparing(GroupModel::getName)); } @Override - public List getTopLevelGroups(RealmModel realm, Integer first, Integer max) { + public Stream getTopLevelGroupsStream(RealmModel realm, Integer first, Integer max) { String cacheKey = getTopGroupsQueryCacheKey(realm.getId() + first + max); boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId() + first + max); if (queryDB) { - return getGroupDelegate().getTopLevelGroups(realm, first, max); + return getGroupDelegate().getTopLevelGroupsStream(realm, first, max); } GroupListQuery query = cache.get(cacheKey, GroupListQuery.class); @@ -978,33 +974,31 @@ public class RealmCacheSession implements CacheRealmProvider { if (Objects.isNull(query)) { Long loaded = cache.getCurrentRevision(cacheKey); - List model = getGroupDelegate().getTopLevelGroups(realm, first, max); - if (model == null) return null; + List model = getGroupDelegate().getTopLevelGroupsStream(realm, first, max).collect(Collectors.toList()); + if (model.isEmpty()) return Stream.empty(); Set ids = new HashSet<>(); for (GroupModel client : model) ids.add(client.getId()); query = new GroupListQuery(loaded, cacheKey, realm, ids); logger.tracev("adding realm getTopLevelGroups cache miss: realm {0} key {1}", realm.getName(), cacheKey); cache.addRevisioned(query, startupRevision); - return model; + return model.stream(); } List list = new LinkedList<>(); for (String id : query.getGroups()) { GroupModel group = session.groups().getGroupById(realm, id); if (Objects.isNull(group)) { invalidations.add(cacheKey); - return getGroupDelegate().getTopLevelGroups(realm); + return getGroupDelegate().getTopLevelGroupsStream(realm); } list.add(group); } - list.sort(Comparator.comparing(GroupModel::getName)); - - return list; + return list.stream().sorted(Comparator.comparing(GroupModel::getName)); } @Override - public List searchForGroupByName(RealmModel realm, String search, Integer first, Integer max) { - return getGroupDelegate().searchForGroupByName(realm, search, first, max); + public Stream searchForGroupByNameStream(RealmModel realm, String search, Integer first, Integer max) { + return getGroupDelegate().searchForGroupByNameStream(realm, search, first, max); } @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 31b3e96578..93459c65fb 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 @@ -37,6 +37,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; +import java.util.stream.Stream; /** * @author Bill Burke @@ -315,7 +316,7 @@ public class UserAdapter implements CachedUserModel { for (RoleModel mapping: mappings) { if (mapping.hasRole(role)) return true; } - return RoleUtils.hasRoleFromGroup(getGroups(), role, true); + return RoleUtils.hasRoleFromGroup(getGroupsStream(), role, true); } @Override @@ -348,20 +349,20 @@ public class UserAdapter implements CachedUserModel { } @Override - public Set getGroups() { - if (updated != null) return updated.getGroups(); + public Stream getGroupsStream() { + if (updated != null) return updated.getGroupsStream(); Set groups = new LinkedHashSet<>(); for (String id : cached.getGroups(modelSupplier)) { GroupModel groupModel = keycloakSession.groups().getGroupById(realm, id); if (groupModel == null) { // chance that role was removed, so just delete to persistence and get user invalidated getDelegateForUpdate(); - return updated.getGroups(); + return updated.getGroupsStream(); } groups.add(groupModel); } - return groups; + return groups.stream(); } @Override @@ -381,8 +382,7 @@ public class UserAdapter implements CachedUserModel { public boolean isMemberOf(GroupModel group) { if (updated != null) return updated.isMemberOf(group); if (cached.getGroups(modelSupplier).contains(group.getId())) return true; - Set roles = getGroups(); - return RoleUtils.isMember(roles, group); + return RoleUtils.isMember(getGroupsStream(), group); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedGroup.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedGroup.java index bf83d27ab4..eaca4b514e 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedGroup.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedGroup.java @@ -49,7 +49,7 @@ public class CachedGroup extends AbstractRevisioned implements InRealm { this.parentId = group.getParentId(); this.attributes = new DefaultLazyLoader<>(source -> new MultivaluedHashMap<>(source.getAttributes()), MultivaluedHashMap::new); this.roleMappings = new DefaultLazyLoader<>(source -> source.getRoleMappings().stream().map(RoleModel::getId).collect(Collectors.toSet()), Collections::emptySet); - this.subGroups = new DefaultLazyLoader<>(source -> source.getSubGroups().stream().map(GroupModel::getId).collect(Collectors.toSet()), Collections::emptySet); + this.subGroups = new DefaultLazyLoader<>(source -> source.getSubGroupsStream().map(GroupModel::getId).collect(Collectors.toSet()), Collections::emptySet); } public String getRealm() { diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java index ad9aa68448..d00e1d9af3 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java @@ -43,6 +43,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** * @author Bill Burke @@ -148,7 +149,7 @@ public class CachedRealm extends AbstractExtendableRevisioned { return identityProviderMapperSet; } - protected List defaultGroups = new LinkedList<>(); + protected List defaultGroups; protected List clientScopes = new LinkedList<>(); protected List defaultDefaultClientScopes = new LinkedList<>(); protected List optionalDefaultClientScopes = new LinkedList<>(); @@ -282,9 +283,7 @@ public class CachedRealm extends AbstractExtendableRevisioned { requiredActionProvidersByAlias.put(action.getAlias(), action); } - for (GroupModel group : model.getDefaultGroups()) { - defaultGroups.add(group.getId()); - } + defaultGroups = model.getDefaultGroupsStream().map(GroupModel::getId).collect(Collectors.toList()); browserFlow = model.getBrowserFlow(); registrationFlow = model.getRegistrationFlow(); diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java index b2dc2f9acb..b0a5d5d9f3 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java @@ -65,7 +65,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm this.requiredActions = new DefaultLazyLoader<>(UserModel::getRequiredActions, Collections::emptySet); this.attributes = new DefaultLazyLoader<>(userModel -> new MultivaluedHashMap<>(userModel.getAttributes()), MultivaluedHashMap::new); this.roleMappings = new DefaultLazyLoader<>(userModel -> userModel.getRoleMappings().stream().map(RoleModel::getId).collect(Collectors.toSet()), Collections::emptySet); - this.groups = new DefaultLazyLoader<>(userModel -> userModel.getGroups().stream().map(GroupModel::getId).collect(Collectors.toCollection(LinkedHashSet::new)), LinkedHashSet::new); + this.groups = new DefaultLazyLoader<>(userModel -> userModel.getGroupsStream().map(GroupModel::getId).collect(Collectors.toCollection(LinkedHashSet::new)), LinkedHashSet::new); } public String getRealm() { diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java index c1055733bc..f537d84b07 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java @@ -36,9 +36,13 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.stream.Stream; import javax.persistence.LockModeType; +import static org.keycloak.utils.StreamsUtil.closing; + /** * @author Bill Burke * @version $Revision: 1 $ @@ -119,17 +123,10 @@ public class GroupAdapter implements GroupModel , JpaModel { } @Override - public Set getSubGroups() { + public Stream getSubGroupsStream() { TypedQuery query = em.createNamedQuery("getGroupIdsByParent", String.class); query.setParameter("parent", group.getId()); - List ids = query.getResultList(); - Set set = new HashSet<>(); - for (String id : ids) { - GroupModel subGroup = realm.getGroupById(id); - if (subGroup == null) continue; - set.add(subGroup); - } - return set; + return closing(query.getResultStream().map(realm::getGroupById).filter(Objects::nonNull)); } @Override @@ -203,14 +200,10 @@ public class GroupAdapter implements GroupModel , JpaModel { } @Override - public List getAttribute(String name) { - List result = new ArrayList<>(); - for (GroupAttributeEntity attr : group.getAttributes()) { - if (attr.getName().equals(name)) { - result.add(attr.getValue()); - } - } - return result; + public Stream getAttributeStream(String name) { + return group.getAttributes().stream() + .filter(attr -> Objects.equals(attr.getName(), name)) + .map(GroupAttributeEntity::getValue); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java index 86656b2097..116ca2b99e 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java @@ -171,9 +171,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, GroupPro removeRoles(adapter); - for (GroupModel group : adapter.getGroups()) { - session.groups().removeGroup(adapter, group); - } + adapter.getGroupsStream().forEach(adapter::removeGroup); num = em.createNamedQuery("removeClientInitialAccessByRealm") .setParameter("realm", realm).executeUpdate(); @@ -420,14 +418,12 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, GroupPro } @Override - public List getGroups(RealmModel realm) { + public Stream getGroupsStream(RealmModel realm) { RealmEntity ref = em.getReference(RealmEntity.class, realm.getId()); return ref.getGroups().stream() .map(g -> session.groups().getGroupById(realm, g.getId())) - .sorted(Comparator.comparing(GroupModel::getName)) - .collect(Collectors.collectingAndThen( - Collectors.toList(), Collections::unmodifiableList)); + .sorted(Comparator.comparing(GroupModel::getName)); } @Override @@ -454,11 +450,11 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, GroupPro @Override public Long getGroupsCountByNameContaining(RealmModel realm, String search) { - return (long) searchForGroupByName(realm, search, null, null).size(); + return searchForGroupByNameStream(realm, search, null, null).count(); } @Override - public List getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults) { + public Stream getGroupsByRoleStream(RealmModel realm, RoleModel role, int firstResult, int maxResults) { TypedQuery query = em.createNamedQuery("groupsInRole", GroupEntity.class); query.setParameter("roleId", role.getId()); if (firstResult != -1) { @@ -467,44 +463,33 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, GroupPro if (maxResults != -1) { query.setMaxResults(maxResults); } - List results = query.getResultList(); + Stream results = query.getResultStream(); - return results.stream() - .map(g -> new GroupAdapter(realm, em, g)) - .sorted(Comparator.comparing(GroupModel::getName)) - .collect(Collectors.collectingAndThen( - Collectors.toList(), Collections::unmodifiableList)); + return closing(results + .map(g -> (GroupModel) new GroupAdapter(realm, em, g)) + .sorted(Comparator.comparing(GroupModel::getName))); } @Override - public List getTopLevelGroups(RealmModel realm) { + public Stream getTopLevelGroupsStream(RealmModel realm) { RealmEntity ref = em.getReference(RealmEntity.class, realm.getId()); return ref.getGroups().stream() .filter(g -> GroupEntity.TOP_PARENT_ID.equals(g.getParentId())) .map(g -> session.groups().getGroupById(realm, g.getId())) - .sorted(Comparator.comparing(GroupModel::getName)) - .collect(Collectors.collectingAndThen( - Collectors.toList(), Collections::unmodifiableList)); + .sorted(Comparator.comparing(GroupModel::getName)); } @Override - public List getTopLevelGroups(RealmModel realm, Integer first, Integer max) { - List groupIds = em.createNamedQuery("getTopLevelGroupIds", String.class) + public Stream getTopLevelGroupsStream(RealmModel realm, Integer first, Integer max) { + Stream groupIds = em.createNamedQuery("getTopLevelGroupIds", String.class) .setParameter("realm", realm.getId()) .setParameter("parent", GroupEntity.TOP_PARENT_ID) .setFirstResult(first) - .setMaxResults(max) - .getResultList(); - List list = new ArrayList<>(); - if(Objects.nonNull(groupIds) && !groupIds.isEmpty()) { - for (String id : groupIds) { - GroupModel group = getGroupById(realm, id); - list.add(group); - } - } - // no need to sort, it's sorted at database level - return Collections.unmodifiableList(list); + .setMaxResults(max) + .getResultStream(); + + return closing(groupIds.map(realm::getGroupById)); } @Override @@ -534,9 +519,8 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, GroupPro session.users().preRemove(realm, group); realm.removeDefaultGroup(group); - for (GroupModel subGroup : group.getSubGroups()) { - session.groups().removeGroup(realm, subGroup); - } + group.getSubGroupsStream().forEach(realm::removeGroup); + GroupEntity groupEntity = em.find(GroupEntity.class, group.getId(), LockModeType.PESSIMISTIC_WRITE); if ((groupEntity == null) || (!groupEntity.getRealm().equals(realm.getId()))) { return false; @@ -745,28 +729,22 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, GroupPro } @Override - public List searchForGroupByName(RealmModel realm, String search, Integer first, Integer max) { + public Stream searchForGroupByNameStream(RealmModel realm, String search, Integer first, Integer max) { TypedQuery query = em.createNamedQuery("getGroupIdsByNameContaining", String.class) .setParameter("realm", realm.getId()) .setParameter("search", search); if(Objects.nonNull(first) && Objects.nonNull(max)) { query= query.setFirstResult(first).setMaxResults(max); } - List groups = query.getResultList(); - if (Objects.isNull(groups)) return Collections.EMPTY_LIST; - List list = new ArrayList<>(); - for (String id : groups) { + Stream groups = query.getResultStream(); + + return closing(groups.map(id -> { GroupModel groupById = session.groups().getGroupById(realm, id); - while(Objects.nonNull(groupById.getParentId())) { + while (Objects.nonNull(groupById.getParentId())) { groupById = session.groups().getGroupById(realm, groupById.getParentId()); } - if(!list.contains(groupById)) { - list.add(groupById); - } - } - list.sort(Comparator.comparing(GroupModel::getName)); - - return Collections.unmodifiableList(list); + return groupById; + }).sorted(Comparator.comparing(GroupModel::getName)).distinct()); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index 696cc84d96..206f567fad 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -50,7 +50,6 @@ import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.client.ClientStorageProvider; import javax.persistence.EntityManager; -import javax.persistence.Query; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -110,9 +109,8 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore { if (addDefaultRoles) { DefaultRoles.addDefaultRoles(realm, userModel); - for (GroupModel g : realm.getDefaultGroups()) { - userModel.joinGroupImpl(g); // No need to check if user has group as it's new user - } + // No need to check if user has group as it's new user + realm.getDefaultGroupsStream().forEach(userModel::joinGroupImpl); } if (addDefaultRequiredActions){ 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 c4a87ed048..144e3be69f 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 @@ -792,14 +792,10 @@ public class RealmAdapter implements RealmModel, JpaModel { } @Override - public List getDefaultGroups() { + public Stream getDefaultGroupsStream() { Collection entities = realm.getDefaultGroups(); - if (entities == null || entities.isEmpty()) return Collections.EMPTY_LIST; - List defaultGroups = new LinkedList<>(); - for (GroupEntity entity : entities) { - defaultGroups.add(session.groups().getGroupById(this, entity.getId())); - } - return Collections.unmodifiableList(defaultGroups); + if (entities == null || entities.isEmpty()) return Stream.empty(); + return entities.stream().map(GroupEntity::getId).map(this::getGroupById); } @Override @@ -2036,8 +2032,8 @@ public class RealmAdapter implements RealmModel, JpaModel { } @Override - public List getGroups() { - return session.groups().getGroups(this); + public Stream getGroupsStream() { + return session.groups().getGroupsStream(this); } @Override @@ -2051,18 +2047,18 @@ public class RealmAdapter implements RealmModel, JpaModel { } @Override - public List getTopLevelGroups() { - return session.groups().getTopLevelGroups(this); + public Stream getTopLevelGroupsStream() { + return session.groups().getTopLevelGroupsStream(this); } @Override - public List getTopLevelGroups(Integer first, Integer max) { - return session.groups().getTopLevelGroups(this, first, max); + public Stream getTopLevelGroupsStream(Integer first, Integer max) { + return session.groups().getTopLevelGroupsStream(this, first, max); } @Override - public List searchForGroupByName(String search, Integer first, Integer max) { - return session.groups().searchForGroupByName(this, search, first, max); + public Stream searchForGroupByNameStream(String search, Integer first, Integer max) { + return session.groups().searchForGroupByNameStream(this, search, first, max); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java index ad80953ebc..8a366ded31 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java @@ -43,11 +43,9 @@ import javax.persistence.criteria.Join; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -55,6 +53,8 @@ import java.util.Objects; import java.util.stream.Stream; import javax.persistence.LockModeType; +import static org.keycloak.utils.StreamsUtil.closing; + /** * @author Bill Burke * @version $Revision: 1 $ @@ -401,22 +401,18 @@ public class UserAdapter implements UserModel, JpaModel { return em.createQuery(queryBuilder); } - private Set getGroupModels(Collection groupIds) { - Set groups = new LinkedHashSet<>(); - for (String id : groupIds) { - groups.add(realm.getGroupById(id)); - } - return groups; + private Stream getGroupModels(Stream groupIds) { + return groupIds.map(realm::getGroupById); } @Override - public Set getGroups() { - return getGroupModels(createGetGroupsQuery(null, null, null).getResultList()); + public Stream getGroupsStream() { + return closing(getGroupModels(createGetGroupsQuery(null, null, null).getResultStream())); } @Override - public Set getGroups(String search, int first, int max) { - return getGroupModels(createGetGroupsQuery(search, first, max).getResultList()); + public Stream getGroupsStream(String search, int first, int max) { + return closing(getGroupModels(createGetGroupsQuery(search, first, max).getResultStream())); } @Override @@ -463,8 +459,7 @@ public class UserAdapter implements UserModel, JpaModel { @Override public boolean isMemberOf(GroupModel group) { - Set roles = getGroups(); - return RoleUtils.isMember(roles, group); + return RoleUtils.isMember(getGroupsStream(), group); } protected TypedQuery getUserGroupMappingQuery(GroupModel group) { @@ -479,7 +474,7 @@ public class UserAdapter implements UserModel, JpaModel { public boolean hasRole(RoleModel role) { Set roles = getRoleMappings(); return RoleUtils.hasRole(roles, role) - || RoleUtils.hasRoleFromGroup(getGroups(), role, true); + || RoleUtils.hasRoleFromGroup(getGroupsStream(), role, true); } protected TypedQuery getUserRoleMappingEntityTypedQuery(RoleModel role) { diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java index 385a311ebe..11e6b303e7 100644 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java @@ -37,7 +37,6 @@ import org.keycloak.models.RoleModel; import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserModel; import org.keycloak.models.jpa.JpaUserCredentialStore; -import org.keycloak.models.jpa.entities.CredentialEntity; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.storage.StorageId; import org.keycloak.storage.UserStorageProvider; @@ -61,10 +60,12 @@ import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.ListIterator; import java.util.Set; +import java.util.stream.Stream; import javax.persistence.LockModeType; +import static org.keycloak.utils.StreamsUtil.closing; + /** * @author Bill Burke * @version $Revision: 1 $ @@ -439,17 +440,10 @@ public class JpaUserFederatedStorageProvider implements } @Override - public Set getGroups(RealmModel realm, String userId) { - Set set = new HashSet<>(); + public Stream getGroupsStream(RealmModel realm, String userId) { TypedQuery query = em.createNamedQuery("feduserGroupMembership", FederatedUserGroupMembershipEntity.class); query.setParameter("userId", userId); - List results = query.getResultList(); - if (results.size() == 0) return set; - for (FederatedUserGroupMembershipEntity entity : results) { - GroupModel group = realm.getGroupById(entity.getGroupId()); - set.add(group); - } - return set; + return closing(query.getResultStream().map(FederatedUserGroupMembershipEntity::getGroupId).map(realm::getGroupById)); } @Override diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java index 6ca21838e2..2b144436ae 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java @@ -160,7 +160,7 @@ public class DefaultEvaluation implements Evaluation { } if (checkParent) { - return RoleUtils.isMember(user.getGroups(), group); + return RoleUtils.isMember(user.getGroupsStream(), group); } return user.isMemberOf(group); @@ -253,7 +253,7 @@ public class DefaultEvaluation implements Evaluation { @Override public List getUserGroups(String id) { - return getUser(id, authorizationProvider.getKeycloakSession()).getGroups().stream() + return getUser(id, authorizationProvider.getKeycloakSession()).getGroupsStream() .map(ModelToRepresentation::buildGroupPath) .collect(Collectors.toList()); } diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java index 2144d4dc7e..10f9adea31 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java @@ -57,8 +57,12 @@ import java.security.cert.X509Certificate; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Set of helper methods, which are useful in various model implementations. @@ -382,30 +386,11 @@ public final class KeycloakModelUtils { } - /** - * - * - * @param user - * @param name - * @return - */ - public static String resolveFirstAttribute(UserModel user, String name) { - String value = user.getFirstAttribute(name); - if (value != null) return value; - for (GroupModel group : user.getGroups()) { - value = resolveFirstAttribute(group, name); - if (value != null) return value; - } - return null; - - } - public static List resolveAttribute(GroupModel group, String name) { - List values = group.getAttribute(name); - if (values != null && !values.isEmpty()) return values; + List values = group.getAttributeStream(name).collect(Collectors.toList()); + if (!values.isEmpty()) return values; if (group.getParentId() == null) return null; return resolveAttribute(group.getParent(), name); - } @@ -418,21 +403,24 @@ public final class KeycloakModelUtils { } aggrValues.addAll(values); } - for (GroupModel group : user.getGroups()) { - values = resolveAttribute(group, name); - if (values != null && !values.isEmpty()) { - if (!aggregateAttrs) { - return values; - } - aggrValues.addAll(values); - } + Stream> attributes = user.getGroupsStream() + .map(group -> resolveAttribute(group, name)) + .filter(Objects::nonNull) + .filter(attr -> !attr.isEmpty()); + + if (!aggregateAttrs) { + Optional> first = attributes.findFirst(); + if (first.isPresent()) return first.get(); + } else { + aggrValues.addAll(attributes.flatMap(Collection::stream).collect(Collectors.toSet())); } + return aggrValues; } private static GroupModel findSubGroup(String[] segments, int index, GroupModel parent) { - for (GroupModel group : parent.getSubGroups()) { + return parent.getSubGroupsStream().map(group -> { String groupName = group.getName(); String[] pathSegments = formatPathSegments(segments, index, groupName); @@ -444,14 +432,11 @@ public final class KeycloakModelUtils { if (index + 1 < pathSegments.length) { GroupModel found = findSubGroup(pathSegments, index + 1, group); if (found != null) return found; - } else { - return null; } } - } - } - return null; + return null; + }).filter(Objects::nonNull).findFirst().orElse(null); } /** @@ -504,26 +489,25 @@ public final class KeycloakModelUtils { } String[] split = path.split("/"); if (split.length == 0) return null; - GroupModel found = null; - for (GroupModel group : realm.getTopLevelGroups()) { + + return realm.getTopLevelGroupsStream().map(group -> { String groupName = group.getName(); String[] pathSegments = formatPathSegments(split, 0, groupName); if (groupName.equals(pathSegments[0])) { if (pathSegments.length == 1) { - found = group; - break; + return group; } else { if (pathSegments.length > 1) { - found = findSubGroup(pathSegments, 1, group); - if (found != null) break; + GroupModel subGroup = findSubGroup(pathSegments, 1, group); + if (subGroup != null) return subGroup; } } } - } - return found; + return null; + }).filter(Objects::nonNull).findFirst().orElse(null); } public static Set getClientScopeMappings(ClientModel client, ScopeContainerModel container) { diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index b6983618c2..18d695cba9 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -46,9 +46,9 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Bill Burke @@ -139,63 +139,40 @@ public class ModelToRepresentation { return rep; } - public static List searchForGroupByName(RealmModel realm, boolean full, String search, Integer first, Integer max) { - List result = new LinkedList<>(); - List groups = realm.searchForGroupByName(search, first, max); - if (Objects.isNull(groups)) return result; - for (GroupModel group : groups) { - GroupRepresentation rep = toGroupHierarchy(group, full); - result.add(rep); - } - return result; + public static Stream searchForGroupByName(RealmModel realm, boolean full, String search, Integer first, Integer max) { + return realm.searchForGroupByNameStream(search, first, max) + .map(g -> toGroupHierarchy(g, full)); } - public static List searchForGroupByName(UserModel user, boolean full, String search, Integer first, Integer max) { - return user.getGroups(search, first, max).stream() - .map(group -> toRepresentation(group, full)) - .collect(Collectors.toList()); + public static Stream searchForGroupByName(UserModel user, boolean full, String search, Integer first, Integer max) { + return user.getGroupsStream(search, first, max) + .map(group -> toRepresentation(group, full)); } - public static List toGroupHierarchy(RealmModel realm, boolean full, Integer first, Integer max) { - List hierarchy = new LinkedList<>(); - List groups = realm.getTopLevelGroups(first, max); - if (Objects.isNull(groups)) return hierarchy; - for (GroupModel group : groups) { - GroupRepresentation rep = toGroupHierarchy(group, full); - hierarchy.add(rep); - } - return hierarchy; + public static Stream toGroupHierarchy(RealmModel realm, boolean full, Integer first, Integer max) { + return realm.getTopLevelGroupsStream(first, max) + .map(g -> toGroupHierarchy(g, full)); } - public static List toGroupHierarchy(UserModel user, boolean full, Integer first, Integer max) { - return user.getGroups(first, max).stream() - .map(group -> toRepresentation(group, full)) - .collect(Collectors.toList()); + public static Stream toGroupHierarchy(UserModel user, boolean full, Integer first, Integer max) { + return user.getGroupsStream(null, first, max) + .map(group -> toRepresentation(group, full)); } - public static List toGroupHierarchy(RealmModel realm, boolean full) { - List hierarchy = new LinkedList<>(); - List groups = realm.getTopLevelGroups(); - if (Objects.isNull(groups)) return hierarchy; - for (GroupModel group : groups) { - GroupRepresentation rep = toGroupHierarchy(group, full); - hierarchy.add(rep); - } - return hierarchy; + public static Stream toGroupHierarchy(RealmModel realm, boolean full) { + return realm.getTopLevelGroupsStream() + .map(g -> toGroupHierarchy(g, full)); } - public static List toGroupHierarchy(UserModel user, boolean full) { - return user.getGroups().stream() - .map(group -> toRepresentation(group, full)) - .collect(Collectors.toList()); + public static Stream toGroupHierarchy(UserModel user, boolean full) { + return user.getGroupsStream() + .map(group -> toRepresentation(group, full)); } public static GroupRepresentation toGroupHierarchy(GroupModel group, boolean full) { GroupRepresentation rep = toRepresentation(group, full); - List subGroups = new LinkedList<>(); - for (GroupModel subGroup : group.getSubGroups()) { - subGroups.add(toGroupHierarchy(subGroup, full)); - } + List subGroups = group.getSubGroupsStream() + .map(subGroup -> toGroupHierarchy(subGroup, full)).collect(Collectors.toList()); rep.setSubGroups(subGroups); return rep; } @@ -437,13 +414,10 @@ public class ModelToRepresentation { List roleStrings = new ArrayList<>(defaultRoles); rep.setDefaultRoles(roleStrings); } - List defaultGroups = realm.getDefaultGroups(); + List defaultGroups = realm.getDefaultGroupsStream() + .map(ModelToRepresentation::buildGroupPath).collect(Collectors.toList()); if (!defaultGroups.isEmpty()) { - List groupPaths = new LinkedList<>(); - for (GroupModel group : defaultGroups) { - groupPaths.add(ModelToRepresentation.buildGroupPath(group)); - } - rep.setDefaultGroups(groupPaths); + rep.setDefaultGroups(defaultGroups); } List requiredCredentialModels = realm.getRequiredCredentials(); @@ -502,8 +476,7 @@ public class ModelToRepresentation { } public static void exportGroups(RealmModel realm, RealmRepresentation rep) { - List groups = toGroupHierarchy(realm, true); - rep.setGroups(groups); + rep.setGroups(toGroupHierarchy(realm, true).collect(Collectors.toList())); } public static void exportAuthenticationFlows(RealmModel realm, RealmRepresentation rep) { diff --git a/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java b/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java index f2f541586f..596281e890 100644 --- a/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java +++ b/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java @@ -30,12 +30,12 @@ import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.RoleUtils; import org.keycloak.storage.ReadOnlyException; -import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Stream; /** * @author Bill Burke @@ -79,10 +79,7 @@ public class InMemoryUserAdapter extends UserModelDefaultMethods { public void addDefaults() { DefaultRoles.addDefaultRoles(realm, this); - for (GroupModel g : realm.getDefaultGroups()) { - joinGroup(g); - } - + realm.getDefaultGroupsStream().forEach(this::joinGroup); } public void setReadonly(boolean flag) { @@ -213,13 +210,8 @@ public class InMemoryUserAdapter extends UserModelDefaultMethods { } @Override - public Set getGroups() { - if (groupIds.isEmpty()) return new HashSet<>(); - Set groups = new HashSet<>(); - for (String id : groupIds) { - groups.add(realm.getGroupById(id)); - } - return groups; + public Stream getGroupsStream() { + return groupIds.stream().map(realm::getGroupById); } @Override @@ -240,8 +232,7 @@ public class InMemoryUserAdapter extends UserModelDefaultMethods { public boolean isMemberOf(GroupModel group) { if (groupIds == null) return false; if (groupIds.contains(group.getId())) return true; - Set groups = getGroups(); - return RoleUtils.isMember(groups, group); + return RoleUtils.isMember(getGroupsStream(), group); } @Override @@ -299,7 +290,7 @@ public class InMemoryUserAdapter extends UserModelDefaultMethods { public boolean hasRole(RoleModel role) { Set roles = getRoleMappings(); return RoleUtils.hasRole(roles, role) - || RoleUtils.hasRoleFromGroup(getGroups(), role, true); + || RoleUtils.hasRoleFromGroup(getGroupsStream(), role, true); } @Override diff --git a/server-spi/src/main/java/org/keycloak/models/GroupModel.java b/server-spi/src/main/java/org/keycloak/models/GroupModel.java index aac0ddb348..755ea8f223 100755 --- a/server-spi/src/main/java/org/keycloak/models/GroupModel.java +++ b/server-spi/src/main/java/org/keycloak/models/GroupModel.java @@ -22,6 +22,8 @@ import org.keycloak.provider.ProviderEvent; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Bill Burke @@ -61,13 +63,24 @@ public interface GroupModel extends RoleMapperModel { * @param name * @return list of all attribute values or empty list if there are not any values. Never return null */ - List getAttribute(String name); + @Deprecated + default List getAttribute(String name) { + return getAttributeStream(name).collect(Collectors.toList()); + } + + Stream getAttributeStream(String name); Map> getAttributes(); GroupModel getParent(); String getParentId(); - Set getSubGroups(); + + @Deprecated + default Set getSubGroups() { + return getSubGroupsStream().collect(Collectors.toSet()); + } + + Stream getSubGroupsStream(); /** * You must also call addChild on the parent group, addChild on RealmModel if there is no parent group diff --git a/server-spi/src/main/java/org/keycloak/models/GroupProvider.java b/server-spi/src/main/java/org/keycloak/models/GroupProvider.java index e836ff2504..96482a3623 100644 --- a/server-spi/src/main/java/org/keycloak/models/GroupProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/GroupProvider.java @@ -20,6 +20,8 @@ package org.keycloak.models; import org.keycloak.provider.Provider; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @@ -55,8 +57,20 @@ public interface GroupProvider extends Provider { * * @param realm Realm. * @return List of groups in the Realm. + * @deprecated Use {@link #getGroupsStream(RealmModel)} getGroupsStream} instead. */ - List getGroups(RealmModel realm); + @Deprecated + default List getGroups(RealmModel realm) { + return getGroupsStream(realm).collect(Collectors.toList()); + } + + /** + * Returns groups for the given realm. + * + * @param realm Realm. + * @return Stream of groups in the Realm. + */ + Stream getGroupsStream(RealmModel realm); /** * Returns a number of groups/top level groups (i.e. groups without parent group) for the given realm. @@ -84,16 +98,43 @@ public interface GroupProvider extends Provider { * @param firstResult First result to return. Ignored if negative. * @param maxResults Maximum number of results to return. Ignored if negative. * @return List of groups with the given role. + * @deprecated Use {@link #getGroupsByRoleStream(RealmModel, RoleModel, int, int)} getGroupsByRoleStream} instead. */ - List getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults); + @Deprecated + default List getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults) { + return getGroupsByRoleStream(realm, role, firstResult, maxResults).collect(Collectors.toList()); + } + + /** + * Returns groups with the given role in the given realm. + * + * @param realm Realm. + * @param role Role. + * @param firstResult First result to return. Ignored if negative. + * @param maxResults Maximum number of results to return. Ignored if negative. + * @return Stream of groups with the given role. + */ + Stream getGroupsByRoleStream(RealmModel realm, RoleModel role, int firstResult, int maxResults); /** * Returns all top level groups (i.e. groups without parent group) for the given realm. * * @param realm Realm. * @return List of all top level groups in the realm. + * @deprecated Use {@link #getTopLevelGroupsStream(RealmModel)} getTopLevelGroupsStream} instead. */ - List getTopLevelGroups(RealmModel realm); + @Deprecated + default List getTopLevelGroups(RealmModel realm) { + return getTopLevelGroupsStream(realm).collect(Collectors.toList()); + } + + /** + * Returns all top level groups (i.e. groups without parent group) for the given realm. + * + * @param realm Realm. + * @return Stream of all top level groups in the realm. + */ + Stream getTopLevelGroupsStream(RealmModel realm); /** * Returns top level groups (i.e. groups without parent group) for the given realm. @@ -102,8 +143,22 @@ public interface GroupProvider extends Provider { * @param firstResult First result to return. * @param maxResults Maximum number of results to return. * @return List of top level groups in the realm. + * @deprecated Use {@link #getTopLevelGroupsStream(RealmModel, Integer, Integer)} getTopLevelGroupsStream} instead. */ - List getTopLevelGroups(RealmModel realm, Integer firstResult, Integer maxResults); + @Deprecated + default List getTopLevelGroups(RealmModel realm, Integer firstResult, Integer maxResults) { + return getTopLevelGroupsStream(realm, firstResult, maxResults).collect(Collectors.toList()); + } + + /** + * Returns top level groups (i.e. groups without parent group) for the given realm. + * + * @param realm Realm. + * @param firstResult First result to return. + * @param maxResults Maximum number of results to return. + * @return Stream of top level groups in the realm. + */ + Stream getTopLevelGroupsStream(RealmModel realm, Integer firstResult, Integer maxResults); /** * Returns groups with the given string in name for the given realm. @@ -113,8 +168,23 @@ public interface GroupProvider extends Provider { * @param firstResult First result to return. Ignored if {@code null}. * @param maxResults Maximum number of results to return. Ignored if {@code null}. * @return List of groups with the given string in name. + * @deprecated Use {@link #searchForGroupByNameStream(RealmModel, String, Integer, Integer)} searchForGroupByNameStream} instead. */ - List searchForGroupByName(RealmModel realm, String search, Integer firstResult, Integer maxResults); + @Deprecated + default List searchForGroupByName(RealmModel realm, String search, Integer firstResult, Integer maxResults) { + return searchForGroupByNameStream(realm, search, firstResult, maxResults).collect(Collectors.toList()); + } + + /** + * Returns groups with the given string in name for the given realm. + * + * @param realm Realm. + * @param search Searched string. + * @param firstResult First result to return. Ignored if {@code null}. + * @param maxResults Maximum number of results to return. Ignored if {@code null}. + * @return Stream of groups with the given string in name. + */ + Stream searchForGroupByNameStream(RealmModel realm, String search, Integer firstResult, Integer maxResults); /** * Creates a new group with the given name in the given realm. diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java index a799e3c322..ecca698d24 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -282,7 +282,12 @@ public interface RealmModel extends RoleContainerModel { RoleModel getRoleById(String id); - List getDefaultGroups(); + @Deprecated + default List getDefaultGroups() { + return getDefaultGroupsStream().collect(Collectors.toList()); + } + + Stream getDefaultGroupsStream(); void addDefaultGroup(GroupModel group); @@ -535,12 +540,38 @@ public interface RealmModel extends RoleContainerModel { GroupModel createGroup(String id, String name, GroupModel toParent); GroupModel getGroupById(String id); - List getGroups(); + + @Deprecated + default List getGroups() { + return getGroupsStream().collect(Collectors.toList()); + } + + Stream getGroupsStream(); + Long getGroupsCount(Boolean onlyTopGroups); Long getGroupsCountByNameContaining(String search); - List getTopLevelGroups(); - List getTopLevelGroups(Integer first, Integer max); - List searchForGroupByName(String search, Integer first, Integer max); + + @Deprecated + default List getTopLevelGroups() { + return getTopLevelGroupsStream().collect(Collectors.toList()); + } + + Stream getTopLevelGroupsStream(); + + @Deprecated + default List getTopLevelGroups(Integer first, Integer max) { + return getTopLevelGroupsStream(first, max).collect(Collectors.toList()); + } + + Stream getTopLevelGroupsStream(Integer first, Integer max); + + @Deprecated + default List searchForGroupByName(String search, Integer first, Integer max) { + return searchForGroupByNameStream(search, first, max).collect(Collectors.toList()); + } + + Stream searchForGroupByNameStream(String search, Integer first, Integer max); + boolean removeGroup(GroupModel group); void moveGroup(GroupModel group, GroupModel toParent); diff --git a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java index dbf72c1bc9..c6034f2b6f 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java @@ -219,7 +219,9 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio /** * @deprecated Use the corresponding method from {@link GroupProvider}. */ @Override - List getGroups(RealmModel realm); + default List getGroups(RealmModel realm) { + return getGroupsStream(realm).collect(Collectors.toList()); + } /** * @deprecated Use the corresponding method from {@link GroupProvider}. */ @@ -234,22 +236,30 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio /** * @deprecated Use the corresponding method from {@link GroupProvider}. */ @Override - List getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults); + default List getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults) { + return getGroupsByRoleStream(realm, role, firstResult, maxResults).collect(Collectors.toList()); + } /** * @deprecated Use the corresponding method from {@link GroupProvider}. */ @Override - List getTopLevelGroups(RealmModel realm); + default List getTopLevelGroups(RealmModel realm) { + return getTopLevelGroupsStream(realm).collect(Collectors.toList()); + } /** * @deprecated Use the corresponding method from {@link GroupProvider}. */ @Override - List getTopLevelGroups(RealmModel realm, Integer first, Integer max); + default List getTopLevelGroups(RealmModel realm, Integer first, Integer max) { + return getTopLevelGroupsStream(realm, first, max).collect(Collectors.toList()); + } /** * @deprecated Use the corresponding method from {@link GroupProvider}. */ @Override - List searchForGroupByName(RealmModel realm, String search, Integer first, Integer max); + default List searchForGroupByName(RealmModel realm, String search, Integer first, Integer max) { + return searchForGroupByNameStream(realm, search, first, max).collect(Collectors.toList()); + } /** * @deprecated Use the corresponding method from {@link GroupProvider}. */ diff --git a/server-spi/src/main/java/org/keycloak/models/UserModel.java b/server-spi/src/main/java/org/keycloak/models/UserModel.java index 273e6e0495..3d2de374f1 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserModel.java +++ b/server-spi/src/main/java/org/keycloak/models/UserModel.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Bill Burke @@ -118,18 +119,29 @@ public interface UserModel extends RoleMapperModel { void setEmailVerified(boolean verified); - Set getGroups(); - - default Set getGroups(int first, int max) { - return getGroups(null, first, max); + @Deprecated + default Set getGroups() { + return getGroupsStream().collect(Collectors.toSet()); } + Stream getGroupsStream(); + + @Deprecated + default Set getGroups(int first, int max) { + return getGroupsStream(null, first, max).collect(Collectors.toSet()); + } + + @Deprecated default Set getGroups(String search, int first, int max) { - return getGroups().stream() + return getGroupsStream(search, first, max) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + default Stream getGroupsStream(String search, int first, int max) { + return getGroupsStream() .filter(group -> search == null || group.getName().toLowerCase().contains(search.toLowerCase())) .skip(first) - .limit(max) - .collect(Collectors.toCollection(LinkedHashSet::new)); + .limit(max); } default long getGroupsCount() { @@ -138,11 +150,11 @@ public interface UserModel extends RoleMapperModel { default long getGroupsCountByNameContaining(String search) { if (search == null) { - return getGroups().size(); + return getGroupsStream().count(); } String s = search.toLowerCase(); - return getGroups().stream().filter(group -> group.getName().toLowerCase().contains(s)).count(); + return getGroupsStream().filter(group -> group.getName().toLowerCase().contains(s)).count(); } void joinGroup(GroupModel group); diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java index 025937e4c6..bbf7876525 100644 --- a/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java @@ -39,13 +39,15 @@ public class RoleUtils { * @param groups * @param targetGroup * @return true if targetGroup is in groups (directly or indirectly via parent child relationship) + * @deprecated Use {@link #isMember(Stream, GroupModel)} isMember(Stream, GroupModel)} instead. */ public static boolean isMember(Set groups, GroupModel targetGroup) { + // collecting to set to keep "Breadth First Search" like functionality if (groups.contains(targetGroup)) return true; for (GroupModel mapping : groups) { GroupModel child = mapping; - while(child.getParent() != null) { + while (child.getParent() != null) { if (child.getParent().equals(targetGroup)) return true; child = child.getParent(); } @@ -53,6 +55,27 @@ public class RoleUtils { return false; } + /** + * + * @param groups + * @param targetGroup + * @return true if targetGroup is in groups (directly or indirectly via parent child relationship) + */ + public static boolean isMember(Stream groups, GroupModel targetGroup) { + // collecting to set to keep "Breadth First Search" like functionality + Set groupsSet = groups.collect(Collectors.toSet()); + if (groupsSet.contains(targetGroup)) return true; + + return groupsSet.stream().anyMatch(mapping -> { + GroupModel child = mapping; + while (child.getParent() != null) { + if (child.getParent().equals(targetGroup)) return true; + child = child.getParent(); + } + return false; + }); + } + /** * @param roles * @param targetRole @@ -94,6 +117,7 @@ public class RoleUtils { * @param targetRole * @param checkParentGroup When {@code true}, also parent group is recursively checked for role * @return true if targetRole is in roles (directly or indirectly via composite role) + * @deprecated Use {@link #hasRoleFromGroup(Stream, RoleModel, boolean)} hasRoleFromGroup(Stream, RoleModel, boolean)} instead. */ public static boolean hasRoleFromGroup(Iterable groups, RoleModel targetRole, boolean checkParentGroup) { if (groups == null) { @@ -104,6 +128,22 @@ public class RoleUtils { .anyMatch(group -> hasRoleFromGroup(group, targetRole, checkParentGroup)); } + /** + * Checks whether the {@code targetRole} is contained in any of the {@code groups} or their parents + * (if requested) + * @param groups + * @param targetRole + * @param checkParentGroup When {@code true}, also parent group is recursively checked for role + * @return true if targetRole is in roles (directly or indirectly via composite role) + */ + public static boolean hasRoleFromGroup(Stream groups, RoleModel targetRole, boolean checkParentGroup) { + if (groups == null) { + return false; + } + + return groups.anyMatch(group -> hasRoleFromGroup(group, targetRole, checkParentGroup)); + } + /** * Recursively expands composite roles into their composite. * @param role @@ -156,10 +196,7 @@ public class RoleUtils { */ public static Set getDeepUserRoleMappings(UserModel user) { Set roleMappings = new HashSet<>(user.getRoleMappings()); - for (GroupModel group : user.getGroups()) { - addGroupRoles(group, roleMappings); - } - + user.getGroupsStream().forEach(group -> addGroupRoles(group, roleMappings)); return expandCompositeRoles(roleMappings); } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java index c94767852a..aa86d229a7 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java @@ -25,6 +25,7 @@ import org.keycloak.models.UserModel; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Stream; /** * Delegation pattern. Used to proxy UserModel implementations. @@ -224,8 +225,8 @@ public class UserModelDelegate implements UserModel { } @Override - public Set getGroups() { - return delegate.getGroups(); + public Stream getGroupsStream() { + return delegate.getGroupsStream(); } @Override diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java index ae914a24d7..b3fbd6af70 100644 --- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java +++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java @@ -36,6 +36,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Stream; /** * This abstract class provides implementations for everything but getUsername(). getId() returns a default value @@ -94,8 +95,8 @@ public abstract class AbstractUserAdapter extends UserModelDefaultMethods { * * @return */ - protected Set getGroupsInternal() { - return Collections.emptySet(); + protected Stream getGroupsInternal() { + return Stream.empty(); } /** @@ -110,11 +111,10 @@ public abstract class AbstractUserAdapter extends UserModelDefaultMethods { } @Override - public Set getGroups() { - Set set = new HashSet<>(); - if (appendDefaultGroups()) set.addAll(realm.getDefaultGroups()); - set.addAll(getGroupsInternal()); - return set; + public Stream getGroupsStream() { + Stream groups = getGroupsInternal(); + if (appendDefaultGroups()) groups = Stream.concat(groups, realm.getDefaultGroupsStream()); + return groups; } @Override @@ -131,8 +131,7 @@ public abstract class AbstractUserAdapter extends UserModelDefaultMethods { @Override public boolean isMemberOf(GroupModel group) { - Set roles = getGroups(); - return RoleUtils.isMember(roles, group); + return RoleUtils.isMember(getGroupsStream(), group); } @Override @@ -170,7 +169,7 @@ public abstract class AbstractUserAdapter extends UserModelDefaultMethods { public boolean hasRole(RoleModel role) { Set roles = getRoleMappings(); return RoleUtils.hasRole(roles, role) - || RoleUtils.hasRoleFromGroup(getGroups(), role, true); + || RoleUtils.hasRoleFromGroup(getGroupsStream(), role, true); } @Override diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java index 7703d4d315..074dc67e56 100644 --- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java @@ -36,6 +36,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Stream; /** * Assumes everything is managed by federated storage except for username. getId() returns a default value @@ -103,8 +104,8 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau * * @return */ - protected Set getGroupsInternal() { - return Collections.emptySet(); + protected Stream getGroupsInternal() { + return Stream.empty(); } /** @@ -127,11 +128,10 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau * @return */ @Override - public Set getGroups() { - Set set = new HashSet<>(getFederatedStorage().getGroups(realm, this.getId())); - if (appendDefaultGroups()) set.addAll(realm.getDefaultGroups()); - set.addAll(getGroupsInternal()); - return set; + public Stream getGroupsStream() { + Stream groups = getFederatedStorage().getGroupsStream(realm, this.getId()); + if (appendDefaultGroups()) groups = Stream.concat(groups, realm.getDefaultGroupsStream()); + return Stream.concat(groups, getGroupsInternal()); } @Override @@ -148,8 +148,7 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau @Override public boolean isMemberOf(GroupModel group) { - Set roles = getGroups(); - return RoleUtils.isMember(roles, group); + return RoleUtils.isMember(getGroupsStream(), group); } /** @@ -203,7 +202,7 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau public boolean hasRole(RoleModel role) { Set roles = getRoleMappings(); return RoleUtils.hasRole(roles, role) - || RoleUtils.hasRoleFromGroup(getGroups(), role, true); + || RoleUtils.hasRoleFromGroup(getGroupsStream(), role, true); } @Override diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java index 9ce926344c..eb116d2164 100644 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java @@ -21,13 +21,21 @@ import org.keycloak.models.RealmModel; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Bill Burke * @version $Revision: 1 $ */ public interface UserGroupMembershipFederatedStorage { - Set getGroups(RealmModel realm, String userId); + @Deprecated + default Set getGroups(RealmModel realm, String userId) { + return getGroupsStream(realm, userId).collect(Collectors.toSet()); + } + + Stream getGroupsStream(RealmModel realm, String userId); + void joinGroup(RealmModel realm, String userId, GroupModel group); void leaveGroup(RealmModel realm, String userId, GroupModel group); List getMembership(RealmModel realm, GroupModel group, int firstResult, int max); diff --git a/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java b/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java index 445000d4d0..91a66403b7 100644 --- a/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java +++ b/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java @@ -25,7 +25,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; /** * Optional capability interface implemented by UserStorageProviders. @@ -128,14 +127,9 @@ public interface UserQueryProvider { * @return number of users that are in at least one of the groups */ static int countUsersInGroups(List users, Set groupIds) { - return (int) users.stream().filter(u -> { - for (GroupModel group : u.getGroups()) { - if (groupIds.contains(group.getId())) { - return true; - } - } - return false; - }).count(); + return (int) users.stream() + .filter(u -> u.getGroupsStream().anyMatch(group -> groupIds.contains(group.getId()))) + .count(); } /** diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java index dc70bb60d2..4045e01ea2 100755 --- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java +++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -567,10 +567,7 @@ public class ExportUtils { } if (options.isGroupsAndRolesIncluded()) { - List groups = new LinkedList<>(); - for (GroupModel group : user.getGroups()) { - groups.add(ModelToRepresentation.buildGroupPath(group)); - } + List groups = user.getGroupsStream().map(ModelToRepresentation::buildGroupPath).collect(Collectors.toList()); userRep.setGroups(groups); } return userRep; @@ -737,10 +734,8 @@ public class ExportUtils { userRep.setNotBefore(notBefore); if (options.isGroupsAndRolesIncluded()) { - List groups = new LinkedList<>(); - for (GroupModel group : session.userFederatedStorage().getGroups(realm, id)) { - groups.add(ModelToRepresentation.buildGroupPath(group)); - } + List groups = session.userFederatedStorage().getGroupsStream(realm, id) + .map(ModelToRepresentation::buildGroupPath).collect(Collectors.toList()); userRep.setGroups(groups); } return userRep; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java index 6ffd9761e6..9025860950 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java @@ -27,9 +27,10 @@ import org.keycloak.representations.IDToken; import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; /** * Maps user group membership @@ -93,16 +94,10 @@ public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements * @param userSession */ protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) { + Function toGroupRepresentation = useFullPath(mappingModel) ? + ModelToRepresentation::buildGroupPath : GroupModel::getName; + List membership = userSession.getUser().getGroupsStream().map(toGroupRepresentation).collect(Collectors.toList()); - List membership = new LinkedList<>(); - boolean fullPath = useFullPath(mappingModel); - for (GroupModel group : userSession.getUser().getGroups()) { - if (fullPath) { - membership.add(ModelToRepresentation.buildGroupPath(group)); - } else { - membership.add(group.getName()); - } - } String protocolClaim = mappingModel.getConfig().get(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); token.getOtherClaims().put(protocolClaim, membership); diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/GroupMembershipMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/GroupMembershipMapper.java index 96d4fcf6ef..5d375ba2d8 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/mappers/GroupMembershipMapper.java +++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/GroupMembershipMapper.java @@ -20,7 +20,6 @@ package org.keycloak.protocol.saml.mappers; import org.keycloak.dom.saml.v2.assertion.AttributeStatementType; import org.keycloak.dom.saml.v2.assertion.AttributeType; import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.UserSessionModel; @@ -32,6 +31,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; /** * @author Bill Burke @@ -122,27 +122,27 @@ public class GroupMembershipMapper extends AbstractSAMLProtocolMapper implements boolean singleAttribute = Boolean.parseBoolean(single); boolean fullPath = useFullPath(mappingModel); - AttributeType singleAttributeType = null; - for (GroupModel group : userSession.getUser().getGroups()) { + final AtomicReference singleAttributeType = new AtomicReference<>(null); + userSession.getUser().getGroupsStream().forEach(group -> { String groupName; if (fullPath) { groupName = ModelToRepresentation.buildGroupPath(group); } else { groupName = group.getName(); } - AttributeType attributeType = null; + AttributeType attributeType; if (singleAttribute) { - if (singleAttributeType == null) { - singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel); - attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(singleAttributeType)); + if (singleAttributeType.get() == null) { + singleAttributeType.set(AttributeStatementHelper.createAttributeType(mappingModel)); + attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(singleAttributeType.get())); } - attributeType = singleAttributeType; + attributeType = singleAttributeType.get(); } else { attributeType = AttributeStatementHelper.createAttributeType(mappingModel); attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType)); } attributeType.addAttributeValue(groupName); - } + }); } public static ProtocolMapperModel create(String name, String samlAttributeName, String nameFormat, String friendlyName, boolean singleAttribute) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java index ac919f4af5..6f7c62bc22 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java @@ -52,6 +52,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; /** * @resource Groups @@ -101,11 +103,10 @@ public class GroupResource { public Response updateGroup(GroupRepresentation rep) { this.auth.groups().requireManage(group); - for (GroupModel sibling: siblings()) { - if (Objects.equals(sibling.getId(), group.getId())) continue; - if (sibling.getName().equals(rep.getName())) { - return ErrorResponse.exists("Sibling group named '" + rep.getName() + "' already exists."); - } + boolean exists = siblings().filter(s -> !Objects.equals(s.getId(), group.getId())) + .anyMatch(s -> Objects.equals(s.getName(), rep.getName())); + if (exists) { + return ErrorResponse.exists("Sibling group named '" + rep.getName() + "' already exists."); } updateGroup(rep, group); @@ -114,11 +115,11 @@ public class GroupResource { return Response.noContent().build(); } - private List siblings() { + private Stream siblings() { if (group.getParentId() == null) { - return realm.getTopLevelGroups(); + return realm.getTopLevelGroupsStream(); } else { - return new ArrayList(group.getParent().getSubGroups()); + return group.getParent().getSubGroupsStream(); } } @@ -145,10 +146,8 @@ public class GroupResource { public Response addChild(GroupRepresentation rep) { this.auth.groups().requireManage(group); - for (GroupModel group : group.getSubGroups()) { - if (group.getName().equals(rep.getName())) { - return ErrorResponse.exists("Parent already contains subgroup named '" + rep.getName() + "'"); - } + if (group.getSubGroupsStream().map(GroupModel::getName).anyMatch(Predicate.isEqual(rep.getName()))) { + return ErrorResponse.exists("Parent already contains subgroup named '" + rep.getName() + "'"); } Response.ResponseBuilder builder = Response.status(204); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java index 64d0e47537..b11ee4c59d 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java @@ -42,9 +42,9 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.net.URI; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Stream; /** * @resource Groups @@ -73,23 +73,19 @@ public class GroupsResource { @GET @NoCache @Produces(MediaType.APPLICATION_JSON) - public List getGroups(@QueryParam("search") String search, - @QueryParam("first") Integer firstResult, - @QueryParam("max") Integer maxResults, - @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) { + public Stream getGroups(@QueryParam("search") String search, + @QueryParam("first") Integer firstResult, + @QueryParam("max") Integer maxResults, + @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) { auth.groups().requireList(); - List results; - if (Objects.nonNull(search)) { - results = ModelToRepresentation.searchForGroupByName(realm, !briefRepresentation, search.trim(), firstResult, maxResults); + return ModelToRepresentation.searchForGroupByName(realm, !briefRepresentation, search.trim(), firstResult, maxResults); } else if(Objects.nonNull(firstResult) && Objects.nonNull(maxResults)) { - results = ModelToRepresentation.toGroupHierarchy(realm, !briefRepresentation, firstResult, maxResults); + return ModelToRepresentation.toGroupHierarchy(realm, !briefRepresentation, firstResult, maxResults); } else { - results = ModelToRepresentation.toGroupHierarchy(realm, !briefRepresentation); + return ModelToRepresentation.toGroupHierarchy(realm, !briefRepresentation); } - - return results; } /** diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 274b59ac94..b0febd974c 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; @@ -120,6 +121,7 @@ import org.keycloak.representations.idm.LDAPCapabilityRepresentation; import org.keycloak.utils.ReservedCharValidator; import com.fasterxml.jackson.core.type.TypeReference; +import org.keycloak.utils.ServicesUtils; /** * Base resource class for the admin REST api of one realm @@ -1045,14 +1047,10 @@ public class RealmAdminResource { @NoCache @Produces(MediaType.APPLICATION_JSON) @Path("default-groups") - public List getDefaultGroups() { + public Stream getDefaultGroups() { auth.realm().requireViewRealm(); - List defaults = new LinkedList<>(); - for (GroupModel group : realm.getDefaultGroups()) { - defaults.add(ModelToRepresentation.toRepresentation(group, false)); - } - return defaults; + return realm.getDefaultGroupsStream().map(ServicesUtils::groupToBriefRepresentation); } @PUT @NoCache diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java index 9889a7435e..4ee07aa4c0 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java @@ -59,7 +59,6 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -440,7 +439,7 @@ public class RoleContainerResource extends RoleResource { @GET @Produces(MediaType.APPLICATION_JSON) @NoCache - public List getGroupsInRole(final @PathParam("role-name") String roleName, + public Stream getGroupsInRole(final @PathParam("role-name") String roleName, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults, @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) { @@ -455,10 +454,8 @@ public class RoleContainerResource extends RoleResource { throw new NotFoundException("Could not find role"); } - List groupsModel = session.groups().getGroupsByRole(realm, role, firstResult, maxResults); + Stream groupsModel = session.groups().getGroupsByRoleStream(realm, role, firstResult, maxResults); - return groupsModel.stream() - .map(g -> ModelToRepresentation.toRepresentation(g, !briefRepresentation)) - .collect(Collectors.toList()); + return groupsModel.map(g -> ModelToRepresentation.toRepresentation(g, !briefRepresentation)); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java index 82c69749f6..eea964ce8c 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java @@ -106,6 +106,7 @@ import java.util.Properties; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_ID; import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_USERNAME; @@ -868,22 +869,19 @@ public class UserResource { @Path("groups") @NoCache @Produces(MediaType.APPLICATION_JSON) - public List groupMembership(@QueryParam("search") String search, - @QueryParam("first") Integer firstResult, - @QueryParam("max") Integer maxResults, - @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) { + public Stream groupMembership(@QueryParam("search") String search, + @QueryParam("first") Integer firstResult, + @QueryParam("max") Integer maxResults, + @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) { auth.users().requireView(user); - List results; if (Objects.nonNull(search) && Objects.nonNull(firstResult) && Objects.nonNull(maxResults)) { - results = ModelToRepresentation.searchForGroupByName(user, !briefRepresentation, search.trim(), firstResult, maxResults); + return ModelToRepresentation.searchForGroupByName(user, !briefRepresentation, search.trim(), firstResult, maxResults); } else if(Objects.nonNull(firstResult) && Objects.nonNull(maxResults)) { - results = ModelToRepresentation.toGroupHierarchy(user, !briefRepresentation, firstResult, maxResults); + return ModelToRepresentation.toGroupHierarchy(user, !briefRepresentation, firstResult, maxResults); } else { - results = ModelToRepresentation.toGroupHierarchy(user, !briefRepresentation); + return ModelToRepresentation.toGroupHierarchy(user, !briefRepresentation); } - - return results; } @GET diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java index 92d9aa5165..e221abc332 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java @@ -546,10 +546,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme private boolean evaluateHierarchy(UserModel user, Predicate eval) { Set visited = new HashSet<>(); - for (GroupModel group : user.getGroups()) { - if (evaluateHierarchy(eval, group, visited)) return true; - } - return false; + return user.getGroupsStream().anyMatch(group -> evaluateHierarchy(eval, group, visited)); } private boolean evaluateHierarchy(Predicate eval, GroupModel group, Set visited) { diff --git a/services/src/main/java/org/keycloak/utils/ServicesUtils.java b/services/src/main/java/org/keycloak/utils/ServicesUtils.java index 78825f1bfc..95b634efe5 100644 --- a/services/src/main/java/org/keycloak/utils/ServicesUtils.java +++ b/services/src/main/java/org/keycloak/utils/ServicesUtils.java @@ -19,7 +19,10 @@ package org.keycloak.utils; import org.jboss.logging.Logger; import org.keycloak.executors.ExecutorsProvider; +import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.representations.idm.GroupRepresentation; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -53,4 +56,8 @@ public class ServicesUtils { } }; } + + public static GroupRepresentation groupToBriefRepresentation(GroupModel g) { + return ModelToRepresentation.toRepresentation(g, false); + } } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java index bf2c994fd2..2f2f16b31a 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java @@ -389,14 +389,12 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider, } @Override - public Set getGroups(RealmModel realm, String userId) { + public Stream getGroupsStream(RealmModel realm, String userId) { Set set = userGroups.get(getUserIdInMap(realm, userId)); if (set == null) { - return Collections.EMPTY_SET; + return Stream.empty(); } - return set.stream() - .map(realm::getGroupById) - .collect(Collectors.toSet()); + return set.stream().map(realm::getGroupById); } @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapper2WaySyncTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapper2WaySyncTest.java index 329e72d50d..31fc3226ba 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapper2WaySyncTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapper2WaySyncTest.java @@ -150,7 +150,7 @@ public class LDAPGroupMapper2WaySyncTest extends AbstractLDAPTest { GroupModel kcGroup12 = KeycloakModelUtils.findGroupByPath(realm, "/group12"); GroupModel kcGroup2 = KeycloakModelUtils.findGroupByPath(realm, "/group2"); - Assert.assertEquals(0, kcGroup1.getSubGroups().size()); + Assert.assertEquals(0, kcGroup1.getSubGroupsStream().count()); Assert.assertEquals("group1 - description1", kcGroup1.getFirstAttribute(descriptionAttrName)); Assert.assertNull(kcGroup11.getFirstAttribute(descriptionAttrName)); @@ -212,7 +212,7 @@ public class LDAPGroupMapper2WaySyncTest extends AbstractLDAPTest { GroupModel kcGroup12 = KeycloakModelUtils.findGroupByPath(realm, "/group1/group12"); GroupModel kcGroup2 = KeycloakModelUtils.findGroupByPath(realm, "/group2"); - Assert.assertEquals(2, kcGroup1.getSubGroups().size()); + Assert.assertEquals(2, kcGroup1.getSubGroupsStream().count()); Assert.assertEquals("group1 - description1", kcGroup1.getFirstAttribute(descriptionAttrName)); Assert.assertNull(kcGroup11.getFirstAttribute(descriptionAttrName)); @@ -226,9 +226,7 @@ public class LDAPGroupMapper2WaySyncTest extends AbstractLDAPTest { private static void removeAllModelGroups(RealmModel appRealm) { - for (GroupModel group : appRealm.getTopLevelGroups()) { - appRealm.removeGroup(group); - } + appRealm.getTopLevelGroupsStream().forEach(appRealm::removeGroup); } private static void testDropNonExisting(KeycloakSession session, LDAPTestContext ctx, ComponentModel mapperModel) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncTest.java index 5e8e8500e6..907838d35e 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncTest.java @@ -50,6 +50,7 @@ import javax.ws.rs.BadRequestException; import java.util.Date; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import static org.keycloak.testsuite.util.LDAPTestUtils.getGroupDescriptionLDAPAttrName; @@ -105,10 +106,7 @@ public class LDAPGroupMapperSyncTest extends AbstractLDAPTest { LDAPTestContext ctx = LDAPTestContext.init(session); RealmModel realm = ctx.getRealm(); - List kcGroups = realm.getTopLevelGroups(); - for (GroupModel kcGroup : kcGroups) { - realm.removeGroup(kcGroup); - } + realm.getTopLevelGroupsStream().forEach(realm::removeGroup); }); } @@ -169,7 +167,7 @@ public class LDAPGroupMapperSyncTest extends AbstractLDAPTest { GroupModel kcGroup11 = KeycloakModelUtils.findGroupByPath(realm, "/group11"); GroupModel kcGroup12 = KeycloakModelUtils.findGroupByPath(realm, "/group12"); - Assert.assertEquals(0, kcGroup1.getSubGroups().size()); + Assert.assertEquals(0, kcGroup1.getSubGroupsStream().count()); Assert.assertEquals("group1 - description", kcGroup1.getFirstAttribute(descriptionAttrName)); Assert.assertNull(kcGroup11.getFirstAttribute(descriptionAttrName)); @@ -221,7 +219,7 @@ public class LDAPGroupMapperSyncTest extends AbstractLDAPTest { GroupModel kcGroup11 = KeycloakModelUtils.findGroupByPath(realm, "/group1/group11"); GroupModel kcGroup12 = KeycloakModelUtils.findGroupByPath(realm, "/group1/group12"); - Assert.assertEquals(2, kcGroup1.getSubGroups().size()); + Assert.assertEquals(2, kcGroup1.getSubGroupsStream().count()); Assert.assertEquals("group1 - description", kcGroup1.getFirstAttribute(descriptionAttrName)); Assert.assertNull(kcGroup11.getFirstAttribute(descriptionAttrName)); @@ -272,7 +270,7 @@ public class LDAPGroupMapperSyncTest extends AbstractLDAPTest { Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group11")); Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/group1/group12")); - Assert.assertEquals(2, kcGroup1.getSubGroups().size()); + Assert.assertEquals(2, kcGroup1.getSubGroupsStream().count()); // Create some new groups in keycloak GroupModel model1 = realm.createGroup("model1"); @@ -341,7 +339,7 @@ public class LDAPGroupMapperSyncTest extends AbstractLDAPTest { // Load user from LDAP to Keycloak DB UserModel john = session.users().getUserByUsername("johnkeycloak", realm); - Set johnGroups = john.getGroups(); + Set johnGroups = john.getGroupsStream().collect(Collectors.toSet()); // Assert just those groups, which john was memberOf exists because they were lazily created GroupModel group1 = KeycloakModelUtils.findGroupByPath(realm, "/group1"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncWithGroupsPathTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncWithGroupsPathTest.java index 20c6bcbfaf..2e29bda656 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncWithGroupsPathTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncWithGroupsPathTest.java @@ -92,9 +92,7 @@ public class LDAPGroupMapperSyncWithGroupsPathTest extends AbstractLDAPTest { RealmModel realm = ctx.getRealm(); GroupModel groupsPathGroup = KeycloakModelUtils.findGroupByPath(realm, LDAP_GROUPS_PATH); - for (GroupModel kcGroup : groupsPathGroup.getSubGroups()) { - realm.removeGroup(kcGroup); - } + groupsPathGroup.getSubGroupsStream().forEach(realm::removeGroup); }); } @@ -126,7 +124,7 @@ public class LDAPGroupMapperSyncWithGroupsPathTest extends AbstractLDAPTest { GroupModel kcGroup11 = KeycloakModelUtils.findGroupByPath(realm, LDAP_GROUPS_PATH + "/group1/group11"); GroupModel kcGroup12 = KeycloakModelUtils.findGroupByPath(realm, LDAP_GROUPS_PATH + "/group1/group12"); - Assert.assertEquals(2, kcGroup1.getSubGroups().size()); + Assert.assertEquals(2, kcGroup1.getSubGroupsStream().count()); Assert.assertEquals("group1 - description", kcGroup1.getFirstAttribute(descriptionAttrName)); Assert.assertNull(kcGroup11.getFirstAttribute(descriptionAttrName)); @@ -175,7 +173,7 @@ public class LDAPGroupMapperSyncWithGroupsPathTest extends AbstractLDAPTest { Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, LDAP_GROUPS_PATH + "/group1/group11")); Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, LDAP_GROUPS_PATH + "/group1/group12")); - Assert.assertEquals(2, kcGroup1.getSubGroups().size()); + Assert.assertEquals(2, kcGroup1.getSubGroupsStream().count()); // Create some new groups in keycloak GroupModel groupsPathGroup = KeycloakModelUtils.findGroupByPath(realm, LDAP_GROUPS_PATH); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperTest.java index 2f8dfe23e7..682caeb7d7 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperTest.java @@ -127,20 +127,11 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { RealmModel appRealm = ctx.getRealm(); UserModel johnDb = session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm); - Set johnDbGroups = johnDb.getGroups(); - Assert.assertEquals(2, johnDbGroups.size()); - - Set johnDbGroupsWithGr = johnDb.getGroups("Gr", 0, 10); - Assert.assertEquals(2, johnDbGroupsWithGr.size()); - - Set johnDbGroupsWithGr2 = johnDb.getGroups("Gr", 1, 10); - Assert.assertEquals(1, johnDbGroupsWithGr2.size()); - - Set johnDbGroupsWithGr3 = johnDb.getGroups("Gr", 0, 1); - Assert.assertEquals(1, johnDbGroupsWithGr3.size()); - - Set johnDbGroupsWith12 = johnDb.getGroups("12", 0, 10); - Assert.assertEquals(1, johnDbGroupsWith12.size()); + Assert.assertEquals(2, johnDb.getGroupsStream().count()); + Assert.assertEquals(2, johnDb.getGroupsStream("Gr", 0, 10).count()); + Assert.assertEquals(1, johnDb.getGroupsStream("Gr", 1, 10).count()); + Assert.assertEquals(1, johnDb.getGroupsStream("Gr", 0, 1).count()); + Assert.assertEquals(1, johnDb.getGroupsStream("12", 0, 10).count()); long dbGroupCount = johnDb.getGroupsCount(); Assert.assertEquals(2, dbGroupCount); @@ -161,7 +152,7 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm); UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm); - Set johnGroups = john.getGroups(); + Set johnGroups = john.getGroupsStream().collect(Collectors.toSet()); Assert.assertEquals(4, johnGroups.size()); long groupCount = john.getGroupsCount(); Assert.assertEquals(4, groupCount); @@ -171,23 +162,12 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { Assert.assertTrue(johnGroups.contains(groupTeam20162017)); Assert.assertTrue(johnGroups.contains(groupTeamChild20182019)); - Set johnGroupsWithGr = john.getGroups("gr", 0, 10); - Assert.assertEquals(2, johnGroupsWithGr.size()); - - Set johnGroupsWithGr2 = john.getGroups("gr", 1, 10); - Assert.assertEquals(1, johnGroupsWithGr2.size()); - - Set johnGroupsWithGr3 = john.getGroups("gr", 0, 1); - Assert.assertEquals(1, johnGroupsWithGr3.size()); - - Set johnGroupsWith12 = john.getGroups("12", 0, 10); - Assert.assertEquals(1, johnGroupsWith12.size()); - - Set johnGroupsWith2017 = john.getGroups("2017", 0, 10); - Assert.assertEquals(1, johnGroupsWith2017.size()); - - Set johnGroupsWith2018 = john.getGroups("2018", 0, 10); - Assert.assertEquals(1, johnGroupsWith2017.size()); + Assert.assertEquals(2, john.getGroupsStream("gr", 0, 10).count()); + Assert.assertEquals(1, john.getGroupsStream("gr", 1, 10).count()); + Assert.assertEquals(1, john.getGroupsStream("gr", 0, 1).count()); + Assert.assertEquals(1, john.getGroupsStream("12", 0, 10).count()); + Assert.assertEquals(1, john.getGroupsStream("2017", 0, 10).count()); + Assert.assertEquals(1, john.getGroupsStream("2018", 0, 10).count()); // 4 - Check through userProvider List group1Members = session.users().getGroupMembers(appRealm, group1, 0, 10); @@ -217,8 +197,7 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { mary.leaveGroup(groupTeam20162017); mary.leaveGroup(groupTeamChild20182019); - johnGroups = john.getGroups(); - Assert.assertEquals(0, johnGroups.size()); + Assert.assertEquals(0, john.getGroupsStream().count()); groupCount = john.getGroupsCount(); Assert.assertEquals(0, groupCount); @@ -266,7 +245,7 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { mary.joinGroup(group12); // Assert that mary has both LDAP and DB mapped groups - Set maryGroups = mary.getGroups(); + Set maryGroups = mary.getGroupsStream().collect(Collectors.toSet()); Assert.assertEquals(5, maryGroups.size()); Assert.assertTrue(maryGroups.contains(group1)); Assert.assertTrue(maryGroups.contains(group11)); @@ -275,17 +254,10 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { long groupCount = mary.getGroupsCount(); Assert.assertEquals(5, groupCount); - Set maryGroupsWithGr = mary.getGroups("gr", 0, 10); - Assert.assertEquals(5, maryGroupsWithGr.size()); - - Set maryGroupsWithGr2 = mary.getGroups("gr", 1, 10); - Assert.assertEquals(4, maryGroupsWithGr2.size()); - - Set maryGroupsWithGr3 = mary.getGroups("gr", 0, 1); - Assert.assertEquals(1, maryGroupsWithGr3.size()); - - Set maryGroupsWith12 = mary.getGroups("12", 0, 10); - Assert.assertEquals(2, maryGroupsWith12.size()); + Assert.assertEquals(5, mary.getGroupsStream("gr", 0, 10).count()); + Assert.assertEquals(4, mary.getGroupsStream("gr", 1, 10).count()); + Assert.assertEquals(1, mary.getGroupsStream("gr", 0, 1).count()); + Assert.assertEquals(2, mary.getGroupsStream("12", 0, 10).count()); }); } else { testingClient.server().run(session -> { @@ -315,7 +287,7 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12"); // Assert that mary has both LDAP and DB mapped groups - Set maryGroups = mary.getGroups(); + Set maryGroups = mary.getGroupsStream().collect(Collectors.toSet()); Assert.assertEquals(4, maryGroups.size()); Assert.assertTrue(maryGroups.contains(group1)); Assert.assertTrue(maryGroups.contains(group11)); @@ -324,17 +296,10 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { long groupCount = mary.getGroupsCount(); Assert.assertEquals(4, groupCount); - Set maryGroupsWithGr = mary.getGroups("gr", 0, 10); - Assert.assertEquals(4, maryGroupsWithGr.size()); - - Set maryGroupsWithGr2 = mary.getGroups("gr", 1, 10); - Assert.assertEquals(3, maryGroupsWithGr2.size()); - - Set maryGroupsWithGr3 = mary.getGroups("gr", 0, 1); - Assert.assertEquals(1, maryGroupsWithGr3.size()); - - Set maryGroupsWith12 = mary.getGroups("12", 0, 10); - Assert.assertEquals(1, maryGroupsWith12.size()); + Assert.assertEquals(4, mary.getGroupsStream("gr", 0, 10).count()); + Assert.assertEquals(3, mary.getGroupsStream("gr", 1, 10).count()); + Assert.assertEquals(1, mary.getGroupsStream("gr", 0, 1).count()); + Assert.assertEquals(1, mary.getGroupsStream("12", 0, 10).count()); }); } @@ -350,22 +315,15 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { UserModel maryDB = session.userLocalStorage().getUserByUsername("marykeycloak", appRealm); - Set maryDBGroups = maryDB.getGroups(); + Set maryDBGroups = maryDB.getGroupsStream().collect(Collectors.toSet()); Assert.assertFalse(maryDBGroups.contains(group1)); Assert.assertFalse(maryDBGroups.contains(group11)); Assert.assertTrue(maryDBGroups.contains(group12)); - Set maryDBGroupsWithGr = maryDB.getGroups("Gr", 0, 10); - Assert.assertEquals(3, maryDBGroupsWithGr.size()); - - Set maryDBGroupsWithGr2 = maryDB.getGroups("Gr", 1, 10); - Assert.assertEquals(2, maryDBGroupsWithGr2.size()); - - Set maryDBGroupsWithGr3 = maryDB.getGroups("Gr", 0, 1); - Assert.assertEquals(1, maryDBGroupsWithGr3.size()); - - Set maryDBGroupsWith12 = maryDB.getGroups("12", 0, 10); - Assert.assertEquals(2, maryDBGroupsWith12.size()); + Assert.assertEquals(3, maryDB.getGroupsStream("Gr", 0, 10).count()); + Assert.assertEquals(2, maryDB.getGroupsStream("Gr", 1, 10).count()); + Assert.assertEquals(1, maryDB.getGroupsStream("Gr", 0, 1).count()); + Assert.assertEquals(2, maryDB.getGroupsStream("12", 0, 10).count()); long dbGroupCount = maryDB.getGroupsCount(); Assert.assertEquals(3, dbGroupCount); @@ -455,23 +413,16 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { // Get user and check that he has requested groups from LDAP UserModel rob = session.users().getUserByUsername("robkeycloak", appRealm); - Set robGroups = rob.getGroups(); + Set robGroups = rob.getGroupsStream().collect(Collectors.toSet()); Assert.assertFalse(robGroups.contains(group1)); Assert.assertTrue(robGroups.contains(group11)); Assert.assertTrue(robGroups.contains(group12)); - Set robGroupsWithGr = rob.getGroups("Gr", 0, 10); - Assert.assertEquals(4, robGroupsWithGr.size()); - - Set robGroupsWithGr2 = rob.getGroups("Gr", 1, 10); - Assert.assertEquals(3, robGroupsWithGr2.size()); - - Set robGroupsWithGr3 = rob.getGroups("Gr", 0, 1); - Assert.assertEquals(1, robGroupsWithGr3.size()); - - Set robGroupsWith12 = rob.getGroups("12", 0, 10); - Assert.assertEquals(2, robGroupsWith12.size()); + Assert.assertEquals(4, rob.getGroupsStream("Gr", 0, 10).count()); + Assert.assertEquals(3, rob.getGroupsStream("Gr", 1, 10).count()); + Assert.assertEquals(1, rob.getGroupsStream("Gr", 0, 1).count()); + Assert.assertEquals(2, rob.getGroupsStream("12", 0, 10).count()); long dbGroupCount = rob.getGroupsCount(); Assert.assertEquals(4, dbGroupCount); @@ -494,7 +445,7 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { ldapGroup = groupMapper.loadLDAPGroupByName("group12"); groupMapper.deleteGroupMappingInLDAP(robLdap, ldapGroup); - robGroups = rob.getGroups(); + robGroups = rob.getGroupsStream().collect(Collectors.toSet()); Assert.assertTrue(robGroups.contains(group11)); Assert.assertTrue(robGroups.contains(group12)); @@ -512,8 +463,7 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { // Delete group mappings through model and verifies that user doesn't have them anymore rob.leaveGroup(group11); rob.leaveGroup(group12); - robGroups = rob.getGroups(); - Assert.assertEquals(2, robGroups.size()); + Assert.assertEquals(2, rob.getGroupsStream().count()); }); } @@ -604,7 +554,7 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { // Get user in Keycloak. Ensure that he is member of requested group UserModel carlos = session.users().getUserByUsername("carloskeycloak", appRealm); - Set carlosGroups = carlos.getGroups(); + Set carlosGroups = carlos.getGroupsStream().collect(Collectors.toSet()); GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1"); GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11"); @@ -676,7 +626,7 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { GroupModel group32 = KeycloakModelUtils.findGroupByPath(appRealm, "/group3/group32"); GroupModel group4 = KeycloakModelUtils.findGroupByPath(appRealm, "/group4"); - Set groups = john.getGroups(); + Set groups = john.getGroupsStream().collect(Collectors.toSet()); Assert.assertTrue(groups.contains(group14)); Assert.assertFalse(groups.contains(group3)); Assert.assertTrue(groups.contains(group31)); @@ -686,20 +636,11 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { long groupsCount = john.getGroupsCount(); Assert.assertEquals(4, groupsCount); - Set groupsWith3v1 = john.getGroups("3", 0, 10); - Assert.assertEquals(2, groupsWith3v1.size()); - - Set groupsWith3v2 = john.getGroups("3", 1, 10); - Assert.assertEquals(1, groupsWith3v2.size()); - - Set groupsWith3v3 = john.getGroups("3", 1, 1); - Assert.assertEquals(1, groupsWith3v3.size()); - - Set groupsWith3v4 = john.getGroups("3", 1, 0); - Assert.assertEquals(0, groupsWith3v4.size()); - - Set groupsWithKeycloak = john.getGroups("Keycloak", 0, 10); - Assert.assertEquals(0, groupsWithKeycloak.size()); + Assert.assertEquals(2, john.getGroupsStream("3", 0, 10).count()); + Assert.assertEquals(1, john.getGroupsStream("3", 1, 10).count()); + Assert.assertEquals(1, john.getGroupsStream("3", 1, 1).count()); + Assert.assertEquals(0, john.getGroupsStream("3", 1, 0).count()); + Assert.assertEquals(0, john.getGroupsStream("Keycloak", 0, 10).count()); }); } @@ -731,7 +672,7 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { GroupModel group4 = KeycloakModelUtils.findGroupByPath(appRealm, "/group4"); Assert.assertNotNull(group4); - Set groups = david.getGroups(); + Set groups = david.getGroupsStream().collect(Collectors.toSet()); Assert.assertTrue(groups.contains(defaultGroup11)); Assert.assertTrue(groups.contains(defaultGroup12)); Assert.assertFalse(groups.contains(group31)); @@ -797,7 +738,7 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { // check all the users have the group assigned for (int i = 0; i < membersToTest; i++) { UserModel kcUser = session.users().getUserByUsername(String.format("user%02d", i), appRealm); - Assert.assertTrue("User contains biggroup " + i, kcUser.getGroups().contains(kcBigGroup)); + Assert.assertTrue("User contains biggroup " + i, kcUser.getGroupsStream().collect(Collectors.toSet()).contains(kcBigGroup)); } // check the group contains all the users as member List groupMembers = session.users().getGroupMembers(appRealm, kcBigGroup, 0, membersToTest); @@ -846,7 +787,7 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest { List groupMembers = session.users().getGroupMembers(appRealm, kcDeleteGroup, 0, 5); Assert.assertEquals(1, groupMembers.size()); Assert.assertEquals("marykeycloak", groupMembers.iterator().next().getUsername()); - Set maryGroups = mary.getGroups(); + Set maryGroups = mary.getGroupsStream().collect(Collectors.toSet()); Assert.assertEquals(1, maryGroups.size()); Assert.assertEquals("deletegroup", maryGroups.iterator().next().getName()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSpecialCharsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSpecialCharsTest.java index aade2e7ea4..04c9af6390 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSpecialCharsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPSpecialCharsTest.java @@ -41,6 +41,7 @@ import org.keycloak.testsuite.util.LDAPTestUtils; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import static org.keycloak.testsuite.util.LDAPTestUtils.getGroupDescriptionLDAPAttrName; @@ -162,7 +163,7 @@ public class LDAPSpecialCharsTest extends AbstractLDAPTest { // 2 - Check that group mappings are in LDAP and hence available through federation - Set userGroups = specialUser.getGroups(); + Set userGroups = specialUser.getGroupsStream().collect(Collectors.toSet()); Assert.assertEquals(2, userGroups.size()); Assert.assertTrue(userGroups.contains(specialGroup)); @@ -182,8 +183,7 @@ public class LDAPSpecialCharsTest extends AbstractLDAPTest { specialUser.leaveGroup(specialGroup); specialUser.leaveGroup(groupWithSlashes); - userGroups = specialUser.getGroups(); - Assert.assertEquals(0, userGroups.size()); + Assert.assertEquals(0, specialUser.getGroupsStream().count()); }); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java index e4ef4c3f32..50fb07756f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java @@ -42,6 +42,7 @@ import javax.ws.rs.NotFoundException; import java.io.File; import java.util.LinkedList; import java.util.List; +import java.util.stream.Collectors; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer; @@ -149,7 +150,7 @@ public class FederatedStorageExportImportTest extends AbstractAuthTest { Assert.assertTrue(attributes.getList("list1").contains("2")); Assert.assertTrue(session.userFederatedStorage().getRequiredActions(realm, userId).contains("UPDATE_PASSWORD")); Assert.assertTrue(session.userFederatedStorage().getRoleMappings(realm, userId).contains(role)); - Assert.assertTrue(session.userFederatedStorage().getGroups(realm, userId).contains(group)); + Assert.assertTrue(session.userFederatedStorage().getGroupsStream(realm, userId).collect(Collectors.toSet()).contains(group)); List creds = session.userFederatedStorage().getStoredCredentials(realm, userId); Assert.assertEquals(1, creds.size()); Assert.assertTrue(FederatedStorageExportImportTest.getHashProvider(session, realm.getPasswordPolicy()) @@ -216,7 +217,7 @@ public class FederatedStorageExportImportTest extends AbstractAuthTest { Assert.assertTrue(attributes.getList("list1").contains("2")); Assert.assertTrue(session.userFederatedStorage().getRequiredActions(realm, userId).contains("UPDATE_PASSWORD")); Assert.assertTrue(session.userFederatedStorage().getRoleMappings(realm, userId).contains(role)); - Assert.assertTrue(session.userFederatedStorage().getGroups(realm, userId).contains(group)); + Assert.assertTrue(session.userFederatedStorage().getGroupsStream(realm, userId).collect(Collectors.toSet()).contains(group)); Assert.assertEquals(50, session.userFederatedStorage().getNotBeforeOfUser(realm, userId)); List creds = session.userFederatedStorage().getStoredCredentials(realm, userId); Assert.assertEquals(1, creds.size()); diff --git a/testsuite/utils/src/main/java/org/keycloak/testsuite/util/cli/TestCacheUtils.java b/testsuite/utils/src/main/java/org/keycloak/testsuite/util/cli/TestCacheUtils.java index 5d153fed8d..c2fd00dacc 100644 --- a/testsuite/utils/src/main/java/org/keycloak/testsuite/util/cli/TestCacheUtils.java +++ b/testsuite/utils/src/main/java/org/keycloak/testsuite/util/cli/TestCacheUtils.java @@ -44,9 +44,7 @@ public class TestCacheUtils { cacheRoles(session, realm, realm); - for (GroupModel group : realm.getTopLevelGroups()) { - cacheGroupRecursive(realm, group); - } + realm.getTopLevelGroupsStream().forEach(group -> cacheGroupRecursive(realm, group)); for (ClientScopeModel clientScope : realm.getClientScopes()) { realm.getClientScopeById(clientScope.getId()); @@ -81,8 +79,6 @@ public class TestCacheUtils { private static void cacheGroupRecursive(RealmModel realm, GroupModel group) { realm.getGroupById(group.getId()); - for (GroupModel sub : group.getSubGroups()) { - cacheGroupRecursive(realm, sub); - } + group.getSubGroupsStream().forEach(sub -> cacheGroupRecursive(realm, sub)); } }