1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 13:08:17 -05:00

[AC-2538] Limit admin access - fix ManageUsers custom permission (#4032)

* Fix issue where ManageUsers custom permission could not
  grant access to collections
* Split ModifyAccess operation to ModifyUserAccess and
  ModifyGroupAccess to reflect more granular operations
This commit is contained in:
Thomas Rittson 2024-05-01 10:06:24 +10:00 committed by GitHub
parent 3749fa6113
commit 5012d56e5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 193 additions and 89 deletions

View File

@ -199,7 +199,7 @@ public class OrganizationUsersController : Controller
{ {
var collections = await _collectionRepository.GetManyByManyIdsAsync(model.Collections.Select(a => a.Id)); var collections = await _collectionRepository.GetManyByManyIdsAsync(model.Collections.Select(a => a.Id));
var authorized = var authorized =
(await _authorizationService.AuthorizeAsync(User, collections, BulkCollectionOperations.ModifyAccess)) (await _authorizationService.AuthorizeAsync(User, collections, BulkCollectionOperations.ModifyUserAccess))
.Succeeded; .Succeeded;
if (!authorized) if (!authorized)
{ {
@ -390,7 +390,7 @@ public class OrganizationUsersController : Controller
var readonlyCollectionIds = new HashSet<Guid>(); var readonlyCollectionIds = new HashSet<Guid>();
foreach (var collection in currentCollections) foreach (var collection in currentCollections)
{ {
if (!(await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyAccess)) if (!(await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyUserAccess))
.Succeeded) .Succeeded)
{ {
readonlyCollectionIds.Add(collection.Id); readonlyCollectionIds.Add(collection.Id);

View File

@ -335,7 +335,8 @@ public class CollectionsController : Controller
throw new NotFoundException("One or more collections not found."); throw new NotFoundException("One or more collections not found.");
} }
var result = await _authorizationService.AuthorizeAsync(User, collections, BulkCollectionOperations.ModifyAccess); var result = await _authorizationService.AuthorizeAsync(User, collections,
new[] { BulkCollectionOperations.ModifyUserAccess, BulkCollectionOperations.ModifyGroupAccess });
if (!result.Succeeded) if (!result.Succeeded)
{ {
@ -686,7 +687,7 @@ public class CollectionsController : Controller
private async Task PutUsers_vNext(Guid id, IEnumerable<SelectionReadOnlyRequestModel> model) private async Task PutUsers_vNext(Guid id, IEnumerable<SelectionReadOnlyRequestModel> model)
{ {
var collection = await _collectionRepository.GetByIdAsync(id); var collection = await _collectionRepository.GetByIdAsync(id);
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyAccess)).Succeeded; var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyUserAccess)).Succeeded;
if (!authorized) if (!authorized)
{ {
throw new NotFoundException(); throw new NotFoundException();
@ -710,7 +711,7 @@ public class CollectionsController : Controller
private async Task DeleteUser_vNext(Guid id, Guid orgUserId) private async Task DeleteUser_vNext(Guid id, Guid orgUserId)
{ {
var collection = await _collectionRepository.GetByIdAsync(id); var collection = await _collectionRepository.GetByIdAsync(id);
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyAccess)).Succeeded; var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyUserAccess)).Succeeded;
if (!authorized) if (!authorized)
{ {
throw new NotFoundException(); throw new NotFoundException();

View File

@ -64,61 +64,68 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC
var org = _currentContext.GetOrganization(_targetOrganizationId); var org = _currentContext.GetOrganization(_targetOrganizationId);
var authorized = false;
switch (requirement) switch (requirement)
{ {
case not null when requirement == BulkCollectionOperations.Create: case not null when requirement == BulkCollectionOperations.Create:
await CanCreateAsync(context, requirement, org); authorized = await CanCreateAsync(org);
break; break;
case not null when requirement == BulkCollectionOperations.Read: case not null when requirement == BulkCollectionOperations.Read:
case not null when requirement == BulkCollectionOperations.ReadAccess: case not null when requirement == BulkCollectionOperations.ReadAccess:
await CanReadAsync(context, requirement, resources, org); authorized = await CanReadAsync(resources, org);
break; break;
case not null when requirement == BulkCollectionOperations.ReadWithAccess: case not null when requirement == BulkCollectionOperations.ReadWithAccess:
await CanReadWithAccessAsync(context, requirement, resources, org); authorized = await CanReadWithAccessAsync(resources, org);
break; break;
case not null when requirement == BulkCollectionOperations.Update: case not null when requirement == BulkCollectionOperations.Update:
case not null when requirement == BulkCollectionOperations.ModifyAccess:
case not null when requirement == BulkCollectionOperations.ImportCiphers: case not null when requirement == BulkCollectionOperations.ImportCiphers:
await CanUpdateCollectionAsync(context, requirement, resources, org); authorized = await CanUpdateCollectionAsync(resources, org);
break;
case not null when requirement == BulkCollectionOperations.ModifyUserAccess:
authorized = await CanUpdateUserAccessAsync(resources, org);
break;
case not null when requirement == BulkCollectionOperations.ModifyGroupAccess:
authorized = await CanUpdateGroupAccessAsync(resources, org);
break; break;
case not null when requirement == BulkCollectionOperations.Delete: case not null when requirement == BulkCollectionOperations.Delete:
await CanDeleteAsync(context, requirement, resources, org); authorized = await CanDeleteAsync(resources, org);
break; break;
} }
if (authorized)
{
context.Succeed(requirement);
}
} }
private async Task CanCreateAsync(AuthorizationHandlerContext context, IAuthorizationRequirement requirement, private async Task<bool> CanCreateAsync(CurrentContextOrganization? org)
CurrentContextOrganization? org)
{ {
// Owners, Admins, and users with CreateNewCollections permission can always create collections // Owners, Admins, and users with CreateNewCollections permission can always create collections
if (org is if (org is
{ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or
{ Permissions.CreateNewCollections: true }) { Permissions.CreateNewCollections: true })
{ {
context.Succeed(requirement); return true;
return;
} }
// If the limit collection management setting is disabled, allow any user to create collections // If the limit collection management setting is disabled, allow any user to create collections
if (await GetOrganizationAbilityAsync(org) is { LimitCollectionCreationDeletion: false }) if (await GetOrganizationAbilityAsync(org) is { LimitCollectionCreationDeletion: false })
{ {
context.Succeed(requirement); return true;
return;
} }
// Allow provider users to create collections if they are a provider for the target organization // Allow provider users to create collections if they are a provider for the target organization
if (await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId)) return await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId);
{
context.Succeed(requirement);
}
} }
private async Task CanReadAsync(AuthorizationHandlerContext context, IAuthorizationRequirement requirement, private async Task<bool> CanReadAsync(ICollection<Collection> resources, CurrentContextOrganization? org)
ICollection<Collection> resources, CurrentContextOrganization? org)
{ {
// Owners, Admins, and users with EditAnyCollection or DeleteAnyCollection permission can always read a collection // Owners, Admins, and users with EditAnyCollection or DeleteAnyCollection permission can always read a collection
if (org is if (org is
@ -126,8 +133,7 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC
{ Permissions.EditAnyCollection: true } or { Permissions.EditAnyCollection: true } or
{ Permissions.DeleteAnyCollection: true }) { Permissions.DeleteAnyCollection: true })
{ {
context.Succeed(requirement); return true;
return;
} }
// The acting user is a member of the target organization, // The acting user is a member of the target organization,
@ -137,20 +143,15 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC
var canManageCollections = await CanManageCollectionsAsync(resources); var canManageCollections = await CanManageCollectionsAsync(resources);
if (canManageCollections) if (canManageCollections)
{ {
context.Succeed(requirement); return true;
return;
} }
} }
// Allow provider users to read collections if they are a provider for the target organization // Allow provider users to read collections if they are a provider for the target organization
if (await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId)) return await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId);
{
context.Succeed(requirement);
}
} }
private async Task CanReadWithAccessAsync(AuthorizationHandlerContext context, IAuthorizationRequirement requirement, private async Task<bool> CanReadWithAccessAsync(ICollection<Collection> resources, CurrentContextOrganization? org)
ICollection<Collection> resources, CurrentContextOrganization? org)
{ {
// Owners, Admins, and users with EditAnyCollection, DeleteAnyCollection or ManageUsers permission can always read a collection // Owners, Admins, and users with EditAnyCollection, DeleteAnyCollection or ManageUsers permission can always read a collection
if (org is if (org is
@ -159,8 +160,7 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC
{ Permissions.DeleteAnyCollection: true } or { Permissions.DeleteAnyCollection: true } or
{ Permissions.ManageUsers: true }) { Permissions.ManageUsers: true })
{ {
context.Succeed(requirement); return true;
return;
} }
// The acting user is a member of the target organization, // The acting user is a member of the target organization,
@ -170,31 +170,23 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC
var canManageCollections = await CanManageCollectionsAsync(resources); var canManageCollections = await CanManageCollectionsAsync(resources);
if (canManageCollections) if (canManageCollections)
{ {
context.Succeed(requirement); return true;
return;
} }
} }
// Allow provider users to read collections if they are a provider for the target organization // Allow provider users to read collections if they are a provider for the target organization
if (await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId)) return await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId);
{
context.Succeed(requirement);
}
} }
/// <summary> /// <summary>
/// Ensures the acting user is allowed to update the target collections or manage access permissions for them. /// Ensures the acting user is allowed to update the target collections or manage access permissions for them.
/// </summary> /// </summary>
private async Task CanUpdateCollectionAsync(AuthorizationHandlerContext context, private async Task<bool> CanUpdateCollectionAsync(ICollection<Collection> resources, CurrentContextOrganization? org)
IAuthorizationRequirement requirement, ICollection<Collection> resources,
CurrentContextOrganization? org)
{ {
// Users with EditAnyCollection permission can always update a collection // Users with EditAnyCollection permission can always update a collection
if (org is if (org is { Permissions.EditAnyCollection: true })
{ Permissions.EditAnyCollection: true })
{ {
context.Succeed(requirement); return true;
return;
} }
// If V1 is enabled, Owners and Admins can update any collection only if permitted by collection management settings // If V1 is enabled, Owners and Admins can update any collection only if permitted by collection management settings
@ -203,8 +195,7 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC
organizationAbility is { AllowAdminAccessToAllCollectionItems: true }; organizationAbility is { AllowAdminAccessToAllCollectionItems: true };
if (allowAdminAccessToAllCollectionItems && org is { Type: OrganizationUserType.Owner or OrganizationUserType.Admin }) if (allowAdminAccessToAllCollectionItems && org is { Type: OrganizationUserType.Owner or OrganizationUserType.Admin })
{ {
context.Succeed(requirement); return true;
return;
} }
// The acting user is a member of the target organization, // The acting user is a member of the target organization,
@ -214,28 +205,32 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC
var canManageCollections = await CanManageCollectionsAsync(resources); var canManageCollections = await CanManageCollectionsAsync(resources);
if (canManageCollections) if (canManageCollections)
{ {
context.Succeed(requirement); return true;
return;
} }
} }
// Allow providers to manage collections if they are a provider for the target organization // Allow providers to manage collections if they are a provider for the target organization
if (await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId)) return await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId);
{
context.Succeed(requirement);
}
} }
private async Task CanDeleteAsync(AuthorizationHandlerContext context, IAuthorizationRequirement requirement, private async Task<bool> CanUpdateUserAccessAsync(ICollection<Collection> resources, CurrentContextOrganization? org)
ICollection<Collection> resources, CurrentContextOrganization? org) {
return await CanUpdateCollectionAsync(resources, org) || org?.Permissions.ManageUsers == true;
}
private async Task<bool> CanUpdateGroupAccessAsync(ICollection<Collection> resources, CurrentContextOrganization? org)
{
return await CanUpdateCollectionAsync(resources, org) || org?.Permissions.ManageGroups == true;
}
private async Task<bool> CanDeleteAsync(ICollection<Collection> resources, CurrentContextOrganization? org)
{ {
// Owners, Admins, and users with DeleteAnyCollection permission can always delete collections // Owners, Admins, and users with DeleteAnyCollection permission can always delete collections
if (org is if (org is
{ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or
{ Permissions.DeleteAnyCollection: true }) { Permissions.DeleteAnyCollection: true })
{ {
context.Succeed(requirement); return true;
return;
} }
// Check for non-null org here: the user must be apart of the organization for this setting to take affect // Check for non-null org here: the user must be apart of the organization for this setting to take affect
@ -246,16 +241,12 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC
var canManageCollections = await CanManageCollectionsAsync(resources); var canManageCollections = await CanManageCollectionsAsync(resources);
if (canManageCollections) if (canManageCollections)
{ {
context.Succeed(requirement); return true;
return;
} }
} }
// Allow providers to delete collections if they are a provider for the target organization // Allow providers to delete collections if they are a provider for the target organization
if (await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId)) return await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId);
{
context.Succeed(requirement);
}
} }
private async Task<bool> CanManageCollectionsAsync(ICollection<Collection> targetCollections) private async Task<bool> CanManageCollectionsAsync(ICollection<Collection> targetCollections)

