From ea49ff7dcb39150221dafac1bff8e352cd4d49e7 Mon Sep 17 00:00:00 2001 From: Jason Ng <jcory.ng@gmail.com> Date: Tue, 7 May 2024 11:02:59 -0400 Subject: [PATCH] [AC-1121] Update authorization for orphaned collections (#4047) * update BulkCollectionAuthorizationHandler to account for orphaned collections --- .../BulkCollectionAuthorizationHandler.cs | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/src/Api/Vault/AuthorizationHandlers/Collections/BulkCollectionAuthorizationHandler.cs b/src/Api/Vault/AuthorizationHandlers/Collections/BulkCollectionAuthorizationHandler.cs index c75e529b6d..497c8b9a18 100644 --- a/src/Api/Vault/AuthorizationHandlers/Collections/BulkCollectionAuthorizationHandler.cs +++ b/src/Api/Vault/AuthorizationHandlers/Collections/BulkCollectionAuthorizationHandler.cs @@ -25,6 +25,8 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC private Guid _targetOrganizationId; private HashSet<Guid>? _managedCollectionsIds; + private HashSet<Guid>? _orphanedCollectionsIds; + public BulkCollectionAuthorizationHandler( ICurrentContext currentContext, ICollectionRepository collectionRepository, @@ -140,7 +142,7 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC // ensure they have access for the collection being read if (org is not null) { - var canManageCollections = await CanManageCollectionsAsync(resources); + var canManageCollections = await CanManageCollectionsAsync(resources, org); if (canManageCollections) { return true; @@ -167,7 +169,7 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC // ensure they have access with manage permission for the collection being read if (org is not null) { - var canManageCollections = await CanManageCollectionsAsync(resources); + var canManageCollections = await CanManageCollectionsAsync(resources, org); if (canManageCollections) { return true; @@ -202,7 +204,7 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC // ensure they have manage permission for the collection being managed if (org is not null) { - var canManageCollections = await CanManageCollectionsAsync(resources); + var canManageCollections = await CanManageCollectionsAsync(resources, org); if (canManageCollections) { return true; @@ -238,7 +240,7 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC // ensure acting user has manage permissions for all collections being deleted if (await GetOrganizationAbilityAsync(org) is { LimitCollectionCreationDeletion: false }) { - var canManageCollections = await CanManageCollectionsAsync(resources); + var canManageCollections = await CanManageCollectionsAsync(resources, org); if (canManageCollections) { return true; @@ -249,19 +251,50 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC return await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId); } - private async Task<bool> CanManageCollectionsAsync(ICollection<Collection> targetCollections) + private async Task<bool> CanManageCollectionsAsync(ICollection<Collection> targetCollections, + CurrentContextOrganization? org) { if (_managedCollectionsIds == null) { var allUserCollections = await _collectionRepository .GetManyByUserIdAsync(_currentContext.UserId!.Value, useFlexibleCollections: true); - _managedCollectionsIds = allUserCollections + + var managedCollectionIds = allUserCollections .Where(c => c.Manage) - .Select(c => c.Id) + .Select(c => c.Id); + + _managedCollectionsIds = managedCollectionIds .ToHashSet(); } - return targetCollections.All(tc => _managedCollectionsIds.Contains(tc.Id)); + var canManageTargetCollections = targetCollections.All(tc => _managedCollectionsIds.Contains(tc.Id)); + + // The user can manage all target collections, stop here, return true. + if (canManageTargetCollections) + { + return true; + } + + // The user is not assigned to manage all target collections + // If the user is an Owner/Admin/Custom user with edit, check if any targets are orphaned collections + if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true })) + { + // User is not allowed to manage orphaned collections + return false; + } + + if (_orphanedCollectionsIds == null) + { + var orgCollections = await _collectionRepository.GetManyByOrganizationIdWithAccessAsync(_targetOrganizationId); + + // Orphaned collections are collections that have no users or groups with manage permissions + _orphanedCollectionsIds = orgCollections.Where(c => + !c.Item2.Users.Any(u => u.Manage) && !c.Item2.Groups.Any(g => g.Manage)) + .Select(c => c.Item1.Id) + .ToHashSet(); + } + + return targetCollections.All(tc => _orphanedCollectionsIds.Contains(tc.Id) || _managedCollectionsIds.Contains(tc.Id)); } private async Task<OrganizationAbility?> GetOrganizationAbilityAsync(CurrentContextOrganization? organization)