diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java index a807d4095..8e06ed79b 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java @@ -1406,9 +1406,9 @@ private void handleAndThrowClientExceptionForActionFailure(UserStoreException e) private int handleLimitEqualsNULL(Integer limit) { // Limit equal to null implies return all users. Return all users scenario handled by the following methods by - // expecting count as zero. + // expecting count as integer max value. if (limit == null) { - limit = 0; + limit = Integer.MAX_VALUE; } return limit; } @@ -3051,8 +3051,6 @@ public GroupsGetResponse listGroupsWithGET(Node rootNode, Integer startIndex, In startIndex = handleStartIndexEqualsNULL(startIndex); if (sortBy != null || sortOrder != null) { throw new NotImplementedException("Sorting is not supported"); - } else if (startIndex != 1 && count != null) { - throw new NotImplementedException("Pagination is not supported"); } else if (rootNode != null) { return filterGroups(rootNode, startIndex, count, sortBy, sortOrder, domainName, requiredAttributes); } else { @@ -3096,6 +3094,7 @@ private GroupsGetResponse listGroups(int startIndex, Integer count, String sortB GroupsGetResponse groupsResponse = new GroupsGetResponse(0, Collections.emptyList()); List groupList = new ArrayList<>(); + int totalGroupCount; try { Set groupNames; if (carbonUM.isRoleAndGroupSeparationEnabled()) { @@ -3104,6 +3103,25 @@ private GroupsGetResponse listGroups(int startIndex, Integer count, String sortB groupNames = getRoleNamesForGroupsEndpoint(domainName); } + totalGroupCount = groupNames.size(); + // Adjust startIndex and endIndex to ensure they are within bounds + if (startIndex > 0) { + if (startIndex > totalGroupCount) { + startIndex = totalGroupCount; + } else { + startIndex -= 1; + } + } else { + startIndex = 0; + } + int endIndex; + if (count == null) { + endIndex = totalGroupCount; + } else { + endIndex = Math.min(startIndex + count, groupNames.size()); + } + groupNames = new HashSet<>(new ArrayList<>(groupNames).subList(startIndex, endIndex)); + for (String groupName : groupNames) { String userStoreDomainName = IdentityUtil.extractDomainFromName(groupName); if (isInternalOrApplicationGroup(userStoreDomainName) || isSCIMEnabled(userStoreDomainName)) { @@ -3154,7 +3172,7 @@ private GroupsGetResponse listGroups(int startIndex, Integer count, String sortB } catch (IdentitySCIMException | BadRequestException e) { throw new CharonException("Error in retrieving SCIM Group information from database.", e); } - groupsResponse.setTotalGroups(groupList.size()); + groupsResponse.setTotalGroups(totalGroupCount); groupsResponse.setGroups(groupList); return groupsResponse; } @@ -3222,19 +3240,15 @@ private Set getRoleNamesForGroupsEndpoint(String domainName) private Set getGroupNamesForGroupsEndpoint(String domainName) throws UserStoreException, IdentitySCIMException { - if (StringUtils.isEmpty(domainName)) { - Set groupsList = new HashSet<>(Arrays.asList(carbonUM.getRoleNames())); - // Remove roles. - groupsList.removeIf(SCIMCommonUtils::isHybridRole); - return groupsList; - } else { + String searchValue = SCIMCommonConstants.ANY; + if (StringUtils.isNotEmpty(domainName)) { // If the domain is specified create a attribute value with the domain name. - String searchValue = domainName + CarbonConstants.DOMAIN_SEPARATOR + SCIMCommonConstants.ANY; - // Retrieve roles using the above attribute value. - List roleList = Arrays - .asList(carbonUM.getRoleNames(searchValue, MAX_ITEM_LIMIT_UNLIMITED, true, true, true)); - return new HashSet<>(roleList); + searchValue = domainName + CarbonConstants.DOMAIN_SEPARATOR + SCIMCommonConstants.ANY; } + // Retrieve roles using the above attribute value. + List roleList = Arrays + .asList(carbonUM.getRoleNames(searchValue, MAX_ITEM_LIMIT_UNLIMITED, true, true, true)); + return new HashSet<>(roleList); } /** @@ -3311,22 +3325,38 @@ private GroupsGetResponse filterGroupsBySingleAttribute(ExpressionNode node, int groupsList.removeIf(SCIMCommonUtils::isHybridRole); } - if (groupsList != null) { - for (String groupName : groupsList) { - if (groupName != null && carbonUM.isExistingRole(groupName, false)) { - // Skip internal roles. - if (CarbonConstants.REGISTRY_ANONNYMOUS_ROLE_NAME.equals(groupName) || UserCoreUtil - .isEveryoneRole(groupName, carbonUM.getRealmConfiguration())) { - continue; - } - Group group = getRoleWithDefaultAttributes(groupName, requiredAttributes); - if (group != null && group.getId() != null) { - filteredGroups.add(group); - } - } else { - // Returning null will send a resource not found error to client by Charon. - return new GroupsGetResponse(0, null); + int totalGroupCount = groupsList.size(); + // Adjust startIndex and endIndex to ensure they are within bounds + if (startIndex > 0) { + if (startIndex > totalGroupCount) { + startIndex = totalGroupCount; + } else { + startIndex -= 1; + } + } else { + startIndex = 0; + } + int endIndex; + if (count < 0) { + count = 0; + } + endIndex = Math.min(startIndex + count, groupsList.size()); + groupsList = groupsList.subList(startIndex, endIndex); + + for (String groupName : groupsList) { + if (groupName != null && carbonUM.isExistingRole(groupName, false)) { + // Skip internal roles. + if (CarbonConstants.REGISTRY_ANONNYMOUS_ROLE_NAME.equals(groupName) || UserCoreUtil + .isEveryoneRole(groupName, carbonUM.getRealmConfiguration())) { + continue; + } + Group group = getRoleWithDefaultAttributes(groupName, requiredAttributes); + if (group != null && group.getId() != null) { + filteredGroups.add(group); } + } else { + // Returning null will send a resource not found error to client by Charon. + return new GroupsGetResponse(0, null); } } } catch (org.wso2.carbon.user.core.UserStoreException e) { diff --git a/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java b/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java index bac59712a..de97cb254 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java @@ -438,6 +438,22 @@ public Object[][] groupNameWithFilters() throws Exception { }; } + @DataProvider(name = "groupPagination") + public Object[][] groupWithPagination() throws Exception { + + return new Object[][]{ + // start Index = 1, count = 2, results = 2, total = 6 + {1, 2, 2, 6}, + // start Index = 2, count = not specified, results = 5, total = 6 + {2, null, 5, 6}, + // start Index = not specified, count = not specified, results = 6, total = 6 + {null, null, 6, 6}, + // start Index = 7, count = 1, results = 0, total = 6 + {7, 1, 0, 6} + + }; + } + @Test(dataProvider = "groupNameWithFilters") public void testListGroupsWithFilter(String filter, String roleName, String userStoreDomain) throws Exception { @@ -500,7 +516,31 @@ public void testListGroupsWithFilter(String filter, String roleName, String user null, requiredAttributes); assertEquals(groupsResponse.getGroups().size(), 1); + } + + @Test(dataProvider = "groupPagination") + public void testListGroups(Integer startIndex, Integer count, Integer results, Integer totalResult) + throws Exception { + + String[] groups = new String[]{"group1", "group2", "group3", "group4", "group5", "group6"}; + mockedUserStoreManager = mock(AbstractUserStoreManager.class); + when(mockedUserStoreManager.isRoleAndGroupSeparationEnabled()).thenReturn(true); + when(mockedUserStoreManager.getRoleNames(anyString(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean())) + .thenReturn(groups); + when(mockedUserStoreManager.getSecondaryUserStoreManager(anyString())).thenReturn(mockedUserStoreManager); + when(mockedUserStoreManager.isSCIMEnabled()).thenReturn(true); + when(mockIdentityUtil.extractDomainFromName(anyString())).thenReturn("PRIMARY"); + for (String group : groups) { + when(mockedUserStoreManager.getGroupByGroupName(group, null)). + thenReturn(buildUserCoreGroupResponse(group, "123456789", null)); + } + SCIMUserManager scimUserManager = new SCIMUserManager(mockedUserStoreManager, mockedClaimManager); + GroupsGetResponse groupsResponse = scimUserManager.listGroupsWithGET(null, startIndex, count, null, null, + null, null); + + assertEquals(groupsResponse.getGroups().size(), results); + assertEquals(groupsResponse.getTotalGroups(), totalResult); } @Test(dataProvider = "listUser") @@ -625,9 +665,44 @@ public void testFilteringUsersWithGET(List givenNameAttributeList = new ArrayList<>(); + givenNameAttributeList.add(givenNameAttributePrimary); + givenNameAttributeList.add(givenNameAttributeSecondary); + + AttributeMapping emailAttributePrimary = new AttributeMapping("PRIMARY", "http://wso2.org/claims/emailaddress"); + AttributeMapping emailAttributeSecondary = new AttributeMapping("SECONDARY", "http://wso2.org/claims/emailaddress"); + List emailAttributeList = new ArrayList<>(); + emailAttributeList.add(emailAttributePrimary); + emailAttributeList.add(emailAttributeSecondary); + + Map supportedByDefaultProperties = new HashMap() {{ + put("SupportedByDefault", "true"); + put("ReadOnly", "true"); + }}; + + List localClaimList = new ArrayList() {{ + add(new LocalClaim(GIVEN_NAME_LOCAL_CLAIM, givenNameAttributeList, supportedByDefaultProperties)); + add(new LocalClaim(EMAIL_ADDRESS_LOCAL_CLAIM, emailAttributeList, supportedByDefaultProperties)); + }}; + + List externalClaimList = new ArrayList() {{ + add(new ExternalClaim(claimDialectUri, claimDialectUri + ":name.givenName", GIVEN_NAME_LOCAL_CLAIM)); + add(new ExternalClaim(claimDialectUri, claimDialectUri + ":emails", EMAIL_ADDRESS_LOCAL_CLAIM)); + }}; + + when(mockClaimMetadataManagementService.getLocalClaims(anyString())).thenReturn(localClaimList); + when(mockClaimMetadataManagementService.getExternalClaims(anyString(), anyString())) + .thenReturn(externalClaimList); + HashMap requiredClaimsMap = new HashMap<>(); requiredClaimsMap.put("urn:ietf:params:scim:schemas:core:2.0:User:userName", false); - SCIMUserManager scimUserManager = new SCIMUserManager(mockedUserStoreManager, mockedClaimManager); + + SCIMUserManager scimUserManager = new SCIMUserManager(mockedUserStoreManager, + mockClaimMetadataManagementService, "carbon.super"); Node node = null; if (StringUtils.isNotBlank(filter)) {