View File

@ -6,17 +6,31 @@ public class BulkCollectionOperationRequirement : OperationAuthorizationRequirem
public static class BulkCollectionOperations public static class BulkCollectionOperations
{ {
/// <summary>
/// Create a new collection
/// </summary>
public static readonly BulkCollectionOperationRequirement Create = new() { Name = nameof(Create) }; public static readonly BulkCollectionOperationRequirement Create = new() { Name = nameof(Create) };
public static readonly BulkCollectionOperationRequirement Read = new() { Name = nameof(Read) }; public static readonly BulkCollectionOperationRequirement Read = new() { Name = nameof(Read) };
public static readonly BulkCollectionOperationRequirement ReadAccess = new() { Name = nameof(ReadAccess) }; public static readonly BulkCollectionOperationRequirement ReadAccess = new() { Name = nameof(ReadAccess) };
public static readonly BulkCollectionOperationRequirement ReadWithAccess = new() { Name = nameof(ReadWithAccess) }; public static readonly BulkCollectionOperationRequirement ReadWithAccess = new() { Name = nameof(ReadWithAccess) };
/// <summary>
/// Update a collection, including user and group access
/// </summary>
public static readonly BulkCollectionOperationRequirement Update = new() { Name = nameof(Update) }; public static readonly BulkCollectionOperationRequirement Update = new() { Name = nameof(Update) };
/// <summary> /// <summary>
/// The operation that represents creating, updating, or removing collection access. /// Delete a collection
/// Combined together to allow for a single requirement to be used for each operation
/// as they all currently share the same underlying authorization logic.
/// </summary> /// </summary>
public static readonly BulkCollectionOperationRequirement ModifyAccess = new() { Name = nameof(ModifyAccess) };
public static readonly BulkCollectionOperationRequirement Delete = new() { Name = nameof(Delete) }; public static readonly BulkCollectionOperationRequirement Delete = new() { Name = nameof(Delete) };
/// <summary>
/// Import ciphers into a collection
/// </summary>
public static readonly BulkCollectionOperationRequirement ImportCiphers = new() { Name = nameof(ImportCiphers) }; public static readonly BulkCollectionOperationRequirement ImportCiphers = new() { Name = nameof(ImportCiphers) };
/// <summary>
/// Create, update or delete user access (CollectionUser)
/// </summary>
public static readonly BulkCollectionOperationRequirement ModifyUserAccess = new() { Name = nameof(ModifyUserAccess) };
/// <summary>
/// Create, update or delete group access (CollectionGroup)
/// </summary>
public static readonly BulkCollectionOperationRequirement ModifyGroupAccess = new() { Name = nameof(ModifyGroupAccess) };
} }

