1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 16:12:49 -05:00

[AC-1139] Resolved conflict when resolving operations between CollectionOperations and BulkCollectionOperations

This commit is contained in:
Rui Tome
2023-11-25 13:06:45 +00:00
parent 95e6211ab9
commit 0229165849
7 changed files with 844 additions and 196 deletions

View File

@ -198,7 +198,7 @@ public class CollectionsController : Controller
var collection = model.ToCollection(orgId); var collection = model.ToCollection(orgId);
var authorized = UseFlexibleCollections var authorized = UseFlexibleCollections
? (await _authorizationService.AuthorizeAsync(User, collection, CollectionOperations.Create)).Succeeded ? (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Create)).Succeeded
: await CanCreateCollection(orgId, collection.Id) || await CanEditCollectionAsync(orgId, collection.Id); : await CanCreateCollection(orgId, collection.Id) || await CanEditCollectionAsync(orgId, collection.Id);
if (!authorized) if (!authorized)
{ {
@ -269,7 +269,7 @@ 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, CollectionOperations.ModifyAccess); var result = await _authorizationService.AuthorizeAsync(User, collections, BulkCollectionOperations.ModifyAccess);
if (!result.Succeeded) if (!result.Succeeded)
{ {
@ -311,7 +311,7 @@ public class CollectionsController : Controller
{ {
// New flexible collections logic // New flexible collections logic
var collections = await _collectionRepository.GetManyByManyIdsAsync(model.Ids); var collections = await _collectionRepository.GetManyByManyIdsAsync(model.Ids);
var result = await _authorizationService.AuthorizeAsync(User, collections, CollectionOperations.Delete); var result = await _authorizationService.AuthorizeAsync(User, collections, BulkCollectionOperations.Delete);
if (!result.Succeeded) if (!result.Succeeded)
{ {
throw new NotFoundException(); throw new NotFoundException();
@ -496,7 +496,7 @@ public class CollectionsController : Controller
private async Task<CollectionResponseModel> Get_vNext(Guid collectionId) private async Task<CollectionResponseModel> Get_vNext(Guid collectionId)
{ {
var collection = await _collectionRepository.GetByIdAsync(collectionId); var collection = await _collectionRepository.GetByIdAsync(collectionId);
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, CollectionOperations.Read)).Succeeded; var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Read)).Succeeded;
if (!authorized) if (!authorized)
{ {
throw new NotFoundException(); throw new NotFoundException();
@ -509,7 +509,7 @@ public class CollectionsController : Controller
{ {
// New flexible collections logic // New flexible collections logic
var (collection, access) = await _collectionRepository.GetByIdWithAccessAsync(id); var (collection, access) = await _collectionRepository.GetByIdWithAccessAsync(id);
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, CollectionOperations.Read)).Succeeded; var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Read)).Succeeded;
if (!authorized) if (!authorized)
{ {
throw new NotFoundException(); throw new NotFoundException();
@ -570,7 +570,7 @@ public class CollectionsController : Controller
private async Task<IEnumerable<SelectionReadOnlyResponseModel>> GetUsers_vNext(Guid id) private async Task<IEnumerable<SelectionReadOnlyResponseModel>> GetUsers_vNext(Guid id)
{ {
var collection = await _collectionRepository.GetByIdAsync(id); var collection = await _collectionRepository.GetByIdAsync(id);
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, CollectionOperations.ReadAccess)).Succeeded; var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ReadAccess)).Succeeded;
if (!authorized) if (!authorized)
{ {
throw new NotFoundException(); throw new NotFoundException();
@ -584,7 +584,7 @@ public class CollectionsController : Controller
private async Task<CollectionResponseModel> Put_vNext(Guid id, CollectionRequestModel model) private async Task<CollectionResponseModel> Put_vNext(Guid id, CollectionRequestModel model)
{ {
var collection = await _collectionRepository.GetByIdAsync(id); var collection = await _collectionRepository.GetByIdAsync(id);
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, CollectionOperations.Update)).Succeeded; var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Update)).Succeeded;
if (!authorized) if (!authorized)
{ {
throw new NotFoundException(); throw new NotFoundException();
@ -599,7 +599,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, CollectionOperations.ModifyAccess)).Succeeded; var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyAccess)).Succeeded;
if (!authorized) if (!authorized)
{ {
throw new NotFoundException(); throw new NotFoundException();
@ -611,7 +611,7 @@ public class CollectionsController : Controller
private async Task Delete_vNext(Guid id) private async Task Delete_vNext(Guid id)
{ {
var collection = await _collectionRepository.GetByIdAsync(id); var collection = await _collectionRepository.GetByIdAsync(id);
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, CollectionOperations.Delete)).Succeeded; var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Delete)).Succeeded;
if (!authorized) if (!authorized)
{ {
throw new NotFoundException(); throw new NotFoundException();
@ -623,7 +623,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, CollectionOperations.ModifyAccess)).Succeeded; var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyAccess)).Succeeded;
if (!authorized) if (!authorized)
{ {
throw new NotFoundException(); throw new NotFoundException();

View File

@ -122,8 +122,8 @@ public static class ServiceCollectionExtensions
public static void AddAuthorizationHandlers(this IServiceCollection services) public static void AddAuthorizationHandlers(this IServiceCollection services)
{ {
services.AddScoped<IAuthorizationHandler, CollectionAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, BulkCollectionAuthorizationHandler>(); services.AddScoped<IAuthorizationHandler, BulkCollectionAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, CollectionAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, GroupAuthorizationHandler>(); services.AddScoped<IAuthorizationHandler, GroupAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, OrganizationUserAuthorizationHandler>(); services.AddScoped<IAuthorizationHandler, OrganizationUserAuthorizationHandler>();
} }

View File

@ -15,7 +15,7 @@ namespace Bit.Api.Vault.AuthorizationHandlers.Collections;
/// Handles authorization logic for Collection objects, including access permissions for users and groups. /// Handles authorization logic for Collection objects, including access permissions for users and groups.
/// This uses new logic implemented in the Flexible Collections initiative. /// This uses new logic implemented in the Flexible Collections initiative.
/// </summary> /// </summary>
public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<CollectionOperationRequirement, Collection> public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkCollectionOperationRequirement, Collection>
{ {
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly ICollectionRepository _collectionRepository; private readonly ICollectionRepository _collectionRepository;
@ -35,7 +35,7 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<Colle
} }
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
CollectionOperationRequirement requirement, ICollection<Collection>? resources) BulkCollectionOperationRequirement requirement, ICollection<Collection>? resources)
{ {
if (!UseFlexibleCollections) if (!UseFlexibleCollections)
{ {
@ -69,27 +69,30 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<Colle
switch (requirement) switch (requirement)
{ {
case not null when requirement == CollectionOperations.Create: case not null when requirement == BulkCollectionOperations.Create:
await CanCreateAsync(context, requirement, org); await CanCreateAsync(context, requirement, org);
break; break;
case not null when requirement == CollectionOperations.Read: case not null when requirement == BulkCollectionOperations.Read:
case not null when requirement == CollectionOperations.ReadAccess:
await CanReadAsync(context, requirement, resources, org); await CanReadAsync(context, requirement, resources, org);
break; break;
case not null when requirement == CollectionOperations.Delete: case not null when requirement == BulkCollectionOperations.ReadAccess:
await CanDeleteAsync(context, requirement, resources, org); await CanReadAccessAsync(context, requirement, resources, org);
break; break;
case not null when requirement == CollectionOperations.Update: case not null when requirement == BulkCollectionOperations.Update:
case not null when requirement == CollectionOperations.ModifyAccess: case not null when requirement == BulkCollectionOperations.ModifyAccess:
await CanManageCollectionAccessAsync(context, requirement, resources, org); await CanManageCollectionAccessAsync(context, requirement, resources, org);
break; break;
case not null when requirement == BulkCollectionOperations.Delete:
await CanDeleteAsync(context, requirement, resources, org);
break;
} }
} }
private async Task CanCreateAsync(AuthorizationHandlerContext context, CollectionOperationRequirement requirement, private async Task CanCreateAsync(AuthorizationHandlerContext context, IAuthorizationRequirement requirement,
CurrentContextOrganization? org) CurrentContextOrganization? org)
{ {
// 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
@ -110,25 +113,111 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<Colle
} }
} }
private async Task CanReadAsync(AuthorizationHandlerContext context, CollectionOperationRequirement requirement, private async Task CanReadAsync(AuthorizationHandlerContext context, IAuthorizationRequirement requirement,
ICollection<Collection> targetCollections, CurrentContextOrganization org) ICollection<Collection> resources, CurrentContextOrganization? org)
{ {
if (org.Type is OrganizationUserType.Owner or OrganizationUserType.Admin || // Owners, Admins, and users with EditAnyCollection or DeleteAnyCollection permission can always read a collection
org.Permissions.EditAnyCollection || org.Permissions.DeleteAnyCollection || if (org is
await _currentContext.ProviderUserForOrgAsync(org.Id)) { LimitCollectionCreationDeletion: false } or
{ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or
{ Permissions.EditAnyCollection: true } or
{ Permissions.DeleteAnyCollection: true } or
{ Permissions.CreateNewCollections: true } or
{ Permissions.ManageUsers: true })
{ {
context.Succeed(requirement); context.Succeed(requirement);
return; return;
} }
var canManageCollections = await HasCollectionAccessAsync(targetCollections, org, requireManagePermission: false); // The acting user is a member of the target organization,
if (canManageCollections) // ensure they have access for the collection being read
if (org is not null)
{
var canManageCollections = await HasCollectionAccessAsync(resources, org, requireManagePermission: false);
if (canManageCollections)
{
context.Succeed(requirement);
return;
}
}
// Allow provider users to read collections if they are a provider for the target organization
if (await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId))
{ {
context.Succeed(requirement); context.Succeed(requirement);
} }
} }
private async Task CanDeleteAsync(AuthorizationHandlerContext context, CollectionOperationRequirement requirement, private async Task CanReadAccessAsync(AuthorizationHandlerContext context, IAuthorizationRequirement requirement,
ICollection<Collection> resources, CurrentContextOrganization? org)
{
// Owners, Admins, and users with EditAnyCollection or DeleteAnyCollection permission can always read a collection
if (org is
{ LimitCollectionCreationDeletion: false } or
{ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or
{ Permissions.EditAnyCollection: true } or
{ Permissions.DeleteAnyCollection: true } or
{ Permissions.CreateNewCollections: true })
{
context.Succeed(requirement);
return;
}
// The acting user is a member of the target organization,
// ensure they have access for the collection being read
if (org is not null)
{
var canManageCollections = await HasCollectionAccessAsync(resources, org, requireManagePermission: false);
if (canManageCollections)
{
context.Succeed(requirement);
return;
}
}
// Allow provider users to read collections if they are a provider for the target organization
if (await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId))
{
context.Succeed(requirement);
}
}
/// <summary>
/// Ensures the acting user is allowed to manage access permissions for the target collections.
/// </summary>
private async Task CanManageCollectionAccessAsync(AuthorizationHandlerContext context,
IAuthorizationRequirement requirement, ICollection<Collection> resources,
CurrentContextOrganization? org)
{
// Owners, Admins, and users with EditAnyCollection permission can always manage collection access
if (org is
{ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or
{ Permissions.EditAnyCollection: true })
{
context.Succeed(requirement);
return;
}
// The acting user is a member of the target organization,
// ensure they have manage permission for the collection being managed
if (org is not null)
{
var canManageCollections = await HasCollectionAccessAsync(resources, org, requireManagePermission: true);
if (canManageCollections)
{
context.Succeed(requirement);
return;
}
}
// Allow providers to manage collections if they are a provider for the target organization
if (await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId))
{
context.Succeed(requirement);
}
}
private async Task CanDeleteAsync(AuthorizationHandlerContext context, IAuthorizationRequirement requirement,
ICollection<Collection> resources, CurrentContextOrganization? org) 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
@ -159,41 +248,6 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<Colle
} }
} }
/// <summary>
/// Ensures the acting user is allowed to manage access permissions for the target collections.
/// </summary>
private async Task CanManageCollectionAccessAsync(AuthorizationHandlerContext context,
IAuthorizationRequirement requirement, ICollection<Collection> resources,
CurrentContextOrganization? org)
{
// Owners, Admins, and users with EditAnyCollection permission can always manage collection access
if (org is
{ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or
{ Permissions.EditAnyCollection: true })
{
context.Succeed(requirement);
return;
}
// The limit collection management setting is disabled,
// ensure acting user has manage permissions for all collections being deleted
if (org is { LimitCollectionCreationDeletion: false })
{
var canManageCollections = await HasCollectionAccessAsync(resources, org, requireManagePermission: true);
if (canManageCollections)
{
context.Succeed(requirement);
return;
}
}
// Allow providers to manage collections if they are a provider for the target organization
if (await _currentContext.ProviderUserForOrgAsync(_targetOrganizationId))
{
context.Succeed(requirement);
}
}
private async Task<bool> HasCollectionAccessAsync( private async Task<bool> HasCollectionAccessAsync(
ICollection<Collection> targetCollections, ICollection<Collection> targetCollections,
CurrentContextOrganization org, CurrentContextOrganization org,

View File

@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Authorization.Infrastructure;
namespace Bit.Api.Vault.AuthorizationHandlers.Collections;
public class BulkCollectionOperationRequirement : OperationAuthorizationRequirement { }
public static class BulkCollectionOperations
{
public static readonly BulkCollectionOperationRequirement Create = new() { Name = nameof(Create) };
public static readonly BulkCollectionOperationRequirement Read = new() { Name = nameof(Read) };
public static readonly BulkCollectionOperationRequirement ReadAccess = new() { Name = nameof(ReadAccess) };
public static readonly BulkCollectionOperationRequirement Update = new() { Name = nameof(Update) };
/// <summary>
/// The operation that represents creating, updating, or removing collection access.
/// 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>
public static readonly BulkCollectionOperationRequirement ModifyAccess = new() { Name = nameof(ModifyAccess) };
public static readonly BulkCollectionOperationRequirement Delete = new() { Name = nameof(Delete) };
}

View File

@ -6,8 +6,6 @@ public class CollectionOperationRequirement : OperationAuthorizationRequirement
{ {
public Guid OrganizationId { get; init; } public Guid OrganizationId { get; init; }
public CollectionOperationRequirement() { }
public CollectionOperationRequirement(string name, Guid organizationId) public CollectionOperationRequirement(string name, Guid organizationId)
{ {
Name = name; Name = name;
@ -17,9 +15,6 @@ public class CollectionOperationRequirement : OperationAuthorizationRequirement
public static class CollectionOperations public static class CollectionOperations
{ {
public static readonly CollectionOperationRequirement Create = new() { Name = nameof(Create) };
public static readonly CollectionOperationRequirement Read = new() { Name = nameof(Read) };
public static readonly CollectionOperationRequirement ReadAccess = new() { Name = nameof(ReadAccess) };
public static CollectionOperationRequirement ReadAll(Guid organizationId) public static CollectionOperationRequirement ReadAll(Guid organizationId)
{ {
return new CollectionOperationRequirement(nameof(ReadAll), organizationId); return new CollectionOperationRequirement(nameof(ReadAll), organizationId);
@ -28,12 +23,5 @@ public static class CollectionOperations
{ {
return new CollectionOperationRequirement(nameof(ReadAllWithAccess), organizationId); return new CollectionOperationRequirement(nameof(ReadAllWithAccess), organizationId);
} }
public static readonly CollectionOperationRequirement Update = new() { Name = nameof(Update) };
public static readonly CollectionOperationRequirement Delete = new() { Name = nameof(Delete) };
/// <summary>
/// The operation that represents creating, updating, or removing collection access.
/// 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>
public static readonly CollectionOperationRequirement ModifyAccess = new() { Name = nameof(ModifyAccess) };
} }

View File

@ -35,7 +35,7 @@ public class CollectionsControllerTests
sutProvider.GetDependency<IAuthorizationService>() sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), .AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
ExpectedCollection(), ExpectedCollection(),
Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(CollectionOperations.Create))) Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.Create)))
.Returns(AuthorizationResult.Success()); .Returns(AuthorizationResult.Success());
_ = await sutProvider.Sut.Post(orgId, collectionRequest); _ = await sutProvider.Sut.Post(orgId, collectionRequest);
@ -61,7 +61,7 @@ public class CollectionsControllerTests
sutProvider.GetDependency<IAuthorizationService>() sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), .AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
collection, collection,
Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(CollectionOperations.Update))) Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.Update)))
.Returns(AuthorizationResult.Success()); .Returns(AuthorizationResult.Success());
_ = await sutProvider.Sut.Put(collection.OrganizationId, collection.Id, collectionRequest); _ = await sutProvider.Sut.Put(collection.OrganizationId, collection.Id, collectionRequest);
@ -79,7 +79,7 @@ public class CollectionsControllerTests
sutProvider.GetDependency<IAuthorizationService>() sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), .AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
collection, collection,
Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(CollectionOperations.Update))) Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.Update)))
.Returns(AuthorizationResult.Failed()); .Returns(AuthorizationResult.Failed());
sutProvider.GetDependency<ICollectionRepository>() sutProvider.GetDependency<ICollectionRepository>()
@ -160,7 +160,7 @@ public class CollectionsControllerTests
sutProvider.GetDependency<IAuthorizationService>() sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), .AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
collections, collections,
Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(CollectionOperations.Delete))) Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.Delete)))
.Returns(AuthorizationResult.Success()); .Returns(AuthorizationResult.Success());
// Act // Act
@ -202,7 +202,7 @@ public class CollectionsControllerTests
sutProvider.GetDependency<IAuthorizationService>() sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), .AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
collections, collections,
Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(CollectionOperations.Delete))) Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.Delete)))
.Returns(AuthorizationResult.Failed()); .Returns(AuthorizationResult.Failed());
// Assert // Assert
@ -237,7 +237,7 @@ 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(CollectionOperations.ModifyAccess) r => r.Contains(BulkCollectionOperations.ModifyAccess)
)) ))
.Returns(AuthorizationResult.Success()); .Returns(AuthorizationResult.Success());
@ -251,7 +251,7 @@ public class CollectionsControllerTests
Arg.Any<ClaimsPrincipal>(), Arg.Any<ClaimsPrincipal>(),
ExpectedCollectionAccess(), ExpectedCollectionAccess(),
Arg.Is<IEnumerable<IAuthorizationRequirement>>( Arg.Is<IEnumerable<IAuthorizationRequirement>>(
r => r.Contains(CollectionOperations.ModifyAccess)) r => r.Contains(BulkCollectionOperations.ModifyAccess))
); );
await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().Received() await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().Received()
.AddAccessAsync( .AddAccessAsync(
@ -313,7 +313,7 @@ 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(CollectionOperations.ModifyAccess) r => r.Contains(BulkCollectionOperations.ModifyAccess)
)) ))
.Returns(AuthorizationResult.Failed()); .Returns(AuthorizationResult.Failed());
@ -324,7 +324,7 @@ public class CollectionsControllerTests
Arg.Any<ClaimsPrincipal>(), Arg.Any<ClaimsPrincipal>(),
ExpectedCollectionAccess(), ExpectedCollectionAccess(),
Arg.Is<IEnumerable<IAuthorizationRequirement>>( Arg.Is<IEnumerable<IAuthorizationRequirement>>(
r => r.Contains(CollectionOperations.ModifyAccess)) r => r.Contains(BulkCollectionOperations.ModifyAccess))
); );
await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().DidNotReceiveWithAnyArgs()
.AddAccessAsync(default, default, default); .AddAccessAsync(default, default, default);

