diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMRoleManager.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMRoleManager.java index e9976dc07..c2b54c048 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMRoleManager.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMRoleManager.java @@ -95,7 +95,14 @@ public SCIMRoleManager(RoleManagementService roleManagementService, String tenan } @Override - public Role createRole(Role role) throws CharonException, ConflictException, BadRequestException { + public Role createRole(Role role) throws CharonException, ConflictException, + BadRequestException, ForbiddenException { + + if (SCIMCommonUtils.isBulkRequest()) { + SCIMCommonUtils.validateAuthorizedScopes(Arrays.asList( + "internal_bulk_resource_create", "internal_bulk_role_create", + "internal_org_bulk_resource_create", "internal_org_bulk_role_create")); + } if (log.isDebugEnabled()) { log.debug("Creating role: " + role.getDisplayName()); @@ -188,7 +195,14 @@ public Role getRole(String roleID, Map requiredAttributes) } @Override - public void deleteRole(String roleID) throws CharonException, NotFoundException, BadRequestException { + public void deleteRole(String roleID) throws CharonException, NotFoundException, + BadRequestException, ForbiddenException{ + + if (SCIMCommonUtils.isBulkRequest()) { + SCIMCommonUtils.validateAuthorizedScopes(Arrays.asList( + "internal_bulk_resource_create", "internal_bulk_role_delete", + "internal_org_bulk_resource_create", "internal_org_bulk_role_delete")); + } try { roleManagementService.deleteRole(roleID, tenantDomain); @@ -373,7 +387,13 @@ private List getScimRolesList(List roles) throws BadRequest @Override public Role updateRole(Role oldRole, Role newRole) - throws BadRequestException, CharonException, ConflictException, NotFoundException { + throws BadRequestException, CharonException, ConflictException, NotFoundException, ForbiddenException { + + if (SCIMCommonUtils.isBulkRequest()) { + SCIMCommonUtils.validateAuthorizedScopes(Arrays.asList( + "internal_bulk_resource_create", "internal_bulk_role_update", + "internal_org_bulk_resource_create", "internal_org_bulk_role_update")); + } doUpdateRoleName(oldRole, newRole); doUpdateUsers(oldRole, newRole); @@ -555,6 +575,12 @@ public RolesGetResponse listRolesWithPost(SearchRequest searchRequest) public Role patchRole(String roleId, Map> patchOperations) throws BadRequestException, CharonException, ConflictException, NotFoundException, ForbiddenException { + if (SCIMCommonUtils.isBulkRequest()) { + SCIMCommonUtils.validateAuthorizedScopes(Arrays.asList( + "internal_bulk_resource_create", "internal_bulk_role_update", + "internal_org_bulk_resource_create", "internal_org_bulk_role_update")); + } + String currentRoleName = getCurrentRoleName(roleId, tenantDomain); if (log.isDebugEnabled()) { diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMRoleManagerV2.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMRoleManagerV2.java index 171942a26..c29441404 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMRoleManagerV2.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMRoleManagerV2.java @@ -122,7 +122,14 @@ public SCIMRoleManagerV2(RoleManagementService roleManagementService, String ten } public RoleV2 createRole(RoleV2 role) - throws CharonException, ConflictException, NotImplementedException, BadRequestException { + throws CharonException, ConflictException, NotImplementedException, + BadRequestException, ForbiddenException { + + if (SCIMCommonUtils.isBulkRequest()) { + SCIMCommonUtils.validateAuthorizedScopes(Arrays.asList( + "internal_bulk_resource_create", "internal_bulk_role_create", + "internal_org_bulk_resource_create", "internal_org_bulk_role_create")); + } try { // Check if the role already exists. @@ -356,7 +363,14 @@ private List convertRolePropertiesToMultiValuedComplexTy return rolePropertyValues; } - public void deleteRole(String roleID) throws CharonException, NotFoundException, BadRequestException { + public void deleteRole(String roleID) throws CharonException, NotFoundException, + BadRequestException, ForbiddenException { + + if (SCIMCommonUtils.isBulkRequest()) { + SCIMCommonUtils.validateAuthorizedScopes(Arrays.asList( + "internal_bulk_resource_create", "internal_bulk_role_delete", + "internal_org_bulk_resource_create", "internal_org_bulk_role_delete")); + } try { if (isSharedRole(roleID)) { @@ -401,7 +415,13 @@ public RolesV2GetResponse listRolesWithPost(SearchRequest searchRequest, List> patchOperations) throws BadRequestException, CharonException, ConflictException, NotFoundException, ForbiddenException { + if (SCIMCommonUtils.isBulkRequest()) { + SCIMCommonUtils.validateAuthorizedScopes(Arrays.asList( + "internal_bulk_resource_create", "internal_bulk_role_update", + "internal_org_bulk_resource_create", "internal_org_bulk_role_update")); + } + String currentRoleName = getCurrentRoleName(roleId, tenantDomain); if (LOG.isDebugEnabled()) { LOG.debug("Updating Role: " + roleId); diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java index 72eb5fe73..259e0d70c 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java @@ -224,6 +224,12 @@ public SCIMUserManager(UserStoreManager carbonUserStoreManager, public User createUser(User user, Map requiredAttributes) throws CharonException, ConflictException, BadRequestException, ForbiddenException { + if (SCIMCommonUtils.isBulkRequest()) { + SCIMCommonUtils.validateAuthorizedScopes(Arrays.asList("internal_bulk_user_create", + "internal_bulk_resource_create", "internal_org_bulk_user_create", + "internal_org_bulk_resource_create")); + } + String userStoreName = null; try { String userStoreDomainFromSP = getUserStoreDomainFromSP(); @@ -3643,7 +3649,8 @@ public void updateGroup(Group oldGroup, Group newGroup) throws CharonException, @Override public void patchGroup(String groupId, String currentGroupName, Map> patchOperations) - throws NotImplementedException, BadRequestException, CharonException, NotFoundException { + throws NotImplementedException, BadRequestException, CharonException, + NotFoundException, ForbiddenException { doPatchGroup(groupId, currentGroupName, patchOperations); } @@ -3651,14 +3658,15 @@ public void patchGroup(String groupId, String currentGroupName, Map> patchOperations, Map requiredAttributes) throws NotImplementedException, - BadRequestException, CharonException, NotFoundException { + BadRequestException, CharonException, NotFoundException, ForbiddenException { doPatchGroup(groupId, currentGroupName, patchOperations); return getGroup(groupId, requiredAttributes); } private void doPatchGroup(String groupId, String currentGroupName, Map> patchOperations) throws - NotImplementedException, BadRequestException, CharonException, NotFoundException { + NotImplementedException, BadRequestException, CharonException, NotFoundException, + ForbiddenException { if (log.isDebugEnabled()) { log.debug("Updating group: " + currentGroupName); @@ -3692,6 +3700,14 @@ private void doPatchGroup(String groupId, String currentGroupName, Map newlyAddedMemberIds = new HashSet<>(); Set deletedMemberIds = new HashSet<>(); + if (CollectionUtils.isNotEmpty(memberOperations)){ + if (SCIMCommonUtils.isBulkRequest()) { + SCIMCommonUtils.validateAuthorizedScopes(Arrays.asList("internal_bulk_resource_create", + "internal_bulk_group_update", "internal_org_bulk_resource_create", + "internal_org_bulk_group_update")); + } + } + for (PatchOperation memberOperation : memberOperations) { if (memberOperation.getValues() instanceof Map) { Map memberObject = (Map) memberOperation.getValues(); diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonConstants.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonConstants.java index b6a5b79ad..2e9e18874 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonConstants.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonConstants.java @@ -39,6 +39,9 @@ public class SCIMCommonConstants { public static final String SERVICE_PROVIDER_CONFIG = "/ServiceProviderConfig"; public static final String RESOURCE_TYPE = "/ResourceTypes"; public static final String DEFAULT = "default"; + public static final String AUTHORIZED_SCOPES = "authorizedScopes"; + public static final String NORMALIZED_REQUEST_URI = "normalizedRequestURI"; + public static final String BULK_ENDPOINT = "/scim2/Bulk"; public static final int USER = 1; public static final int GROUP = 2; diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonUtils.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonUtils.java index 639d1b90d..967f310ab 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonUtils.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonUtils.java @@ -53,6 +53,7 @@ import org.wso2.charon3.core.config.SCIMSystemSchemaExtensionBuilder; import org.wso2.charon3.core.config.SCIMUserSchemaExtensionBuilder; import org.wso2.charon3.core.exceptions.CharonException; +import org.wso2.charon3.core.exceptions.ForbiddenException; import org.wso2.charon3.core.exceptions.InternalErrorException; import org.wso2.charon3.core.schema.AttributeSchema; import org.wso2.charon3.core.schema.SCIMConstants; @@ -1072,4 +1073,28 @@ public static boolean isConsiderServerWideUserEndpointMaxLimitEnabled() { } return Boolean.parseBoolean(considerServerWideUserEndpointMaxLimitProperty); } + + public static boolean isBulkRequest() { + + if (IdentityUtil.threadLocalProperties.get().get(SCIMCommonConstants.NORMALIZED_REQUEST_URI) != null) { + return IdentityUtil.threadLocalProperties.get() + .get(SCIMCommonConstants.NORMALIZED_REQUEST_URI).toString() + .contains(SCIMCommonConstants.BULK_ENDPOINT); + } + + return false; + } + + public static void validateAuthorizedScopes(List requiredScopes) + throws ForbiddenException { + + List authorizedScopes = (List) IdentityUtil.threadLocalProperties.get().get( + SCIMCommonConstants.AUTHORIZED_SCOPES); + + if (authorizedScopes != null && + requiredScopes.stream().noneMatch(authorizedScopes::contains)) { + throw new ForbiddenException( + "Operation is not permitted. You do not have permissions to make this request."); + } + } } diff --git a/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMRoleManagerTest.java b/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMRoleManagerTest.java index 65796a7f4..7f882318f 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMRoleManagerTest.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMRoleManagerTest.java @@ -43,6 +43,7 @@ import org.wso2.charon3.core.exceptions.BadRequestException; import org.wso2.charon3.core.exceptions.CharonException; import org.wso2.charon3.core.exceptions.ConflictException; +import org.wso2.charon3.core.exceptions.ForbiddenException; import org.wso2.charon3.core.exceptions.NotFoundException; import org.wso2.charon3.core.exceptions.NotImplementedException; import org.wso2.charon3.core.objects.Group; @@ -236,7 +237,7 @@ public Object[][] dataProviderForCreateRolePositive() { @Test(dataProvider = "dataProviderForCreateRolePositive") public void testCreateRolePositive(String roleId, String roleDisplayName, String tenantDomain) throws IdentityRoleManagementException, BadRequestException, CharonException, ConflictException, - OrganizationManagementException { + OrganizationManagementException, ForbiddenException { Role role = getDummyRole(roleId, roleDisplayName); when(mockRoleManagementService.addRole(nullable(String.class), anyList(), anyList(), @@ -388,7 +389,8 @@ public Object[][] dataProviderForDeleteRolePositive() { @Test(dataProvider = "dataProviderForDeleteRolePositive") public void testDeleteRolePositive(String roleId, String tenantDomain) - throws IdentityRoleManagementException, NotFoundException, BadRequestException, CharonException { + throws IdentityRoleManagementException, NotFoundException, BadRequestException, + CharonException, ForbiddenException { doNothing().when(mockRoleManagementService).deleteRole(roleId, tenantDomain); SCIMRoleManager roleManager = new SCIMRoleManager(mockRoleManagementService, tenantDomain); @@ -648,7 +650,7 @@ public Object[][] dataProviderForUpdateRoleUpdateRoleName() { public void testUpdateRoleUpdateRoleName(String roleId, String oldRoleName, String newRoleName, String tenantDomain, String type) throws IdentityRoleManagementException, BadRequestException, CharonException, ConflictException, - NotFoundException { + NotFoundException, ForbiddenException { RoleBasicInfo roleBasicInfo = new RoleBasicInfo(roleId, newRoleName); Role[] oldAndNewRoles = getOldAndNewRoleDummies(roleId, oldRoleName, newRoleName, type); @@ -714,7 +716,7 @@ public Object[][] dataProviderForUpdateRoleUpdateRoleNameThrowingErrors() { public void testUpdateRoleUpdateRoleNameThrowingErrors(String roleId, String oldRoleName, String newRoleName, String tenantDomain, String sError) throws IdentityRoleManagementException, BadRequestException, CharonException, ConflictException, - NotFoundException { + NotFoundException, ForbiddenException { Role[] oldAndNewRoles = getOldAndNewRoleDummies(roleId, oldRoleName, newRoleName); @@ -767,7 +769,7 @@ public Object[][] dataProviderForUpdateRoleUpdateUserListOfRoleThrowingErrors() public void testUpdateRoleUpdateUserListOfRoleThrowingErrors(String roleId, String oldRoleName, String newRoleName, String tenantDomain, String type, String sError) throws IdentityRoleManagementException, BadRequestException, CharonException, ConflictException, - NotFoundException { + NotFoundException, ForbiddenException { RoleBasicInfo roleBasicInfo = new RoleBasicInfo(roleId, newRoleName); Role[] oldAndNewRoles = getOldAndNewRoleDummies(roleId, oldRoleName, newRoleName, type); @@ -813,7 +815,7 @@ public Object[][] dataProviderForUpdateRoleUpdateGroupListOfRoleThrowingErrors() public void testUpdateRoleUpdateGroupListOfRoleThrowingErrors(String roleId, String oldRoleName, String newRoleName, String tenantDomain, String type, String sError) throws IdentityRoleManagementException, BadRequestException, CharonException, ConflictException, - NotFoundException { + NotFoundException, ForbiddenException { RoleBasicInfo roleBasicInfo = new RoleBasicInfo(roleId, newRoleName); Role[] oldAndNewRoles = getOldAndNewRoleDummies(roleId, oldRoleName, newRoleName, type); @@ -860,7 +862,7 @@ public void testRoleUpdatePermissionListOfRoleThrowingErrors(String roleId, Stri String tenantDomain, String permissionType, String sError) throws IdentityRoleManagementException, BadRequestException, CharonException, - ConflictException, NotFoundException { + ConflictException, NotFoundException, ForbiddenException { RoleBasicInfo roleBasicInfo = new RoleBasicInfo(roleId, newRoleName); Role[] oldAndNewRoles = getOldAndNewRoleDummies(roleId, oldRoleName, newRoleName, permissionType);