View File

@ -120,7 +120,7 @@ public class OrganizationUsersControllerTests
sutProvider.GetDependency<IAuthorizationService>() sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), .AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
Arg.Any<IEnumerable<Collection>>(), Arg.Any<IEnumerable<Collection>>(),
Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs => reqs.Contains(BulkCollectionOperations.ModifyAccess))) Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess)))
.Returns(AuthorizationResult.Success()); .Returns(AuthorizationResult.Success());
sutProvider.GetDependency<IUserService>().GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(userId); sutProvider.GetDependency<IUserService>().GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(userId);
@ -147,7 +147,7 @@ public class OrganizationUsersControllerTests
sutProvider.GetDependency<IAuthorizationService>() sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), .AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
Arg.Any<IEnumerable<Collection>>(), Arg.Any<IEnumerable<Collection>>(),
Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs => reqs.Contains(BulkCollectionOperations.ModifyAccess))) Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess)))
.Returns(AuthorizationResult.Failed()); .Returns(AuthorizationResult.Failed());
sutProvider.GetDependency<IUserService>().GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(userId); sutProvider.GetDependency<IUserService>().GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(userId);
@ -309,13 +309,13 @@ public class OrganizationUsersControllerTests
// Authorize the editedCollection // Authorize the editedCollection
sutProvider.GetDependency<IAuthorizationService>() sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), Arg.Is<Collection>(c => c.Id == editedCollectionId), .AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), Arg.Is<Collection>(c => c.Id == editedCollectionId),
Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs => reqs.Contains(BulkCollectionOperations.ModifyAccess))) Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess)))
.Returns(AuthorizationResult.Success()); .Returns(AuthorizationResult.Success());
// Do not authorize the readonly collections // Do not authorize the readonly collections
sutProvider.GetDependency<IAuthorizationService>() sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), Arg.Is<Collection>(c => c.Id == readonlyCollectionId1 || c.Id == readonlyCollectionId2), .AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), Arg.Is<Collection>(c => c.Id == readonlyCollectionId1 || c.Id == readonlyCollectionId2),
Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs => reqs.Contains(BulkCollectionOperations.ModifyAccess))) Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess)))
.Returns(AuthorizationResult.Failed()); .Returns(AuthorizationResult.Failed());
await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model);
@ -357,7 +357,7 @@ public class OrganizationUsersControllerTests
sutProvider.GetDependency<IAuthorizationService>() sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), Arg.Is<Collection>(c => collections.Contains(c)), .AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), Arg.Is<Collection>(c => collections.Contains(c)),
Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs => reqs.Contains(BulkCollectionOperations.ModifyAccess))) Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess)))
.Returns(AuthorizationResult.Failed()); .Returns(AuthorizationResult.Failed());
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model)); var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model));
@ -466,7 +466,7 @@ public class OrganizationUsersControllerTests
sutProvider.GetDependency<IAuthorizationService>() sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), Arg.Is<Collection>(c => collections.Contains(c)), .AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), Arg.Is<Collection>(c => collections.Contains(c)),
Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.ModifyAccess))) Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.ModifyUserAccess)))
.Returns(AuthorizationResult.Success()); .Returns(AuthorizationResult.Success());
} }
} }

