From 1d9aeb37aaa6abeb0cfa5b3b37acc77896ef7c5c Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Thu, 13 Jul 2023 11:46:01 -0500 Subject: [PATCH] [SM-707] Refactor authorization for Access Policy Commands (#2905) * Extract authorization from access policy commands * Use auto mapper to ignore unwanted properties --------- --- .../AccessPolicyAuthorizationHandler.cs | 266 ++++++ .../CreateAccessPoliciesCommand.cs | 118 +-- .../DeleteAccessPolicyCommand.cs | 94 +-- .../UpdateAccessPolicyCommand.cs | 86 +- .../SecretsManagerCollectionExtensions.cs | 4 +- .../AccessPolicyAuthorizationHandlerTests.cs | 761 ++++++++++++++++++ .../CreateAccessPoliciesCommandTests.cs | 193 +---- .../DeleteAccessPolicyCommandTests.cs | 216 +---- .../UpdateAccessPolicyCommandTests.cs | 241 +----- .../Controllers/AccessPoliciesController.cs | 74 +- .../Request/AccessPoliciesCreateRequest.cs | 56 +- .../Request/GrantedAccessPolicyRequest.cs | 6 +- .../AccessPolicyOperationRequirement.cs | 14 + .../ICreateAccessPoliciesCommand.cs | 5 +- .../Interfaces/IDeleteAccessPolicyCommand.cs | 2 +- .../Interfaces/IUpdateAccessPolicyCommand.cs | 2 +- .../SecretsManager/Models/AccessPolicy.cs | 29 +- .../AccessPoliciesControllerTests.cs | 388 +++++---- .../AccessPoliciesControllerTests.cs | 315 +++++++- 19 files changed, 1732 insertions(+), 1138 deletions(-) create mode 100644 bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandler.cs create mode 100644 bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandlerTests.cs create mode 100644 src/Core/SecretsManager/AuthorizationRequirements/AccessPolicyOperationRequirement.cs diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandler.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandler.cs new file mode 100644 index 000000000..2915b4393 --- /dev/null +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandler.cs @@ -0,0 +1,266 @@ +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Bit.Core.SecretsManager.AuthorizationRequirements; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Queries.Interfaces; +using Bit.Core.SecretsManager.Repositories; +using Microsoft.AspNetCore.Authorization; + +namespace Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies; + +public class AccessPolicyAuthorizationHandler : AuthorizationHandler +{ + private readonly ICurrentContext _currentContext; + private readonly IAccessClientQuery _accessClientQuery; + private readonly IGroupRepository _groupRepository; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly IProjectRepository _projectRepository; + private readonly IServiceAccountRepository _serviceAccountRepository; + + public AccessPolicyAuthorizationHandler(ICurrentContext currentContext, + IAccessClientQuery accessClientQuery, + IGroupRepository groupRepository, + IOrganizationUserRepository organizationUserRepository, + IProjectRepository projectRepository, + IServiceAccountRepository serviceAccountRepository) + { + _currentContext = currentContext; + _accessClientQuery = accessClientQuery; + _groupRepository = groupRepository; + _organizationUserRepository = organizationUserRepository; + _projectRepository = projectRepository; + _serviceAccountRepository = serviceAccountRepository; + } + + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + AccessPolicyOperationRequirement requirement, + BaseAccessPolicy resource) + { + switch (requirement) + { + case not null when requirement == AccessPolicyOperations.Create: + await CanCreateAccessPolicyAsync(context, requirement, resource); + break; + case not null when requirement == AccessPolicyOperations.Update: + await CanUpdateAccessPolicyAsync(context, requirement, resource); + break; + case not null when requirement == AccessPolicyOperations.Delete: + await CanDeleteAccessPolicyAsync(context, requirement, resource); + break; + default: + throw new ArgumentException("Unsupported operation requirement type provided.", + nameof(requirement)); + } + } + + private async Task CanCreateAccessPolicyAsync(AuthorizationHandlerContext context, + AccessPolicyOperationRequirement requirement, BaseAccessPolicy resource) + { + switch (resource) + { + case UserProjectAccessPolicy ap: + await CanCreateAsync(context, requirement, ap); + break; + case GroupProjectAccessPolicy ap: + await CanCreateAsync(context, requirement, ap); + break; + case ServiceAccountProjectAccessPolicy ap: + await CanCreateAsync(context, requirement, ap); + break; + case UserServiceAccountAccessPolicy ap: + await CanCreateAsync(context, requirement, ap); + break; + case GroupServiceAccountAccessPolicy ap: + await CanCreateAsync(context, requirement, ap); + break; + default: + throw new ArgumentException("Unsupported access policy type provided."); + } + } + + private async Task CanUpdateAccessPolicyAsync(AuthorizationHandlerContext context, + AccessPolicyOperationRequirement requirement, BaseAccessPolicy resource) + { + var access = await GetAccessPolicyAccessAsync(context, resource); + + if (access.Write) + { + context.Succeed(requirement); + } + } + + private async Task CanDeleteAccessPolicyAsync(AuthorizationHandlerContext context, + AccessPolicyOperationRequirement requirement, BaseAccessPolicy resource) + { + var access = await GetAccessPolicyAccessAsync(context, resource); + + if (access.Write) + { + context.Succeed(requirement); + } + } + + private async Task CanCreateAsync(AuthorizationHandlerContext context, + AccessPolicyOperationRequirement requirement, UserProjectAccessPolicy resource) + { + var user = await _organizationUserRepository.GetByIdAsync(resource.OrganizationUserId!.Value); + if (user.OrganizationId != resource.GrantedProject?.OrganizationId) + { + return; + } + + var access = await GetAccessAsync(context, resource.GrantedProject!.OrganizationId, resource.GrantedProjectId); + + if (access.Write) + { + context.Succeed(requirement); + } + } + + private async Task CanCreateAsync(AuthorizationHandlerContext context, + AccessPolicyOperationRequirement requirement, GroupProjectAccessPolicy resource) + { + var group = await _groupRepository.GetByIdAsync(resource.GroupId!.Value); + if (group.OrganizationId != resource.GrantedProject?.OrganizationId) + { + return; + } + + var access = await GetAccessAsync(context, resource.GrantedProject!.OrganizationId, resource.GrantedProjectId); + + if (access.Write) + { + context.Succeed(requirement); + } + } + + private async Task CanCreateAsync(AuthorizationHandlerContext context, + AccessPolicyOperationRequirement requirement, ServiceAccountProjectAccessPolicy resource) + { + var projectOrganizationId = resource.GrantedProject?.OrganizationId; + var serviceAccountOrgId = resource.ServiceAccount?.OrganizationId; + + if (projectOrganizationId == null) + { + var project = await _projectRepository.GetByIdAsync(resource.GrantedProjectId!.Value); + projectOrganizationId = project?.OrganizationId; + } + + if (serviceAccountOrgId == null) + { + var serviceAccount = await _serviceAccountRepository.GetByIdAsync(resource.ServiceAccountId!.Value); + serviceAccountOrgId = serviceAccount?.OrganizationId; + } + + if (!serviceAccountOrgId.HasValue || !projectOrganizationId.HasValue || + serviceAccountOrgId != projectOrganizationId) + { + return; + } + + var access = await GetAccessAsync(context, projectOrganizationId.Value, resource.GrantedProjectId, + resource.ServiceAccountId); + + if (access.Write) + { + context.Succeed(requirement); + } + } + + private async Task CanCreateAsync(AuthorizationHandlerContext context, + AccessPolicyOperationRequirement requirement, UserServiceAccountAccessPolicy resource) + { + var user = await _organizationUserRepository.GetByIdAsync(resource.OrganizationUserId!.Value); + if (user.OrganizationId != resource.GrantedServiceAccount!.OrganizationId) + { + return; + } + + var access = await GetAccessAsync(context, resource.GrantedServiceAccount!.OrganizationId, + serviceAccountIdToCheck: resource.GrantedServiceAccountId); + + if (access.Write) + { + context.Succeed(requirement); + } + } + + private async Task CanCreateAsync(AuthorizationHandlerContext context, + AccessPolicyOperationRequirement requirement, GroupServiceAccountAccessPolicy resource) + { + var group = await _groupRepository.GetByIdAsync(resource.GroupId!.Value); + if (group.OrganizationId != resource.GrantedServiceAccount!.OrganizationId) + { + return; + } + + var access = await GetAccessAsync(context, resource.GrantedServiceAccount!.OrganizationId, + serviceAccountIdToCheck: resource.GrantedServiceAccountId); + + if (access.Write) + { + context.Succeed(requirement); + } + } + + private async Task<(bool Read, bool Write)> GetAccessPolicyAccessAsync(AuthorizationHandlerContext context, + BaseAccessPolicy resource) => + resource switch + { + UserProjectAccessPolicy ap => await GetAccessAsync(context, ap.GrantedProject!.OrganizationId, + ap.GrantedProjectId), + GroupProjectAccessPolicy ap => await GetAccessAsync(context, ap.GrantedProject!.OrganizationId, + ap.GrantedProjectId), + ServiceAccountProjectAccessPolicy ap => await GetAccessAsync(context, ap.GrantedProject!.OrganizationId, + ap.GrantedProjectId), + UserServiceAccountAccessPolicy ap => await GetAccessAsync(context, ap.GrantedServiceAccount!.OrganizationId, + serviceAccountIdToCheck: ap.GrantedServiceAccountId), + GroupServiceAccountAccessPolicy ap => await GetAccessAsync(context, + ap.GrantedServiceAccount!.OrganizationId, serviceAccountIdToCheck: ap.GrantedServiceAccountId), + _ => throw new ArgumentException("Unsupported access policy type provided."), + }; + + private async Task<(bool Read, bool Write)> GetAccessAsync(AuthorizationHandlerContext context, + Guid organizationId, Guid? projectIdToCheck = null, + Guid? serviceAccountIdToCheck = null) + { + if (!_currentContext.AccessSecretsManager(organizationId)) + { + return (false, false); + } + + var (accessClient, userId) = await _accessClientQuery.GetAccessClientAsync(context.User, organizationId); + + // Only users and admins should be able to manipulate access policies + if (accessClient != AccessClientType.User && accessClient != AccessClientType.NoAccessCheck) + { + return (false, false); + } + + if (projectIdToCheck.HasValue && serviceAccountIdToCheck.HasValue) + { + var projectAccess = + await _projectRepository.AccessToProjectAsync(projectIdToCheck.Value, userId, accessClient); + var serviceAccountAccess = + await _serviceAccountRepository.AccessToServiceAccountAsync(serviceAccountIdToCheck.Value, userId, + accessClient); + return ( + projectAccess.Read && serviceAccountAccess.Read, + projectAccess.Write && serviceAccountAccess.Write); + } + + if (projectIdToCheck.HasValue) + { + return await _projectRepository.AccessToProjectAsync(projectIdToCheck.Value, userId, accessClient); + } + + if (serviceAccountIdToCheck.HasValue) + { + return await _serviceAccountRepository.AccessToServiceAccountAsync(serviceAccountIdToCheck.Value, userId, + accessClient); + } + + throw new ArgumentException("No ID to check provided."); + } +} diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs index aab7c75c2..ada5ebd04 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs @@ -1,5 +1,4 @@ -using Bit.Core.Enums; -using Bit.Core.Exceptions; +using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -9,92 +8,18 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand { private readonly IAccessPolicyRepository _accessPolicyRepository; - private readonly IProjectRepository _projectRepository; - private readonly IServiceAccountRepository _serviceAccountRepository; - public CreateAccessPoliciesCommand( - IAccessPolicyRepository accessPolicyRepository, - IProjectRepository projectRepository, - IServiceAccountRepository serviceAccountRepository) + public CreateAccessPoliciesCommand(IAccessPolicyRepository accessPolicyRepository) { _accessPolicyRepository = accessPolicyRepository; - _projectRepository = projectRepository; - _serviceAccountRepository = serviceAccountRepository; } - private static IEnumerable GetDistinctGrantedProjectIds(List accessPolicies) + public async Task> CreateManyAsync(List accessPolicies) { - var userGrantedIds = accessPolicies.OfType().Select(ap => ap.GrantedProjectId); - var groupGrantedIds = accessPolicies.OfType().Select(ap => ap.GrantedProjectId); - var saGrantedIds = accessPolicies.OfType().Select(ap => ap.GrantedProjectId); - return userGrantedIds.Concat(groupGrantedIds).Concat(saGrantedIds).Distinct(); - } - - private static IEnumerable GetDistinctGrantedServiceAccountIds(List accessPolicies) - { - var userGrantedIds = accessPolicies.OfType().Select(ap => ap.GrantedServiceAccountId); - var groupGrantedIds = accessPolicies.OfType() - .Select(ap => ap.GrantedServiceAccountId); - return userGrantedIds.Concat(groupGrantedIds).Distinct(); - } - - private static void CheckForDistinctAccessPolicies(IReadOnlyCollection accessPolicies) - { - var distinctAccessPolicies = accessPolicies.DistinctBy(baseAccessPolicy => - { - return baseAccessPolicy switch - { - UserProjectAccessPolicy ap => new Tuple(ap.OrganizationUserId, ap.GrantedProjectId), - GroupProjectAccessPolicy ap => new Tuple(ap.GroupId, ap.GrantedProjectId), - ServiceAccountProjectAccessPolicy ap => new Tuple(ap.ServiceAccountId, - ap.GrantedProjectId), - UserServiceAccountAccessPolicy ap => new Tuple(ap.OrganizationUserId, - ap.GrantedServiceAccountId), - GroupServiceAccountAccessPolicy ap => new Tuple(ap.GroupId, ap.GrantedServiceAccountId), - _ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)), - }; - }).ToList(); - - if (accessPolicies.Count != distinctAccessPolicies.Count) - { - throw new BadRequestException("Resources must be unique"); - } - } - - public async Task> CreateManyAsync(List accessPolicies, Guid userId, AccessClientType accessType) - { - CheckForDistinctAccessPolicies(accessPolicies); await CheckAccessPoliciesDoNotExistAsync(accessPolicies); - await CheckCanCreateAsync(accessPolicies, userId, accessType); return await _accessPolicyRepository.CreateManyAsync(accessPolicies); } - private async Task CheckCanCreateAsync(List accessPolicies, Guid userId, AccessClientType accessType) - { - var projectIds = GetDistinctGrantedProjectIds(accessPolicies).ToList(); - var serviceAccountIds = GetDistinctGrantedServiceAccountIds(accessPolicies).ToList(); - - if (projectIds.Any()) - { - foreach (var projectId in projectIds) - { - await CheckPermissionAsync(accessType, userId, projectId); - } - } - if (serviceAccountIds.Any()) - { - foreach (var serviceAccountId in serviceAccountIds) - { - await CheckPermissionAsync(accessType, userId, serviceAccountIdToCheck: serviceAccountId); - } - } - - if (!projectIds.Any() && !serviceAccountIds.Any()) - { - throw new BadRequestException("No granted IDs specified"); - } - } - private async Task CheckAccessPoliciesDoNotExistAsync(List accessPolicies) { foreach (var accessPolicy in accessPolicies) @@ -105,41 +30,4 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand } } } - - private async Task CheckPermissionAsync(AccessClientType accessClient, Guid userId, Guid? projectIdToCheck = null, - Guid? serviceAccountIdToCheck = null) - { - bool hasAccess; - switch (accessClient) - { - case AccessClientType.NoAccessCheck: - hasAccess = true; - break; - case AccessClientType.User: - if (projectIdToCheck.HasValue) - { - hasAccess = (await _projectRepository.AccessToProjectAsync(projectIdToCheck.Value, userId, accessClient)).Write; - } - else if (serviceAccountIdToCheck.HasValue) - { - hasAccess = - await _serviceAccountRepository.UserHasWriteAccessToServiceAccount( - serviceAccountIdToCheck.Value, userId); - } - else - { - throw new ArgumentException("No ID to check provided."); - } - - break; - default: - hasAccess = false; - break; - } - - if (!hasAccess) - { - throw new NotFoundException(); - } - } } diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommand.cs index 53955c170..9ac807ce2 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommand.cs @@ -1,8 +1,4 @@ -using Bit.Core.Context; -using Bit.Core.Enums; -using Bit.Core.Exceptions; -using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; -using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Repositories; namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; @@ -10,98 +6,14 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; public class DeleteAccessPolicyCommand : IDeleteAccessPolicyCommand { private readonly IAccessPolicyRepository _accessPolicyRepository; - private readonly ICurrentContext _currentContext; - private readonly IProjectRepository _projectRepository; - private readonly IServiceAccountRepository _serviceAccountRepository; - public DeleteAccessPolicyCommand( - IAccessPolicyRepository accessPolicyRepository, - ICurrentContext currentContext, - IProjectRepository projectRepository, - IServiceAccountRepository serviceAccountRepository) + public DeleteAccessPolicyCommand(IAccessPolicyRepository accessPolicyRepository) { - _projectRepository = projectRepository; - _serviceAccountRepository = serviceAccountRepository; _accessPolicyRepository = accessPolicyRepository; - _currentContext = currentContext; } - public async Task DeleteAsync(Guid id, Guid userId) + public async Task DeleteAsync(Guid id) { - var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id); - if (accessPolicy == null) - { - throw new NotFoundException(); - } - - if (!await IsAllowedToDeleteAsync(accessPolicy, userId)) - { - throw new NotFoundException(); - } - await _accessPolicyRepository.DeleteAsync(id); } - - private async Task IsAllowedToDeleteAsync(BaseAccessPolicy baseAccessPolicy, Guid userId) => - baseAccessPolicy switch - { - UserProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId, userId, - ap.GrantedProjectId), - GroupProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId, userId, - ap.GrantedProjectId), - ServiceAccountProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId, - userId, ap.GrantedProjectId), - UserServiceAccountAccessPolicy ap => await HasPermissionAsync( - ap.GrantedServiceAccount!.OrganizationId, - userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId), - GroupServiceAccountAccessPolicy ap => await HasPermissionAsync( - ap.GrantedServiceAccount!.OrganizationId, - userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId), - _ => throw new ArgumentException("Unsupported access policy type provided."), - }; - - private async Task HasPermissionAsync( - Guid organizationId, - Guid userId, - Guid? projectIdToCheck = null, - Guid? serviceAccountIdToCheck = null) - { - if (!_currentContext.AccessSecretsManager(organizationId)) - { - return false; - } - - var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); - var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); - - bool hasAccess; - switch (accessClient) - { - case AccessClientType.NoAccessCheck: - hasAccess = true; - break; - case AccessClientType.User: - if (projectIdToCheck.HasValue) - { - hasAccess = (await _projectRepository.AccessToProjectAsync(projectIdToCheck.Value, userId, accessClient)).Write; - } - else if (serviceAccountIdToCheck.HasValue) - { - hasAccess = - await _serviceAccountRepository.UserHasWriteAccessToServiceAccount( - serviceAccountIdToCheck.Value, userId); - } - else - { - throw new ArgumentException("No ID to check provided."); - } - - break; - default: - hasAccess = false; - break; - } - - return hasAccess; - } } diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommand.cs index b911d52e9..f014b247e 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommand.cs @@ -1,6 +1,4 @@ -using Bit.Core.Context; -using Bit.Core.Enums; -using Bit.Core.Exceptions; +using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -10,23 +8,13 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; public class UpdateAccessPolicyCommand : IUpdateAccessPolicyCommand { private readonly IAccessPolicyRepository _accessPolicyRepository; - private readonly ICurrentContext _currentContext; - private readonly IProjectRepository _projectRepository; - private readonly IServiceAccountRepository _serviceAccountRepository; - public UpdateAccessPolicyCommand( - IAccessPolicyRepository accessPolicyRepository, - ICurrentContext currentContext, - IProjectRepository projectRepository, - IServiceAccountRepository serviceAccountRepository) + public UpdateAccessPolicyCommand(IAccessPolicyRepository accessPolicyRepository) { _accessPolicyRepository = accessPolicyRepository; - _currentContext = currentContext; - _projectRepository = projectRepository; - _serviceAccountRepository = serviceAccountRepository; } - public async Task UpdateAsync(Guid id, bool read, bool write, Guid userId) + public async Task UpdateAsync(Guid id, bool read, bool write) { var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id); if (accessPolicy == null) @@ -34,78 +22,10 @@ public class UpdateAccessPolicyCommand : IUpdateAccessPolicyCommand throw new NotFoundException(); } - if (!await IsAllowedToUpdateAsync(accessPolicy, userId)) - { - throw new NotFoundException(); - } - accessPolicy.Read = read; accessPolicy.Write = write; accessPolicy.RevisionDate = DateTime.UtcNow; await _accessPolicyRepository.ReplaceAsync(accessPolicy); return accessPolicy; } - - private async Task IsAllowedToUpdateAsync(BaseAccessPolicy baseAccessPolicy, Guid userId) => - baseAccessPolicy switch - { - UserProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId, userId, - ap.GrantedProjectId), - GroupProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId, userId, - ap.GrantedProjectId), - ServiceAccountProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId, - userId, ap.GrantedProjectId), - UserServiceAccountAccessPolicy ap => await HasPermissionsAsync( - ap.GrantedServiceAccount!.OrganizationId, - userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId), - GroupServiceAccountAccessPolicy ap => await HasPermissionsAsync( - ap.GrantedServiceAccount!.OrganizationId, - userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId), - _ => throw new ArgumentException("Unsupported access policy type provided."), - }; - - private async Task HasPermissionsAsync( - Guid organizationId, - Guid userId, - Guid? projectIdToCheck = null, - Guid? serviceAccountIdToCheck = null) - { - if (!_currentContext.AccessSecretsManager(organizationId)) - { - return false; - } - - var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); - var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); - - bool hasAccess; - switch (accessClient) - { - case AccessClientType.NoAccessCheck: - hasAccess = true; - break; - case AccessClientType.User: - if (projectIdToCheck.HasValue) - { - hasAccess = (await _projectRepository.AccessToProjectAsync(projectIdToCheck.Value, userId, accessClient)).Write; - } - else if (serviceAccountIdToCheck.HasValue) - { - hasAccess = - await _serviceAccountRepository.UserHasWriteAccessToServiceAccount( - serviceAccountIdToCheck.Value, userId); - } - else - { - throw new ArgumentException("No ID to check provided."); - } - - break; - default: - hasAccess = false; - break; - } - - return hasAccess; - } } diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs index b5266e286..614bd4610 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs @@ -1,4 +1,5 @@ -using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Projects; +using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies; +using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Projects; using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Secrets; using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.ServiceAccounts; using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; @@ -29,6 +30,7 @@ public static class SecretsManagerCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandlerTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandlerTests.cs new file mode 100644 index 000000000..6f95f3c45 --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandlerTests.cs @@ -0,0 +1,761 @@ +using System.Reflection; +using System.Security.Claims; +using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies; +using Bit.Commercial.Core.Test.SecretsManager.Enums; +using Bit.Core.Context; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Bit.Core.SecretsManager.AuthorizationRequirements; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Queries.Interfaces; +using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.AspNetCore.Authorization; +using NSubstitute; +using Xunit; + +namespace Bit.Commercial.Core.Test.SecretsManager.AuthorizationHandlers.AccessPolicies; + +[SutProviderCustomize] +[ProjectCustomize] +public class AccessPolicyAuthorizationHandlerTests +{ + private static void SetupCurrentUserPermission(SutProvider sutProvider, + PermissionType permissionType, Guid organizationId, Guid userId = new()) + { + sutProvider.GetDependency().AccessSecretsManager(organizationId) + .Returns(true); + + switch (permissionType) + { + case PermissionType.RunAsAdmin: + sutProvider.GetDependency().GetAccessClientAsync(default, organizationId).ReturnsForAnyArgs( + (AccessClientType.NoAccessCheck, userId)); + break; + case PermissionType.RunAsUserWithPermission: + sutProvider.GetDependency().GetAccessClientAsync(default, organizationId).ReturnsForAnyArgs( + (AccessClientType.User, userId)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null); + } + } + + private static BaseAccessPolicy CreatePolicy(AccessPolicyType accessPolicyType, Project grantedProject, + ServiceAccount grantedServiceAccount, Guid? serviceAccountId = null) + { + switch (accessPolicyType) + { + case AccessPolicyType.UserProjectAccessPolicy: + return + new UserProjectAccessPolicy + { + Id = Guid.NewGuid(), + OrganizationUserId = Guid.NewGuid(), + Read = true, + Write = true, + GrantedProjectId = grantedProject.Id, + GrantedProject = grantedProject, + }; + case AccessPolicyType.GroupProjectAccessPolicy: + return + new GroupProjectAccessPolicy + { + Id = Guid.NewGuid(), + GroupId = Guid.NewGuid(), + GrantedProjectId = grantedProject.Id, + Read = true, + Write = true, + GrantedProject = grantedProject, + }; + case AccessPolicyType.ServiceAccountProjectAccessPolicy: + return new ServiceAccountProjectAccessPolicy + { + Id = Guid.NewGuid(), + ServiceAccountId = serviceAccountId, + GrantedProjectId = grantedProject.Id, + Read = true, + Write = true, + GrantedProject = grantedProject, + }; + case AccessPolicyType.UserServiceAccountAccessPolicy: + return + new UserServiceAccountAccessPolicy + { + Id = Guid.NewGuid(), + OrganizationUserId = Guid.NewGuid(), + Read = true, + Write = true, + GrantedServiceAccountId = grantedServiceAccount.Id, + GrantedServiceAccount = grantedServiceAccount, + }; + case AccessPolicyType.GroupServiceAccountAccessPolicy: + return new GroupServiceAccountAccessPolicy + { + Id = Guid.NewGuid(), + GroupId = Guid.NewGuid(), + GrantedServiceAccountId = grantedServiceAccount.Id, + GrantedServiceAccount = grantedServiceAccount, + Read = true, + Write = true, + }; + default: + throw new ArgumentOutOfRangeException(nameof(accessPolicyType), accessPolicyType, null); + } + } + + private static void SetupMockAccess(SutProvider sutProvider, + Guid userId, BaseAccessPolicy accessPolicy, bool read, bool write) + { + switch (accessPolicy) + { + case UserProjectAccessPolicy ap: + sutProvider.GetDependency() + .AccessToProjectAsync(ap.GrantedProjectId!.Value, userId, Arg.Any()) + .Returns((read, write)); + break; + case GroupProjectAccessPolicy ap: + sutProvider.GetDependency() + .AccessToProjectAsync(ap.GrantedProjectId!.Value, userId, Arg.Any()) + .Returns((read, write)); + break; + case UserServiceAccountAccessPolicy ap: + sutProvider.GetDependency() + .AccessToServiceAccountAsync(ap.GrantedServiceAccountId!.Value, userId, Arg.Any()) + .Returns((read, write)); + break; + case GroupServiceAccountAccessPolicy ap: + sutProvider.GetDependency() + .AccessToServiceAccountAsync(ap.GrantedServiceAccountId!.Value, userId, Arg.Any()) + .Returns((read, write)); + break; + case ServiceAccountProjectAccessPolicy ap: + sutProvider.GetDependency() + .AccessToProjectAsync(ap.GrantedProjectId!.Value, userId, Arg.Any()) + .Returns((read, write)); + sutProvider.GetDependency() + .AccessToServiceAccountAsync(ap.ServiceAccountId!.Value, userId, Arg.Any()) + .Returns((read, write)); + break; + } + } + + private static void SetupOrganizationMismatch(SutProvider sutProvider, + BaseAccessPolicy accessPolicy) + { + switch (accessPolicy) + { + case UserProjectAccessPolicy resource: + sutProvider.GetDependency() + .GetByIdAsync(resource.OrganizationUserId!.Value) + .Returns(new OrganizationUser + { + Id = resource.OrganizationUserId!.Value, + OrganizationId = Guid.NewGuid() + }); + break; + case GroupProjectAccessPolicy resource: + sutProvider.GetDependency().GetByIdAsync(resource.GroupId!.Value) + .Returns(new Group { Id = resource.GroupId!.Value, OrganizationId = Guid.NewGuid() }); + break; + case UserServiceAccountAccessPolicy resource: + sutProvider.GetDependency() + .GetByIdAsync(resource.OrganizationUserId!.Value) + .Returns(new OrganizationUser + { + Id = resource.OrganizationUserId!.Value, + OrganizationId = Guid.NewGuid() + }); + break; + case GroupServiceAccountAccessPolicy resource: + sutProvider.GetDependency().GetByIdAsync(resource.GroupId!.Value) + .Returns(new Group { Id = resource.GroupId!.Value, OrganizationId = Guid.NewGuid() }); + break; + default: + throw new ArgumentOutOfRangeException(nameof(accessPolicy), accessPolicy, null); + } + } + + private static void SetupOrganizationMatch(SutProvider sutProvider, + BaseAccessPolicy accessPolicy, Guid organizationId) + { + switch (accessPolicy) + { + case UserProjectAccessPolicy resource: + sutProvider.GetDependency() + .GetByIdAsync(resource.OrganizationUserId!.Value) + .Returns(new OrganizationUser + { + Id = resource.OrganizationUserId!.Value, + OrganizationId = organizationId + }); + break; + case GroupProjectAccessPolicy resource: + sutProvider.GetDependency().GetByIdAsync(resource.GroupId!.Value) + .Returns(new Group { Id = resource.GroupId!.Value, OrganizationId = organizationId }); + break; + case UserServiceAccountAccessPolicy resource: + sutProvider.GetDependency() + .GetByIdAsync(resource.OrganizationUserId!.Value) + .Returns(new OrganizationUser + { + Id = resource.OrganizationUserId!.Value, + OrganizationId = organizationId + }); + break; + case GroupServiceAccountAccessPolicy resource: + sutProvider.GetDependency().GetByIdAsync(resource.GroupId!.Value) + .Returns(new Group { Id = resource.GroupId!.Value, OrganizationId = organizationId }); + break; + default: + throw new ArgumentOutOfRangeException(nameof(accessPolicy), accessPolicy, null); + } + } + + [Fact] + public void AccessPolicyOperations_OnlyPublicStatic() + { + var publicStaticFields = typeof(AccessPolicyOperations).GetFields(BindingFlags.Public | BindingFlags.Static); + var allFields = typeof(AccessPolicyOperations).GetFields(); + Assert.Equal(publicStaticFields.Length, allFields.Length); + } + + [Theory] + [BitAutoData] + public async Task Handler_UnsupportedAccessPolicyOperationRequirement_Throws( + SutProvider sutProvider, UserProjectAccessPolicy resource, + ClaimsPrincipal claimsPrincipal) + { + var requirement = new AccessPolicyOperationRequirement(); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await Assert.ThrowsAsync(() => sutProvider.Sut.HandleAsync(authzContext)); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] + public async Task CanCreate_OrgMismatch_DoesNotSucceed( + AccessPolicyType accessPolicyType, + SutProvider sutProvider, + Project mockGrantedProject, + ServiceAccount mockGrantedServiceAccount, + ClaimsPrincipal claimsPrincipal) + { + var requirement = AccessPolicyOperations.Create; + var resource = CreatePolicy(accessPolicyType, mockGrantedProject, mockGrantedServiceAccount); + SetupOrganizationMismatch(sutProvider, resource); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] + public async Task CanCreate_AccessToSecretsManagerFalse_DoesNotSucceed( + AccessPolicyType accessPolicyType, + SutProvider sutProvider, + Guid organizationId, + Project mockGrantedProject, + ServiceAccount mockGrantedServiceAccount, + ClaimsPrincipal claimsPrincipal) + { + var requirement = AccessPolicyOperations.Create; + mockGrantedProject.OrganizationId = organizationId; + mockGrantedServiceAccount.OrganizationId = organizationId; + var resource = CreatePolicy(accessPolicyType, mockGrantedProject, mockGrantedServiceAccount); + SetupOrganizationMatch(sutProvider, resource, organizationId); + sutProvider.GetDependency().AccessSecretsManager(organizationId) + .Returns(false); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.GroupServiceAccountAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.GroupServiceAccountAccessPolicy)] + public async Task CanCreate_UnsupportedClientTypes_DoesNotSucceed( + AccessClientType clientType, + AccessPolicyType accessPolicyType, + SutProvider sutProvider, + Guid organizationId, + Project mockGrantedProject, + ServiceAccount mockGrantedServiceAccount, + ClaimsPrincipal claimsPrincipal) + { + var requirement = AccessPolicyOperations.Create; + mockGrantedProject.OrganizationId = organizationId; + mockGrantedServiceAccount.OrganizationId = organizationId; + var resource = CreatePolicy(accessPolicyType, mockGrantedProject, mockGrantedServiceAccount); + SetupOrganizationMatch(sutProvider, resource, organizationId); + sutProvider.GetDependency().AccessSecretsManager(organizationId) + .Returns(true); + sutProvider.GetDependency().GetAccessClientAsync(default, organizationId).ReturnsForAnyArgs( + (clientType, Guid.NewGuid())); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + public async Task CanCreate_AccessCheck( + AccessPolicyType accessPolicyType, + PermissionType permissionType, + bool read, bool write, bool expected, + SutProvider sutProvider, + Guid organizationId, + Guid userId, + Guid serviceAccountId, + Project mockGrantedProject, + ServiceAccount mockGrantedServiceAccount, + ClaimsPrincipal claimsPrincipal) + { + var requirement = AccessPolicyOperations.Create; + mockGrantedProject.OrganizationId = organizationId; + mockGrantedServiceAccount.OrganizationId = organizationId; + var resource = CreatePolicy(accessPolicyType, mockGrantedProject, mockGrantedServiceAccount, serviceAccountId); + SetupCurrentUserPermission(sutProvider, permissionType, organizationId, userId); + SetupOrganizationMatch(sutProvider, resource, organizationId); + SetupMockAccess(sutProvider, userId, resource, read, write); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.Equal(expected, authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(false, false)] + [BitAutoData(false, true)] + [BitAutoData(true, false)] + public async Task CanCreate_ServiceAccountProjectAccessPolicy_TargetsDontExist_DoesNotSucceed(bool projectExists, + bool serviceAccountExists, + SutProvider sutProvider, ServiceAccountProjectAccessPolicy resource, + Project mockProject, ServiceAccount mockServiceAccount, + ClaimsPrincipal claimsPrincipal) + { + var requirement = AccessPolicyOperations.Create; + resource.GrantedProject = null; + resource.ServiceAccount = null; + + if (projectExists) + { + resource.GrantedProject = null; + mockProject.Id = resource.GrantedProjectId!.Value; + sutProvider.GetDependency().GetByIdAsync(resource.GrantedProjectId!.Value) + .Returns(mockProject); + } + + if (serviceAccountExists) + { + resource.ServiceAccount = null; + sutProvider.GetDependency().GetByIdAsync(resource.ServiceAccountId!.Value) + .Returns(mockServiceAccount); + } + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(false, false)] + [BitAutoData(false, true)] + [BitAutoData(true, false)] + [BitAutoData(true, true)] + public async Task CanCreate_ServiceAccountProjectAccessPolicy_OrgMismatch_DoesNotSucceed(bool fetchProject, + bool fetchSa, + SutProvider sutProvider, ServiceAccountProjectAccessPolicy resource, + Project mockProject, ServiceAccount mockServiceAccount, + ClaimsPrincipal claimsPrincipal) + { + var requirement = AccessPolicyOperations.Create; + + if (fetchProject) + { + resource.GrantedProject = null; + mockProject.Id = resource.GrantedProjectId!.Value; + sutProvider.GetDependency().GetByIdAsync(resource.GrantedProjectId!.Value) + .Returns(mockProject); + } + + if (fetchSa) + { + resource.ServiceAccount = null; + mockServiceAccount.Id = resource.ServiceAccountId!.Value; + sutProvider.GetDependency().GetByIdAsync(resource.ServiceAccountId!.Value) + .Returns(mockServiceAccount); + } + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData] + public async Task CanCreate_ServiceAccountProjectAccessPolicy_AccessToSecretsManagerFalse_DoesNotSucceed( + SutProvider sutProvider, ServiceAccountProjectAccessPolicy resource, + ClaimsPrincipal claimsPrincipal) + { + var requirement = AccessPolicyOperations.Create; + resource.ServiceAccount!.OrganizationId = resource.GrantedProject!.OrganizationId; + sutProvider.GetDependency().AccessSecretsManager(resource.GrantedProject!.OrganizationId) + .Returns(false); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessClientType.ServiceAccount)] + [BitAutoData(AccessClientType.Organization)] + public async Task CanCreate_ServiceAccountProjectAccessPolicy_UnsupportedClientTypes_DoesNotSucceed( + AccessClientType clientType, + SutProvider sutProvider, ServiceAccountProjectAccessPolicy resource, + ClaimsPrincipal claimsPrincipal) + { + var requirement = AccessPolicyOperations.Create; + resource.ServiceAccount!.OrganizationId = resource.GrantedProject!.OrganizationId; + sutProvider.GetDependency().AccessSecretsManager(resource.GrantedProject!.OrganizationId) + .Returns(true); + sutProvider.GetDependency().GetAccessClientAsync(default, resource.ServiceAccount!.OrganizationId).ReturnsForAnyArgs( + (clientType, new Guid())); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin, true, true, true, true, true)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, false, false, false, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, false, false, true, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, false, true, false, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, false, true, true, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, true, false, false, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, true, false, true, true)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, true, true, false, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, true, true, true, true)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false, false, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false, true, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, false, true, false, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, false, true, true, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, true, false, false, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, true, false, true, true)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, true, true, false, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, true, true, true, true)] + public async Task CanCreate_ServiceAccountProjectAccessPolicy_AccessCheck(PermissionType permissionType, + bool projectRead, + bool projectWrite, bool saRead, bool saWrite, bool expected, + SutProvider sutProvider, ServiceAccountProjectAccessPolicy resource, + ClaimsPrincipal claimsPrincipal, Guid userId) + { + var requirement = AccessPolicyOperations.Create; + resource.ServiceAccount!.OrganizationId = resource.GrantedProject!.OrganizationId; + SetupCurrentUserPermission(sutProvider, permissionType, resource.GrantedProject!.OrganizationId, userId); + sutProvider.GetDependency() + .AccessToProjectAsync(resource.GrantedProjectId!.Value, userId, Arg.Any()) + .Returns((projectRead, projectWrite)); + sutProvider.GetDependency() + .AccessToServiceAccountAsync(resource.ServiceAccountId!.Value, userId, Arg.Any()) + .Returns((saRead, saWrite)); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.Equal(expected, authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] + public async Task CanUpdate_AccessToSecretsManagerFalse_DoesNotSucceed(AccessPolicyType accessPolicyType, + SutProvider sutProvider, + Guid organizationId, + Project mockGrantedProject, + ServiceAccount mockGrantedServiceAccount, + ClaimsPrincipal claimsPrincipal) + { + var requirement = AccessPolicyOperations.Update; + mockGrantedProject.OrganizationId = organizationId; + mockGrantedServiceAccount.OrganizationId = organizationId; + var resource = CreatePolicy(accessPolicyType, mockGrantedProject, mockGrantedServiceAccount); + sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(false); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.ServiceAccountProjectAccessPolicy)] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.GroupServiceAccountAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.ServiceAccountProjectAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.GroupServiceAccountAccessPolicy)] + public async Task CanUpdate_UnsupportedClientTypes_DoesNotSucceed( + AccessClientType clientType, + AccessPolicyType accessPolicyType, + SutProvider sutProvider, + Guid organizationId, + Project mockGrantedProject, + ServiceAccount mockGrantedServiceAccount, + ClaimsPrincipal claimsPrincipal) + { + var requirement = AccessPolicyOperations.Update; + mockGrantedProject.OrganizationId = organizationId; + mockGrantedServiceAccount.OrganizationId = organizationId; + var resource = CreatePolicy(accessPolicyType, mockGrantedProject, mockGrantedServiceAccount); + sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); + sutProvider.GetDependency().GetAccessClientAsync(default, organizationId).ReturnsForAnyArgs( + (clientType, new Guid())); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + public async Task CanUpdate_AccessCheck( + AccessPolicyType accessPolicyType, + PermissionType permissionType, bool read, + bool write, bool expected, + SutProvider sutProvider, + Guid organizationId, + Project mockGrantedProject, + ServiceAccount mockGrantedServiceAccount, + ClaimsPrincipal claimsPrincipal, Guid userId, Guid serviceAccountId) + { + var requirement = AccessPolicyOperations.Update; + mockGrantedProject.OrganizationId = organizationId; + mockGrantedServiceAccount.OrganizationId = organizationId; + + var resource = CreatePolicy(accessPolicyType, mockGrantedProject, mockGrantedServiceAccount, + serviceAccountId); + SetupCurrentUserPermission(sutProvider, permissionType, organizationId, userId); + SetupMockAccess(sutProvider, userId, resource, read, write); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.Equal(expected, authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] + public async Task CanDelete_AccessToSecretsManagerFalse_DoesNotSucceed(AccessPolicyType accessPolicyType, + SutProvider sutProvider, + Guid organizationId, + Project mockGrantedProject, + ServiceAccount mockGrantedServiceAccount, + ClaimsPrincipal claimsPrincipal) + { + var requirement = AccessPolicyOperations.Delete; + mockGrantedProject.OrganizationId = organizationId; + mockGrantedServiceAccount.OrganizationId = organizationId; + var resource = CreatePolicy(accessPolicyType, mockGrantedProject, mockGrantedServiceAccount); + sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(false); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.ServiceAccountProjectAccessPolicy)] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessClientType.ServiceAccount, AccessPolicyType.GroupServiceAccountAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.ServiceAccountProjectAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessClientType.Organization, AccessPolicyType.GroupServiceAccountAccessPolicy)] + public async Task CanDelete_UnsupportedClientTypes_DoesNotSucceed( + AccessClientType clientType, + AccessPolicyType accessPolicyType, + SutProvider sutProvider, + Guid organizationId, + Project mockGrantedProject, + ServiceAccount mockGrantedServiceAccount, + ClaimsPrincipal claimsPrincipal) + { + var requirement = AccessPolicyOperations.Delete; + mockGrantedProject.OrganizationId = organizationId; + mockGrantedServiceAccount.OrganizationId = organizationId; + var resource = CreatePolicy(accessPolicyType, mockGrantedProject, mockGrantedServiceAccount); + sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); + sutProvider.GetDependency().GetAccessClientAsync(default, organizationId).ReturnsForAnyArgs( + (clientType, new Guid())); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission, true, true, true)] + public async Task CanDelete_AccessCheck( + AccessPolicyType accessPolicyType, + PermissionType permissionType, + bool read, bool write, bool expected, + SutProvider sutProvider, + Guid organizationId, + Project mockGrantedProject, + ServiceAccount mockGrantedServiceAccount, + ClaimsPrincipal claimsPrincipal, Guid userId, Guid serviceAccountId) + { + var requirement = AccessPolicyOperations.Delete; + mockGrantedProject.OrganizationId = organizationId; + mockGrantedServiceAccount.OrganizationId = organizationId; + + var resource = CreatePolicy(accessPolicyType, mockGrantedProject, mockGrantedServiceAccount, + serviceAccountId); + SetupCurrentUserPermission(sutProvider, permissionType, organizationId, userId); + SetupMockAccess(sutProvider, userId, resource, read, write); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resource); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.Equal(expected, authzContext.HasSucceeded); + } +} diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommandTests.cs index f4a3852f7..bf26d6234 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommandTests.cs @@ -1,6 +1,4 @@ using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; -using Bit.Commercial.Core.Test.SecretsManager.Enums; -using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -25,14 +23,20 @@ public class CreateAccessPoliciesCommandTests foreach (var ap in userProjectAccessPolicies) { ap.GrantedProjectId = grantedProjectId; + ap.GrantedProject = null; + ap.User = null; } foreach (var ap in groupProjectAccessPolicies) { ap.GrantedProjectId = grantedProjectId; + ap.GrantedProject = null; + ap.Group = null; } foreach (var ap in serviceAccountProjectAccessPolicies) { ap.GrantedProjectId = grantedProjectId; + ap.GrantedProject = null; + ap.ServiceAccount = null; } data.AddRange(userProjectAccessPolicies); data.AddRange(groupProjectAccessPolicies); @@ -47,114 +51,23 @@ public class CreateAccessPoliciesCommandTests foreach (var ap in userServiceAccountAccessPolicies) { ap.GrantedServiceAccountId = grantedServiceAccountId; + ap.GrantedServiceAccount = null; + ap.User = null; } foreach (var ap in groupServiceAccountAccessPolicies) { ap.GrantedServiceAccountId = grantedServiceAccountId; + ap.GrantedServiceAccount = null; + ap.Group = null; } data.AddRange(userServiceAccountAccessPolicies); data.AddRange(groupServiceAccountAccessPolicies); return data; } - private static List MakeDuplicate(List data, AccessPolicyType accessPolicyType) - { - switch (accessPolicyType) - { - case AccessPolicyType.UserProjectAccessPolicy: - { - var mockAccessPolicy = new UserProjectAccessPolicy - { - OrganizationUserId = Guid.NewGuid(), - GrantedProjectId = Guid.NewGuid(), - }; - data.Add(mockAccessPolicy); - - // Add a duplicate policy - data.Add(mockAccessPolicy); - break; - } - case AccessPolicyType.GroupProjectAccessPolicy: - { - var mockAccessPolicy = new GroupProjectAccessPolicy - { - GroupId = Guid.NewGuid(), - GrantedProjectId = Guid.NewGuid(), - }; - data.Add(mockAccessPolicy); - - // Add a duplicate policy - data.Add(mockAccessPolicy); - break; - } - case AccessPolicyType.ServiceAccountProjectAccessPolicy: - { - var mockAccessPolicy = new ServiceAccountProjectAccessPolicy - { - ServiceAccountId = Guid.NewGuid(), - GrantedProjectId = Guid.NewGuid(), - }; - data.Add(mockAccessPolicy); - - // Add a duplicate policy - data.Add(mockAccessPolicy); - break; - } - case AccessPolicyType.UserServiceAccountAccessPolicy: - { - var mockAccessPolicy = new UserServiceAccountAccessPolicy - { - OrganizationUserId = Guid.NewGuid(), - GrantedServiceAccountId = Guid.NewGuid(), - }; - data.Add(mockAccessPolicy); - - // Add a duplicate policy - data.Add(mockAccessPolicy); - break; - } - case AccessPolicyType.GroupServiceAccountAccessPolicy: - { - var mockAccessPolicy = new GroupServiceAccountAccessPolicy - { - GroupId = Guid.NewGuid(), - GrantedServiceAccountId = Guid.NewGuid(), - }; - data.Add(mockAccessPolicy); - - // Add a duplicate policy - data.Add(mockAccessPolicy); - break; - } - } - - return data; - } - - private static void SetupPermission(SutProvider sutProvider, - PermissionType permissionType, Project project, Guid userId) - { - if (permissionType == PermissionType.RunAsUserWithPermission) - { - sutProvider.GetDependency().AccessToProjectAsync(project.Id, userId, AccessClientType.User) - .Returns((true, true)); - } - } - - private static void SetupPermission(SutProvider sutProvider, - PermissionType permissionType, ServiceAccount serviceAccount, Guid userId) - { - if (permissionType == PermissionType.RunAsUserWithPermission) - { - sutProvider.GetDependency() - .UserHasWriteAccessToServiceAccount(serviceAccount.Id, userId).Returns(true); - } - } - [Theory] [BitAutoData] public async Task CreateMany_AlreadyExists_Throws_BadRequestException( - Guid userId, Project project, ServiceAccount serviceAccount, List userProjectAccessPolicies, @@ -173,83 +86,34 @@ public class CreateAccessPoliciesCommandTests .Returns(true); await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.NoAccessCheck)); + sutProvider.Sut.CreateManyAsync(data)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateManyAsync(default!); } [Theory] - [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] - [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] - [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)] - [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] - [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] - public async Task CreateMany_NotUnique_ThrowsException( - AccessPolicyType accessPolicyType, - Guid userId, - Project project, - ServiceAccount serviceAccount, - List userProjectAccessPolicies, - List groupProjectAccessPolicies, - List serviceAccountProjectAccessPolicies, - List userServiceAccountAccessPolicies, - List groupServiceAccountAccessPolicies, - SutProvider sutProvider - ) + [BitAutoData] + public async Task CreateMany_ClearsReferences(SutProvider sutProvider, Guid projectId) { - var data = MakeGrantedProjectAccessPolicies(project.Id, userProjectAccessPolicies, groupProjectAccessPolicies, - serviceAccountProjectAccessPolicies); - var saData = MakeGrantedServiceAccountAccessPolicies(serviceAccount.Id, userServiceAccountAccessPolicies, groupServiceAccountAccessPolicies); - data = data.Concat(saData).ToList(); - data = MakeDuplicate(data, accessPolicyType); - - await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.NoAccessCheck)); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .CreateManyAsync(Arg.Any>()); - } - - [Theory] - [BitAutoData(PermissionType.RunAsAdmin)] - [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async Task CreateMany_Success( - PermissionType permissionType, - Guid userId, - Project project, - ServiceAccount serviceAccount, - List userProjectAccessPolicies, - List groupProjectAccessPolicies, - List serviceAccountProjectAccessPolicies, - List userServiceAccountAccessPolicies, - List groupServiceAccountAccessPolicies, - SutProvider sutProvider) - { - var data = MakeGrantedProjectAccessPolicies(project.Id, userProjectAccessPolicies, groupProjectAccessPolicies, - serviceAccountProjectAccessPolicies); - var saData = MakeGrantedServiceAccountAccessPolicies(serviceAccount.Id, userServiceAccountAccessPolicies, groupServiceAccountAccessPolicies); - data = data.Concat(saData).ToList(); - - SetupPermission(sutProvider, permissionType, serviceAccount, userId); - SetupPermission(sutProvider, permissionType, project, userId); - - if (permissionType == PermissionType.RunAsAdmin) + var userProjectAp = new UserProjectAccessPolicy { - await sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.NoAccessCheck); - } - else if (permissionType == PermissionType.RunAsUserWithPermission) - { - await sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.User); - } + GrantedProjectId = projectId, + OrganizationUserId = new Guid(), + }; + var data = new List() { userProjectAp, }; + + userProjectAp.GrantedProject = new Project() { Id = new Guid() }; + var expectedCall = new List() { userProjectAp, }; + + await sutProvider.Sut.CreateManyAsync(data); await sutProvider.GetDependency().Received(1) - .CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data))); + .CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(expectedCall))); } [Theory] [BitAutoData] - public async Task CreateMany_UserWithoutPermission_Throws( - Guid userId, + public async Task CreateMany_Success( Project project, ServiceAccount serviceAccount, List userProjectAccessPolicies, @@ -264,10 +128,9 @@ public class CreateAccessPoliciesCommandTests var saData = MakeGrantedServiceAccountAccessPolicies(serviceAccount.Id, userServiceAccountAccessPolicies, groupServiceAccountAccessPolicies); data = data.Concat(saData).ToList(); - await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.User)); + await sutProvider.Sut.CreateManyAsync(data); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .CreateManyAsync(Arg.Any>()); + await sutProvider.GetDependency().Received(1) + .CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data))); } } diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommandTests.cs index 11814f577..39025012b 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommandTests.cs @@ -1,16 +1,10 @@ using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; -using Bit.Commercial.Core.Test.SecretsManager.Enums; -using Bit.Core.Context; -using Bit.Core.Entities; -using Bit.Core.Enums; -using Bit.Core.Exceptions; -using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; +using Bit.Test.Common.Helpers; using NSubstitute; -using NSubstitute.ReturnsExtensions; using Xunit; namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessPolicies; @@ -19,213 +13,13 @@ namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessPolicies; [ProjectCustomize] public class DeleteAccessPolicyCommandTests { - private static void SetupPermission(SutProvider sutProvider, - PermissionType permissionType, Project grantedProject, Guid userId) - { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(grantedProject.OrganizationId) - .Returns(true); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency().AccessToProjectAsync(grantedProject.Id, userId, AccessClientType.User) - .Returns((true, true)); - break; - default: - throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null); - } - } - - private static void SetupPermission(SutProvider sutProvider, - PermissionType permissionType, ServiceAccount grantedServiceAccount, Guid userId) - { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(grantedServiceAccount.OrganizationId) - .Returns(true); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency() - .UserHasWriteAccessToServiceAccount(grantedServiceAccount.Id, userId) - .Returns(true); - break; - default: - throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null); - } - } - - private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, Guid data, - Project grantedProject, Group mockGroup, ServiceAccount mockServiceAccount) => - accessPolicyType switch - { - AccessPolicyType.UserProjectAccessPolicy => new UserProjectAccessPolicy - { - Id = data, - GrantedProjectId = grantedProject.Id, - GrantedProject = grantedProject, - }, - AccessPolicyType.GroupProjectAccessPolicy => new GroupProjectAccessPolicy - { - Id = data, - GrantedProjectId = grantedProject.Id, - Group = mockGroup, - GrantedProject = grantedProject, - }, - AccessPolicyType.ServiceAccountProjectAccessPolicy => new ServiceAccountProjectAccessPolicy - { - Id = data, - GrantedProjectId = grantedProject.Id, - ServiceAccount = mockServiceAccount, - GrantedProject = grantedProject, - }, - _ => null, - }; - - private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, Guid data, - ServiceAccount grantedServiceAccount, Group mockGroup) => - accessPolicyType switch - { - AccessPolicyType.UserServiceAccountAccessPolicy => new UserServiceAccountAccessPolicy - { - Id = data, - GrantedServiceAccountId = grantedServiceAccount.Id, - GrantedServiceAccount = grantedServiceAccount, - }, - AccessPolicyType.GroupServiceAccountAccessPolicy => new GroupServiceAccountAccessPolicy - { - Id = data, - GrantedServiceAccountId = grantedServiceAccount.Id, - Group = mockGroup, - GrantedServiceAccount = grantedServiceAccount, - }, - _ => null, - }; - [Theory] [BitAutoData] - public async Task DeleteAccessPolicy_Throws_NotFoundException(Guid data, Guid userId, - SutProvider sutProvider) + public async Task DeleteAsync_Success(SutProvider sutProvider, Guid data) { - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); - sutProvider.GetDependency().GetByIdAsync(data).ReturnsNull(); - await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteAsync(data, userId)); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteAsync(default); - } + await sutProvider.Sut.DeleteAsync(data); - [Theory] - [BitAutoData] - public async Task DeleteAccessPolicy_SmNotEnabled_Throws_NotFoundException(Guid data, Guid userId, - SutProvider sutProvider) - { - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(false); - sutProvider.GetDependency().GetByIdAsync(data).ReturnsNull(); - await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteAsync(data, userId)); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteAsync(default); - } - - [Theory] - [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsAdmin)] - [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission)] - [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsAdmin)] - [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission)] - [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsAdmin)] - [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission)] - public async Task DeleteAccessPolicy_ProjectGrants_PermissionsCheck_Success( - AccessPolicyType accessPolicyType, - PermissionType permissionType, - Guid data, - Guid userId, - Project grantedProject, - Group mockGroup, - ServiceAccount mockServiceAccount, - SutProvider sutProvider) - { - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); - var policyToReturn = - CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount); - SetupPermission(sutProvider, permissionType, grantedProject, userId); - - sutProvider.GetDependency().GetByIdAsync(data) - .Returns(policyToReturn); - - await sutProvider.Sut.DeleteAsync(data, userId); - - await sutProvider.GetDependency().Received(1).DeleteAsync(Arg.Is(data)); - } - - [Theory] - [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] - [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] - [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)] - public async Task DeleteAccessPolicy_UserProjectAccessPolicy_PermissionsCheck_ThrowsNotAuthorized( - AccessPolicyType accessPolicyType, - Guid data, - Guid userId, - Group mockGroup, - ServiceAccount mockServiceAccount, - Project grantedProject, - SutProvider sutProvider) - { - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); - var policyToReturn = - CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount); - - sutProvider.GetDependency().GetByIdAsync(data) - .Returns(policyToReturn); - - await Assert.ThrowsAsync(() => - sutProvider.Sut.DeleteAsync(data, userId)); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteAsync(default); - } - - [Theory] - [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsAdmin)] - [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)] - [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsAdmin)] - [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)] - public async Task DeleteAccessPolicy_ServiceAccountGrants_PermissionsCheck_Success( - AccessPolicyType accessPolicyType, - PermissionType permissionType, - Guid data, - Guid userId, - ServiceAccount grantedServiceAccount, - Group mockGroup, - SutProvider sutProvider) - { - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); - var policyToReturn = CreatePolicyToReturn(accessPolicyType, data, grantedServiceAccount, mockGroup); - SetupPermission(sutProvider, permissionType, grantedServiceAccount, userId); - sutProvider.GetDependency().GetByIdAsync(data) - .Returns(policyToReturn); - - await sutProvider.Sut.DeleteAsync(data, userId); - - await sutProvider.GetDependency().Received(1).DeleteAsync(Arg.Is(data)); - } - - [Theory] - [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] - [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] - public async Task DeleteAccessPolicy_ServiceAccountGrants_PermissionsCheck_Throws( - AccessPolicyType accessPolicyType, - Guid data, - Guid userId, - ServiceAccount grantedServiceAccount, - Group mockGroup, - SutProvider sutProvider) - { - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); - var policyToReturn = CreatePolicyToReturn(accessPolicyType, data, grantedServiceAccount, mockGroup); - - sutProvider.GetDependency().GetByIdAsync(data) - .Returns(policyToReturn); - - await Assert.ThrowsAsync(() => - sutProvider.Sut.DeleteAsync(data, userId)); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteAsync(default); + await sutProvider.GetDependency().Received(1) + .DeleteAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data))); } } diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommandTests.cs index a5892d142..e96aa810c 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommandTests.cs @@ -1,8 +1,4 @@ using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; -using Bit.Commercial.Core.Test.SecretsManager.Enums; -using Bit.Core.Context; -using Bit.Core.Entities; -using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -19,243 +15,30 @@ namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessPolicies; [ProjectCustomize] public class UpdateAccessPolicyCommandTests { - private static void SetupPermission(SutProvider sutProvider, - PermissionType permissionType, Project grantedProject, Guid userId) + [Theory] + [BitAutoData] + public async Task UpdateAsync_DoesNotExist_ThrowsNotFound(Guid data, bool read, bool write, + SutProvider sutProvider) { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(grantedProject.OrganizationId) - .Returns(true); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency().AccessToProjectAsync(grantedProject.Id, userId, AccessClientType.User) - .Returns((true, true)); - break; - default: - throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null); - } - } - - private static void SetupPermission(SutProvider sutProvider, - PermissionType permissionType, ServiceAccount grantedServiceAccount, Guid userId) - { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(grantedServiceAccount.OrganizationId) - .Returns(true); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency() - .UserHasWriteAccessToServiceAccount(grantedServiceAccount.Id, userId) - .Returns(true); - break; - default: - throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null); - } - } - - private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, - ServiceAccount grantedServiceAccount, Guid data, Group mockGroup) - { - switch (accessPolicyType) - { - case AccessPolicyType.UserServiceAccountAccessPolicy: - return - new UserServiceAccountAccessPolicy - { - Id = data, - Read = true, - Write = true, - GrantedServiceAccountId = grantedServiceAccount.Id, - GrantedServiceAccount = grantedServiceAccount, - }; - case AccessPolicyType.GroupServiceAccountAccessPolicy: - mockGroup.OrganizationId = grantedServiceAccount.OrganizationId; - return new GroupServiceAccountAccessPolicy - { - Id = data, - GrantedServiceAccountId = grantedServiceAccount.Id, - GrantedServiceAccount = grantedServiceAccount, - Read = true, - Write = true, - Group = mockGroup, - }; - default: - throw new ArgumentOutOfRangeException(nameof(accessPolicyType), accessPolicyType, null); - } - } - - private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, Guid data, - Project grantedProject, Group mockGroup, ServiceAccount mockServiceAccount) - { - switch (accessPolicyType) - { - case AccessPolicyType.UserProjectAccessPolicy: - return - new UserProjectAccessPolicy - { - Id = data, - Read = true, - Write = true, - GrantedProjectId = grantedProject.Id, - GrantedProject = grantedProject, - }; - case AccessPolicyType.GroupProjectAccessPolicy: - mockGroup.OrganizationId = grantedProject.OrganizationId; - return - new GroupProjectAccessPolicy - { - Id = data, - GrantedProjectId = grantedProject.Id, - Read = true, - Write = true, - Group = mockGroup, - GrantedProject = grantedProject, - }; - case AccessPolicyType.ServiceAccountProjectAccessPolicy: - mockServiceAccount.OrganizationId = grantedProject.OrganizationId; - return new ServiceAccountProjectAccessPolicy - { - Id = data, - GrantedProjectId = grantedProject.Id, - Read = true, - Write = true, - ServiceAccount = mockServiceAccount, - GrantedProject = grantedProject, - }; - default: - throw new ArgumentOutOfRangeException(nameof(accessPolicyType), accessPolicyType, null); - } + await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(data, read, write)); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(Arg.Any()); } [Theory] [BitAutoData] - public async Task UpdateAsync_Throws_NotFoundException(Guid data, bool read, bool write, Guid userId, + public async Task UpdateAsync_Success(Guid data, bool read, bool write, UserProjectAccessPolicy accessPolicy, SutProvider sutProvider) { - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); - await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(data, read, write, userId)); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); - } + accessPolicy.Id = data; + sutProvider.GetDependency().GetByIdAsync(data).Returns(accessPolicy); - [Theory] - [BitAutoData] - public async Task UpdateAsync_SmNotEnabled_Throws_NotFoundException(Guid data, bool read, bool write, Guid userId, - SutProvider sutProvider) - { - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(false); - await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(data, read, write, userId)); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); - } + var result = await sutProvider.Sut.UpdateAsync(data, read, write); - [Theory] - [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsAdmin)] - [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission)] - [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsAdmin)] - [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission)] - [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsAdmin)] - [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission)] - public async Task UpdateAsync_ProjectGrants_PermissionsCheck_Success( - AccessPolicyType accessPolicyType, - PermissionType permissionType, - Guid data, - bool read, - bool write, - Guid userId, - Project grantedProject, - Group mockGroup, - ServiceAccount mockServiceAccount, - SutProvider sutProvider) - { - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); - var policyToReturn = - CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount); - SetupPermission(sutProvider, permissionType, grantedProject, userId); - - sutProvider.GetDependency().GetByIdAsync(data).Returns(policyToReturn); - var result = await sutProvider.Sut.UpdateAsync(data, read, write, userId); - await sutProvider.GetDependency().Received(1).ReplaceAsync(policyToReturn); + await sutProvider.GetDependency().Received(1) + .ReplaceAsync(Arg.Any()); AssertHelper.AssertRecent(result.RevisionDate); Assert.Equal(read, result.Read); Assert.Equal(write, result.Write); } - - [Theory] - [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] - [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] - [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)] - public async Task UpdateAsync_ProjectGrants_PermissionsCheck_Throws( - AccessPolicyType accessPolicyType, - Guid data, - bool read, - bool write, - Guid userId, - Project grantedProject, - Group mockGroup, - ServiceAccount mockServiceAccount, - SutProvider sutProvider) - { - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); - var policyToReturn = - CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount); - sutProvider.GetDependency().GetByIdAsync(data).Returns(policyToReturn); - - await Assert.ThrowsAsync(() => - sutProvider.Sut.UpdateAsync(data, read, write, userId)); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); - } - - [Theory] - [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsAdmin)] - [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)] - [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsAdmin)] - [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)] - public async Task UpdateAsync_ServiceAccountGrants_PermissionsCheck_Success( - AccessPolicyType accessPolicyType, - PermissionType permissionType, - Guid data, - bool read, - bool write, - Guid userId, - ServiceAccount grantedServiceAccount, - Group mockGroup, - SutProvider sutProvider) - { - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); - var policyToReturn = CreatePolicyToReturn(accessPolicyType, grantedServiceAccount, data, mockGroup); - SetupPermission(sutProvider, permissionType, grantedServiceAccount, userId); - - sutProvider.GetDependency().GetByIdAsync(data).Returns(policyToReturn); - var result = await sutProvider.Sut.UpdateAsync(data, read, write, userId); - await sutProvider.GetDependency().Received(1).ReplaceAsync(policyToReturn); - - AssertHelper.AssertRecent(result.RevisionDate); - Assert.Equal(read, result.Read); - Assert.Equal(write, result.Write); - } - - [Theory] - [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] - [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] - public async Task UpdateAsync_ServiceAccountGrants_PermissionsCheck_Throws( - AccessPolicyType accessPolicyType, - Guid data, - bool read, - bool write, - Guid userId, - ServiceAccount grantedServiceAccount, - Group mockGroup, - SutProvider sutProvider) - { - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); - var policyToReturn = CreatePolicyToReturn(accessPolicyType, grantedServiceAccount, data, mockGroup); - sutProvider.GetDependency().GetByIdAsync(data).Returns(policyToReturn); - - await Assert.ThrowsAsync(() => - sutProvider.Sut.UpdateAsync(data, read, write, userId)); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); - } } diff --git a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs index c07cf3a37..64dacfa0a 100644 --- a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs +++ b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs @@ -5,6 +5,7 @@ using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Repositories; +using Bit.Core.SecretsManager.AuthorizationRequirements; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -31,8 +32,10 @@ public class AccessPoliciesController : Controller private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IUpdateAccessPolicyCommand _updateAccessPolicyCommand; private readonly IUserService _userService; + private readonly IAuthorizationService _authorizationService; public AccessPoliciesController( + IAuthorizationService authorizationService, IUserService userService, ICurrentContext currentContext, IAccessPolicyRepository accessPolicyRepository, @@ -44,6 +47,7 @@ public class AccessPoliciesController : Controller IDeleteAccessPolicyCommand deleteAccessPolicyCommand, IUpdateAccessPolicyCommand updateAccessPolicyCommand) { + _authorizationService = authorizationService; _userService = userService; _currentContext = currentContext; _serviceAccountRepository = serviceAccountRepository; @@ -71,9 +75,17 @@ public class AccessPoliciesController : Controller throw new NotFoundException(); } - var (accessClient, userId) = await GetAccessClientTypeAsync(project.OrganizationId); - var policies = request.ToBaseAccessPoliciesForProject(id); - var results = await _createAccessPoliciesCommand.CreateManyAsync(policies, userId, accessClient); + var policies = request.ToBaseAccessPoliciesForProject(id, project.OrganizationId); + foreach (var policy in policies) + { + var authorizationResult = await _authorizationService.AuthorizeAsync(User, policy, AccessPolicyOperations.Create); + if (!authorizationResult.Succeeded) + { + throw new NotFoundException(); + } + } + + var results = await _createAccessPoliciesCommand.CreateManyAsync(policies); return new ProjectAccessPoliciesResponseModel(results); } @@ -102,9 +114,17 @@ public class AccessPoliciesController : Controller throw new NotFoundException(); } - var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId); - var policies = request.ToBaseAccessPoliciesForServiceAccount(id); - var results = await _createAccessPoliciesCommand.CreateManyAsync(policies, userId, accessClient); + var policies = request.ToBaseAccessPoliciesForServiceAccount(id, serviceAccount.OrganizationId); + foreach (var policy in policies) + { + var authorizationResult = await _authorizationService.AuthorizeAsync(User, policy, AccessPolicyOperations.Create); + if (!authorizationResult.Succeeded) + { + throw new NotFoundException(); + } + } + + var results = await _createAccessPoliciesCommand.CreateManyAsync(policies); return new ServiceAccountAccessPoliciesResponseModel(results); } @@ -128,17 +148,29 @@ public class AccessPoliciesController : Controller throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once."); } + if (requests.Count != requests.DistinctBy(request => request.GrantedId).Count()) + { + throw new BadRequestException("Resources must be unique"); + } + var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id); if (serviceAccount == null) { throw new NotFoundException(); } - var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId); - var policies = requests.Select(request => request.ToServiceAccountProjectAccessPolicy(id)); + var policies = requests.Select(request => request.ToServiceAccountProjectAccessPolicy(id, serviceAccount.OrganizationId)).ToList(); + foreach (var policy in policies) + { + var authorizationResult = await _authorizationService.AuthorizeAsync(User, policy, AccessPolicyOperations.Create); + if (!authorizationResult.Succeeded) + { + throw new NotFoundException(); + } + } + var results = - await _createAccessPoliciesCommand.CreateManyAsync(new List(policies), userId, - accessClient); + await _createAccessPoliciesCommand.CreateManyAsync(new List(policies)); var responses = results.Select(ap => new ServiceAccountProjectAccessPolicyResponseModel((ServiceAccountProjectAccessPolicy)ap)); return new ListResponseModel(responses); @@ -165,8 +197,15 @@ public class AccessPoliciesController : Controller public async Task UpdateAccessPolicyAsync([FromRoute] Guid id, [FromBody] AccessPolicyUpdateRequest request) { - var userId = _userService.GetProperUserId(User).Value; - var result = await _updateAccessPolicyCommand.UpdateAsync(id, request.Read, request.Write, userId); + var ap = await _accessPolicyRepository.GetByIdAsync(id); + var authorizationResult = + await _authorizationService.AuthorizeAsync(User, ap, AccessPolicyOperations.Update); + if (!authorizationResult.Succeeded) + { + throw new NotFoundException(); + } + + var result = await _updateAccessPolicyCommand.UpdateAsync(id, request.Read, request.Write); return result switch { @@ -185,8 +224,15 @@ public class AccessPoliciesController : Controller [HttpDelete("{id}")] public async Task DeleteAccessPolicyAsync([FromRoute] Guid id) { - var userId = _userService.GetProperUserId(User).Value; - await _deleteAccessPolicyCommand.DeleteAsync(id, userId); + var ap = await _accessPolicyRepository.GetByIdAsync(id); + var authorizationResult = + await _authorizationService.AuthorizeAsync(User, ap, AccessPolicyOperations.Delete); + if (!authorizationResult.Succeeded) + { + throw new NotFoundException(); + } + + await _deleteAccessPolicyCommand.DeleteAsync(id); } [HttpGet("/organizations/{id}/access-policies/people/potential-grantees")] diff --git a/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs b/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs index e4de0a87a..727d0ab25 100644 --- a/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs +++ b/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs @@ -7,13 +7,36 @@ namespace Bit.Api.SecretsManager.Models.Request; public class AccessPoliciesCreateRequest { + private static void CheckForDistinctAccessPolicies(IReadOnlyCollection accessPolicies) + { + var distinctAccessPolicies = accessPolicies.DistinctBy(baseAccessPolicy => + { + return baseAccessPolicy switch + { + UserProjectAccessPolicy ap => new Tuple(ap.OrganizationUserId, ap.GrantedProjectId), + GroupProjectAccessPolicy ap => new Tuple(ap.GroupId, ap.GrantedProjectId), + ServiceAccountProjectAccessPolicy ap => new Tuple(ap.ServiceAccountId, + ap.GrantedProjectId), + UserServiceAccountAccessPolicy ap => new Tuple(ap.OrganizationUserId, + ap.GrantedServiceAccountId), + GroupServiceAccountAccessPolicy ap => new Tuple(ap.GroupId, ap.GrantedServiceAccountId), + _ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)), + }; + }).ToList(); + + if (accessPolicies.Count != distinctAccessPolicies.Count) + { + throw new BadRequestException("Resources must be unique"); + } + } + public IEnumerable? UserAccessPolicyRequests { get; set; } public IEnumerable? GroupAccessPolicyRequests { get; set; } public IEnumerable? ServiceAccountAccessPolicyRequests { get; set; } - public List ToBaseAccessPoliciesForProject(Guid grantedProjectId) + public List ToBaseAccessPoliciesForProject(Guid grantedProjectId, Guid organizationId) { if (UserAccessPolicyRequests == null && GroupAccessPolicyRequests == null && ServiceAccountAccessPolicyRequests == null) { @@ -21,13 +44,13 @@ public class AccessPoliciesCreateRequest } var userAccessPolicies = UserAccessPolicyRequests? - .Select(x => x.ToUserProjectAccessPolicy(grantedProjectId)).ToList(); + .Select(x => x.ToUserProjectAccessPolicy(grantedProjectId, organizationId)).ToList(); var groupAccessPolicies = GroupAccessPolicyRequests? - .Select(x => x.ToGroupProjectAccessPolicy(grantedProjectId)).ToList(); + .Select(x => x.ToGroupProjectAccessPolicy(grantedProjectId, organizationId)).ToList(); var serviceAccountAccessPolicies = ServiceAccountAccessPolicyRequests? - .Select(x => x.ToServiceAccountProjectAccessPolicy(grantedProjectId)).ToList(); + .Select(x => x.ToServiceAccountProjectAccessPolicy(grantedProjectId, organizationId)).ToList(); var policies = new List(); if (userAccessPolicies != null) @@ -44,10 +67,12 @@ public class AccessPoliciesCreateRequest { policies.AddRange(serviceAccountAccessPolicies); } + + CheckForDistinctAccessPolicies(policies); return policies; } - public List ToBaseAccessPoliciesForServiceAccount(Guid grantedServiceAccountId) + public List ToBaseAccessPoliciesForServiceAccount(Guid grantedServiceAccountId, Guid organizationId) { if (UserAccessPolicyRequests == null && GroupAccessPolicyRequests == null) { @@ -55,10 +80,10 @@ public class AccessPoliciesCreateRequest } var userAccessPolicies = UserAccessPolicyRequests? - .Select(x => x.ToUserServiceAccountAccessPolicy(grantedServiceAccountId)).ToList(); + .Select(x => x.ToUserServiceAccountAccessPolicy(grantedServiceAccountId, organizationId)).ToList(); var groupAccessPolicies = GroupAccessPolicyRequests? - .Select(x => x.ToGroupServiceAccountAccessPolicy(grantedServiceAccountId)).ToList(); + .Select(x => x.ToGroupServiceAccountAccessPolicy(grantedServiceAccountId, organizationId)).ToList(); var policies = new List(); if (userAccessPolicies != null) @@ -70,6 +95,8 @@ public class AccessPoliciesCreateRequest { policies.AddRange(groupAccessPolicies); } + + CheckForDistinctAccessPolicies(policies); return policies; } @@ -105,47 +132,52 @@ public class AccessPolicyRequest [Required] public bool Write { get; set; } - public UserProjectAccessPolicy ToUserProjectAccessPolicy(Guid projectId) => + public UserProjectAccessPolicy ToUserProjectAccessPolicy(Guid projectId, Guid organizationId) => new() { OrganizationUserId = GranteeId, GrantedProjectId = projectId, + GrantedProject = new Project { OrganizationId = organizationId, Id = projectId }, Read = Read, Write = Write }; - public GroupProjectAccessPolicy ToGroupProjectAccessPolicy(Guid projectId) => + public GroupProjectAccessPolicy ToGroupProjectAccessPolicy(Guid projectId, Guid organizationId) => new() { GroupId = GranteeId, GrantedProjectId = projectId, + GrantedProject = new Project { OrganizationId = organizationId, Id = projectId }, Read = Read, Write = Write }; - public ServiceAccountProjectAccessPolicy ToServiceAccountProjectAccessPolicy(Guid projectId) => + public ServiceAccountProjectAccessPolicy ToServiceAccountProjectAccessPolicy(Guid projectId, Guid organizationId) => new() { ServiceAccountId = GranteeId, GrantedProjectId = projectId, + GrantedProject = new Project { OrganizationId = organizationId, Id = projectId }, Read = Read, Write = Write }; - public UserServiceAccountAccessPolicy ToUserServiceAccountAccessPolicy(Guid id) => + public UserServiceAccountAccessPolicy ToUserServiceAccountAccessPolicy(Guid id, Guid organizationId) => new() { OrganizationUserId = GranteeId, GrantedServiceAccountId = id, + GrantedServiceAccount = new ServiceAccount() { OrganizationId = organizationId, Id = id }, Read = Read, Write = Write }; - public GroupServiceAccountAccessPolicy ToGroupServiceAccountAccessPolicy(Guid id) => + public GroupServiceAccountAccessPolicy ToGroupServiceAccountAccessPolicy(Guid id, Guid organizationId) => new() { GroupId = GranteeId, GrantedServiceAccountId = id, + GrantedServiceAccount = new ServiceAccount() { OrganizationId = organizationId, Id = id }, Read = Read, Write = Write }; diff --git a/src/Api/SecretsManager/Models/Request/GrantedAccessPolicyRequest.cs b/src/Api/SecretsManager/Models/Request/GrantedAccessPolicyRequest.cs index 0b8f1633c..763768608 100644 --- a/src/Api/SecretsManager/Models/Request/GrantedAccessPolicyRequest.cs +++ b/src/Api/SecretsManager/Models/Request/GrantedAccessPolicyRequest.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; -using Bit.Core.SecretsManager.Entities; +using Bit.Infrastructure.EntityFramework.SecretsManager.Models; +using ServiceAccountProjectAccessPolicy = Bit.Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy; namespace Bit.Api.SecretsManager.Models.Request; @@ -14,10 +15,11 @@ public class GrantedAccessPolicyRequest [Required] public bool Write { get; set; } - public ServiceAccountProjectAccessPolicy ToServiceAccountProjectAccessPolicy(Guid serviceAccountId) => + public ServiceAccountProjectAccessPolicy ToServiceAccountProjectAccessPolicy(Guid serviceAccountId, Guid organizationId) => new() { ServiceAccountId = serviceAccountId, + ServiceAccount = new ServiceAccount() { Id = serviceAccountId, OrganizationId = organizationId }, GrantedProjectId = GrantedId, Read = Read, Write = Write, diff --git a/src/Core/SecretsManager/AuthorizationRequirements/AccessPolicyOperationRequirement.cs b/src/Core/SecretsManager/AuthorizationRequirements/AccessPolicyOperationRequirement.cs new file mode 100644 index 000000000..b1ab18979 --- /dev/null +++ b/src/Core/SecretsManager/AuthorizationRequirements/AccessPolicyOperationRequirement.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Authorization.Infrastructure; + +namespace Bit.Core.SecretsManager.AuthorizationRequirements; + +public class AccessPolicyOperationRequirement : OperationAuthorizationRequirement +{ +} + +public static class AccessPolicyOperations +{ + public static readonly AccessPolicyOperationRequirement Create = new() { Name = nameof(Create) }; + public static readonly AccessPolicyOperationRequirement Update = new() { Name = nameof(Update) }; + public static readonly AccessPolicyOperationRequirement Delete = new() { Name = nameof(Delete) }; +} diff --git a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs index 5baa486e0..2d0fdd987 100644 --- a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs +++ b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs @@ -1,9 +1,8 @@ -using Bit.Core.Enums; -using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Entities; namespace Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; public interface ICreateAccessPoliciesCommand { - Task> CreateManyAsync(List accessPolicies, Guid userId, AccessClientType accessType); + Task> CreateManyAsync(List accessPolicies); } diff --git a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IDeleteAccessPolicyCommand.cs b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IDeleteAccessPolicyCommand.cs index e4d313b40..de3215a02 100644 --- a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IDeleteAccessPolicyCommand.cs +++ b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IDeleteAccessPolicyCommand.cs @@ -2,5 +2,5 @@ public interface IDeleteAccessPolicyCommand { - Task DeleteAsync(Guid id, Guid userId); + Task DeleteAsync(Guid id); } diff --git a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IUpdateAccessPolicyCommand.cs b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IUpdateAccessPolicyCommand.cs index afeb47e9f..225f6a752 100644 --- a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IUpdateAccessPolicyCommand.cs +++ b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IUpdateAccessPolicyCommand.cs @@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; public interface IUpdateAccessPolicyCommand { - public Task UpdateAsync(Guid id, bool read, bool write, Guid userId); + public Task UpdateAsync(Guid id, bool read, bool write); } diff --git a/src/Infrastructure.EntityFramework/SecretsManager/Models/AccessPolicy.cs b/src/Infrastructure.EntityFramework/SecretsManager/Models/AccessPolicy.cs index a30707018..f8eeee2fe 100644 --- a/src/Infrastructure.EntityFramework/SecretsManager/Models/AccessPolicy.cs +++ b/src/Infrastructure.EntityFramework/SecretsManager/Models/AccessPolicy.cs @@ -12,13 +12,32 @@ public class AccessPolicyMapperProfile : Profile { public AccessPolicyMapperProfile() { - CreateMap().ReverseMap() + CreateMap() + .ForMember(dst => dst.GrantedProject, opt => opt.Ignore()) + .ForMember(dst => dst.OrganizationUser, opt => opt.Ignore()) + .ReverseMap() .ForMember(dst => dst.User, opt => opt.MapFrom(src => src.OrganizationUser.User)); - CreateMap().ReverseMap() + + CreateMap() + .ForMember(dst => dst.GrantedServiceAccount, opt => opt.Ignore()) + .ForMember(dst => dst.OrganizationUser, opt => opt.Ignore()) + .ReverseMap() .ForMember(dst => dst.User, opt => opt.MapFrom(src => src.OrganizationUser.User)); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); + + CreateMap() + .ForMember(dst => dst.GrantedProject, opt => opt.Ignore()) + .ForMember(dst => dst.Group, opt => opt.Ignore()) + .ReverseMap(); + + CreateMap() + .ForMember(dst => dst.GrantedServiceAccount, opt => opt.Ignore()) + .ForMember(dst => dst.Group, opt => opt.Ignore()) + .ReverseMap(); + + CreateMap() + .ForMember(dst => dst.GrantedProject, opt => opt.Ignore()) + .ForMember(dst => dst.ServiceAccount, opt => opt.Ignore()) + .ReverseMap(); } } diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTests.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTests.cs index 5250541a3..68951b0fb 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTests.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTests.cs @@ -64,79 +64,93 @@ public class AccessPoliciesControllerTests : IClassFixture { - new() { GranteeId = serviceAccount.Id, Read = true, Write = true }, + new() { GranteeId = serviceAccountId, Read = true, Write = true }, }, }; - var response = await _client.PostAsJsonAsync($"/projects/{project.Id}/access-policies", request); + var response = await _client.PostAsJsonAsync($"/projects/{projectId}/access-policies", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task CreateProjectAccessPolicies_NoPermission() + { + // Create a new account as a user + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id); + var request = new AccessPoliciesCreateRequest + { + ServiceAccountAccessPolicyRequests = new List + { + new() { GranteeId = serviceAccountId, Read = true, Write = true }, + }, + }; + + var response = await _client.PostAsJsonAsync($"/projects/{projectId}/access-policies", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Theory] [InlineData(PermissionType.RunAsAdmin)] [InlineData(PermissionType.RunAsUserWithPermission)] - public async Task CreateProjectAccessPolicies(PermissionType permissionType) + public async Task CreateProjectAccessPolicies_MismatchedOrgIds_NotFound(PermissionType permissionType) { var (org, _) = await _organizationHelper.Initialize(true, true); await LoginAsync(_email); - var project = await _projectRepository.CreateAsync(new Project - { - OrganizationId = org.Id, - Name = _mockEncryptedString, - }); - - if (permissionType == PermissionType.RunAsUserWithPermission) - { - var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); - await LoginAsync(email); - var accessPolicies = new List - { - new UserProjectAccessPolicy - { - GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true, - }, - }; - await _accessPolicyRepository.CreateManyAsync(accessPolicies); - } - - var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount - { - OrganizationId = org.Id, - Name = _mockEncryptedString, - }); + var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id, true); + await SetupProjectAndServiceAccountPermissionAsync(permissionType, projectId, serviceAccountId); var request = new AccessPoliciesCreateRequest { ServiceAccountAccessPolicyRequests = new List { - new() { GranteeId = serviceAccount.Id, Read = true, Write = true }, + new() { GranteeId = serviceAccountId, Read = true, Write = true }, }, }; - var response = await _client.PostAsJsonAsync($"/projects/{project.Id}/access-policies", request); + + var response = await _client.PostAsJsonAsync($"/projects/{projectId}/access-policies", request); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task CreateProjectAccessPolicies_Success(PermissionType permissionType) + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id); + await SetupProjectAndServiceAccountPermissionAsync(permissionType, projectId, serviceAccountId); + + var request = new AccessPoliciesCreateRequest + { + ServiceAccountAccessPolicyRequests = new List + { + new() { GranteeId = serviceAccountId, Read = true, Write = true }, + }, + }; + + var response = await _client.PostAsJsonAsync($"/projects/{projectId}/access-policies", request); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(); Assert.NotNull(result); - Assert.Equal(serviceAccount.Id, result!.ServiceAccountAccessPolicies.First().ServiceAccountId); + Assert.Equal(serviceAccountId, result!.ServiceAccountAccessPolicies.First().ServiceAccountId); Assert.True(result.ServiceAccountAccessPolicies.First().Read); Assert.True(result.ServiceAccountAccessPolicies.First().Write); AssertHelper.AssertRecent(result.ServiceAccountAccessPolicies.First().RevisionDate); @@ -152,39 +166,6 @@ public class AccessPoliciesControllerTests : IClassFixture - { - new() { GranteeId = serviceAccount.Id, Read = true, Write = true }, - }, - }; - - var response = await _client.PostAsJsonAsync($"/projects/{project.Id}/access-policies", request); - - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - [Theory] [InlineData(false, false)] [InlineData(true, false)] @@ -225,7 +206,7 @@ public class AccessPoliciesControllerTests : IClassFixture - { - new() { GranteeId = orgUser.Id, Read = true, Write = true }, - }, - }; - - - if (permissionType == PermissionType.RunAsUserWithPermission) - { - var (email, newOrgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); - await LoginAsync(email); - var accessPolicies = new List - { - new UserServiceAccountAccessPolicy - { - GrantedServiceAccountId = serviceAccount.Id, - OrganizationUserId = newOrgUser.Id, - Read = true, - Write = true, - }, - }; - await _accessPolicyRepository.CreateManyAsync(accessPolicies); - - request = new AccessPoliciesCreateRequest - { - UserAccessPolicyRequests = new List - { - new() { GranteeId = ownerOrgUserId, Read = true, Write = true }, - }, - }; - } - + var request = + await SetupUserServiceAccountAccessPolicyRequestAsync(permissionType, org.Id, orgUser.Id, + serviceAccount.Id); var response = await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request); @@ -860,67 +832,6 @@ public class AccessPoliciesControllerTests : IClassFixture { new() { GrantedId = project.Id, Read = true, Write = true } }; - - - if (permissionType == PermissionType.RunAsUserWithPermission) - { - var (email, newOrgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); - await LoginAsync(email); - var accessPolicies = new List - { - new UserProjectAccessPolicy - { - GrantedProjectId = project.Id, OrganizationUserId = newOrgUser.Id, Read = true, Write = true, - }, - }; - await _accessPolicyRepository.CreateManyAsync(accessPolicies); - } - - - var response = - await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/granted-policies", request); - response.EnsureSuccessStatusCode(); - - var result = await response.Content - .ReadFromJsonAsync>(); - - Assert.NotNull(result); - Assert.NotEmpty(result!.Data); - Assert.Equal(project.Id, result.Data.First().GrantedProjectId); - - var createdAccessPolicy = - await _accessPolicyRepository.GetByIdAsync(result.Data.First().Id); - Assert.NotNull(createdAccessPolicy); - Assert.Equal(result.Data.First().Read, createdAccessPolicy!.Read); - Assert.Equal(result.Data.First().Write, createdAccessPolicy.Write); - Assert.Equal(result.Data.First().Id, createdAccessPolicy.Id); - AssertHelper.AssertRecent(createdAccessPolicy.CreationDate); - AssertHelper.AssertRecent(createdAccessPolicy.RevisionDate); - } - [Fact] public async Task CreateServiceAccountGrantedPolicies_NoPermission() { @@ -949,6 +860,63 @@ public class AccessPoliciesControllerTests : IClassFixture { new() { GrantedId = projectId, Read = true, Write = true } }; + + var response = + await _client.PostAsJsonAsync($"/service-accounts/{serviceAccountId}/granted-policies", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task CreateServiceAccountGrantedPolicies_Success(PermissionType permissionType) + { + var (org, orgUser) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + var ownerOrgUserId = orgUser.Id; + + var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id); + await SetupProjectAndServiceAccountPermissionAsync(permissionType, projectId, serviceAccountId); + + var request = + new List { new() { GrantedId = projectId, Read = true, Write = true } }; + + var response = + await _client.PostAsJsonAsync($"/service-accounts/{serviceAccountId}/granted-policies", request); + response.EnsureSuccessStatusCode(); + + var result = await response.Content + .ReadFromJsonAsync>(); + + Assert.NotNull(result); + Assert.NotEmpty(result!.Data); + Assert.Equal(projectId, result.Data.First().GrantedProjectId); + + var createdAccessPolicy = + await _accessPolicyRepository.GetByIdAsync(result.Data.First().Id); + Assert.NotNull(createdAccessPolicy); + Assert.Equal(result.Data.First().Read, createdAccessPolicy!.Read); + Assert.Equal(result.Data.First().Write, createdAccessPolicy.Write); + Assert.Equal(result.Data.First().Id, createdAccessPolicy.Id); + AssertHelper.AssertRecent(createdAccessPolicy.CreationDate); + AssertHelper.AssertRecent(createdAccessPolicy.RevisionDate); + } + [Theory] [InlineData(false, false)] [InlineData(true, false)] @@ -1071,6 +1039,78 @@ public class AccessPoliciesControllerTests : IClassFixture CreateProjectAndServiceAccountAsync(Guid organizationId, + bool misMatchOrganization = false) + { + var project = await _projectRepository.CreateAsync(new Project + { + OrganizationId = misMatchOrganization ? Guid.NewGuid() : organizationId, + Name = _mockEncryptedString, + }); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = organizationId, + Name = _mockEncryptedString, + }); + + return (project.Id, serviceAccount.Id); + } + + private async Task SetupProjectAndServiceAccountPermissionAsync(PermissionType permissionType, Guid projectId, + Guid serviceAccountId) + { + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = projectId, OrganizationUserId = orgUser.Id, Read = true, Write = true, + }, + new UserServiceAccountAccessPolicy + { + GrantedServiceAccountId = serviceAccountId, + OrganizationUserId = orgUser.Id, + Read = true, + Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + } + + private async Task SetupUserServiceAccountAccessPolicyRequestAsync( + PermissionType permissionType, Guid organizationId, Guid userId, Guid serviceAccountId) + { + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, newOrgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + var accessPolicies = new List + { + new UserServiceAccountAccessPolicy + { + GrantedServiceAccountId = serviceAccountId, + OrganizationUserId = newOrgUser.Id, + Read = true, + Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + return new AccessPoliciesCreateRequest + { + UserAccessPolicyRequests = new List + { + new() { GranteeId = userId, Read = true, Write = true }, + }, + }; + } + private class RequestSetupData { public Guid ProjectId { get; set; } diff --git a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs index ddf503774..e369120a9 100644 --- a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs @@ -1,4 +1,5 @@ -using Bit.Api.SecretsManager.Controllers; +using System.Security.Claims; +using Bit.Api.SecretsManager.Controllers; using Bit.Api.SecretsManager.Models.Request; using Bit.Api.Test.SecretsManager.Enums; using Bit.Core.Context; @@ -14,6 +15,7 @@ using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; +using Microsoft.AspNetCore.Authorization; using NSubstitute; using NSubstitute.ReturnsExtensions; using Xunit; @@ -383,9 +385,7 @@ public class AccessPoliciesControllerTests AccessPoliciesCreateRequest request) { sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(mockProject); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - sutProvider.GetDependency().CreateManyAsync(default, default, default) + sutProvider.GetDependency().CreateManyAsync(default) .ReturnsForAnyArgs(new List { data }); request = AddRequestsOverMax(request); @@ -394,7 +394,72 @@ public class AccessPoliciesControllerTests sutProvider.Sut.CreateProjectAccessPoliciesAsync(id, request)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .CreateManyAsync(Arg.Any>(), Arg.Any(), Arg.Any()); + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async void CreateProjectAccessPolicies_ProjectDoesNotExist_Throws( + SutProvider sutProvider, + Guid id, + AccessPoliciesCreateRequest request) + { + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateProjectAccessPoliciesAsync(id, request)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async void CreateProjectAccessPolicies_DuplicatePolicy_Throws( + SutProvider sutProvider, + Guid id, + Project mockProject, + UserProjectAccessPolicy data, + AccessPoliciesCreateRequest request) + { + var dup = new AccessPolicyRequest() { GranteeId = Guid.NewGuid(), Read = true, Write = true }; + request.UserAccessPolicyRequests = new[] { dup, dup }; + mockProject.Id = id; + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(mockProject); + sutProvider.GetDependency().CreateManyAsync(default) + .ReturnsForAnyArgs(new List { data }); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateProjectAccessPoliciesAsync(id, request)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async void CreateProjectAccessPolicies_NoAccess_Throws( + SutProvider sutProvider, + Guid id, + Project mockProject, + UserProjectAccessPolicy data, + AccessPoliciesCreateRequest request) + { + mockProject.Id = id; + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(mockProject); + var policies = request.ToBaseAccessPoliciesForProject(id, mockProject.OrganizationId); + foreach (var policy in policies) + { + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), policy, + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Failed()); + } + sutProvider.GetDependency().CreateManyAsync(default) + .ReturnsForAnyArgs(new List { data }); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateProjectAccessPoliciesAsync(id, request)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); } [Theory] @@ -406,16 +471,22 @@ public class AccessPoliciesControllerTests UserProjectAccessPolicy data, AccessPoliciesCreateRequest request) { + mockProject.Id = id; sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(mockProject); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - sutProvider.GetDependency().CreateManyAsync(default, default, default) + var policies = request.ToBaseAccessPoliciesForProject(id, mockProject.OrganizationId); + foreach (var policy in policies) + { + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), policy, + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); + } + sutProvider.GetDependency().CreateManyAsync(default) .ReturnsForAnyArgs(new List { data }); await sutProvider.Sut.CreateProjectAccessPoliciesAsync(id, request); await sutProvider.GetDependency().Received(1) - .CreateManyAsync(Arg.Any>(), Arg.Any(), Arg.Any()); + .CreateManyAsync(Arg.Any>()); } [Theory] @@ -428,10 +499,8 @@ public class AccessPoliciesControllerTests AccessPoliciesCreateRequest request) { sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); sutProvider.GetDependency() - .CreateManyAsync(default, default, default) + .CreateManyAsync(default) .ReturnsForAnyArgs(new List { data }); request = AddRequestsOverMax(request); @@ -440,7 +509,74 @@ public class AccessPoliciesControllerTests sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .CreateManyAsync(Arg.Any>(), Arg.Any(), Arg.Any()); + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async void CreateServiceAccountAccessPolicies_ServiceAccountDoesNotExist_Throws( + SutProvider sutProvider, + Guid id, + AccessPoliciesCreateRequest request) + { + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async void CreateServiceAccountAccessPolicies_DuplicatePolicy_Throws( + SutProvider sutProvider, + Guid id, + ServiceAccount serviceAccount, + UserServiceAccountAccessPolicy data, + AccessPoliciesCreateRequest request) + { + var dup = new AccessPolicyRequest() { GranteeId = Guid.NewGuid(), Read = true, Write = true }; + request.UserAccessPolicyRequests = new[] { dup, dup }; + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount); + + sutProvider.GetDependency() + .CreateManyAsync(default) + .ReturnsForAnyArgs(new List { data }); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async void CreateServiceAccountAccessPolicies_NoAccess_Throws( + SutProvider sutProvider, + Guid id, + ServiceAccount serviceAccount, + UserServiceAccountAccessPolicy data, + AccessPoliciesCreateRequest request) + { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount); + var policies = request.ToBaseAccessPoliciesForServiceAccount(id, serviceAccount.OrganizationId); + foreach (var policy in policies) + { + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), policy, + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Failed()); + } + + sutProvider.GetDependency() + .CreateManyAsync(default) + .ReturnsForAnyArgs(new List { data }); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); } [Theory] @@ -453,16 +589,22 @@ public class AccessPoliciesControllerTests AccessPoliciesCreateRequest request) { sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + var policies = request.ToBaseAccessPoliciesForServiceAccount(id, serviceAccount.OrganizationId); + foreach (var policy in policies) + { + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), policy, + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); + } + sutProvider.GetDependency() - .CreateManyAsync(default, default, default) + .CreateManyAsync(default) .ReturnsForAnyArgs(new List { data }); await sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request); await sutProvider.GetDependency().Received(1) - .CreateManyAsync(Arg.Any>(), Arg.Any(), Arg.Any()); + .CreateManyAsync(Arg.Any>()); } [Theory] @@ -475,10 +617,8 @@ public class AccessPoliciesControllerTests List request) { sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); sutProvider.GetDependency() - .CreateManyAsync(default, default, default) + .CreateManyAsync(default) .ReturnsForAnyArgs(new List { data }); request = AddRequestsOverMax(request); @@ -487,7 +627,73 @@ public class AccessPoliciesControllerTests sutProvider.Sut.CreateServiceAccountGrantedPoliciesAsync(id, request)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .CreateManyAsync(Arg.Any>(), Arg.Any(), Arg.Any()); + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async void CreateServiceAccountGrantedPolicies_ServiceAccountDoesNotExist_Throws( + SutProvider sutProvider, + Guid id, + List request) + { + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateServiceAccountGrantedPoliciesAsync(id, request)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async void CreateServiceAccountGrantedPolicies_DuplicatePolicy_Throws( + SutProvider sutProvider, + Guid id, + ServiceAccount serviceAccount, + ServiceAccountProjectAccessPolicy data, + List request) + { + var dup = new GrantedAccessPolicyRequest() { GrantedId = Guid.NewGuid(), Read = true, Write = true }; + request.Add(dup); + request.Add(dup); + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount); + + sutProvider.GetDependency() + .CreateManyAsync(default) + .ReturnsForAnyArgs(new List { data }); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateServiceAccountGrantedPoliciesAsync(id, request)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async void CreateServiceAccountGrantedPolicies_NoAccess_Throws( + SutProvider sutProvider, + Guid id, + ServiceAccount serviceAccount, + ServiceAccountProjectAccessPolicy data, + List request) + { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount); + sutProvider.GetDependency() + .CreateManyAsync(default) + .ReturnsForAnyArgs(new List { data }); + foreach (var policy in request) + { + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), policy, + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Failed()); + } + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateServiceAccountGrantedPoliciesAsync(id, request)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); } [Theory] @@ -500,16 +706,42 @@ public class AccessPoliciesControllerTests List request) { sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); sutProvider.GetDependency() - .CreateManyAsync(default, default, default) + .CreateManyAsync(default) .ReturnsForAnyArgs(new List { data }); + foreach (var policy in request) + { + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), policy, + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); + } await sutProvider.Sut.CreateServiceAccountGrantedPoliciesAsync(id, request); await sutProvider.GetDependency().Received(1) - .CreateManyAsync(Arg.Any>(), Arg.Any(), Arg.Any()); + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async void UpdateAccessPolicies_NoAccess_Throws( + SutProvider sutProvider, + Guid id, + UserProjectAccessPolicy data, + AccessPolicyUpdateRequest request) + { + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), data, + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Failed()); + sutProvider.GetDependency().GetByIdAsync(id).Returns(data); + sutProvider.GetDependency().UpdateAsync(default, default, default) + .ReturnsForAnyArgs(data); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.UpdateAccessPolicyAsync(id, request)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .UpdateAsync(Arg.Any(), Arg.Is(request.Read), Arg.Is(request.Write)); } [Theory] @@ -520,27 +752,48 @@ public class AccessPoliciesControllerTests UserProjectAccessPolicy data, AccessPolicyUpdateRequest request) { - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - sutProvider.GetDependency().UpdateAsync(default, default, default, default) + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), data, + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); + sutProvider.GetDependency().GetByIdAsync(id).Returns(data); + sutProvider.GetDependency().UpdateAsync(default, default, default) .ReturnsForAnyArgs(data); await sutProvider.Sut.UpdateAccessPolicyAsync(id, request); await sutProvider.GetDependency().Received(1) - .UpdateAsync(Arg.Any(), Arg.Is(request.Read), Arg.Is(request.Write), Arg.Any()); + .UpdateAsync(Arg.Any(), Arg.Is(request.Read), Arg.Is(request.Write)); + } + + [Theory] + [BitAutoData] + public async void DeleteAccessPolicies_NoAccess_Throws(SutProvider sutProvider, Guid id) + { + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), new UserProjectAccessPolicy(), + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Failed()); + sutProvider.GetDependency().DeleteAsync(default).ReturnsNull(); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.DeleteAccessPolicyAsync(id)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .DeleteAsync(Arg.Any()); } [Theory] [BitAutoData] public async void DeleteAccessPolicies_Success(SutProvider sutProvider, Guid id) { - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - sutProvider.GetDependency().DeleteAsync(default, default).ReturnsNull(); + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), new UserProjectAccessPolicy(), + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); + sutProvider.GetDependency().DeleteAsync(default).ReturnsNull(); await sutProvider.Sut.DeleteAccessPolicyAsync(id); await sutProvider.GetDependency().Received(1) - .DeleteAsync(Arg.Any(), Arg.Any()); + .DeleteAsync(Arg.Any()); } [Theory]