View File

@ -22,35 +22,25 @@ namespace Bit.Api.Test.Vault.AuthorizationHandlers;
public class BulkCollectionAuthorizationHandlerTests public class BulkCollectionAuthorizationHandlerTests
{ {
[Theory, CollectionCustomization] [Theory, CollectionCustomization]
[BitAutoData(OrganizationUserType.User, false, true)] [BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Admin, false, false)] [BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Owner, false, false)] public async Task CanCreateAsync_WhenAdminOrOwner_Success(
[BitAutoData(OrganizationUserType.Custom, true, false)] OrganizationUserType userType,
[BitAutoData(OrganizationUserType.Owner, true, true)] Guid userId, SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
public async Task CanManageCollectionAccessAsync_Success(
OrganizationUserType userType, bool editAnyCollection, bool manageCollections,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections, ICollection<Collection> collections,
ICollection<CollectionDetails> collectionDetails,
CurrentContextOrganization organization) CurrentContextOrganization organization)
{ {
var actingUserId = Guid.NewGuid();
foreach (var collectionDetail in collectionDetails)
{
collectionDetail.Manage = manageCollections;
}
organization.Type = userType; organization.Type = userType;
organization.Permissions.EditAnyCollection = editAnyCollection; organization.LimitCollectionCreationDeletion = true;
organization.Permissions = new Permissions();
var context = new AuthorizationHandlerContext( var context = new AuthorizationHandlerContext(
new[] { CollectionOperations.ModifyAccess }, new[] { BulkCollectionOperations.Create },
new ClaimsPrincipal(), new ClaimsPrincipal(),
collections); collections);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId); sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization); sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collectionDetails);
await sutProvider.Sut.HandleAsync(context); await sutProvider.Sut.HandleAsync(context);
@ -58,24 +48,25 @@ public class BulkCollectionAuthorizationHandlerTests
} }
[Theory, CollectionCustomization] [Theory, CollectionCustomization]
[BitAutoData(OrganizationUserType.User, false, false)] [BitAutoData(true, true)]
[BitAutoData(OrganizationUserType.Admin, false, true)] [BitAutoData(false, false)]
[BitAutoData(OrganizationUserType.Owner, false, true)] public async Task CanCreateAsync_WhenCustomUserWithRequiredPermissions_Success(
[BitAutoData(OrganizationUserType.Custom, true, true)] bool createNewCollections, bool limitCollectionCreationDeletion,
public async Task CanCreateAsync_Success(
OrganizationUserType userType, bool createNewCollection, bool limitCollectionCreateDelete,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider, SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections, ICollection<Collection> collections,
CurrentContextOrganization organization) CurrentContextOrganization organization)
{ {
var actingUserId = Guid.NewGuid(); var actingUserId = Guid.NewGuid();
organization.Type = userType; organization.Type = OrganizationUserType.Custom;
organization.Permissions.CreateNewCollections = createNewCollection; organization.LimitCollectionCreationDeletion = limitCollectionCreationDeletion;
organization.LimitCollectionCreationDeletion = limitCollectionCreateDelete; organization.Permissions = new Permissions
{
CreateNewCollections = createNewCollections
};
var context = new AuthorizationHandlerContext( var context = new AuthorizationHandlerContext(
new[] { CollectionOperations.Create }, new[] { BulkCollectionOperations.Create },
new ClaimsPrincipal(), new ClaimsPrincipal(),
collections); collections);
@ -88,48 +79,693 @@ public class BulkCollectionAuthorizationHandlerTests
} }
[Theory, CollectionCustomization] [Theory, CollectionCustomization]
[BitAutoData(OrganizationUserType.User, false, false, true)] [BitAutoData(OrganizationUserType.User)]
[BitAutoData(OrganizationUserType.Admin, false, true, false)] [BitAutoData(OrganizationUserType.Custom)]
[BitAutoData(OrganizationUserType.Owner, false, true, false)] public async Task CanCreateAsync_WhenMissingPermissions_NoSuccess(
[BitAutoData(OrganizationUserType.Custom, true, true, false)] OrganizationUserType userType,
public async Task CanDeleteAsync_Success(
OrganizationUserType userType, bool deleteAnyCollection, bool limitCollectionCreateDelete, bool manageCollections,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider, SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections, ICollection<Collection> collections,
ICollection<CollectionDetails> collectionDetails,
CurrentContextOrganization organization) CurrentContextOrganization organization)
{ {
var actingUserId = Guid.NewGuid(); var actingUserId = Guid.NewGuid();
foreach (var collectionDetail in collectionDetails)
{
collectionDetail.Manage = manageCollections;
}
organization.Type = userType; organization.Type = userType;
organization.Permissions.DeleteAnyCollection = deleteAnyCollection; organization.LimitCollectionCreationDeletion = true;
organization.LimitCollectionCreationDeletion = limitCollectionCreateDelete; organization.Permissions = new Permissions
{
EditAnyCollection = false,
DeleteAnyCollection = false,
ManageGroups = false,
ManageUsers = false
};
var context = new AuthorizationHandlerContext( var context = new AuthorizationHandlerContext(
new[] { CollectionOperations.Delete }, new[] { BulkCollectionOperations.Create },
new ClaimsPrincipal(), new ClaimsPrincipal(),
collections); collections);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId); sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization); sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collectionDetails);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task CanCreateAsync_WhenMissingOrgAccess_NoSuccess(
Guid userId,
ICollection<Collection> collections,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider)
{
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.Create },
new ClaimsPrincipal(),
collections
);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(Arg.Any<Guid>()).Returns((CurrentContextOrganization)null);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory, CollectionCustomization]
[BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner)]
public async Task CanReadAsync_WhenAdminOrOwner_Success(
OrganizationUserType userType,
Guid userId, SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
CurrentContextOrganization organization)
{
organization.Type = userType;
organization.LimitCollectionCreationDeletion = true;
organization.Permissions = new Permissions();
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.Read },
new ClaimsPrincipal(),
collections);
await sutProvider.Sut.HandleAsync(context); await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded); Assert.True(context.HasSucceeded);
} }
[Theory, CollectionCustomization]
[BitAutoData(true, false, false, false, true)]
[BitAutoData(false, true, false, false, true)]
[BitAutoData(false, false, true, false, true)]
[BitAutoData(false, false, false, true, true)]
[BitAutoData(false, false, false, false, false)]
public async Task CanReadAsync_WhenCustomUserWithRequiredPermissions_Success(
bool manageUsers, bool editAnyCollection, bool deleteAnyCollection,
bool createNewCollections, bool limitCollectionCreationDeletion,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = OrganizationUserType.Custom;
organization.LimitCollectionCreationDeletion = limitCollectionCreationDeletion;
organization.Permissions = new Permissions
{
ManageUsers = manageUsers,
EditAnyCollection = editAnyCollection,
DeleteAnyCollection = deleteAnyCollection,
CreateNewCollections = createNewCollections
};
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.Read },
new ClaimsPrincipal(),
collections);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task CanReadAsync_WhenUserWithRequiredPermissions_Success(
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<CollectionDetails> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = OrganizationUserType.User;
organization.LimitCollectionCreationDeletion = false;
organization.Permissions = new Permissions();
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.Read },
new ClaimsPrincipal(),
collections);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory, CollectionCustomization]
[BitAutoData(OrganizationUserType.User)]
[BitAutoData(OrganizationUserType.Custom)]
public async Task CanReadAsync_WhenMissingPermissions_NoSuccess(
OrganizationUserType userType,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = userType;
organization.LimitCollectionCreationDeletion = true;
organization.Permissions = new Permissions
{
EditAnyCollection = false,
DeleteAnyCollection = false,
ManageGroups = false,
ManageUsers = false
};
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.Read },
new ClaimsPrincipal(),
collections);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task CanReadAsync_WhenMissingOrgAccess_NoSuccess(
Guid userId,
ICollection<Collection> collections,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(Arg.Any<Guid>()).Returns((CurrentContextOrganization)null);
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.Read },
new ClaimsPrincipal(),
collections
);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
//
[Theory, CollectionCustomization]
[BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner)]
public async Task CanReadAccessAsync_WhenAdminOrOwner_Success(
OrganizationUserType userType,
Guid userId, SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
CurrentContextOrganization organization)
{
organization.Type = userType;
organization.LimitCollectionCreationDeletion = true;
organization.Permissions = new Permissions();
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.ReadAccess },
new ClaimsPrincipal(),
collections);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory, CollectionCustomization]
[BitAutoData(true, false, false, true)]
[BitAutoData(false, true, false, true)]
[BitAutoData(false, false, true, true)]
[BitAutoData(false, false, false, false)]
public async Task CanReadAccessAsync_WhenCustomUserWithRequiredPermissions_Success(
bool editAnyCollection, bool deleteAnyCollection,
bool createNewCollections, bool limitCollectionCreationDeletion,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = OrganizationUserType.Custom;
organization.LimitCollectionCreationDeletion = limitCollectionCreationDeletion;
organization.Permissions = new Permissions
{
EditAnyCollection = editAnyCollection,
DeleteAnyCollection = deleteAnyCollection,
CreateNewCollections = createNewCollections
};
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.ReadAccess },
new ClaimsPrincipal(),
collections);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task CanReadAccessAsync_WhenUserWithRequiredPermissions_Success(
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<CollectionDetails> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = OrganizationUserType.User;
organization.LimitCollectionCreationDeletion = false;
organization.Permissions = new Permissions();
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.ReadAccess },
new ClaimsPrincipal(),
collections);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory, CollectionCustomization]
[BitAutoData(OrganizationUserType.User)]
[BitAutoData(OrganizationUserType.Custom)]
public async Task CanReadAccessAsync_WhenMissingPermissions_NoSuccess(
OrganizationUserType userType,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = userType;
organization.LimitCollectionCreationDeletion = true;
organization.Permissions = new Permissions
{
EditAnyCollection = false,
DeleteAnyCollection = false,
ManageGroups = false,
ManageUsers = false
};
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.ReadAccess },
new ClaimsPrincipal(),
collections);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task CanReadAccessAsync_WhenMissingOrgAccess_NoSuccess(
Guid userId,
ICollection<Collection> collections,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(Arg.Any<Guid>()).Returns((CurrentContextOrganization)null);
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.ReadAccess },
new ClaimsPrincipal(),
collections
);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
//
[Theory, CollectionCustomization]
[BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner)]
public async Task CanManageCollectionAccessAsync_WhenAdminOrOwner_Success(
OrganizationUserType userType,
Guid userId, SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
CurrentContextOrganization organization)
{
organization.Type = userType;
organization.LimitCollectionCreationDeletion = true;
organization.Permissions = new Permissions();
var operationsToTest = new[]
{
BulkCollectionOperations.Update, BulkCollectionOperations.ModifyAccess
};
foreach (var op in operationsToTest)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
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 CanManageCollectionAccessAsync_WithEditAnyCollectionPermission_Success(
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = OrganizationUserType.Custom;
organization.Permissions = new Permissions
{
EditAnyCollection = true
};
var operationsToTest = new[]
{
BulkCollectionOperations.Update, BulkCollectionOperations.ModifyAccess
};
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 CanManageCollectionAccessAsync_WithManageCollectionPermission_Success(
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<CollectionDetails> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = OrganizationUserType.User;
organization.Permissions = new Permissions();
foreach (var c in collections)
{
c.Manage = true;
}
var operationsToTest = new[]
{
BulkCollectionOperations.Update, BulkCollectionOperations.ModifyAccess
};
foreach (var op in operationsToTest)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
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]
[BitAutoData(OrganizationUserType.User)]
[BitAutoData(OrganizationUserType.Custom)]
public async Task CanManageCollectionAccessAsync_WhenMissingPermissions_NoSuccess(
OrganizationUserType userType,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<CollectionDetails> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = userType;
organization.LimitCollectionCreationDeletion = false;
organization.Permissions = new Permissions
{
EditAnyCollection = false,
DeleteAnyCollection = false,
ManageGroups = false,
ManageUsers = false
};
foreach (var collectionDetail in collections)
{
collectionDetail.Manage = true;
}
// Simulate one collection missing the manage permission
collections.First().Manage = false;
var operationsToTest = new[]
{
BulkCollectionOperations.Update, BulkCollectionOperations.ModifyAccess
};
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.False(context.HasSucceeded);
// Recreate the SUT to reset the mocks/dependencies between tests
sutProvider.Recreate();
}
}
[Theory, BitAutoData, CollectionCustomization]
public async Task CanManageCollectionAccessAsync_WhenMissingOrgAccess_NoSuccess(
Guid userId,
ICollection<Collection> collections,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider)
{
var operationsToTest = new[]
{
BulkCollectionOperations.Update, BulkCollectionOperations.ModifyAccess
};
foreach (var op in operationsToTest)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(Arg.Any<Guid>()).Returns((CurrentContextOrganization)null);
var context = new AuthorizationHandlerContext(
new[] { op },
new ClaimsPrincipal(),
collections
);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
// Recreate the SUT to reset the mocks/dependencies between tests
sutProvider.Recreate();
}
}
[Theory, CollectionCustomization]
[BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner)]
public async Task CanDeleteAsync_WhenAdminOrOwner_Success(
OrganizationUserType userType,
Guid userId, SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
CurrentContextOrganization organization)
{
organization.Type = userType;
organization.LimitCollectionCreationDeletion = true;
organization.Permissions = new Permissions();
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.Delete },
new ClaimsPrincipal(),
collections);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task CanDeleteAsync_WithDeleteAnyCollectionPermission_Success(
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = OrganizationUserType.Custom;
organization.LimitCollectionCreationDeletion = false;
organization.Permissions = new Permissions
{
DeleteAnyCollection = true
};
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.Delete },
new ClaimsPrincipal(),
collections);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task CanDeleteAsync_WithManageCollectionPermission_Success(
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<CollectionDetails> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = OrganizationUserType.User;
organization.Permissions = new Permissions();
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
foreach (var c in collections)
{
c.Manage = true;
}
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.Delete },
new ClaimsPrincipal(),
collections);
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory, CollectionCustomization]
[BitAutoData(OrganizationUserType.User)]
[BitAutoData(OrganizationUserType.Custom)]
public async Task CanDeleteAsync_WhenMissingPermissions_NoSuccess(
OrganizationUserType userType,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
organization.Type = userType;
organization.LimitCollectionCreationDeletion = true;
organization.Permissions = new Permissions
{
EditAnyCollection = false,
DeleteAnyCollection = false,
ManageGroups = false,
ManageUsers = false
};
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.Delete },
new ClaimsPrincipal(),
collections);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task CanDeleteAsync_WhenMissingOrgAccess_NoSuccess(
Guid userId,
ICollection<Collection> collections,
SutProvider<BulkCollectionAuthorizationHandler> sutProvider)
{
var context = new AuthorizationHandlerContext(
new[] { BulkCollectionOperations.Delete },
new ClaimsPrincipal(),
collections
);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(Arg.Any<Guid>()).Returns((CurrentContextOrganization)null);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory, BitAutoData, CollectionCustomization] [Theory, BitAutoData, CollectionCustomization]
public async Task HandleRequirementAsync_MissingUserId_Failure( public async Task HandleRequirementAsync_MissingUserId_Failure(
SutProvider<BulkCollectionAuthorizationHandler> sutProvider, SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections) ICollection<Collection> collections)
{ {
var context = new AuthorizationHandlerContext( var context = new AuthorizationHandlerContext(
new[] { CollectionOperations.Create }, new[] { BulkCollectionOperations.Create },
new ClaimsPrincipal(), new ClaimsPrincipal(),
collections collections
); );
@ -153,7 +789,7 @@ public class BulkCollectionAuthorizationHandlerTests
collections.First().OrganizationId = Guid.NewGuid(); collections.First().OrganizationId = Guid.NewGuid();
var context = new AuthorizationHandlerContext( var context = new AuthorizationHandlerContext(
new[] { CollectionOperations.Create }, new[] { BulkCollectionOperations.Create },
new ClaimsPrincipal(), new ClaimsPrincipal(),
collections collections
); );
@ -165,51 +801,38 @@ public class BulkCollectionAuthorizationHandlerTests
sutProvider.GetDependency<ICurrentContext>().DidNotReceiveWithAnyArgs().GetOrganization(default); sutProvider.GetDependency<ICurrentContext>().DidNotReceiveWithAnyArgs().GetOrganization(default);
} }
[Theory, BitAutoData, CollectionCustomization]
public async Task HandleRequirementAsync_MissingOrg_NoSuccess(
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections)
{
var actingUserId = Guid.NewGuid();
var context = new AuthorizationHandlerContext(
new[] { CollectionOperations.Create },
new ClaimsPrincipal(),
collections
);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(Arg.Any<Guid>()).Returns((CurrentContextOrganization)null);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
}
[Theory, BitAutoData, CollectionCustomization] [Theory, BitAutoData, CollectionCustomization]
public async Task HandleRequirementAsync_Provider_Success( public async Task HandleRequirementAsync_Provider_Success(
SutProvider<BulkCollectionAuthorizationHandler> sutProvider, SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections) ICollection<Collection> collections)
{ {
var actingUserId = Guid.NewGuid();
var orgId = collections.First().OrganizationId;
var operationsToTest = new[] var operationsToTest = new[]
{ {
CollectionOperations.Create, CollectionOperations.Delete, CollectionOperations.ModifyAccess BulkCollectionOperations.Create,
BulkCollectionOperations.Read,
BulkCollectionOperations.ReadAccess,
BulkCollectionOperations.Update,
BulkCollectionOperations.ModifyAccess,
BulkCollectionOperations.Delete,
}; };
foreach (var op in operationsToTest) foreach (var op in operationsToTest)
{ {
var actingUserId = Guid.NewGuid(); sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(orgId).Returns((CurrentContextOrganization)null);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(true);
var context = new AuthorizationHandlerContext( var context = new AuthorizationHandlerContext(
new[] { op }, new[] { op },
new ClaimsPrincipal(), new ClaimsPrincipal(),
collections collections
); );
var orgId = collections.First().OrganizationId;
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(orgId).Returns((CurrentContextOrganization)null);
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(true);
await sutProvider.Sut.HandleAsync(context); await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded); Assert.True(context.HasSucceeded);
await sutProvider.GetDependency<ICurrentContext>().Received().ProviderUserForOrgAsync(orgId); await sutProvider.GetDependency<ICurrentContext>().Received().ProviderUserForOrgAsync(orgId);
@ -217,41 +840,4 @@ public class BulkCollectionAuthorizationHandlerTests
sutProvider.Recreate(); sutProvider.Recreate();
} }
} }
[Theory, BitAutoData, CollectionCustomization]
public async Task CanManageCollectionAccessAsync_MissingManageCollectionPermission_NoSuccess(
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
ICollection<Collection> collections,
ICollection<CollectionDetails> collectionDetails,
CurrentContextOrganization organization)
{
var actingUserId = Guid.NewGuid();
foreach (var collectionDetail in collectionDetails)
{
collectionDetail.Manage = true;
}
// Simulate one collection missing the manage permission
collectionDetails.First().Manage = false;
// Ensure the user is not an owner/admin and does not have edit any collection permission
organization.Type = OrganizationUserType.User;
organization.Permissions.EditAnyCollection = false;
var context = new AuthorizationHandlerContext(
new[] { CollectionOperations.ModifyAccess },
new ClaimsPrincipal(),
collections
);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().GetOrganization(Arg.Any<Guid>()).Returns(organization);
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collectionDetails);
await sutProvider.Sut.HandleAsync(context);
Assert.False(context.HasSucceeded);
sutProvider.GetDependency<ICurrentContext>().ReceivedWithAnyArgs().GetOrganization(default);
await sutProvider.GetDependency<ICollectionRepository>().ReceivedWithAnyArgs()
.GetManyByUserIdAsync(default);
}
} }