View File

@ -345,7 +345,9 @@ public class CollectionsControllerTests
sutProvider.GetDependency<IAuthorizationService>().AuthorizeAsync( sutProvider.GetDependency<IAuthorizationService>().AuthorizeAsync(
Arg.Any<ClaimsPrincipal>(), ExpectedCollectionAccess(), Arg.Any<ClaimsPrincipal>(), ExpectedCollectionAccess(),
Arg.Is<IEnumerable<IAuthorizationRequirement>>( Arg.Is<IEnumerable<IAuthorizationRequirement>>(
r => r.Contains(BulkCollectionOperations.ModifyAccess) reqs => reqs.All(r =>
r == BulkCollectionOperations.ModifyUserAccess ||
r == BulkCollectionOperations.ModifyGroupAccess)
)) ))
.Returns(AuthorizationResult.Success()); .Returns(AuthorizationResult.Success());
@ -359,8 +361,9 @@ public class CollectionsControllerTests
Arg.Any<ClaimsPrincipal>(), Arg.Any<ClaimsPrincipal>(),
ExpectedCollectionAccess(), ExpectedCollectionAccess(),
Arg.Is<IEnumerable<IAuthorizationRequirement>>( Arg.Is<IEnumerable<IAuthorizationRequirement>>(
r => r.Contains(BulkCollectionOperations.ModifyAccess)) reqs => reqs.All(r =>
); r == BulkCollectionOperations.ModifyUserAccess ||
r == BulkCollectionOperations.ModifyGroupAccess)));
await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().Received() await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().Received()
.AddAccessAsync( .AddAccessAsync(
Arg.Is<ICollection<Collection>>(g => g.SequenceEqual(collections)), Arg.Is<ICollection<Collection>>(g => g.SequenceEqual(collections)),
@ -500,7 +503,9 @@ public class CollectionsControllerTests
sutProvider.GetDependency<IAuthorizationService>().AuthorizeAsync( sutProvider.GetDependency<IAuthorizationService>().AuthorizeAsync(
Arg.Any<ClaimsPrincipal>(), ExpectedCollectionAccess(), Arg.Any<ClaimsPrincipal>(), ExpectedCollectionAccess(),
Arg.Is<IEnumerable<IAuthorizationRequirement>>( Arg.Is<IEnumerable<IAuthorizationRequirement>>(
r => r.Contains(BulkCollectionOperations.ModifyAccess) reqs => reqs.All(r =>
r == BulkCollectionOperations.ModifyUserAccess ||
r == BulkCollectionOperations.ModifyGroupAccess)
)) ))
.Returns(AuthorizationResult.Failed()); .Returns(AuthorizationResult.Failed());
@ -511,7 +516,9 @@ public class CollectionsControllerTests
Arg.Any<ClaimsPrincipal>(), Arg.Any<ClaimsPrincipal>(),
ExpectedCollectionAccess(), ExpectedCollectionAccess(),
Arg.Is<IEnumerable<IAuthorizationRequirement>>( Arg.Is<IEnumerable<IAuthorizationRequirement>>(
r => r.Contains(BulkCollectionOperations.ModifyAccess)) reqs => reqs.All(r =>
r == BulkCollectionOperations.ModifyUserAccess ||
r == BulkCollectionOperations.ModifyGroupAccess))
); );
await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().DidNotReceiveWithAnyArgs()
.AddAccessAsync(default, default, default); .AddAccessAsync(default, default, default);

