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 68a032baa..52331d4db 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 @@ -1446,7 +1446,14 @@ private UsersGetResponse filterUsersBySingleAttribute(ExpressionNode node, Map<S maxLimit = Math.max(maxLimit, limit); } // Get total users based on the filter query without depending on pagination params. - totalResults += filterUsers(node, 1, maxLimit, sortBy, sortOrder, domainName).size(); + if (SCIMCommonUtils.isGroupBasedUserFilteringImprovementsEnabled() && + (isJDBCUSerStore(domainName) || isAllConfiguredUserStoresJDBC())) { + // Get the total user count by the filter query. + // This is only implemented for JDBC userstores. + totalResults += getUserCountByAttribute(node, 1, maxLimit, sortBy, sortOrder, domainName); + } else { + totalResults += filterUsers(node, 1, maxLimit, sortBy, sortOrder, domainName).size(); + } } else { totalResults += users.size(); } @@ -1457,6 +1464,30 @@ private UsersGetResponse filterUsersBySingleAttribute(ExpressionNode node, Map<S return getDetailedUsers(filteredUsers, totalResults); } + /** + * method to get user count by filtering parameter. + * + * @param node Expression node for single attribute filtering + * @param offset Starting index of the count + * @param limit Counting value + * @param sortBy SortBy + * @param sortOrder Sorting order + * @param domainName Domain to run the filter + * @return User count + * @throws BadRequestException Exception occurred due to a bad request. + * @throws CharonException Error while filtering the users. + */ + private int getUserCountByAttribute(Node node, int offset, int limit, String sortBy, + String sortOrder, String domainName) + throws BadRequestException, CharonException { + + if (SCIMConstants.UserSchemaConstants.GROUP_URI.equals(((ExpressionNode) node).getAttributeValue())) { + return getUserCountByGroup(node, domainName); + } + + return filterUsers(node, 1, limit, sortBy, sortOrder, domainName).size(); + } + /** * Method to check whether the all configured user stores are JDBC. * @@ -1651,7 +1682,12 @@ private Set<org.wso2.carbon.user.core.common.User> filterUsersByGroup(Node node, try { List<String> roleNames = getRoleNames(attributeName, filterOperation, attributeValue); - Set<org.wso2.carbon.user.core.common.User> users = getUserListOfRoles(roleNames); + Set<org.wso2.carbon.user.core.common.User> users; + if (SCIMCommonUtils.isGroupBasedUserFilteringImprovementsEnabled()) { + users = getUserListOfGroups(roleNames); + } else { + users = getUserListOfRoles(roleNames); + } users = paginateUsers(users, limit, offset); return users; } catch (UserStoreException e) { @@ -1694,6 +1730,54 @@ private Set<org.wso2.carbon.user.core.common.User> filterUsers(Node node, int of } } + /** + * Method to get User Count by Group filter + * + * @param node Expression or Operation node. + * @param domainName Domain name. + * @return User count for the filtered group. + * @throws CharonException Error while filtering the users. + * @throws BadRequestException Exception occurred due to a bad request. + */ + private int getUserCountByGroup(Node node, String domainName) + throws CharonException, BadRequestException { + + int count = 0; + // Set filter values. + String attributeName = ((ExpressionNode) node).getAttributeValue(); + String filterOperation = ((ExpressionNode) node).getOperation(); + String attributeValue = ((ExpressionNode) node).getValue(); + + /* + If there is a domain and if the domain separator is not found in the attribute value, append the domain + with the domain separator in front of the new attribute value. + */ + attributeValue = UserCoreUtil.addDomainToName(((ExpressionNode) node).getValue(), domainName); + + try { + List<String> roleNames = getRoleNames(attributeName, filterOperation, attributeValue); + count = getUserCountForGroup(roleNames); + return count; + } catch (UserStoreException e) { + String errorMessage = String.format("Error while filtering the users for filter with attribute name: " + + "%s, filter operation: %s and attribute value: %s.", attributeName, filterOperation, + attributeValue); + throw resolveError(e, errorMessage); + } + } + + private int getUserCountForGroup(List<String> groupNames) throws + org.wso2.carbon.user.core.UserStoreException { + + int count = 0; + if (groupNames != null) { + for (String groupName : groupNames) { + count += carbonUM.getUserCountForGroup(groupName); + } + } + return count; + } + /** * Method to perform a multiple domain search when the domain is not specified in the request. The same function * can be used to listing users by passing a condition for conditionForListingUsers parameter. @@ -1976,7 +2060,11 @@ private Set<org.wso2.carbon.user.core.common.User> filterUsersUsingLegacyAPIs(Ex try { if (SCIMConstants.UserSchemaConstants.GROUP_URI.equals(attributeName)) { List<String> roleNames = getRoleNames(attributeName, filterOperation, attributeValue); - users = getUserListOfRoles(roleNames); + if (SCIMCommonUtils.isGroupBasedUserFilteringImprovementsEnabled()) { + users = getUserListOfGroups(roleNames); + } else { + users = getUserListOfRoles(roleNames); + } } else { // Get the user name of the user with this id. users = getUserNames(attributeName, filterOperation, attributeValue); @@ -4735,6 +4823,18 @@ private Set<org.wso2.carbon.user.core.common.User> getUserListOfRoles(List<Strin return users; } + private Set<org.wso2.carbon.user.core.common.User> getUserListOfGroups(List<String> groupNames) + throws org.wso2.carbon.user.core.UserStoreException { + + Set<org.wso2.carbon.user.core.common.User> users = new HashSet<>(); + if (groupNames != null) { + for (String groupName : groupNames) { + users.addAll(new HashSet<>(carbonUM.getUserListOfGroupWithID(groupName))); + } + } + return users; + } + /** * Get the search value after appending the delimiters according to the attribute name to be filtered. * diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonConstants.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonConstants.java index b372bd78b..08a8147a0 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonConstants.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonConstants.java @@ -102,6 +102,8 @@ public class SCIMCommonConstants { "SCIM2.ConsiderMaxLimitForTotalResult"; public static final String SCIM_ENABLE_CONSIDER_TOTAL_RECORDS_FOR_TOTAL_RESULT_OF_LDAP = "SCIM2.ConsiderTotalRecordsForTotalResultOfLDAP"; + public static final String SCIM_ENABLE_GROUP_BASED_USER_FILTERING_IMPROVEMENTS = + "SCIM2.EnableGroupBasedUserFilteringImprovements"; public static final String SCIM_ENABLE_MANDATE_DOMAIN_FOR_GROUPNAMES_IN_GROUPS_RESPONSE = "SCIM2.MandateDomainForGroupNamesInGroupsResponse"; public static final String SCIM_ENABLE_MANDATE_DOMAIN_FOR_USERNAMES_AND_GROUPNAMES_IN_RESPONSE = diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonUtils.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonUtils.java index 0db6f9028..b57949a90 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonUtils.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonUtils.java @@ -619,6 +619,17 @@ public static boolean isEnterpriseUserExtensionEnabled() { .getProperty(SCIMCommonConstants.ENTERPRISE_USER_EXTENSION_ENABLED)); } + /** + * Checks whether the identity.xml config is available to enable group based user filtering improvements. + * + * @return Whether 'SCIM_ENABLE_GROUP_BASED_USER_FILTERING_IMPROVEMENTS' property is enabled in identity.xml. + */ + public static boolean isGroupBasedUserFilteringImprovementsEnabled() { + + return Boolean.parseBoolean(IdentityUtil.getProperty( + SCIMCommonConstants.SCIM_ENABLE_GROUP_BASED_USER_FILTERING_IMPROVEMENTS)); + } + /** * Checks whether the identity.xml config is available to notify userstore availability. * diff --git a/pom.xml b/pom.xml index af2cabf94..df55808bf 100644 --- a/pom.xml +++ b/pom.xml @@ -284,7 +284,7 @@ <cxf-bundle.version>3.3.7</cxf-bundle.version> <inbound.auth.oauth.version>6.5.3</inbound.auth.oauth.version> <commons-collections.version>3.2.0.wso2v1</commons-collections.version> - <carbon.kernel.version>4.10.2</carbon.kernel.version> + <carbon.kernel.version>4.10.12</carbon.kernel.version> <identity.framework.version>7.0.112</identity.framework.version> <junit.version>4.13.1</junit.version> <commons.lang.version>20030203.000129</commons.lang.version>