diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/UniqueIDUserStoreManager.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/UniqueIDUserStoreManager.java index fc5ad751768..c45bc8cf465 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/UniqueIDUserStoreManager.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/UniqueIDUserStoreManager.java @@ -26,6 +26,7 @@ import org.wso2.carbon.user.core.model.Condition; import org.wso2.carbon.user.core.model.UniqueIDUserClaimSearchEntry; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; @@ -401,6 +402,25 @@ List getUserListWithID(String claim, String claimValue, String profileName List getUserListWithID(Condition condition, String domain, String profileName, int limit, int offset, String sortBy, String sortOrder) throws UserStoreException; + /** + * Retrieves a list of paginated usernames conditionally (using cursor pagination). + * + * @param condition Conditional filter. + * @param domain User Store Domain. + * @param profileName User profile name. + * @param limit No of search results. If the given value is greater than the system configured max limit. + * it will be reset to the system configured max limit. + * @param cursor Starting cursor value of the user search. + * @param direction Pagination direction. + * @return An array of usernames. + * @throws UserStoreException User Store Exception. + */ + default List getUserListWithID(Condition condition, String domain, String profileName, int limit, + String cursor, String direction, String sortBy, String sortOrder) throws UserStoreException { + + return Collections.emptyList(); + } + /** * Get claim values of users. * diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/UserCoreConstants.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/UserCoreConstants.java index f3fe60a75b2..4e9aaf6f766 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/UserCoreConstants.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/UserCoreConstants.java @@ -121,6 +121,11 @@ public class UserCoreConstants { public static final String USER_LOCKED = "true"; public static final String USER_UNLOCKED = "false"; + // Properties used for cursor pagination direction + public enum PaginationDirection { + PREVIOUS, NEXT + } + public static final class RealmConfig { public static final String LOCAL_NAME_USER_MANAGER = "UserManager"; public static final String LOCAL_NAME_REALM = "Realm"; diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserManagementErrorListener.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserManagementErrorListener.java index b6e83cc900d..d33909ac326 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserManagementErrorListener.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserManagementErrorListener.java @@ -20,6 +20,7 @@ package org.wso2.carbon.user.core.common; import org.wso2.carbon.user.api.Permission; +import org.wso2.carbon.user.core.UserCoreConstants; import org.wso2.carbon.user.core.UserStoreException; import org.wso2.carbon.user.core.UserStoreManager; import org.wso2.carbon.user.core.listener.UniqueIDUserManagementErrorEventListener; @@ -298,6 +299,15 @@ public boolean onGetUserListFailureWithID(String errorCode, String errorMassage, return true; } + @Override + public boolean onGetUserListFailureWithID(String errorCode, String errorMassage, Condition condition, String domain, + String profileName, int limit, String cursor, UserCoreConstants.PaginationDirection direction, + String sortBy, String sortOrder, UserStoreManager userStoreManager) + throws UserStoreException { + + return true; + } + @Override public boolean onGetUserFailureWithID(String errorCode, String errorMessage, String userID, String[] requestedClaims, String profileName, UserStoreManager userStoreManager) throws UserStoreException { diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserOperationEventListener.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserOperationEventListener.java index 71825da23ff..2ae186014f2 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserOperationEventListener.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserOperationEventListener.java @@ -20,6 +20,7 @@ package org.wso2.carbon.user.core.common; import org.wso2.carbon.user.api.Permission; +import org.wso2.carbon.user.core.UserCoreConstants; import org.wso2.carbon.user.core.UserStoreException; import org.wso2.carbon.user.core.UserStoreManager; import org.wso2.carbon.user.core.listener.UniqueIDUserOperationEventListener; @@ -400,6 +401,15 @@ public boolean doPreGetUserListWithID(Condition condition, String domain, String return true; } + @Override + public boolean doPreGetUserListWithID(Condition condition, String domain, String profileName, int limit, + String cursor, UserCoreConstants.PaginationDirection direction, String sortBy, + String sortOrder, UserStoreManager userStoreManager) + throws UserStoreException { + + return true; + } + @Override public boolean doPreGetUserListWithID(String claimUri, String claimValue, int limit, int offset, final List returnUsersList, UserStoreManager userStoreManager) throws UserStoreException { @@ -444,6 +454,15 @@ public boolean doPostGetUserListWithID(Condition condition, String domain, Strin return true; } + @Override + public boolean doPostGetUserListWithID(Condition condition, String domain, String profileName, int limit, + String cursor, UserCoreConstants.PaginationDirection direction, String sortBy, String sortOrder, + List users, UserStoreManager userStoreManager) + throws UserStoreException { + + return true; + } + @Override public boolean doPreGetUserWithID(String userID, String[] requestedClaims, String profileName, UserStoreManager userStoreManager) throws UserStoreException { diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserStoreManager.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserStoreManager.java index 25b2aa2b5e4..fe859211526 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserStoreManager.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserStoreManager.java @@ -2318,30 +2318,49 @@ private void handleGetUserListFailureWithID(String errorCode, String errorMessag } private void handleGetUserListFailure(String errorCode, String errorMassage, Condition condition, String domain, - String profileName, int limit, int offset, String sortBy, String sortOrder) throws UserStoreException { + String profileName, int limit, Integer offset, String cursor, UserCoreConstants.PaginationDirection direction, + String sortBy, String sortOrder) throws UserStoreException { for (UserManagementErrorEventListener listener : UMListenerServiceComponent .getUserManagementErrorEventListeners()) { - if (listener.isEnable() && listener instanceof AbstractUserManagementErrorListener - && !listener - .onGetUserListFailure(errorCode, errorMassage, condition, domain, profileName, limit, offset, - sortBy, sortOrder, this)) { - return; + if (cursor == null) { + if (listener.isEnable() && listener instanceof AbstractUserManagementErrorListener + && !listener + .onGetUserListFailure(errorCode, errorMassage, condition, domain, profileName, limit, offset, + sortBy, sortOrder, this)) { + return; + } + } else { + if (listener.isEnable() && listener instanceof AbstractUserManagementErrorListener && !listener + .onGetUserListFailure(errorCode, errorMassage, condition, domain, profileName, limit, cursor, + direction, sortBy, sortOrder, this)) { + return; + } } } } private void handleGetUserListFailureWithID(String errorCode, String errorMassage, Condition condition, - String domain, String profileName, int limit, int offset, String sortBy, String sortOrder) + String domain, String profileName, int limit, Integer offset, String cursor, + UserCoreConstants.PaginationDirection direction, String sortBy, String sortOrder) throws UserStoreException { for (UserManagementErrorEventListener listener : UMListenerServiceComponent .getUserManagementErrorEventListeners()) { - if (listener.isEnable() && listener instanceof AbstractUserManagementErrorListener - && !((AbstractUserManagementErrorListener) listener) - .onGetUserListFailureWithID(errorCode, errorMassage, condition, domain, profileName, limit, offset, - sortBy, sortOrder, this)) { - return; + if (cursor == null) { + if (listener.isEnable() && listener instanceof AbstractUserManagementErrorListener + && !((AbstractUserManagementErrorListener) listener) + .onGetUserListFailureWithID(errorCode, errorMassage, condition, domain, profileName, limit, offset, + sortBy, sortOrder, this)) { + return; + } + } else { + if (listener.isEnable() && listener instanceof AbstractUserManagementErrorListener + && !((AbstractUserManagementErrorListener) listener) + .onGetUserListFailureWithID(errorCode, errorMassage, condition, domain, profileName, limit, + cursor, direction, sortBy, sortOrder, this)) { + return; + } } } } @@ -2600,13 +2619,15 @@ private void handlePostGetUserList(Condition condition, String domain, String pr } catch (UserStoreException ex) { handleGetUserListFailure(ErrorMessages.ERROR_CODE_ERROR_DURING_POST_GET_CONDITIONAL_USER_LIST.getCode(), String.format(ErrorMessages.ERROR_CODE_ERROR_DURING_POST_GET_CONDITIONAL_USER_LIST.getMessage(), - ex.getMessage()), condition, domain, profileName, limit, offset, sortBy, sortOrder); + ex.getMessage()), condition, domain, profileName, limit, offset, null, null, sortBy, + sortOrder); throw ex; } } private void handlePostGetUserListWithID(Condition condition, String domain, String profileName, int limit, - int offset, String sortBy, String sortOrder, List users, boolean isAuditLogOnly) + Integer offset, String cursor, UserCoreConstants.PaginationDirection direction, String sortBy, + String sortOrder, List users, boolean isAuditLogOnly) throws UserStoreException { try { @@ -2617,9 +2638,16 @@ private void handlePostGetUserListWithID(Condition condition, String domain, Str continue; } AbstractUserOperationEventListener newListener = (AbstractUserOperationEventListener) listener; - if (!newListener.doPostGetUserListWithID(condition, domain, profileName, limit, offset, sortBy, - sortOrder, users, this)) { - break; + if (cursor == null) { + if (!newListener.doPostGetUserListWithID(condition, domain, profileName, limit, offset, sortBy, + sortOrder, users, this)) { + break; + } + } else { + if (!newListener.doPostGetUserListWithID(condition, domain, profileName, limit, cursor, + direction, sortBy, sortOrder, users, this)) { + break; + } } } } @@ -2627,7 +2655,8 @@ private void handlePostGetUserListWithID(Condition condition, String domain, Str handleGetUserListFailureWithID( ErrorMessages.ERROR_CODE_ERROR_DURING_POST_GET_CONDITIONAL_USER_LIST.getCode(), String.format(ErrorMessages.ERROR_CODE_ERROR_DURING_POST_GET_CONDITIONAL_USER_LIST.getMessage(), - ex.getMessage()), condition, domain, profileName, limit, offset, sortBy, sortOrder); + ex.getMessage()), condition, domain, profileName, limit, offset, cursor, direction, sortBy, + sortOrder); throw ex; } } @@ -2648,7 +2677,7 @@ private void handlePreGetUserList(Condition condition, String domain, String pro String.format(ErrorMessages.ERROR_CODE_ERROR_DURING_PRE_GET__CONDITIONAL_USER_LIST .getMessage(), UserCoreErrorConstants.PRE_LISTENER_TASKS_FAILED_MESSAGE), condition, domain, - profileName, limit, offset, sortBy, sortOrder); + profileName, limit, offset, null, null, sortBy, sortOrder); break; } } @@ -2657,36 +2686,54 @@ private void handlePreGetUserList(Condition condition, String domain, String pro handleGetUserListFailure(ErrorMessages.ERROR_CODE_ERROR_DURING_PRE_GET__CONDITIONAL_USER_LIST.getCode(), String.format(ErrorMessages.ERROR_CODE_ERROR_DURING_PRE_GET__CONDITIONAL_USER_LIST.getMessage(), ex.getMessage()), condition, domain, - profileName, limit, offset, sortBy, sortOrder); + profileName, limit, offset, null, null, sortBy, sortOrder); throw ex; } } private void handlePreGetUserListWithID(Condition condition, String domain, String profileName, int limit, - int offset, - String sortBy, String sortOrder) throws UserStoreException { + Integer offset, String cursor, UserCoreConstants.PaginationDirection direction, String sortBy, + String sortOrder) + throws UserStoreException { try { for (UserOperationEventListener listener : UMListenerServiceComponent .getUserOperationEventListeners()) { if (listener instanceof AbstractUserOperationEventListener) { AbstractUserOperationEventListener newListener = (AbstractUserOperationEventListener) listener; - if (!newListener.doPreGetUserListWithID(condition, domain, profileName, limit, offset, sortBy, - sortOrder, this)) { - - handleGetUserListFailure(ErrorMessages.ERROR_CODE_ERROR_DURING_PRE_GET__CONDITIONAL_USER_LIST.getCode(), - String.format(ErrorMessages.ERROR_CODE_ERROR_DURING_PRE_GET__CONDITIONAL_USER_LIST.getMessage(), - UserCoreErrorConstants.PRE_LISTENER_TASKS_FAILED_MESSAGE), condition, domain, - profileName, limit, offset, sortBy, sortOrder); - break; + if (cursor == null) { + if (!newListener.doPreGetUserListWithID(condition, domain, profileName, limit, offset, sortBy, + sortOrder, this)) { + + handleGetUserListFailure(ErrorMessages. + ERROR_CODE_ERROR_DURING_PRE_GET__CONDITIONAL_USER_LIST.getCode(), + String.format(ErrorMessages. + ERROR_CODE_ERROR_DURING_PRE_GET__CONDITIONAL_USER_LIST.getMessage(), + UserCoreErrorConstants.PRE_LISTENER_TASKS_FAILED_MESSAGE), condition, + domain, profileName, limit, offset, null, null, sortBy, sortOrder); + break; + } + } else { + if (!newListener.doPreGetUserListWithID(condition, domain, profileName, limit, cursor, + direction, sortBy, sortOrder, this)) { + + handleGetUserListFailure(ErrorMessages. + ERROR_CODE_ERROR_DURING_PRE_GET__CONDITIONAL_USER_LIST.getCode(), + String.format(ErrorMessages. + ERROR_CODE_ERROR_DURING_PRE_GET__CONDITIONAL_USER_LIST.getMessage(), + UserCoreErrorConstants.PRE_LISTENER_TASKS_FAILED_MESSAGE), condition, + domain, profileName, limit, null, cursor, direction, sortBy, sortOrder); + break; + } } + } } } catch (UserStoreException ex) { handleGetUserListFailure(ErrorMessages.ERROR_CODE_ERROR_DURING_PRE_GET__CONDITIONAL_USER_LIST.getCode(), String.format(ErrorMessages.ERROR_CODE_ERROR_DURING_PRE_GET__CONDITIONAL_USER_LIST.getMessage(), ex.getMessage()), condition, domain, - profileName, limit, offset, sortBy, sortOrder); + profileName, limit, offset, null, null, sortBy, sortOrder); throw ex; } } @@ -10233,7 +10280,7 @@ public String[] getUserList(Condition condition, String domain, String profileNa if (secManager instanceof AbstractUserStoreManager) { if (((AbstractUserStoreManager) secManager).isUniqueUserIdEnabled()) { UniqueIDPaginatedSearchResult users = ((AbstractUserStoreManager) secManager).doGetUserListWithID(condition, - profileName, limit, offset, sortBy, sortOrder); + profileName, limit, offset, null, null, sortBy, sortOrder); addUsersToUserIdCache(users.getUsers()); addUsersToUserNameCache(users.getUsers()); filteredUsers = users.getUsers().stream().map(User::getUsername).toArray(String[]::new); @@ -10260,7 +10307,9 @@ protected PaginatedSearchResult doGetUserList(Condition condition, String profil } protected UniqueIDPaginatedSearchResult doGetUserListWithID(Condition condition, String profileName, int limit, - int offset, String sortBy, String sortOrder) throws UserStoreException { + Integer offset, String cursor, UserCoreConstants.PaginationDirection direction, String sortBy, + String sortOrder) + throws UserStoreException { if (log.isDebugEnabled()) { log.debug("doGetUserListWithID operation is not implemented in: " + this.getClass()); @@ -10268,7 +10317,6 @@ protected UniqueIDPaginatedSearchResult doGetUserListWithID(Condition condition, throw new NotImplementedException("doGetUserListWithID operation is not implemented in: " + this.getClass()); } - @Override public String[] listUsers(String filter, int limit, int offset) throws UserStoreException { @@ -10448,7 +10496,10 @@ private boolean isNotSupportedExpressionOperation(Condition condition) { ExpressionOperation.SW.toString().equals(condition.getOperation()) || ExpressionOperation.EW.toString().equals(condition.getOperation()) || ExpressionOperation.GE.toString().equals(condition.getOperation()) || - ExpressionOperation.LE.toString().equals(condition.getOperation())); + ExpressionOperation.LE.toString().equals(condition.getOperation()) || + ExpressionOperation.GT.toString().equals(condition.getOperation()) || + ExpressionOperation.LT.toString().equals(condition.getOperation()) || + ExpressionOperation.NE.toString().equals(condition.getOperation())); } private boolean isAnInternalRole(String roleName) { @@ -15508,6 +15559,10 @@ public List getUserListWithID(String claim, String claimValue, String prof public List getUserListWithID(Condition condition, String domain, String profileName, int limit, int offset, String sortBy, String sortOrder) throws UserStoreException { + // This method will flow down to use offset pagination, so the cursor and direction will be null. + String cursor = null; + UserCoreConstants.PaginationDirection direction = null; + validateCondition(condition); if (StringUtils.isNotEmpty(sortBy) && StringUtils.isNotEmpty(sortOrder)) { throw new UserStoreException("Sorting is not supported."); @@ -15521,7 +15576,7 @@ public List getUserListWithID(Condition condition, String domain, String p profileName = UserCoreConstants.DEFAULT_PROFILE; } - handlePreGetUserListWithID(condition, domain, profileName, limit, offset, sortBy, sortOrder); + handlePreGetUserListWithID(condition, domain, profileName, limit, offset, cursor, direction, sortBy, sortOrder); if (log.isDebugEnabled()) { log.debug("Pre listener get conditional user list for domain: " + domain); } @@ -15550,8 +15605,8 @@ public List getUserListWithID(Condition condition, String domain, String p // Call the listeners to get the filtered users from relevant identity store. if (secondaryUserStoreManager != null) { - handlePreGetUserListWithIdentityClaims(duplicateCondition, domain, profileName, limit, offset, sortBy, - sortOrder, secondaryUserStoreManager, identityClaimFilteredUserNames, + handlePreGetUserListWithIdentityClaims(duplicateCondition, domain, profileName, limit, offset, cursor, + direction, sortBy, sortOrder, secondaryUserStoreManager, identityClaimFilteredUserNames, hasNonIdentityClaimFilterConditions); } @@ -15600,8 +15655,8 @@ public List getUserListWithID(Condition condition, String domain, String p Set prevIterationFilteredUsers = new HashSet<>(); while (aggregateUserList.size() < paginationLimit) { - tempFilteredUsers = getFilteredUsers(duplicateCondition, profileName, limit, offsetCounter, sortBy, - sortOrder, secondaryUserStoreManager); + tempFilteredUsers = getFilteredUsers(duplicateCondition, profileName, limit, offsetCounter, cursor, + direction, sortBy, sortOrder, secondaryUserStoreManager); if (tempFilteredUsers.isEmpty()) { // Means no users has been filtered in this particular iteration and hence can exit the flow. @@ -15646,12 +15701,176 @@ public List getUserListWithID(Condition condition, String domain, String p } else { /* When the filters has only the non-identity claims or if Identity claims are persisted in User store based Identity Data store.*/ - filteredUsers = getFilteredUsers(duplicateCondition, profileName, limit, offset, sortBy, sortOrder, - secondaryUserStoreManager); + filteredUsers = getFilteredUsers(duplicateCondition, profileName, limit, offset, cursor, direction, sortBy, + sortOrder, secondaryUserStoreManager); + } + + handlePostGetUserListWithID(condition, domain, profileName, limit, offset, cursor, direction, sortBy, sortOrder, + filteredUsers, false); + + if (log.isDebugEnabled()) { + log.debug("post listener get conditional user list for domain: " + domain); + } + return filteredUsers; + } + + + @Override + public List getUserListWithID(Condition condition, String domain, String profileName, int limit, + String cursor, String directionStr, String sortBy, String sortOrder) + throws UserStoreException, NullPointerException { + + // This method will flow down to use cursor pagination, so the offset will be null. + Integer offset = null; + + UserCoreConstants.PaginationDirection direction = UserCoreConstants.PaginationDirection.valueOf(directionStr); + + validateCondition(condition); + if (StringUtils.isNotEmpty(sortBy) && StringUtils.isNotEmpty(sortOrder)) { + throw new UserStoreException("Sorting is not supported."); + } + + if (StringUtils.isEmpty(domain)) { + domain = UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME; + } + + if (StringUtils.isEmpty(profileName)) { + profileName = UserCoreConstants.DEFAULT_PROFILE; + } + + handlePreGetUserListWithID(condition, domain, profileName, limit, offset, cursor, direction, sortBy, + sortOrder); + if (log.isDebugEnabled()) { + log.debug("Pre listener get conditional user list for domain: " + domain); + } + + UserStoreManager secondaryUserStoreManager = getSecondaryUserStoreManager(domain); + List identityClaimFilteredUsers = new ArrayList<>(); + List identityClaimFilteredUserNames = new ArrayList<>(); + List filteredUsers = new ArrayList<>(); + boolean hasNonIdentityClaimFilterConditions = false; + boolean isIdentityClaimsInIdentityStore = false; + boolean isIdentityClaimFilterExistInPostCondition; + List expressionConditions = new ArrayList<>(); + + /* Duplicating condition object to ensure that nullifying the conditions in the flow does not affect the + validation in the next iteration of flow when the domain name is not in query params.*/ + Condition duplicateCondition = getDuplicateCondition(condition); + getExpressionConditions(duplicateCondition, expressionConditions); + + // Check whether the request has IdentityClaims in filters. + boolean identityClaimsExistsInInitialCondition = hasIdentityClaimInitially(expressionConditions); + + if (identityClaimsExistsInInitialCondition) { + if (expressionConditions.size() != countIdentityClaims(expressionConditions)) { + hasNonIdentityClaimFilterConditions = true; + } + + // Call the listeners to get the filtered users from relevant identity store. + if (secondaryUserStoreManager != null) { + handlePreGetUserListWithIdentityClaims(duplicateCondition, domain, profileName, limit, offset, cursor, + direction, sortBy, sortOrder, secondaryUserStoreManager, identityClaimFilteredUserNames, + hasNonIdentityClaimFilterConditions); + } + + // Check whether the request has IdentityClaims in filters after trying to filter at Identity Data Store. + expressionConditions = new ArrayList<>(); + getExpressionConditions(duplicateCondition, expressionConditions); + isIdentityClaimFilterExistInPostCondition = containsIdentityClaims(expressionConditions); + + // Means the identity claims are identity store based. (Else it is user store based) + if (!isIdentityClaimFilterExistInPostCondition) { + isIdentityClaimsInIdentityStore = true; + } else { + updateCondition(duplicateCondition, domain); + } + + /* If identity claims are in JDBCIdentityDataStore, and filtering in JDBCIdentityDataStore returned an empty + list, we can skip filtering in user store.*/ + if (isIdentityClaimsInIdentityStore && identityClaimFilteredUserNames.isEmpty()) { + return filteredUsers; + } + + for (String username : identityClaimFilteredUserNames) { + User user = getUser(getUserIDFromUserName(username), username); + user.setUserStoreDomain(UserCoreUtil.extractDomainFromName(username)); + identityClaimFilteredUsers.add(user); + } + + // After filtering based on identity claims, if there are no other filters can return the list. + if (expressionConditions.isEmpty()) { + return identityClaimFilteredUsers; + } + } + + if (identityClaimsExistsInInitialCondition && isIdentityClaimsInIdentityStore) { + // The identity claims are not user store based. + List tempFilteredUsers; + List aggregateUserList = new ArrayList<>(); + String cursorCounter = cursor; + + Set prevIterationFilteredUsers = new HashSet<>(); + while (aggregateUserList.size() < limit) { + tempFilteredUsers = getFilteredUsers(duplicateCondition, profileName, limit, offset, cursorCounter, + direction, sortBy, sortOrder, secondaryUserStoreManager); + + if (tempFilteredUsers.isEmpty()) { + // Means no users has been filtered in this particular iteration and hence can exit the flow. + break; + } + + // Prevent same set of users being returned and break the loop if so. + if (isExactSameFilteredUsers(tempFilteredUsers, prevIterationFilteredUsers)) { + break; + } + + prevIterationFilteredUsers.clear(); + prevIterationFilteredUsers.addAll(tempFilteredUsers); + + // For next iteration consider the offset from last fetched size of users. + if (!(tempFilteredUsers.size() < limit)) { + if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + cursorCounter = tempFilteredUsers.get(0).getPreferredUsername(); + } else { + cursorCounter = tempFilteredUsers.get(tempFilteredUsers.size() - 1).getPreferredUsername(); + } + // Taking the interception of the user list. + tempFilteredUsers.retainAll(identityClaimFilteredUsers); + aggregateUserList.addAll(tempFilteredUsers); + } else { + // Taking the interception of the user list. + tempFilteredUsers.retainAll(identityClaimFilteredUsers); + aggregateUserList.addAll(tempFilteredUsers); + break; + } + } + + // Removing duplicates. + aggregateUserList = aggregateUserList.stream().distinct().collect(Collectors.toList()); + + // Intersecting data maybe added in no particular order. Needs to be organized in ASC order of userName. + if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + Collections.sort(aggregateUserList, (user1, user2) -> + user1.getUsername().compareTo(user2.getUsername())); + } + + if (aggregateUserList.isEmpty()) { + filteredUsers = aggregateUserList; + } else if (aggregateUserList.size() < limit) { + filteredUsers = aggregateUserList.subList(0, aggregateUserList.size()); + } else { + filteredUsers = aggregateUserList.subList(0, limit); + } + } else { + /* When the filters has only the non-identity claims or if Identity claims are persisted in User store + based Identity Data store.*/ + filteredUsers = getFilteredUsers(duplicateCondition, profileName, limit, offset, cursor, direction, sortBy, + sortOrder, secondaryUserStoreManager); } - handlePostGetUserListWithID(condition, domain, profileName, limit, offset, sortBy, sortOrder, filteredUsers, - false); + + handlePostGetUserListWithID(condition, domain, profileName, limit, offset, cursor, direction, sortBy, + sortOrder, filteredUsers, false); if (log.isDebugEnabled()) { log.debug("post listener get conditional user list for domain: " + domain); @@ -15673,15 +15892,18 @@ private boolean isExactSameFilteredUsers(List tempFilteredUsers, Set } } - private List getFilteredUsers(Condition condition, String profileName, int limit, int offset, String sortBy, - String sortOrder, UserStoreManager secManager) throws UserStoreException { + private List getFilteredUsers(Condition condition, String profileName, int limit, Integer offset, + String cursor, UserCoreConstants.PaginationDirection direction, String sortBy, String sortOrder, + UserStoreManager secManager) + throws UserStoreException { List filteredUsers = new ArrayList<>(); if (secManager != null) { if (secManager instanceof AbstractUserStoreManager) { if (isUniqueUserIdEnabled(secManager)) { UniqueIDPaginatedSearchResult users = ((AbstractUserStoreManager) secManager) - .doGetUserListWithID(condition, profileName, limit, offset, sortBy, sortOrder); + .doGetUserListWithID(condition, profileName, limit, offset, cursor, direction, sortBy, + sortOrder); addUsersToUserIdCache(users.getUsers()); addUsersToUserNameCache(users.getUsers()); filteredUsers = users.getUsers(); @@ -15736,8 +15958,8 @@ private Condition getDuplicateCondition(Condition condition) { * @throws UserStoreException User store exception. */ private void handlePreGetUserListWithIdentityClaims(Condition condition, String domain, String profileName, - int limit, int offset, String sortBy, String sortOrder, - UserStoreManager secManager, + int limit, Integer offset, String cursor, UserCoreConstants.PaginationDirection direction, + String sortBy, String sortOrder, UserStoreManager secManager, List identityClaimFilteredUserNames, boolean hasNonIdentityClaimFilters) throws UserStoreException { @@ -15749,16 +15971,31 @@ private void handlePreGetUserListWithIdentityClaims(Condition condition, String (AbstractUserOperationEventListener) listener; if (!hasNonIdentityClaimFilters) { - // In this case, can filter with Identity claim filters and paginate at DB level. - if (!newListener.doPreGetPaginatedUserList(condition, identityClaimFilteredUserNames, - domain, secManager, limit, offset)) { - handleGetUserListFailure( - ErrorMessages.ERROR_CODE_ERROR_WHILE_GETTING_PAGINATED_USER_LIST.getCode(), - String.format(ErrorMessages.ERROR_CODE_ERROR_WHILE_GETTING_PAGINATED_USER_LIST - .getMessage(), - UserCoreErrorConstants.PRE_LISTENER_TASKS_FAILED_MESSAGE), - condition, domain, profileName, limit, offset, sortBy, sortOrder); - break; + if (cursor == null) { + // In this case, can filter with Identity claim filters and paginate at DB level. + if (!newListener.doPreGetPaginatedUserList(condition, identityClaimFilteredUserNames, + domain, secManager, limit, offset)) { + handleGetUserListFailure( + ErrorMessages.ERROR_CODE_ERROR_WHILE_GETTING_PAGINATED_USER_LIST.getCode(), + String.format(ErrorMessages.ERROR_CODE_ERROR_WHILE_GETTING_PAGINATED_USER_LIST + .getMessage(), UserCoreErrorConstants.PRE_LISTENER_TASKS_FAILED_MESSAGE), + condition, domain, profileName, limit, offset, null, null, sortBy, sortOrder); + break; + } + } else { + // In this case, can filter with Identity claim filters and paginate at DB level. + if (!newListener.doPreGetPaginatedUserList(condition, identityClaimFilteredUserNames, + domain, secManager, limit, cursor, direction)) { + //Introduced a new method because there may be more instances where this + // method is called in other repos. + handleGetUserListFailure( + ErrorMessages.ERROR_CODE_ERROR_WHILE_GETTING_PAGINATED_USER_LIST.getCode(), + String.format(ErrorMessages.ERROR_CODE_ERROR_WHILE_GETTING_PAGINATED_USER_LIST + .getMessage(), UserCoreErrorConstants.PRE_LISTENER_TASKS_FAILED_MESSAGE), + condition, domain, profileName, limit, null, cursor, direction, sortBy, + sortOrder); + break; + } } } else { // In this case, can filter with Identity claim filters and fetch whole filtered list. @@ -15769,7 +16006,7 @@ private void handlePreGetUserListWithIdentityClaims(Condition condition, String String.format(ErrorMessages.ERROR_CODE_ERROR_DURING_PRE_GET__CONDITIONAL_USER_LIST .getMessage(), UserCoreErrorConstants.PRE_LISTENER_TASKS_FAILED_MESSAGE), - condition, domain, profileName, limit, offset, sortBy, sortOrder); + condition, domain, profileName, limit, offset, null, null, sortBy, sortOrder); break; } } @@ -15858,8 +16095,8 @@ private boolean hasIdentityClaimInitially(List expressionCo for (ExpressionCondition expressionCondition : expressionConditions) { List mappedClaim = - Arrays.stream(claimMapping).filter(mapping -> mapping.getMappedAttribute() == - expressionCondition.getAttributeName()).collect(Collectors.toList()); + Arrays.stream(claimMapping).filter(mapping -> mapping.getMappedAttribute() + .equals(expressionCondition.getAttributeName())).collect(Collectors.toList()); //Obtaining relevant URI for the mapped attribute. if (mappedClaim.size() == 1) { @@ -16611,7 +16848,7 @@ public List getUserListOfGroup(String groupID, int limit, int offset, Stri if (isUniqueUserIdEnabled()) { UniqueIDPaginatedSearchResult users = this.doGetUserListWithID(condition, UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME, resolveUserListLimit(limit), offset, - sortBy, sortOrder); + null, null, sortBy, sortOrder); addUsersToUserIdCache(users.getUsers()); addUsersToUserNameCache(users.getUsers()); filteredUsers = users.getUsers(); diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCUserStoreManager.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCUserStoreManager.java index 80654526fef..f60205f3fb0 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCUserStoreManager.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCUserStoreManager.java @@ -75,6 +75,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import static org.wso2.carbon.user.core.constants.UserCoreErrorConstants.ErrorMessages.ERROR_CODE_DUPLICATE_WHILE_ADDING_A_USER; @@ -82,6 +83,8 @@ import static org.wso2.carbon.user.core.constants.UserCoreErrorConstants.ErrorMessages.ERROR_CODE_DUPLICATE_WHILE_WRITING_TO_DATABASE; import static org.wso2.carbon.user.core.util.DatabaseUtil.getLoggableSqlString; +import static java.lang.Math.max; + public class UniqueIDJDBCUserStoreManager extends JDBCUserStoreManager { private static Log log = LogFactory.getLog(UniqueIDJDBCUserStoreManager.class); @@ -3260,7 +3263,9 @@ protected PaginatedSearchResult doGetUserList(Condition condition, String profil @Override protected UniqueIDPaginatedSearchResult doGetUserListWithID(Condition condition, String profileName, int limit, - int offset, String sortBy, String sortOrder) throws UserStoreException { + Integer offset, String cursor, UserCoreConstants.PaginationDirection direction, String sortBy, + String sortOrder) + throws UserStoreException { boolean isGroupFiltering = false; boolean isUsernameFiltering = false; @@ -3302,33 +3307,41 @@ protected UniqueIDPaginatedSearchResult doGetUserListWithID(Condition condition, ResultSet rs = null; List list = new ArrayList<>(); + SqlBuilder sqlBuilder = null; try { dbConnection = getDBConnection(); String type = DatabaseCreator.getDatabaseType(dbConnection); - if (offset <= 0) { - offset = 0; - } else { - offset = offset - 1; - } + if (offset != null) { + if (offset <= 0) { + offset = 0; + } else { + offset = offset - 1; + } - if (DB2.equalsIgnoreCase(type)) { - int initialOffset = offset; - offset = offset + limit; - limit = initialOffset + 1; - } else if (ORACLE.equalsIgnoreCase(type)) { - limit = offset + limit; - } else if (MSSQL.equalsIgnoreCase(type)) { - int initialOffset = offset; - offset = limit + offset; - limit = initialOffset + 1; - } + if (DB2.equalsIgnoreCase(type)) { + int initialOffset = offset; + offset = offset + limit; + limit = initialOffset + 1; + } else if (ORACLE.equalsIgnoreCase(type)) { + limit = offset + limit; + } else if (MSSQL.equalsIgnoreCase(type)) { + int initialOffset = offset; + offset = limit + offset; + limit = initialOffset + 1; + } - SqlBuilder sqlBuilder = getQueryString(isGroupFiltering, isUsernameFiltering, isClaimFiltering, - expressionConditions, limit, offset, sortBy, sortOrder, profileName, type, totalMultiGroupFilters, - totalMultiClaimFilters); + sqlBuilder = getQueryString(isGroupFiltering, isUsernameFiltering, isClaimFiltering, + expressionConditions, limit, offset, null, null, sortBy, sortOrder, profileName, + type, totalMultiGroupFilters, totalMultiClaimFilters); + } else { + sqlBuilder = getQueryString(isGroupFiltering, isUsernameFiltering, isClaimFiltering, + expressionConditions, limit, null, cursor, direction, sortBy, sortOrder, profileName, + type, totalMultiGroupFilters, totalMultiClaimFilters); + } - if ((MYSQL.equals(type) || MARIADB.equals(type)) && totalMultiGroupFilters > 1 && totalMultiClaimFilters > 1) { + if ((MYSQL.equals(type) || MARIADB.equals(type)) && totalMultiGroupFilters > 1 + && totalMultiClaimFilters > 1) { String fullQuery = sqlBuilder.getQuery(); String[] splits = fullQuery.split("INTERSECT "); int startIndex = 0; @@ -3413,9 +3426,11 @@ private void populatePrepareStatement(SqlBuilder sqlBuilder, PreparedStatement p } } - protected SqlBuilder getQueryString(boolean isGroupFiltering, boolean isUsernameFiltering, boolean isClaimFiltering, - List expressionConditions, int limit, int offset, String sortBy, String sortOrder, - String profileName, String dbType, int totalMultiGroupFilters, int totalMultiClaimFilters) + protected SqlBuilder getQueryString(boolean isGroupFiltering, boolean isUsernameFiltering, + boolean isClaimFiltering, List expressionConditions, int limit, + Integer offset, String cursor, UserCoreConstants.PaginationDirection direction, String sortBy, + String sortOrder, String profileName, String dbType, int totalMultiGroupFilters, + int totalMultiClaimFilters) throws UserStoreException { StringBuilder sqlStatement; @@ -3428,10 +3443,19 @@ protected SqlBuilder getQueryString(boolean isGroupFiltering, boolean isUsername if (isGroupFiltering && isUsernameFiltering && isClaimFiltering || isGroupFiltering && isClaimFiltering) { if (DB2.equals(dbType)) { - sqlStatement = new StringBuilder("SELECT U.UM_USER_ID, U.UM_USER_NAME FROM (SELECT " - + "ROW_NUMBER() OVER (ORDER BY UM_USER_NAME) AS rn, p.* FROM (SELECT DISTINCT UM_USER_NAME " - + "FROM UM_ROLE R INNER JOIN UM_USER_ROLE UR ON R.UM_ID = UR.UM_ROLE_ID INNER JOIN UM_USER U " - + "ON UR.UM_USER_ID =U.UM_ID INNER JOIN UM_USER_ATTRIBUTE UA ON U.UM_ID = UA.UM_USER_ID"); + if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + sqlStatement = new StringBuilder("SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT " + + "ROW_NUMBER() OVER (ORDER BY UM_USER_NAME DESC) AS rn, UM_USER_ID, UM_USER_NAME FROM " + + "(SELECT DISTINCT U.UM_USER_ID, UM_USER_NAME FROM UM_ROLE R INNER JOIN UM_USER_ROLE UR " + + "ON R.UM_ID = UR.UM_ROLE_ID INNER JOIN UM_USER U ON UR.UM_USER_ID = U.UM_ID " + + "INNER JOIN UM_USER_ATTRIBUTE UA ON U.UM_ID = UA.UM_USER_ID"); + } else { + sqlStatement = new StringBuilder("SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT " + + "ROW_NUMBER() OVER (ORDER BY UM_USER_NAME) AS rn, UM_USER_ID, UM_USER_NAME FROM " + + "(SELECT DISTINCT U.UM_USER_ID, UM_USER_NAME " + + "FROM UM_ROLE R INNER JOIN UM_USER_ROLE UR ON R.UM_ID = UR.UM_ROLE_ID INNER JOIN UM_USER U " + + "ON UR.UM_USER_ID =U.UM_ID INNER JOIN UM_USER_ATTRIBUTE UA ON U.UM_ID = UA.UM_USER_ID"); + } } else if (H2.equals(dbType)) { sqlStatement = new StringBuilder( "SELECT DISTINCT U.UM_USER_ID, U.UM_USER_NAME FROM UM_ROLE R INNER JOIN " + @@ -3439,17 +3463,26 @@ protected SqlBuilder getQueryString(boolean isGroupFiltering, boolean isUsername "UM_USER U ON UR.UM_USER_ID = U.UM_ID INNER JOIN " + "UM_USER_ATTRIBUTE UA ON U.UM_ID = UA.UM_USER_ID"); } else if (MSSQL.equals(dbType)) { - sqlStatement = new StringBuilder( + if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + sqlStatement = new StringBuilder( + "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT UM_USER_ID, UM_USER_NAME, ROW_NUMBER() " + + "OVER (ORDER BY UM_USER_NAME DESC) AS RowNum FROM (SELECT DISTINCT " + + "U.UM_USER_ID, UM_USER_NAME FROM UM_ROLE R INNER JOIN UM_USER_ROLE UR ON " + + "R.UM_ID = UR.UM_ROLE_ID INNER JOIN UM_USER U ON UR.UM_USER_ID =U.UM_ID " + + "INNER JOIN UM_USER_ATTRIBUTE UA ON U.UM_ID = UA.UM_USER_ID"); + } else { + sqlStatement = new StringBuilder( "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT UM_USER_ID, UM_USER_NAME, ROW_NUMBER() OVER " + "(ORDER BY UM_USER_NAME) AS RowNum FROM (SELECT DISTINCT U.UM_USER_ID, " + "UM_USER_NAME FROM UM_ROLE R INNER JOIN UM_USER_ROLE UR ON R.UM_ID = UR.UM_ROLE_ID " + "INNER JOIN UM_USER U ON UR.UM_USER_ID =U.UM_ID INNER JOIN UM_USER_ATTRIBUTE UA ON" + " U.UM_ID = UA.UM_USER_ID"); + } } else if (ORACLE.equals(dbType)) { sqlStatement = new StringBuilder( - "SELECT U.UM_USER_ID, U.UM_USER_NAME FROM (SELECT UM_USER_NAME, rownum AS rnum " - + "FROM (SELECT UM_USER_NAME FROM UM_ROLE R INNER JOIN UM_USER_ROLE UR ON R.UM_ID = UR" - + ".UM_ROLE_ID INNER JOIN UM_USER U ON UR.UM_USER_ID =U.UM_ID INNER JOIN " + "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT UM_USER_ID, UM_USER_NAME, rownum AS rnum " + + "FROM (SELECT U.UM_USER_ID, UM_USER_NAME FROM UM_ROLE R INNER JOIN UM_USER_ROLE UR ON" + + " R.UM_ID = UR.UM_ROLE_ID INNER JOIN UM_USER U ON UR.UM_USER_ID = U.UM_ID INNER JOIN " + "UM_USER_ATTRIBUTE UA ON U.UM_ID = UA.UM_USER_ID"); } else if (POSTGRE_SQL.equals(dbType)) { sqlStatement = new StringBuilder( @@ -3468,33 +3501,51 @@ protected SqlBuilder getQueryString(boolean isGroupFiltering, boolean isUsername .where("UA.UM_TENANT_ID = ?", tenantId).where("UA.UM_PROFILE_ID = ?", profileName); } else if (isGroupFiltering && isUsernameFiltering || isGroupFiltering) { if (DB2.equals(dbType)) { - sqlStatement = new StringBuilder( - "SELECT U.UM_USER_ID, U.UM_USER_NAME FROM (SELECT ROW_NUMBER() OVER (ORDER BY " - + "UM_USER_NAME) AS rn, p.* FROM (SELECT DISTINCT UM_USER_NAME FROM UM_ROLE R INNER" - + " JOIN UM_USER_ROLE UR ON R.UM_ID = UR.UM_ROLE_ID INNER JOIN UM_USER U ON UR" - + ".UM_USER_ID " - + "=U.UM_ID "); + if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + sqlStatement = new StringBuilder( + "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT ROW_NUMBER() OVER (ORDER BY " + + "UM_USER_NAME DESC) AS rn, UM_USER_ID, UM_USER_NAME FROM " + + "(SELECT DISTINCT U.UM_USER_ID, UM_USER_NAME FROM UM_ROLE R INNER" + + " JOIN UM_USER_ROLE UR ON R.UM_ID = UR.UM_ROLE_ID INNER JOIN UM_USER U ON UR" + + ".UM_USER_ID = U.UM_ID "); + } else { + sqlStatement = new StringBuilder( + "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT ROW_NUMBER() OVER (ORDER BY " + + "UM_USER_NAME) AS rn, UM_USER_ID, UM_USER_NAME FROM " + + "(SELECT DISTINCT U.UM_USER_ID, UM_USER_NAME FROM UM_ROLE R INNER" + + " JOIN UM_USER_ROLE UR ON R.UM_ID = UR.UM_ROLE_ID INNER JOIN UM_USER U ON UR" + + ".UM_USER_ID = U.UM_ID "); + } } else if (H2.equals(dbType)) { sqlStatement = new StringBuilder( "SELECT DISTINCT U.UM_USER_ID, U.UM_USER_NAME FROM UM_ROLE R INNER JOIN " + "UM_USER_ROLE UR ON R.UM_ID = UR.UM_ROLE_ID INNER JOIN " + "UM_USER U ON UR.UM_USER_ID = U.UM_ID"); } else if (MSSQL.equals(dbType)) { - sqlStatement = new StringBuilder( + if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + sqlStatement = new StringBuilder( + "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT UM_USER_ID, UM_USER_NAME, " + + "ROW_NUMBER() OVER (ORDER BY UM_USER_NAME DESC) AS RowNum FROM " + + "(SELECT DISTINCT U.UM_USER_ID, UM_USER_NAME FROM UM_ROLE R INNER JOIN " + + "UM_USER_ROLE UR ON R.UM_ID = UR.UM_ROLE_ID " + + "INNER JOIN UM_USER U ON UR.UM_USER_ID =U.UM_ID"); + } else { + sqlStatement = new StringBuilder( "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT UM_USER_ID, UM_USER_NAME, ROW_NUMBER() OVER " + "(ORDER BY UM_USER_NAME) AS RowNum FROM (SELECT DISTINCT U.UM_USER_ID, " + "UM_USER_NAME FROM UM_ROLE R INNER JOIN UM_USER_ROLE UR ON R.UM_ID = UR.UM_ROLE_ID " + "INNER JOIN UM_USER U ON UR.UM_USER_ID =U.UM_ID"); + } } else if (ORACLE.equals(dbType)) { sqlStatement = new StringBuilder( - "SELECT U.UM_USER_ID, U.UM_USER_NAME FROM (SELECT UM_USER_NAME, rownum AS rnum " - + "FROM (SELECT UM_USER_NAME FROM UM_ROLE R INNER JOIN UM_USER_ROLE UR ON R.UM_ID = UR" - + ".UM_ROLE_ID INNER JOIN UM_USER U ON UR.UM_USER_ID =U.UM_ID"); + "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT UM_USER_ID, UM_USER_NAME, rownum AS rnum " + + "FROM (SELECT U.UM_USER_ID, UM_USER_NAME FROM UM_ROLE R INNER JOIN UM_USER_ROLE " + + "UR ON R.UM_ID = UR.UM_ROLE_ID INNER JOIN UM_USER U ON UR.UM_USER_ID = U.UM_ID"); } else if (POSTGRE_SQL.equals(dbType)) { sqlStatement = new StringBuilder( "SELECT DISTINCT U.UM_USER_ID, U.UM_USER_NAME FROM UM_ROLE R INNER JOIN " + "UM_USER_ROLE UR ON R.UM_ID = UR.UM_ROLE_ID INNER JOIN " + - "UM_USER U ON UR.UM_USER_ID=U.UM_ID"); + "UM_USER U ON UR.UM_USER_ID = U.UM_ID"); } else { sqlStatement = new StringBuilder( "SELECT DISTINCT U.UM_USER_ID, U.UM_USER_NAME FROM UM_ROLE R INNER JOIN " @@ -3506,16 +3557,33 @@ protected SqlBuilder getQueryString(boolean isGroupFiltering, boolean isUsername .where("U.UM_TENANT_ID = ?", tenantId).where("UR.UM_TENANT_ID = ?", tenantId); } else if (isUsernameFiltering && isClaimFiltering || isClaimFiltering) { if (DB2.equals(dbType)) { - sqlStatement = new StringBuilder( - "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT ROW_NUMBER() OVER (ORDER BY " + - "UM_USER_NAME) AS rn, UM_USER_ID, UM_USER_NAME FROM (SELECT DISTINCT U.UM_USER_ID, " + - "U.UM_USER_NAME FROM UM_USER U INNER JOIN UM_USER_ATTRIBUTE UA ON " + - "U.UM_ID = UA.UM_USER_ID"); + if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + sqlStatement = new StringBuilder( + "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT ROW_NUMBER() OVER (ORDER BY " + + "UM_USER_NAME DESC) AS rn, UM_USER_ID, UM_USER_NAME FROM " + + "(SELECT DISTINCT U.UM_USER_ID, U.UM_USER_NAME FROM UM_USER U " + + "INNER JOIN UM_USER_ATTRIBUTE UA ON U.UM_ID = UA.UM_USER_ID"); + } else { + sqlStatement = new StringBuilder( + "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT ROW_NUMBER() OVER (ORDER BY " + + "UM_USER_NAME) AS rn, UM_USER_ID, UM_USER_NAME FROM " + + "(SELECT DISTINCT U.UM_USER_ID, U.UM_USER_NAME FROM UM_USER U " + + "INNER JOIN UM_USER_ATTRIBUTE UA ON U.UM_ID = UA.UM_USER_ID"); + } } else if (MSSQL.equals(dbType)) { - sqlStatement = new StringBuilder( - "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT UM_USER_ID, UM_USER_NAME, ROW_NUMBER() OVER " + - "(ORDER BY UM_USER_NAME) AS RowNum FROM (SELECT DISTINCT U.UM_USER_ID, U.UM_USER_NAME FROM UM_USER U " + - "INNER JOIN UM_USER_ATTRIBUTE UA ON U.UM_ID = UA.UM_USER_ID"); + if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + sqlStatement = new StringBuilder( + "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT UM_USER_ID, UM_USER_NAME, " + + "ROW_NUMBER() OVER (ORDER BY UM_USER_NAME DESC) AS RowNum FROM " + + "(SELECT DISTINCT U.UM_USER_ID, U.UM_USER_NAME FROM UM_USER U " + + "INNER JOIN UM_USER_ATTRIBUTE UA ON U.UM_ID = UA.UM_USER_ID"); + } else { + sqlStatement = new StringBuilder( + "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT UM_USER_ID, UM_USER_NAME, " + + "ROW_NUMBER() OVER (ORDER BY UM_USER_NAME) AS RowNum FROM " + + "(SELECT DISTINCT U.UM_USER_ID, U.UM_USER_NAME FROM UM_USER U " + + "INNER JOIN UM_USER_ATTRIBUTE UA ON U.UM_ID = UA.UM_USER_ID"); + } } else if (ORACLE.equals(dbType)) { sqlStatement = new StringBuilder( "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT UM_USER_ID, UM_USER_NAME, rownum AS rnum FROM " @@ -3530,15 +3598,29 @@ protected SqlBuilder getQueryString(boolean isGroupFiltering, boolean isUsername .where("UA.UM_TENANT_ID = ?", tenantId).where("UA.UM_PROFILE_ID = ?", profileName); } else if (isUsernameFiltering) { if (DB2.equals(dbType)) { - sqlStatement = new StringBuilder( - "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT ROW_NUMBER() OVER (ORDER BY " - + "UM_USER_NAME) AS rn, p.* FROM (SELECT DISTINCT UM_USER_ID, UM_USER_NAME FROM " + - "UM_USER U"); + if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + sqlStatement = new StringBuilder( + "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT ROW_NUMBER() OVER (ORDER BY " + + "UM_USER_NAME DESC) AS rn, UM_USER_ID, UM_USER_NAME FROM (SELECT DISTINCT " + + "UM_USER_ID, UM_USER_NAME FROM UM_USER U"); + } else { + sqlStatement = new StringBuilder( + "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT ROW_NUMBER() OVER (ORDER BY " + + "UM_USER_NAME) AS rn, UM_USER_ID, UM_USER_NAME FROM (SELECT DISTINCT " + + "UM_USER_ID, UM_USER_NAME FROM UM_USER U"); + } } else if (MSSQL.equals(dbType)) { - sqlStatement = new StringBuilder( + if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + sqlStatement = new StringBuilder( + "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT UM_USER_ID, UM_USER_NAME, " + + "ROW_NUMBER() OVER (ORDER BY UM_USER_NAME DESC) AS RowNum FROM " + + "(SELECT DISTINCT UM_USER_NAME, UM_USER_ID FROM UM_USER U"); + } else { + sqlStatement = new StringBuilder( "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT UM_USER_ID, UM_USER_NAME, ROW_NUMBER() OVER " + "(ORDER BY UM_USER_NAME) AS RowNum FROM (SELECT DISTINCT UM_USER_NAME, UM_USER_ID" + " FROM UM_USER U"); + } } else if (ORACLE.equals(dbType)) { sqlStatement = new StringBuilder( "SELECT UM_USER_ID, UM_USER_NAME FROM (SELECT UM_USER_ID, UM_USER_NAME, rownum AS rnum " @@ -3557,7 +3639,8 @@ protected SqlBuilder getQueryString(boolean isGroupFiltering, boolean isUsername for (ExpressionCondition expressionCondition : expressionConditions) { if (ExpressionAttribute.ROLE.toString().equals(expressionCondition.getAttributeName())) { - if (!(MYSQL.equals(dbType) || MARIADB.equals(dbType)) || totalMultiGroupFilters > 1 && totalMultiClaimFilters > 1) { + if (!(MYSQL.equals(dbType) || MARIADB.equals(dbType)) || totalMultiGroupFilters > 1 + && totalMultiClaimFilters > 1) { multiGroupQueryBuilder(sqlBuilder, header, hitGroupFilter, expressionCondition); hitGroupFilter = true; } else { @@ -3595,7 +3678,8 @@ protected SqlBuilder getQueryString(boolean isGroupFiltering, boolean isUsername } } else { // Claim filtering - if (!(MYSQL.equals(dbType) || MARIADB.equals(dbType)) || totalMultiGroupFilters > 1 && totalMultiClaimFilters > 1) { + if (!(MYSQL.equals(dbType) || MARIADB.equals(dbType)) || totalMultiGroupFilters > 1 + && totalMultiClaimFilters > 1) { multiClaimQueryBuilder(sqlBuilder, header, hitClaimFilter, expressionCondition); hitClaimFilter = true; } else { @@ -3605,11 +3689,27 @@ protected SqlBuilder getQueryString(boolean isGroupFiltering, boolean isUsername } } + if (cursor != null && UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + if (!(MSSQL.equals(dbType) || DB2.equals(dbType))) { + sqlBuilder.prependSql("WITH this_set AS("); + } + } + + if (cursor != null) { + if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + sqlBuilder.where("U.UM_USER_NAME < ?", cursor); + } else if (UserCoreConstants.PaginationDirection.NEXT == direction) { + if (!(ORACLE.equals(dbType) && StringUtils.isEmpty(cursor))) { + sqlBuilder.where("U.UM_USER_NAME > ?", cursor); + } + } + } + if (MYSQL.equals(dbType) || MARIADB.equals(dbType)) { sqlBuilder.updateSql(" GROUP BY U.UM_USER_NAME, U.UM_USER_ID "); if (groupFilterCount > 0 && claimFilterCount > 0) { sqlBuilder.updateSql(" HAVING (COUNT(DISTINCT R.UM_ROLE_NAME) = " + groupFilterCount + - " AND COUNT(DISTINCT UA.UM_ATTR_VALUE) = " + claimFilterCount + ")"); + " AND COUNT(DISTINCT UA.UM_ATTR_NAME) = " + claimFilterCount + ")"); } else if (groupFilterCount > 0) { sqlBuilder.updateSql(" HAVING COUNT(DISTINCT R.UM_ROLE_NAME) = " + groupFilterCount); } else if (claimFilterCount > 0) { @@ -3617,42 +3717,146 @@ protected SqlBuilder getQueryString(boolean isGroupFiltering, boolean isUsername } } - if (!((MYSQL.equals(dbType) || MARIADB.equals(dbType)) && totalMultiGroupFilters > 1 && totalMultiClaimFilters > 1)) { - if (DB2.equals(dbType)) { - if (isClaimFiltering && !isGroupFiltering && totalMultiClaimFilters > 1) { - // Handle multi attribute filtering without group filtering. - sqlBuilder.setTail(") AS Q) AS S) AS R) AS p WHERE p.rn BETWEEN ? AND ?", limit, offset); - } else { - sqlBuilder.setTail(") AS p) WHERE rn BETWEEN ? AND ?", limit, offset); - } - } else if (MSSQL.equals(dbType)) { - if (isClaimFiltering && !isGroupFiltering && totalMultiClaimFilters > 1) { - // Handle multi attribute filtering without group filtering. - sqlBuilder.setTail(") AS Q) AS S) AS R) AS P WHERE P.RowNum BETWEEN ? AND ?", limit, offset); + int totalFilters = max(totalMultiClaimFilters, totalMultiGroupFilters); + if (totalMultiClaimFilters > 1 && totalMultiGroupFilters > 1) { + // Reduce 1 from totalFilters because there doesn't need to be an INTERSECT between + // a group filter and a claim filter. + totalFilters = (totalMultiClaimFilters + totalMultiGroupFilters) - 1; + } + + if (cursor != null) { + if (!((MYSQL.equals(dbType) || MARIADB.equals(dbType)) && totalMultiGroupFilters > 1 + && totalMultiClaimFilters > 1)) { + if (DB2.equals(dbType)) { + if (UserCoreConstants.PaginationDirection.NEXT == direction) { + if ((isClaimFiltering && totalMultiClaimFilters > 1) || + (isGroupFiltering && totalMultiGroupFilters > 1)) { + // Handle multi attribute filtering. + StringBuilder brackets = new StringBuilder(") AS R"); + /* + * x is used to count the number of brackets + * (totalFilters * 2) --> totalFilters are multiplied by 2 as 2 new opening + * brackets are created for every new filter, which needs to be closed at the right position + * (totalMultiClaimFilters * 2) - 3 is deducted as there are 2 opening brackets in the SQL + * query and 1 closing brackets in the setTail section below. + */ + for (int x = 0; x <= (totalFilters * 2) - 3; x++) { + brackets.append(" ) AS R"); + } + sqlBuilder.setTail(brackets.toString().concat(") AS p WHERE p.rn BETWEEN 1 AND ?"), + limit); + } else { + sqlBuilder.setTail(") AS p) WHERE rn BETWEEN 1 and ?", limit); + } + } else if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + if ((isClaimFiltering && totalMultiClaimFilters > 1) || + (isGroupFiltering && totalMultiGroupFilters > 1)) { + // Handle multi attribute filtering. + StringBuilder brackets = new StringBuilder(") AS R"); + // - 3 due to 2 opening brackets and the remainder of the query within setTail() includes + // a single ) + for (int x = 0; x <= (totalFilters * 2) - 3; x++) { + brackets.append(" ) AS R"); + } + sqlBuilder.setTail(brackets.toString().concat(") AS p WHERE p.rn BETWEEN 1 AND ? " + + "ORDER BY UM_USER_NAME ASC"), limit); + } else { + sqlBuilder.setTail(") AS p) WHERE p.rn BETWEEN 1 and ? " + + "ORDER BY UM_USER_NAME ASC", limit); + } + } + } else if (MSSQL.equals(dbType)) { + if (UserCoreConstants.PaginationDirection.NEXT == direction) { + if ((isClaimFiltering && totalMultiClaimFilters > 1) || + (isGroupFiltering && totalMultiGroupFilters > 1)) { + // Handle multi attribute filtering. + StringBuilder brackets = new StringBuilder(") AS R"); + // - 3 due to 2 opening brackets and the remainder of the query within setTail() includes + // a single ) + for (int x = 0; x <= (totalFilters * 2) - 3; x++) { + brackets.append(" ) AS R"); + } + sqlBuilder.setTail(brackets.toString().concat(") AS P WHERE P.RowNum BETWEEN 1 AND ?"), + limit); + } else { + sqlBuilder.setTail(") AS R) AS P WHERE P.RowNum BETWEEN 1 AND ?", limit); + } + } else if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + if ((isClaimFiltering && totalMultiClaimFilters > 1) || + (isGroupFiltering && totalMultiGroupFilters > 1)) { + // - 3 due to 2 opening brackets and the remainder of the query within setTail() includes + // a single ) + StringBuilder brackets = new StringBuilder(") AS R"); + for (int x = 0; x <= (totalFilters * 2) - 3; x++) { + brackets.append(" ) AS R"); + } + sqlBuilder.setTail(brackets.toString().concat(") AS P WHERE P.RowNum " + + "BETWEEN 1 AND ? ORDER BY UM_USER_NAME ASC"), limit); + } else { + sqlBuilder.setTail(") AS R) AS P WHERE P.RowNum BETWEEN 1 AND ? " + + "ORDER BY UM_USER_NAME ASC", limit); + } + } + } else if (ORACLE.equals(dbType)) { + if (UserCoreConstants.PaginationDirection.NEXT == direction) { + if ((isClaimFiltering && totalMultiClaimFilters > 1) || + (isGroupFiltering && totalMultiGroupFilters > 1)) { + StringBuilder brackets = new StringBuilder(")"); + // - 3 due to 2 opening brackets and the remainder of the query within setTail() includes + // 2 closing brackets. + for (int x = 0; x <= (totalFilters * 2) - 4; x++) { + brackets.append(" )"); + } + // Handle multi attribute filtering. + sqlBuilder.setTail(brackets.toString().concat(" ORDER BY UM_USER_NAME ) " + + "where rownum <= ?)"), limit); + } else { + sqlBuilder.setTail(" ORDER BY UM_USER_NAME) where rownum <= ?)", limit); + } + } else if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + if ((isClaimFiltering && totalMultiClaimFilters > 1) || + (isGroupFiltering && totalMultiGroupFilters > 1)) { + StringBuilder brackets = new StringBuilder(")"); + // Still - 4 because this is a query to get results in the previous direction which has + // an additional opening ( in the SQL and an additional ) in the setTail section. + for (int x = 0; x <= (totalFilters * 2) - 4; x++) { + brackets.append(" )"); + } + // Handle multi attribute filtering. + sqlBuilder.setTail(brackets.toString().concat("ORDER BY UM_USER_NAME DESC) where " + + "rownum <= ?)) Select * from this_set ORDER BY UM_USER_NAME ASC"), limit); + } else { + sqlBuilder.setTail(" ORDER BY UM_USER_NAME DESC) where rownum <= ?))" + + " Select * from this_set ORDER BY UM_USER_NAME ASC", limit); + } + } } else { - sqlBuilder.setTail(") AS R) AS P WHERE P.RowNum BETWEEN ? AND ?", limit, offset); + if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + sqlBuilder.setTail(" ORDER BY UM_USER_NAME DESC LIMIT ? )SELECT * " + + "from this_set ORDER BY UM_USER_NAME ASC", limit); + } else if (UserCoreConstants.PaginationDirection.NEXT == direction) { + sqlBuilder.setTail(" ORDER BY UM_USER_NAME ASC LIMIT ?", limit); + } } - } else if (ORACLE.equals(dbType)) { - if (isClaimFiltering && !isGroupFiltering && totalMultiClaimFilters > 1) { - StringBuilder brackets = new StringBuilder(")"); - /* - * x is used to count the number of brackets - * (totalMultiClaimFilters * 2) --> totalMultiClaims are multiplied by 2 as 2 new opening - * brackets are created for every new claim, which needs to be closed at the right position. - * (totalMultiClaimFilters * 2) - 4 is deducted as there are 2 opening brackets in the SQL query - * and 2 closing brackets in the setTail section below. - */ - for (int x = 0; x <= (totalMultiClaimFilters * 2) - 4; x++) { - brackets = brackets.append(" )"); + } + } else { + if (!((MYSQL.equals(dbType) || MARIADB.equals(dbType)) && totalMultiGroupFilters > 1 && + totalMultiClaimFilters > 1)) { + if (DB2.equals(dbType)) { + sqlBuilder.setTail(") AS p) WHERE rn BETWEEN ? AND ?", limit, offset); + } else if (MSSQL.equals(dbType)) { + if (isClaimFiltering && !isGroupFiltering && totalMultiClaimFilters > 1) { + // Handle multi attribute filtering without group filtering. + sqlBuilder.setTail(") AS Q) AS S) AS R) AS P WHERE P.RowNum BETWEEN ? AND ?", limit, + offset); + } else { + sqlBuilder.setTail(") AS R) AS P WHERE P.RowNum BETWEEN ? AND ?", limit, offset); } - // Handle multi attribute filtering without group filtering. - sqlBuilder.setTail(brackets.toString() - .concat("ORDER BY UM_USER_NAME ) where rownum <= ?) WHERE rnum > ?"), limit, offset); - } else { + } else if (ORACLE.equals(dbType)) { sqlBuilder.setTail(" ORDER BY UM_USER_NAME) where rownum <= ?) WHERE rnum > ?", limit, offset); + } else { + sqlBuilder.setTail(" ORDER BY UM_USER_NAME ASC LIMIT ? OFFSET ?", limit, offset); } - } else { - sqlBuilder.setTail(" ORDER BY UM_USER_NAME ASC LIMIT ? OFFSET ?", limit, offset); } } return sqlBuilder; diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/LDAPConstants.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/LDAPConstants.java index 1aae065189f..7b8c9683869 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/LDAPConstants.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/LDAPConstants.java @@ -112,4 +112,6 @@ public class LDAPConstants { */ public static final String DEFAULT_LDAP_TIME_FORMATS_PATTERN = "[uuuuMMddHHmmss[,SSS][.SSS]X]" + "[uuuuMMddHHmmss[,SS][.SS]X][uuuuMMddHHmm[,S][.S]X]"; + + public static final boolean DESCENDING = false; } diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/LDAPFilterQueryBuilder.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/LDAPFilterQueryBuilder.java index a0d1b8f0704..b0975d90d23 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/LDAPFilterQueryBuilder.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/LDAPFilterQueryBuilder.java @@ -35,6 +35,7 @@ public class LDAPFilterQueryBuilder { private static final String GREATER_THAN_SIGN = ">"; private static final String LESS_THAN_SIGN = "<"; private static final String ANY_STRING = "*"; + private static final String NOT_SIGN = "!"; private StringBuilder searchFilter; private StringBuilder membershipMultiGroupFilters; @@ -86,6 +87,12 @@ private void buildFilter(StringBuilder queryBuilder, String property, String ope queryBuilder.append(greaterThanOrEqualFilterBuilder(property, value)); } else if (ExpressionOperation.LE.toString().equals(operation)) { queryBuilder.append(lessThanOrEqualFilterBuilder(property, value)); + } else if (ExpressionOperation.GT.toString().equals(operation)) { + queryBuilder.append(greaterThanFilterBuilder(property, value)); + } else if (ExpressionOperation.LT.toString().equals(operation)) { + queryBuilder.append(lessThanFilterBuilder(property, value)); + } else if (ExpressionOperation.NE.toString().equals(operation)) { + queryBuilder.append(notEqualFilterBuilder(property, value)); } } @@ -178,6 +185,55 @@ private String greaterThanOrEqualFilterBuilder(String property, String value) { return filter.toString(); } + /** + * Generate "GT" filter. + * + * @param property Attribute name. + * @param value Attribute value. + * @return Filter query with greater than filter. + */ + private String greaterThanFilterBuilder(String property, String value) { + + StringBuilder filter = new StringBuilder(); + filter.append(OPEN_PARENTHESIS).append(NOT_SIGN).append(OPEN_PARENTHESIS).append(OR_OPERATION). + append(OPEN_PARENTHESIS).append(NOT_SIGN).append(OPEN_PARENTHESIS).append(property). + append(GREATER_THAN_SIGN).append(EQUALS_SIGN).append(value).append(CLOSE_PARENTHESIS). + append(CLOSE_PARENTHESIS).append(OPEN_PARENTHESIS).append(property).append(EQUALS_SIGN).append(value). + append(CLOSE_PARENTHESIS).append(CLOSE_PARENTHESIS).append(CLOSE_PARENTHESIS); + return filter.toString(); + } + + /** + * Generate "LT" filter. + * + * @param property Attribute name. + * @param value Attribute value. + * @return Filter query with less than filter. + */ + private String lessThanFilterBuilder(String property, String value) { + + StringBuilder filter = new StringBuilder(); + filter.append(OPEN_PARENTHESIS).append(NOT_SIGN).append(OPEN_PARENTHESIS).append(property). + append(GREATER_THAN_SIGN).append(EQUALS_SIGN).append(value).append(CLOSE_PARENTHESIS). + append(CLOSE_PARENTHESIS); + return filter.toString(); + } + + /** + * Generate "NE" filter. + * + * @param property Attribute name. + * @param value Attribute value. + * @return Search Filter query with not equal filter. + */ + private String notEqualFilterBuilder(String property, String value) { + + StringBuilder filter = new StringBuilder(); + filter.append(OPEN_PARENTHESIS).append(NOT_SIGN).append(OPEN_PARENTHESIS).append(property).append(EQUALS_SIGN). + append(value).append(CLOSE_PARENTHESIS); + return filter.toString(); + } + /** * Get final search filter query. * diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/UniqueIDReadOnlyLDAPUserStoreManager.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/UniqueIDReadOnlyLDAPUserStoreManager.java index 4b22daab3e7..3d346de4c9c 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/UniqueIDReadOnlyLDAPUserStoreManager.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/UniqueIDReadOnlyLDAPUserStoreManager.java @@ -98,6 +98,7 @@ import javax.naming.ldap.PagedResultsControl; import javax.naming.ldap.Rdn; import javax.naming.ldap.SortControl; +import javax.naming.ldap.SortKey; import static org.wso2.carbon.user.core.UserStoreConfigConstants.GROUP_CREATED_DATE_ATTRIBUTE; import static org.wso2.carbon.user.core.UserStoreConfigConstants.GROUP_ID_ATTRIBUTE; @@ -1500,6 +1501,8 @@ public List doGetUserListFromPropertiesWithID(String property, String va * @param profileName Default profile name. * @param limit The number of entries to return in a page. * @param offset Start index. + * @param cursor Cursor value for pagination. + * @param direction Pagination direction. * @param sortBy Sort according to the given attribute name. * @param sortOrder Sorting order. * @return A non-null UniqueIDPaginatedSearchResult instance. Typically contains users list with pagination. @@ -1508,16 +1511,30 @@ public List doGetUserListFromPropertiesWithID(String property, String va */ @Override protected UniqueIDPaginatedSearchResult doGetUserListWithID(Condition condition, String profileName, int limit, - int offset, String sortBy, String sortOrder) + Integer offset, String cursor, UserCoreConstants.PaginationDirection direction, + String sortBy, String sortOrder) throws UserStoreException { UniqueIDPaginatedSearchResult result = new UniqueIDPaginatedSearchResult(); List expressionConditions = getExpressionConditions(condition); + if (StringUtils.isNotEmpty(cursor)) { + ExpressionCondition cursorCondition = null; + if (UserCoreConstants.PaginationDirection.NEXT == direction) { + cursorCondition = new ExpressionCondition(ExpressionOperation.GT.toString(), + ExpressionAttribute.USERNAME.toString(), cursor); + } else if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + cursorCondition = new ExpressionCondition(ExpressionOperation.LT.toString(), + ExpressionAttribute.USERNAME.toString(), cursor); + } + expressionConditions.add(cursorCondition); + } LDAPSearchSpecification ldapSearchSpecification = new LDAPSearchSpecification(realmConfig, expressionConditions); boolean isMemberShipPropertyFound = ldapSearchSpecification.isMemberShipPropertyFound(); limit = getLimit(limit, isMemberShipPropertyFound); - offset = getOffset(offset); + if (cursor == null) { + offset = getOffset(offset); + } if (limit == 0) { return result; } @@ -1527,9 +1544,27 @@ protected UniqueIDPaginatedSearchResult doGetUserListWithID(Condition condition, List users; String userNameAttribute = realmConfig.getUserStoreProperty(LDAPConstants.USER_NAME_ATTRIBUTE); try { - ldapContext.setRequestControls(new Control[] { new PagedResultsControl(pageSize, Control.CRITICAL), - new SortControl(userNameAttribute, Control.NONCRITICAL) }); - users = performLDAPSearch(ldapContext, ldapSearchSpecification, pageSize, offset, expressionConditions); + if (cursor != null) { + if (UserCoreConstants.PaginationDirection.NEXT == direction) { + // RequestControls similar to offset pagination when direction is NEXT. + ldapContext.setRequestControls(new Control[] { new PagedResultsControl(pageSize, Control.CRITICAL), + new SortControl(userNameAttribute, Control.NONCRITICAL) }); + } else { + // RequestControls have to be arranged in descending order when direction is PREVIOUS. + // Use a SortKeyArray to order in descending order. + SortKey sortKey = new SortKey(userNameAttribute, LDAPConstants.DESCENDING, null); + SortKey[] sortKeyArray = new SortKey[1]; + sortKeyArray[0] = sortKey; + ldapContext.setRequestControls(new Control[] { new PagedResultsControl(pageSize, Control.CRITICAL), + new SortControl(sortKeyArray, Control.CRITICAL) }); + } + users = performCursorLDAPSearch(ldapContext, ldapSearchSpecification, pageSize, + expressionConditions, direction); + } else { + ldapContext.setRequestControls(new Control[] { new PagedResultsControl(pageSize, Control.CRITICAL), + new SortControl(userNameAttribute, Control.NONCRITICAL) }); + users = performLDAPSearch(ldapContext, ldapSearchSpecification, pageSize, offset, expressionConditions); + } result.setUsers(users); return result; } catch (NamingException e) { @@ -3064,6 +3099,81 @@ page, because a page should contain user list which is equal to the page size (. return users; } + private List performCursorLDAPSearch(LdapContext ldapContext, LDAPSearchSpecification ldapSearchSpecification, + int pageSize, List expressionConditions, + UserCoreConstants.PaginationDirection direction) + throws UserStoreException { + + boolean isGroupFiltering = ldapSearchSpecification.isGroupFiltering(); + boolean isUsernameFiltering = ldapSearchSpecification.isUsernameFiltering(); + boolean isClaimFiltering = ldapSearchSpecification.isClaimFiltering(); + boolean isMemberShipPropertyFound = ldapSearchSpecification.isMemberShipPropertyFound(); + + String searchBases = ldapSearchSpecification.getSearchBases(); + String[] searchBaseArray = searchBases.split("#"); + String searchFilter = ldapSearchSpecification.getSearchFilterQuery(); + SearchControls searchControls = ldapSearchSpecification.getSearchControls(); + List returnedAttributes = Arrays.asList(searchControls.getReturningAttributes()); + NamingEnumeration answer = null; + List users = new ArrayList<>(); + List tempUsersList = new ArrayList<>(); + + if (log.isDebugEnabled()) { + log.debug(String.format("Searching for user(s) with SearchFilter: %s and page size %d", searchFilter, + pageSize)); + } + try { + for (String searchBase : searchBaseArray) { + + answer = ldapContext.search(escapeDNForSearch(searchBase), searchFilter, searchControls); + // DirContext.search never returns null + if (answer.hasMore()) { + tempUsersList = getUserListFromSearch(isGroupFiltering, returnedAttributes, answer, + isSingleAttributeFilterOperation(expressionConditions)); + } + if (CollectionUtils.isNotEmpty(tempUsersList)) { + if (isMemberShipPropertyFound) { + /* + * Pagination is not supported for 'member' attribute group filtering. We also + * need do post-processing if we found username filtering or claim filtering, + * because we can't apply claim filtering with memberShip group filtering and + * can't apply username filtering with 'CO', 'EW', 'SW' filter operations. + */ + users = membershipGroupFilterPostProcessing(isUsernameFiltering, isClaimFiltering, + expressionConditions, tempUsersList); + break; + } + } + if (UserCoreConstants.PaginationDirection.PREVIOUS == direction) { + for (int i = tempUsersList.size() - 1; i >= 0; i--) { + users.add(tempUsersList.get(i)); + } + } else if (UserCoreConstants.PaginationDirection.NEXT == direction) { + for (int i = 0; i < tempUsersList.size(); i++) { + users.add(tempUsersList.get(i)); + } + } + } + } catch (PartialResultException e) { + // Can be due to referrals in AD. So just ignore error. + if (isIgnorePartialResultException()) { + if (log.isDebugEnabled()) { + log.debug(String.format("Error occurred while searching for user(s) for filter: %s", searchFilter)); + } + } else { + log.error(String.format("Error occurred while searching for user(s) for filter: %s", searchFilter)); + throw new UserStoreException(e.getMessage(), e); + } + } catch (NamingException e) { + log.error(String.format("Error occurred while searching for user(s) for filter: %s, %s", + searchFilter, e.getMessage())); + throw new UserStoreException(e.getMessage(), e); + } finally { + JNDIUtil.closeNamingEnumeration(answer); + } + return users; + } + /** * Get user list from multi attribute search filter. * diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UniqueIDUserManagementErrorEventListener.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UniqueIDUserManagementErrorEventListener.java index 1b7e67348f7..5a9d8c16562 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UniqueIDUserManagementErrorEventListener.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UniqueIDUserManagementErrorEventListener.java @@ -19,6 +19,7 @@ package org.wso2.carbon.user.core.listener; import org.wso2.carbon.user.api.Permission; +import org.wso2.carbon.user.core.UserCoreConstants; import org.wso2.carbon.user.core.UserStoreException; import org.wso2.carbon.user.core.UserStoreManager; import org.wso2.carbon.user.core.common.AbstractUserStoreManager; @@ -349,6 +350,30 @@ boolean onGetUserListFailureWithID(String errorCode, String errorMassage, Condit String profileName, int limit, int offset, String sortBy, String sortOrder, UserStoreManager userStoreManager) throws UserStoreException; + /** + * Defines any additional actions that need to be done if there is a failure on retrieving cursor paginated + * conditional user list. + * + * @param errorCode Error code. + * @param errorMassage Error Message. + * @param domain User store domain. + * @param profileName Profile name. + * @param limit Number of search results. + * @param cursor Cursor value used in cursor pagination + * @param direction Direction of pagination. + * @param sortBy Sort by attribute. + * @param sortOrder Sort order. + * @param userStoreManager User store domain. + * @throws UserStoreException UserStoreException + */ + default boolean onGetUserListFailureWithID(String errorCode, String errorMassage, Condition condition, + String domain, String profileName, int limit, String cursor, + UserCoreConstants.PaginationDirection direction, String sortBy, + String sortOrder, UserStoreManager userStoreManager) throws UserStoreException { + + return true; + } + /** * Defines any additional actions that need to be done if there is a failure retrieving the user. * diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UniqueIDUserOperationEventListener.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UniqueIDUserOperationEventListener.java index 1a93cb76757..2786b12d30d 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UniqueIDUserOperationEventListener.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UniqueIDUserOperationEventListener.java @@ -19,6 +19,7 @@ package org.wso2.carbon.user.core.listener; import org.wso2.carbon.user.api.Permission; +import org.wso2.carbon.user.core.UserCoreConstants; import org.wso2.carbon.user.core.UserStoreException; import org.wso2.carbon.user.core.UserStoreManager; import org.wso2.carbon.user.core.common.AbstractUserStoreManager; @@ -130,11 +131,14 @@ boolean doPostGetUserClaimValuesWithID(String userID, String[] claims, String pr * @return true if handling succeeds, otherwise false. * @throws UserStoreException User Store Exception. */ - boolean doPreGetUserListWithID(String claimUri, String claimValue, final List returnUsersList, - UserStoreManager userStoreManager) throws UserStoreException; + default boolean doPreGetUserListWithID(String claimUri, String claimValue, final List returnUsersList, + UserStoreManager userStoreManager) throws UserStoreException { + + return true; + } /** - * Pre listener for the get paginated conditional user list method. + * Pre listener for the get offset paginated conditional user list method. * * @param condition condition. * @param domain user store domain. @@ -150,6 +154,29 @@ boolean doPreGetUserListWithID(String claimUri, String claimValue, final List users, UserStoreManager userStoreManager) throws UserStoreException; + /** + * Post listener for the get user with cursor pagination conditional list method. + * + * @param condition Condition. + * @param domain User store domain. + * @param profileName Profile name. + * @param limit Number of search results. + * @param cursor The cursor value used for cursor pagination. + * @param direction The direction of cursor pagination. + * @param sortBy Sort by attribute. + * @param sortOrder Sort order. + * @param userStoreManager User store manager. + * @param users Filtered user list + * @throws UserStoreException UserStoreException + */ + boolean doPostGetUserListWithID(Condition condition, String domain, String profileName, int limit, String cursor, + UserCoreConstants.PaginationDirection direction, String sortBy, String sortOrder, + List users, UserStoreManager userStoreManager) + throws UserStoreException; + /** * Pre listener for the get user method. * diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UserManagementErrorEventListener.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UserManagementErrorEventListener.java index 7f14fe7fa72..41f32b43438 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UserManagementErrorEventListener.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UserManagementErrorEventListener.java @@ -20,6 +20,7 @@ package org.wso2.carbon.user.core.listener; import org.wso2.carbon.user.api.Permission; +import org.wso2.carbon.user.core.UserCoreConstants; import org.wso2.carbon.user.core.UserStoreException; import org.wso2.carbon.user.core.UserStoreManager; import org.wso2.carbon.user.core.common.AbstractUserStoreManager; @@ -349,6 +350,29 @@ default boolean onGetUserListFailure(String errorCode, String errorMassage, Cond return true; } + /** + * Defines any additional actions that need to be done if there is a failure on retrieving conditional user list. + * + * @param errorCode Error code. + * @param errorMassage Error Message. + * @param domain user store domain. + * @param profileName profile name. + * @param limit number of search results. + * @param cursor Cursor value used for cursor-based pagination. + * @param direction Direction of pagination. + * @param sortBy sort by attribute. + * @param sortOrder sort order. + * @param userStoreManager user store domain. + * @throws UserStoreException UserStoreException + */ + default boolean onGetUserListFailure(String errorCode, String errorMassage, Condition condition, String domain, + String profileName, int limit, String cursor, UserCoreConstants.PaginationDirection direction, + String sortBy, String sortOrder, UserStoreManager userStoreManager) + throws UserStoreException { + + return true; + } + /** * Defines any additional actions that need to be done if there is a failure while updating permissions of a role. * diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UserOperationEventListener.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UserOperationEventListener.java index c963aff19bf..48f956bac60 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UserOperationEventListener.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/listener/UserOperationEventListener.java @@ -20,6 +20,7 @@ package org.wso2.carbon.user.core.listener; import org.wso2.carbon.user.api.Permission; +import org.wso2.carbon.user.core.UserCoreConstants; import org.wso2.carbon.user.core.UserStoreException; import org.wso2.carbon.user.core.UserStoreManager; import org.wso2.carbon.user.core.model.Condition; @@ -632,7 +633,7 @@ default boolean doPreGetUserList(String claimUri, String claimValue, final List< } /** - * Pre listener for the get paginated conditional user list method. + * Pre listener for the get offset paginated conditional user list method. * * @param condition condition. * @param domain user store domain. @@ -650,6 +651,27 @@ default boolean doPreGetUserList(Condition condition, String domain, String prof return true; } + /** + * Pre listener for the get cursor paginated conditional user list method. + * + * @param condition Condition. + * @param domain User store domain. + * @param profileName Profile name. + * @param limit Number of search results. + * @param cursor Cursor used for pagination. + * @param direction Direction of paginated search. + * @param sortBy Sort By attribute + * @param sortOrder Sort order. + * @param userStoreManager userStoreManager. + * @throws UserStoreException UserStoreException + */ + default boolean doPreGetUserList(Condition condition, String domain, String profileName, int limit, String cursor, + UserCoreConstants.PaginationDirection direction, String sortBy, String sortOrder, + UserStoreManager userStoreManager) throws UserStoreException { + + return true; + } + /** * Pre listener for the get paginated user list method. @@ -723,6 +745,28 @@ default boolean doPostGetUserList(Condition condition, String domain, String pro return true; } + /** + * Post listener for the get user cursor paginated conditional list method. + * + * @param condition Condition. + * @param domain User store domain. + * @param profileName Profile name. + * @param limit Number of search results. + * @param cursor Cursor value used for cursor pagination. + * @param direction Cursor pagination direction. + * @param sortBy Sort by attribute. + * @param sortOrder Sort order. + * @param userStoreManager User store manager. + * @param users Filtered user list + * @throws UserStoreException UserStoreException + */ + default boolean doPostGetUserList(Condition condition, String domain, String profileName, int limit, String cursor, + UserCoreConstants.PaginationDirection direction, String sortBy, String sortOrder, + String[] users, UserStoreManager userStoreManager) throws UserStoreException { + + return true; + } + /** * Post listener for the get paginated user list method. * @param claimUri Claim URI. @@ -868,7 +912,7 @@ default boolean doPostGetUsersClaimValues(String[] userNames, String[] claims, S } /** - * Pre listener for getting paginated user list for certain claim and value. + * Pre listener for getting paginated user list for certain claim and value - Offset pagination. * * @param condition Conditions with filters. * @param domain User store domain name. @@ -884,4 +928,23 @@ default boolean doPreGetPaginatedUserList(Condition condition, List user return true; } + /** + * Pre listener for getting paginated user list for certain claim and value - Cursor Pagination. + * + * @param condition Conditions with filters. + * @param domain User store domain name. + * @param userStoreManager User store manager. + * @param limit Pagination parameter for the size of the page. + * @param cursor Cursor value to paginate based off. + * @param direction Direction of pagination. + * @throws UserStoreException UserStoreException + */ + default boolean doPreGetPaginatedUserList(Condition condition, List userNames, String domain, + UserStoreManager userStoreManager, int limit, String cursor, + UserCoreConstants.PaginationDirection direction) + throws UserStoreException { + + return true; + } + } diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/model/ExpressionOperation.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/model/ExpressionOperation.java index 7f25e5456f6..7f341f4e09b 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/model/ExpressionOperation.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/model/ExpressionOperation.java @@ -21,5 +21,5 @@ * This represents expression operations. */ public enum ExpressionOperation { - EQ, SW, EW, CO, GE, LE + EQ, SW, EW, CO, GE, LE, GT, LT, NE } diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/model/SqlBuilder.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/model/SqlBuilder.java index 9c144648be2..b8e5a0431c9 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/model/SqlBuilder.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/model/SqlBuilder.java @@ -172,6 +172,16 @@ public void updateSql(String append) { this.sql.append(append); } + /** + * Set sql to the beginning of the statement. + * + * @param prepend SQL string value to prepend to the current SQL statement. + */ + public void prependSql(String prepend) { + + this.sql = new StringBuilder(prepend + " " + this.sql.toString()); + } + public void updateSqlWithOROperation(String expr, Object value) { appendList(sql, wheres); diff --git a/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/ClaimTestUtil.java b/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/ClaimTestUtil.java index b022363069a..fb40c467f91 100644 --- a/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/ClaimTestUtil.java +++ b/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/ClaimTestUtil.java @@ -31,6 +31,8 @@ public class ClaimTestUtil { public static final String CLAIM_URI3 = "http://wso2.org/givenname3"; public static final String CLAIM_URI4 = "http://wso2.org/claims/username"; public static final String CLAIM_URI5 = "http://wso2.org/claims/userid"; + public static final String CLAIM_URI6 = "http://wso2.org/claims/fullname"; + public static final String HOME_PROFILE_NAME = "HomeProfile"; public static Map getClaimTestData() { @@ -97,6 +99,18 @@ public static Map getClaimTestData() { cm5.setClaim(claim5); cm5.setMappedAttribute("scimId"); claims.put(CLAIM_URI5, cm5); + + Claim claim6 = new Claim(); + claim6.setClaimUri(CLAIM_URI6); + claim6.setDescription("Fullname claim URI"); + claim6.setDialectURI("http://wso2.org/"); + claim6.setDisplayTag("Full name"); + claim6.setRequired(true); + claim6.setSupportedByDefault(true); + ClaimMapping cm6 = new ClaimMapping(); + cm6.setClaim(claim6); + cm6.setMappedAttribute("fullname"); + claims.put(CLAIM_URI6, cm6); return claims; diff --git a/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/claim/AdvancedClaimManagerTest.java b/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/claim/AdvancedClaimManagerTest.java index 0b94c117ed8..089f8139cd3 100644 --- a/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/claim/AdvancedClaimManagerTest.java +++ b/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/claim/AdvancedClaimManagerTest.java @@ -86,7 +86,7 @@ public void doClaimStuff() throws Exception{ // get all the claim URIs String[] ClmURI = claimMan.getAllClaimUris(); - assertEquals(5, ClmURI.length); + assertEquals(6, ClmURI.length); // get the attribute name for a given claimURI // if the claimURI is null it will return a null value. @@ -124,19 +124,19 @@ public void doClaimStuff() throws Exception{ ClaimMapping[] CM1 = (ClaimMapping[]) claimMan.getAllSupportClaimMappingsByDefault(); Arrays.sort(CM1,new ClaimSorter());//sorting C1 array Claim C1 = CM1[0].getClaim(); - assertEquals("Given Name",C1.getDisplayTag()); + assertEquals("Full name",C1.getDisplayTag()); //lists all the claims required at the time user registration ClaimMapping[] CM2 = (ClaimMapping[]) claimMan.getAllRequiredClaimMappings(); Arrays.sort(CM2,new ClaimSorter());//sorting C2 array - assertEquals(5, CM2.length); + assertEquals(6, CM2.length); Claim C2 = CM2[2].getClaim(); - assertEquals("Given Name3", C2.getDisplayTag()); - assertEquals(5, claimMan.getAllRequiredClaimMappings().length); - assertEquals(5, claimMan.getAllSupportClaimMappingsByDefault().length); - assertEquals(5, claimMan.getAllClaimMappings().length); + assertEquals("Given Name2", C2.getDisplayTag()); + assertEquals(6, claimMan.getAllRequiredClaimMappings().length); + assertEquals(6, claimMan.getAllSupportClaimMappingsByDefault().length); + assertEquals(6, claimMan.getAllClaimMappings().length); //void addNewClaimMapping(ClaimMapping mapping) add new mappings @@ -144,9 +144,9 @@ public void doClaimStuff() throws Exception{ for(ClaimMapping x:NewclaimMapping){ claimMan.addNewClaimMapping(x); } - assertEquals(6,claimMan.getAllSupportClaimMappingsByDefault().length); - assertEquals(6,claimMan.getAllRequiredClaimMappings().length); - assertEquals(8,claimMan.getAllClaimMappings().length); + assertEquals(7,claimMan.getAllSupportClaimMappingsByDefault().length); + assertEquals(7,claimMan.getAllRequiredClaimMappings().length); + assertEquals(9,claimMan.getAllClaimMappings().length); //update an existing mapping @@ -154,15 +154,15 @@ public void doClaimStuff() throws Exception{ NewclaimMapping[1].getClaim().setRequired(true); claimMan.updateClaimMapping(NewclaimMapping[1]); - assertEquals(7,claimMan.getAllRequiredClaimMappings().length); + assertEquals(8,claimMan.getAllRequiredClaimMappings().length); assertEquals("The Update claim5",NewclaimMapping[1].getClaim().getDescription()); //delete an existing mapping claimMan.deleteClaimMapping(NewclaimMapping[0]); - assertEquals(6,claimMan.getAllRequiredClaimMappings().length); - assertEquals(6,claimMan.getAllSupportClaimMappingsByDefault().length); - assertEquals(7,claimMan.getAllClaimMappings().length); + assertEquals(7,claimMan.getAllRequiredClaimMappings().length); + assertEquals(7,claimMan.getAllSupportClaimMappingsByDefault().length); + assertEquals(8,claimMan.getAllClaimMappings().length); } public Map doClaimBuilderStuff() throws Exception{ diff --git a/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCRealmPrimaryUserStoreTest.java b/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCRealmPrimaryUserStoreTest.java index 21ffe6492a5..d759da90246 100644 --- a/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCRealmPrimaryUserStoreTest.java +++ b/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCRealmPrimaryUserStoreTest.java @@ -39,6 +39,7 @@ import org.wso2.carbon.user.core.model.ExpressionAttribute; import org.wso2.carbon.user.core.model.ExpressionCondition; import org.wso2.carbon.user.core.model.ExpressionOperation; +import org.wso2.carbon.user.core.model.OperationalCondition; import org.wso2.carbon.user.core.model.UniqueIDUserClaimSearchEntry; import org.wso2.carbon.user.core.model.UserClaimSearchEntry; import org.wso2.carbon.user.core.util.DatabaseUtil; @@ -552,6 +553,169 @@ public void test194GetUserListWithIDCondition() throws UserStoreException { assertEquals(0, admin.getUserListWithID(expressionCondition, null, null, 10, 2, null, null).size()); } + public void test194GetUserListWithIDConditionCursor() throws Exception { + + // Current user list admin, user1WithID, user2, user2WithID, user3, user3WithID, user4, user4WithID. + + // userName sw use. + ExpressionCondition expressionCondition = new ExpressionCondition(ExpressionOperation.SW.toString(), + ExpressionAttribute.USERNAME.toString(), "use"); + ExpressionCondition noConditions = new ExpressionCondition(ExpressionOperation.SW.toString(), + ExpressionAttribute.USERNAME.toString(), ""); + + //Making a List of expressionConditions for multi-attribute filtering. + // userName co WithID. + ExpressionCondition expressionCondition2 = new ExpressionCondition(ExpressionOperation.CO.toString(), + ExpressionAttribute.USERNAME.toString(), "WithID"); + // userName sw use and userName co WithID. + OperationalCondition multiFiltering = + new OperationalCondition("AND", expressionCondition, expressionCondition2); + + // No filtering. + assertEquals(8, admin.getUserListWithID(noConditions, null, null, 10, "", "NEXT", null, null).size()); + + // Single userName filtering initial query (no cursor). + assertEquals(7, admin.getUserListWithID(expressionCondition, null, null, 10, "", "NEXT", null, null).size()); + + // Single userName filtering - Forwards cursor pagination. + assertEquals(4, admin.getUserListWithID(expressionCondition, null, null, 10, "user2WithID", "NEXT", null, null) + .size()); + + // Single userName filtering - Backwards cursor pagination. + assertEquals(2, admin.getUserListWithID(expressionCondition, null, null, 10, "user2WithID", "PREVIOUS", + null, null).size()); + + // Multiple filter conditions with userName attribute - Forwards cursor pagination. + assertEquals(4, admin.getUserListWithID(multiFiltering, null, null, 10, "", "NEXT", null, null).size()); + + // Multiple filter conditions with userName attribute - Backwards cursor pagination. + assertEquals(1, admin.getUserListWithID(multiFiltering, null, null, 10, "user2WithID", "PREVIOUS", null, null) + .size()); + + // Setup for claim filtering. + Map claims = new HashMap<>(); + claims.put(ClaimTestUtil.CLAIM_URI1, "Alucard"); + claims.put(ClaimTestUtil.CLAIM_URI6, "Tepes, Alucard"); + admin.addUser("Alucard", "pass100", null, claims, null, false); + claims.clear(); + claims.put(ClaimTestUtil.CLAIM_URI1, "Akira"); + claims.put(ClaimTestUtil.CLAIM_URI6, "Toriyama, Akira"); + admin.addUser("Akira", "pass100", null, claims, null, false); + claims.clear(); + claims.put(ClaimTestUtil.CLAIM_URI1, "Alphonse"); + claims.put(ClaimTestUtil.CLAIM_URI6, "Elrich, Alphonse"); + admin.addUser("Alphonse", "pass100", null, claims, null, false); + claims.clear(); + claims.put(ClaimTestUtil.CLAIM_URI1, "Annie"); + claims.put(ClaimTestUtil.CLAIM_URI6, "Leonhart, Annie"); + admin.addUser("Annie", "pass100", null, claims, null, false); + + // New user list: admin, Akira, Alphonse, Alucard, Annie, user1WithID, user2, user2WithID, user3, user3WithID, + // user4, user4WithID. + // Having claims: Akira, Alphonse, Alucard, Annie. + + // givenname sw A. + ExpressionCondition claimFiltering1 = new ExpressionCondition(ExpressionOperation.SW.toString(), "attr1", "A"); + // fullname sw T. + ExpressionCondition claimFiltering2 = new ExpressionCondition(ExpressionOperation.SW.toString(), "fullname", + "T"); + // givenname sw A and fullname sw T. + OperationalCondition multiClaimFiltering = new OperationalCondition("AND", claimFiltering1, claimFiltering2); + + // Single claim filtering forward cursor pagination. + assertEquals(4, admin.getUserListWithID(claimFiltering1, null, null, 10, "", "NEXT", null, null).size()); + + // Single claim filtering backwards cursor pagination. + assertEquals(2, admin.getUserListWithID(claimFiltering1, null, null, 10, "Alucard", "PREVIOUS", null, null) + .size()); + + // Multi claim filtering forwards cursor pagination. + assertEquals(2, admin.getUserListWithID(multiClaimFiltering, null, null, 10, "", "NEXT", null, null).size()); + + // Multi claim filtering backwards cursor pagination. + assertEquals(1, admin.getUserListWithID(multiClaimFiltering, null, null, 10, "Alucard", "PREVIOUS", null, null) + .size()); + + // Role configuration for group filtering. + + admin.addRole("Manager", new String[]{"Alucard", "Akira", "Alphonse", "Annie", "user2", "user2WithID", "user3", + "user3WithID"}, null); + admin.addRole("HeadManager", new String[]{"Alucard", "Akira", "Alphonse", "Annie", "user2", "user2WithID", + "user3", "user4"}, null); + // New role list: admin, Internal/everyone, role1WithID, role3, role4, Manager, HeadManager. + + // groups eq Manager. + ExpressionCondition groupFiltering = new ExpressionCondition(ExpressionOperation.EQ.toString(), + ExpressionAttribute.ROLE.toString(), "Manager"); + // group eq HeadManager. + ExpressionCondition groupFiltering2 = new ExpressionCondition(ExpressionOperation.EQ.toString(), + ExpressionAttribute.ROLE.toString(), "HeadManager"); + // groups eq Manager and groups eq HeadManager. + OperationalCondition multiGroupFiltering = new OperationalCondition("AND", groupFiltering, groupFiltering2); + + // Group Filtering forwards cursor pagination. + assertEquals(3, admin.getUserListWithID(groupFiltering, null, null, 10, "user2", "NEXT", null, null).size()); + + // Group Filtering backwards cursor pagination. + assertEquals(6, admin.getUserListWithID(groupFiltering, null, null, 10, "user3", "PREVIOUS", null, null) + .size()); + + // Multi-group filtering forwards cursor pagination. + assertEquals(2, admin.getUserListWithID(multiGroupFiltering, null, null, 10, "user2", "NEXT", null, null) + .size()); + + // Multi-group filtering backwards cursor pagination. + assertEquals(3, admin.getUserListWithID(multiGroupFiltering, null, null, 10, "Annie", "PREVIOUS", null, null) + .size()); + + // groups eq Manager and groups eq HeadManager and userName sw use. + OperationalCondition groupAndUserNameFilter = + new OperationalCondition("AND", multiGroupFiltering, expressionCondition); + + // Multi-group filtering and userName filtering forward cursor pagination. + assertEquals(3, admin.getUserListWithID(groupAndUserNameFilter, null, null, 10, "", "NEXT", null, null).size()); + + // Multi-group filtering and userName filtering backwards cursor pagination. + assertEquals(1, admin.getUserListWithID(groupAndUserNameFilter, null, null, 10, "user2WithID", "PREVIOUS", + null, null).size()); + + // givenname sw A and groups eq Manager. + OperationalCondition singleClaimAndGroupFilter = + new OperationalCondition("AND", claimFiltering1, groupFiltering); + // givenname sw A and groups eq Manager and groups eq HeadManager. + OperationalCondition singleClaimAndMultiGroupFilter = + new OperationalCondition("AND", claimFiltering1, multiGroupFiltering); + // givenname sw A and fullname sw T and groups eq Manager and groups eq HeadManager. + OperationalCondition multiGroupAndMultiClaimFilter = + new OperationalCondition("AND", multiClaimFiltering, multiGroupFiltering); + + // Having claims + In the manager and HeadManager groups: Akira, Alphonse, Alucard, Annie. + + // Single group and claim filtering forward cursor pagination. + assertEquals(3, admin.getUserListWithID(singleClaimAndGroupFilter, null, null, 10, "Akira", "NEXT", null, null) + .size()); + + // Single group and claim filtering backward cursor pagination. + assertEquals(0, admin.getUserListWithID(singleClaimAndGroupFilter, null, null, 10, "Akira", "PREVIOUS", null, + null).size()); + + // Multi-group filtering and single claim filtering forward cursor pagination. + assertEquals(4, admin.getUserListWithID(singleClaimAndMultiGroupFilter, null, null, 10, "", "NEXT", null, null) + .size()); + + // Multi-group filtering and single claim filtering backward cursor pagination. + assertEquals(3, admin.getUserListWithID(singleClaimAndMultiGroupFilter, null, null, 10, "Annie", "PREVIOUS", null, + null).size()); + + // Multi-group filtering and multi-claim filtering forward cursor pagination. + assertEquals(1, admin.getUserListWithID(multiGroupAndMultiClaimFilter, null, null, 10, "Akira", "NEXT", + null, null).size()); + + // Multi-group filtering and multi-claim filtering backward cursor pagination. + assertEquals(1, admin.getUserListWithID(multiGroupAndMultiClaimFilter, null, null, 10, "Alucard", "PREVIOUS", + null, null).size()); + } + public void test195GetRoleListOfUserWithID() throws UserStoreException { assertEquals(2, admin.getRoleListOfUserWithID(userId1).size()); diff --git a/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCRealmSecondaryUserStoreTest.java b/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCRealmSecondaryUserStoreTest.java index fe6278166cb..e3f4e12ab61 100644 --- a/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCRealmSecondaryUserStoreTest.java +++ b/core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCRealmSecondaryUserStoreTest.java @@ -49,6 +49,7 @@ import org.wso2.carbon.user.core.model.ExpressionAttribute; import org.wso2.carbon.user.core.model.ExpressionCondition; import org.wso2.carbon.user.core.model.ExpressionOperation; +import org.wso2.carbon.user.core.model.OperationalCondition; import org.wso2.carbon.user.core.model.UniqueIDUserClaimSearchEntry; import org.wso2.carbon.user.core.model.UserClaimSearchEntry; import org.wso2.carbon.user.core.util.DatabaseUtil; @@ -588,6 +589,176 @@ public void test194GetUserListWithIDCondition() throws UserStoreException { null, 10, 2, null, null).size()); } + public void test194GetUserListWithIDConditionCursor() throws Exception { + + // Current user list: user1WithID, user2, user2WithID, user3, user3WithID, user4WithID. + + // userName sw use. + ExpressionCondition expressionCondition = new ExpressionCondition(ExpressionOperation.SW.toString(), + ExpressionAttribute.USERNAME.toString(), "use"); + ExpressionCondition noConditions = new ExpressionCondition(ExpressionOperation.SW.toString(), + ExpressionAttribute.USERNAME.toString(), ""); + + //Making a List of expressionConditions for multi-attribute filtering. + // userName co WithID. + ExpressionCondition expressionCondition2 = new ExpressionCondition(ExpressionOperation.CO.toString(), + ExpressionAttribute.USERNAME.toString(), "WithID"); + // userName sw use and userName co WithID. + OperationalCondition multiFiltering = + new OperationalCondition("AND", expressionCondition, expressionCondition2); + + // No filtering. + assertEquals(6, admin.getUserListWithID(noConditions, "SECONDARY", null, 10, "", "NEXT", null, null).size()); + + // Single userName filtering initial query (no cursor). + assertEquals(6, admin.getUserListWithID(expressionCondition, "SECONDARY", null, 10, "", "NEXT", null, null) + .size()); + + // Single userName filtering - Forwards cursor pagination. + assertEquals(3, admin.getUserListWithID(expressionCondition, "SECONDARY", null, 10, "user2WithID", "NEXT", + null, null).size()); + + // Single userName filtering - Backwards cursor pagination. + assertEquals(2, admin.getUserListWithID(expressionCondition, "SECONDARY", null, 10, "user2WithID", "PREVIOUS", + null, null).size()); + + // Multi userName filtering - Forwards cursor pagination. + assertEquals(4, admin.getUserListWithID(multiFiltering, "SECONDARY", null, 10, "", "NEXT", null, null).size()); + + // Multi userName filtering - Backwards cursor pagination. + assertEquals(1, admin.getUserListWithID(multiFiltering, "SECONDARY", null, 10, "user2WithID", "PREVIOUS", + null, null).size()); + + // Setup for claim filtering. + Map claims = new HashMap<>(); + claims.put(ClaimTestUtil.CLAIM_URI1, "Alucard"); + claims.put(ClaimTestUtil.CLAIM_URI6, "Tepes, Alucard"); + admin.addUser("SECONDARY/Alucard", "pass100", null, claims, null, false); + claims.clear(); + claims.put(ClaimTestUtil.CLAIM_URI1, "Akira"); + claims.put(ClaimTestUtil.CLAIM_URI6, "Toriyama, Akira"); + admin.addUser("SECONDARY/Akira", "pass100", null, claims, null, false); + claims.clear(); + claims.put(ClaimTestUtil.CLAIM_URI1, "Alphonse"); + claims.put(ClaimTestUtil.CLAIM_URI6, "Elrich, Alphonse"); + admin.addUser("SECONDARY/Alphonse", "pass100", null, claims, null, false); + claims.clear(); + claims.put(ClaimTestUtil.CLAIM_URI1, "Annie"); + claims.put(ClaimTestUtil.CLAIM_URI6, "Leonhart, Annie"); + admin.addUser("SECONDARY/Annie", "pass100", null, claims, null, false); + + // New user list: Akira, Alphonse, Alucard, Annie, user1WithID, user2, user2WithID, user3, user3WithID, + // user4WithID. + // Having claims: Akira, Alphonse, Alucard, Annie. + + // givenname sw A. + ExpressionCondition claimFiltering1 = new ExpressionCondition( + ExpressionOperation.SW.toString(), "attr1", "A"); + // fullname sw T. + ExpressionCondition claimFiltering2 = new ExpressionCondition( + ExpressionOperation.SW.toString(), "fullname", "T"); + // givenname sw A and fullname sw T. + OperationalCondition multiClaimFiltering = new OperationalCondition("AND", claimFiltering1, claimFiltering2); + + // Single claim filtering forward cursor pagination. + assertEquals(4, admin.getUserListWithID(claimFiltering1, "SECONDARY", null, 10, "", "NEXT", null, null).size()); + + // Single claim filtering backwards cursor pagination. + assertEquals(2, admin.getUserListWithID(claimFiltering1, "SECONDARY", null, 10, "Alucard", "PREVIOUS", + null, null).size()); + + // Multi claim filtering forwards cursor pagination. + assertEquals(2, admin.getUserListWithID(multiClaimFiltering, "SECONDARY", null, 10, "", "NEXT", null, null) + .size()); + + // Multi claim filtering backwards cursor pagination. + assertEquals(1, admin.getUserListWithID(multiClaimFiltering, "SECONDARY", null, 10, "Alucard", "PREVIOUS", + null, null).size()); + + // Role configuration for group filtering. + + admin.addRole("SECONDARY/Manager", new String[]{"SECONDARY/Alucard", "SECONDARY/Akira", "SECONDARY/Alphonse", + "SECONDARY/Annie", "SECONDARY/user2", "SECONDARY/user2WithID", "SECONDARY/user3", + "SECONDARY/user3WithID"}, null); + admin.addRole("SECONDARY/HeadManager", new String[]{"SECONDARY/Alucard", "SECONDARY/Akira", + "SECONDARY/Alphonse", "SECONDARY/Annie", "SECONDARY/user2", "SECONDARY/user2WithID", + "SECONDARY/user3"}, null); + // New role list: admin, Internal/everyone, role1WithID, role3, role4, Manager, HeadManager. + + // groups eq Manager. + ExpressionCondition groupFiltering = new ExpressionCondition( + ExpressionOperation.EQ.toString(), ExpressionAttribute.ROLE.toString(), "Manager"); + // group eq HeadManager. + ExpressionCondition groupFiltering2 = new ExpressionCondition( + ExpressionOperation.EQ.toString(), ExpressionAttribute.ROLE.toString(), "HeadManager"); + // groups eq Manager and groups eq HeadManager. + OperationalCondition multiGroupFiltering = new OperationalCondition("AND", groupFiltering, groupFiltering2); + + // Group Filtering forwards cursor pagination. + assertEquals(3, admin.getUserListWithID(groupFiltering, "SECONDARY", null, 10, "user2", "NEXT", null, null) + .size()); + + // Group Filtering backwards cursor pagination. + assertEquals(6, admin.getUserListWithID(groupFiltering, "SECONDARY", null, 10, "user3", "PREVIOUS", null, null) + .size()); + + // Multi-group filtering forwards cursor pagination. + assertEquals(2, admin.getUserListWithID(multiGroupFiltering, "SECONDARY", null, 10, "user2", "NEXT", null, null) + .size()); + + // Multi-group filtering backwards cursor pagination. + assertEquals(3, admin.getUserListWithID(multiGroupFiltering, "SECONDARY", null, 10, "Annie", "PREVIOUS", + null, null).size()); + + // groups eq Manager and groups eq HeadManager and userName sw use. + OperationalCondition groupAndUserNameFilter = + new OperationalCondition("AND", multiGroupFiltering, expressionCondition); + + // Multi-group filtering and userName filtering forward cursor pagination. + assertEquals(3, admin.getUserListWithID(groupAndUserNameFilter, "SECONDARY", null, 10, "", "NEXT", null, null) + .size()); + + // Multi-group filtering and userName filtering backwards cursor pagination. + assertEquals(1, admin.getUserListWithID(groupAndUserNameFilter, "SECONDARY", null, 10, "user2WithID", + "PREVIOUS", null, null).size()); + + // givenname sw A and groups eq Manager. + OperationalCondition singleClaimAndGroupFilter = + new OperationalCondition("AND", claimFiltering1, groupFiltering); + // givenname sw A and groups eq Manager and groups eq HeadManager. + OperationalCondition singleClaimAndMultiGroupFilter = + new OperationalCondition("AND", claimFiltering1, multiGroupFiltering); + // givenname sw A and fullname sw T and groups eq Manager and groups eq HeadManager. + OperationalCondition multiGroupAndMultiClaimFilter = + new OperationalCondition("AND", multiClaimFiltering, multiGroupFiltering); + + // Having claims + In the manager and HeadManager groups: Akira, Alphonse, Alucard, Annie. + + // Single group and claim filtering forward cursor pagination. + assertEquals(3, admin.getUserListWithID(singleClaimAndGroupFilter, "SECONDARY", null, 10, "Akira", "NEXT", + null, null).size()); + + // Single group and claim filtering backward cursor pagination. + assertEquals(0, admin.getUserListWithID(singleClaimAndGroupFilter, "SECONDARY", null, 10, "Akira", "PREVIOUS", + null, null).size()); + + // Multi-group filtering and single claim filtering forward cursor pagination. + assertEquals(4, admin.getUserListWithID(singleClaimAndMultiGroupFilter, "SECONDARY", null, 10, "", "NEXT", + null, null).size()); + + // Multi-group filtering and single claim filtering backward cursor pagination. + assertEquals(3, admin.getUserListWithID(singleClaimAndMultiGroupFilter, "SECONDARY", null, 10, "Annie", + "PREVIOUS", null, null).size()); + + // Multi-group filtering and multi-claim filtering forward cursor pagination. + assertEquals(1, admin.getUserListWithID(multiGroupAndMultiClaimFilter, "SECONDARY", null, 10, "Akira", "NEXT", + null, null).size()); + + // Multi-group filtering and multi-claim filtering backward cursor pagination. + assertEquals(1, admin.getUserListWithID(multiGroupAndMultiClaimFilter, "SECONDARY", null, 10, "Alucard", + "PREVIOUS", null, null).size()); + } + public void test195GetRoleListOfUserWithID() throws UserStoreException { assertEquals(2, admin.getRoleListOfUserWithID(userId1).size());