View File

@ -548,7 +548,9 @@ public class BulkCollectionAuthorizationHandlerTests
var operationsToTest = new[] var operationsToTest = new[]
{ {
BulkCollectionOperations.Update, BulkCollectionOperations.ModifyAccess BulkCollectionOperations.Update,
BulkCollectionOperations.ModifyUserAccess,
BulkCollectionOperations.ModifyGroupAccess,
}; };
foreach (var op in operationsToTest) foreach (var op in operationsToTest)
@ -586,7 +588,9 @@ public class BulkCollectionAuthorizationHandlerTests
var operationsToTest = new[] var operationsToTest = new[]
{ {
BulkCollectionOperations.Update, BulkCollectionOperations.ModifyAccess BulkCollectionOperations.Update,
BulkCollectionOperations.ModifyUserAccess,
BulkCollectionOperations.ModifyGroupAccess,
}; };
foreach (var op in operationsToTest) foreach (var op in operationsToTest)
@ -627,7 +631,9 @@ public class BulkCollectionAuthorizationHandlerTests
var operationsToTest = new[] var operationsToTest = new[]
{ {
BulkCollectionOperations.Update, BulkCollectionOperations.ModifyAccess BulkCollectionOperations.Update,
BulkCollectionOperations.ModifyUserAccess,
BulkCollectionOperations.ModifyGroupAccess,
}; };
foreach (var op in operationsToTest) foreach (var op in operationsToTest)
@ -668,7 +674,9 @@ public class BulkCollectionAuthorizationHandlerTests
var operationsToTest = new[] var operationsToTest = new[]
{ {
BulkCollectionOperations.Update, BulkCollectionOperations.ModifyAccess BulkCollectionOperations.Update,
BulkCollectionOperations.ModifyUserAccess,
BulkCollectionOperations.ModifyGroupAccess,
}; };
foreach (var op in operationsToTest) foreach (var op in operationsToTest)
@ -708,7 +716,9 @@ public class BulkCollectionAuthorizationHandlerTests
var operationsToTest = new[] var operationsToTest = new[]
{ {
BulkCollectionOperations.Update, BulkCollectionOperations.ModifyAccess BulkCollectionOperations.Update,
BulkCollectionOperations.ModifyUserAccess,
BulkCollectionOperations.ModifyGroupAccess,
}; };
foreach (var op in operationsToTest) foreach (var op in operationsToTest)
@ -760,7 +770,9 @@ public class BulkCollectionAuthorizationHandlerTests
var operationsToTest = new[] var operationsToTest = new[]
{ {
BulkCollectionOperations.Update, BulkCollectionOperations.ModifyAccess BulkCollectionOperations.Update,
BulkCollectionOperations.ModifyUserAccess,
BulkCollectionOperations.ModifyGroupAccess,
}; };
foreach (var op in operationsToTest) foreach (var op in operationsToTest)
@ -790,7 +802,9 @@ public class BulkCollectionAuthorizationHandlerTests
{ {
var operationsToTest = new[] var operationsToTest = new[]
{ {
BulkCollectionOperations.Update, BulkCollectionOperations.ModifyAccess BulkCollectionOperations.Update,
BulkCollectionOperations.ModifyUserAccess,
BulkCollectionOperations.ModifyGroupAccess,
}; };
foreach (var op in operationsToTest) foreach (var op in operationsToTest)
@ -813,6 +827,82 @@ public class BulkCollectionAuthorizationHandlerTests
} }
} }
[Theory, BitAutoData, CollectionCustomization]
public async Task CanUpdateUsers_WithManageUsersCustomPermission_Success(
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = OrganizationUserType.Custom;
organization.Permissions = new Permissions
{
ManageUsers = true
};
var operationsToTest = new[]
{
BulkCollectionOperations.ModifyUserAccess,
};
foreach (var op in operationsToTest)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
var context = new AuthorizationHandlerContext(
new[] { op },
new ClaimsPrincipal(),
collections);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
// Recreate the SUT to reset the mocks/dependencies between tests
sutProvider.Recreate();
}
}
[Theory, BitAutoData, CollectionCustomization]
public async Task CanUpdateGroups_WithManageGroupsCustomPermission_Success(
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = OrganizationUserType.Custom;
organization.Permissions = new Permissions
{
ManageGroups = true
};
var operationsToTest = new[]
{
BulkCollectionOperations.ModifyGroupAccess,
};
foreach (var op in operationsToTest)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
var context = new AuthorizationHandlerContext(
new[] { op },
new ClaimsPrincipal(),
collections);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
// Recreate the SUT to reset the mocks/dependencies between tests
sutProvider.Recreate();
}
}
[Theory, CollectionCustomization] [Theory, CollectionCustomization]
[BitAutoData(OrganizationUserType.Admin)] [BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner)] [BitAutoData(OrganizationUserType.Owner)]
@ -1023,7 +1113,8 @@ public class BulkCollectionAuthorizationHandlerTests
BulkCollectionOperations.Read, BulkCollectionOperations.Read,
BulkCollectionOperations.ReadAccess, BulkCollectionOperations.ReadAccess,
BulkCollectionOperations.Update, BulkCollectionOperations.Update,
BulkCollectionOperations.ModifyAccess, BulkCollectionOperations.ModifyUserAccess,
BulkCollectionOperations.ModifyGroupAccess,
BulkCollectionOperations.Delete, BulkCollectionOperations.Delete,
}; };