From b629c31de99bc066a9810d53a531edc95933ad03 Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Tue, 11 Jul 2023 15:15:18 -0500 Subject: [PATCH] [SM-787] Extract authorization from project delete command (#2987) * Extract authorization from project delete command * Support service account write access --------- Co-authored-by: Matt Bishop --- .../Projects/ProjectAuthorizationHandler.cs | 17 +++ .../Commands/Projects/DeleteProjectCommand.cs | 71 +---------- .../Repositories/ProjectRepository.cs | 23 ++-- .../ProjectAuthorizationHandlerTests.cs | 110 ++++++++++++++---- .../SecretAuthorizationHandlerTests.cs | 2 +- .../Projects/DeleteProjectCommandTests.cs | 103 ++-------------- .../SecretsManager/Enums/PermissionType.cs | 2 +- .../Controllers/ProjectsController.cs | 42 ++++++- .../ProjectOperationRequirement.cs | 1 + .../Interfaces/IDeleteProjectCommand.cs | 2 +- .../Controllers/ProjectsControllerTests.cs | 109 ++++++++++++++--- 11 files changed, 269 insertions(+), 213 deletions(-) diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Projects/ProjectAuthorizationHandler.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Projects/ProjectAuthorizationHandler.cs index 689b4cb0e..cc46bd580 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Projects/ProjectAuthorizationHandler.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Projects/ProjectAuthorizationHandler.cs @@ -39,6 +39,9 @@ public class ProjectAuthorizationHandler : AuthorizationHandler>> DeleteProjects(List ids, Guid userId) + public async Task DeleteProjects(IEnumerable projects) { - if (ids.Any() != true || userId == new Guid()) - { - throw new ArgumentNullException(); - } - - var projects = (await _projectRepository.GetManyWithSecretsByIds(ids))?.ToList(); - - if (projects?.Any() != true || projects.Count != ids.Count) - { - throw new NotFoundException(); - } - - // Ensure all projects belongs to the same organization - var organizationId = projects.First().OrganizationId; - if (projects.Any(p => p.OrganizationId != organizationId)) - { - throw new BadRequestException(); - } - - if (!_currentContext.AccessSecretsManager(organizationId)) - { - throw new NotFoundException(); - } - - var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); - var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); - - var results = new List>(projects.Count); - var deleteIds = new List(); - - foreach (var project in projects) - { - var access = await _projectRepository.AccessToProjectAsync(project.Id, userId, accessClient); - if (!access.Write) - { - results.Add(new Tuple(project, "access denied")); - } - else - { - results.Add(new Tuple(project, "")); - deleteIds.Add(project.Id); - } - } - - if (deleteIds.Count > 0) - { - var secretIds = results.SelectMany(projTuple => projTuple.Item1?.Secrets?.Select(s => s.Id) ?? Array.Empty()).ToList(); - - if (secretIds.Count > 0) - { - await _secretRepository.UpdateRevisionDates(secretIds); - } - - await _projectRepository.DeleteManyByIdAsync(deleteIds); - } - - return results; + await _projectRepository.DeleteManyByIdAsync(projects.Select(p => p.Id)); } } diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs index 98021ecb2..a350a62e9 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs @@ -60,16 +60,23 @@ public class ProjectRepository : Repository ids) { - using (var scope = ServiceScopeFactory.CreateScope()) + using var scope = ServiceScopeFactory.CreateScope(); + var utcNow = DateTime.UtcNow; + var dbContext = GetDatabaseContext(scope); + var projects = dbContext.Project + .Where(c => ids.Contains(c.Id)) + .Include(p => p.Secrets); + await projects.ForEachAsync(project => { - var dbContext = GetDatabaseContext(scope); - var projects = dbContext.Project.Where(c => ids.Contains(c.Id)); - await projects.ForEachAsync(project => + foreach (var projectSecret in project.Secrets) { - dbContext.Remove(project); - }); - await dbContext.SaveChangesAsync(); - } + projectSecret.RevisionDate = utcNow; + } + + dbContext.Remove(project); + }); + + await dbContext.SaveChangesAsync(); } public async Task> GetManyWithSecretsByIds(IEnumerable ids) diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Projects/ProjectAuthorizationHandlerTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Projects/ProjectAuthorizationHandlerTests.cs index eb07a34ed..aa84db43f 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Projects/ProjectAuthorizationHandlerTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Projects/ProjectAuthorizationHandlerTests.cs @@ -191,7 +191,7 @@ public class ProjectAuthorizationHandlerTests sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId).Returns(false); sutProvider.GetDependency().GetAccessClientAsync(default, project.OrganizationId) .ReturnsForAnyArgs( - (AccessClientType.ServiceAccount, new Guid())); + (AccessClientType.Organization, new Guid())); var requirement = ProjectOperations.Update; var authzContext = new AuthorizationHandlerContext(new List { requirement }, claimsPrincipal, project); @@ -202,19 +202,43 @@ public class ProjectAuthorizationHandlerTests } [Theory] - [BitAutoData(PermissionType.RunAsUserWithPermission, true, false)] - [BitAutoData(PermissionType.RunAsUserWithPermission, false, false)] - [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false)] - [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, false)] - public async Task CanUpdateProject_ShouldNotSucceed(PermissionType permissionType, bool read, bool write, - SutProvider sutProvider, Project project, ClaimsPrincipal claimsPrincipal, + [BitAutoData(PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, true, true)] + [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, false, false)] + [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true, true)] + [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false, false)] + [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true, true)] + public async Task CanUpdateProject_AccessCheck(PermissionType permissionType, bool read, bool write, + bool expected, + SutProvider sutProvider, Project project, + ClaimsPrincipal claimsPrincipal, Guid userId) { + var requirement = ProjectOperations.Update; SetupPermission(sutProvider, permissionType, project.OrganizationId, userId); sutProvider.GetDependency() .AccessToProjectAsync(project.Id, userId, Arg.Any()) .Returns((read, write)); - var requirement = ProjectOperations.Update; + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, project); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.Equal(expected, authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData] + public async Task CanDeleteProject_AccessToSecretsManagerFalse_DoesNotSucceed( + SutProvider sutProvider, Project project, + ClaimsPrincipal claimsPrincipal) + { + var requirement = ProjectOperations.Delete; + sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId) + .Returns(false); var authzContext = new AuthorizationHandlerContext(new List { requirement }, claimsPrincipal, project); @@ -224,26 +248,68 @@ public class ProjectAuthorizationHandlerTests } [Theory] - [BitAutoData(PermissionType.RunAsAdmin, true, true)] - [BitAutoData(PermissionType.RunAsAdmin, false, true)] - [BitAutoData(PermissionType.RunAsUserWithPermission, true, true)] - [BitAutoData(PermissionType.RunAsUserWithPermission, false, true)] - [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true)] - [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true)] - public async Task CanUpdateProject_Success(PermissionType permissionType, bool read, bool write, - SutProvider sutProvider, Project project, ClaimsPrincipal claimsPrincipal, + [BitAutoData] + public async Task CanDeleteProject_NullResource_DoesNotSucceed( + SutProvider sutProvider, Project project, + ClaimsPrincipal claimsPrincipal, Guid userId) { - SetupPermission(sutProvider, permissionType, project.OrganizationId, userId); - sutProvider.GetDependency() - .AccessToProjectAsync(project.Id, userId, Arg.Any()) - .Returns((read, write)); - var requirement = ProjectOperations.Update; + var requirement = ProjectOperations.Delete; + SetupPermission(sutProvider, PermissionType.RunAsAdmin, project.OrganizationId, userId); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, null); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData] + public async Task CanDeleteProject_NotSupportedClientType_DoesNotSucceed( + SutProvider sutProvider, Project project, ClaimsPrincipal claimsPrincipal) + { + var requirement = ProjectOperations.Delete; + sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId) + .Returns(true); + sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId).Returns(false); + sutProvider.GetDependency().GetAccessClientAsync(default, project.OrganizationId) + .ReturnsForAnyArgs( + (AccessClientType.Organization, new Guid())); var authzContext = new AuthorizationHandlerContext(new List { requirement }, claimsPrincipal, project); await sutProvider.Sut.HandleAsync(authzContext); - Assert.True(authzContext.HasSucceeded); + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, true, true)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, true, true)] + [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, false, false)] + [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true, true)] + [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false, false)] + [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true, true)] + public async Task CanDeleteProject_AccessCheck(PermissionType permissionType, bool read, bool write, + bool expected, + SutProvider sutProvider, Project project, + ClaimsPrincipal claimsPrincipal, + Guid userId) + { + var requirement = ProjectOperations.Delete; + SetupPermission(sutProvider, permissionType, project.OrganizationId, userId); + sutProvider.GetDependency() + .AccessToProjectAsync(project.Id, userId, Arg.Any()) + .Returns((read, write)); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, project); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.Equal(expected, authzContext.HasSucceeded); } } diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Secrets/SecretAuthorizationHandlerTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Secrets/SecretAuthorizationHandlerTests.cs index 682af8b02..d7e49fb46 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Secrets/SecretAuthorizationHandlerTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Secrets/SecretAuthorizationHandlerTests.cs @@ -436,7 +436,7 @@ public class SecretAuthorizationHandlerTests [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true, true)] [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false, false)] [BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true, true)] - public async Task CanDeleteProject_AccessCheck(PermissionType permissionType, bool read, bool write, + public async Task CanDeleteSecret_AccessCheck(PermissionType permissionType, bool read, bool write, bool expected, SutProvider sutProvider, Secret secret, ClaimsPrincipal claimsPrincipal, diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Projects/DeleteProjectCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Projects/DeleteProjectCommandTests.cs index dbccc9a96..0411200ec 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Projects/DeleteProjectCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Projects/DeleteProjectCommandTests.cs @@ -1,114 +1,27 @@ using Bit.Commercial.Core.SecretsManager.Commands.Projects; -using Bit.Core.Context; -using Bit.Core.Enums; -using Bit.Core.Exceptions; -using Bit.Core.Identity; 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 Xunit; namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Projects; [SutProviderCustomize] +[ProjectCustomize] public class DeleteProjectCommandTests { [Theory] [BitAutoData] - public async Task DeleteProjects_Throws_NotFoundException(List data, Guid userId, + public async Task DeleteProjects_Success(List data, SutProvider sutProvider) { - sutProvider.GetDependency().GetManyWithSecretsByIds(data).Returns(new List()); - - await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteProjects(data, userId)); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteManyByIdAsync(default); - } - - [Theory] - [BitAutoData] - public async Task Delete_OneIdNotFound_Throws_NotFoundException(List data, Guid userId, - SutProvider sutProvider) - { - var project = new Project() - { - Id = Guid.NewGuid() - }; - sutProvider.GetDependency().GetManyWithSecretsByIds(data).Returns(new List() { project }); - - await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteProjects(data, userId)); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteManyByIdAsync(default); - } - - [Theory] - [BitAutoData] - public async Task DeleteSecrets_User_Success(List data, Guid userId, Guid organizationId, - SutProvider sutProvider) - { - var projects = data.Select(id => new Project { Id = id, OrganizationId = organizationId }).ToList(); - - sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); - sutProvider.GetDependency().ClientType = ClientType.User; - sutProvider.GetDependency().GetManyWithSecretsByIds(data).Returns(projects); - sutProvider.GetDependency().AccessToProjectAsync(Arg.Any(), userId, AccessClientType.User) - .Returns((true, true)); - - var results = await sutProvider.Sut.DeleteProjects(data, userId); - - foreach (var result in results) - { - Assert.Equal("", result.Item2); - } - - await sutProvider.GetDependency().Received(1).DeleteManyByIdAsync(Arg.Is>(d => d.SequenceEqual(data))); - } - - [Theory] - [BitAutoData] - public async Task Delete_User_No_Permission(List data, Guid userId, Guid organizationId, - SutProvider sutProvider) - { - var projects = data.Select(id => new Project { Id = id, OrganizationId = organizationId }).ToList(); - - sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); - sutProvider.GetDependency().ClientType = ClientType.User; - sutProvider.GetDependency().GetManyWithSecretsByIds(data).Returns(projects); - sutProvider.GetDependency().AccessToProjectAsync(Arg.Any(), userId, AccessClientType.User) - .Returns((false, false)); - - var results = await sutProvider.Sut.DeleteProjects(data, userId); - - foreach (var result in results) - { - Assert.Equal("access denied", result.Item2); - } - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteManyByIdAsync(default); - } - - [Theory] - [BitAutoData] - public async Task Delete_OrganizationAdmin_Success(List data, Guid userId, Guid organizationId, - SutProvider sutProvider) - { - var projects = data.Select(id => new Project { Id = id, OrganizationId = organizationId }).ToList(); - - sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); - sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); - sutProvider.GetDependency().GetManyWithSecretsByIds(data).Returns(projects); - sutProvider.GetDependency().AccessToProjectAsync(Arg.Any(), userId, AccessClientType.NoAccessCheck) - .Returns((true, true)); - - - var results = await sutProvider.Sut.DeleteProjects(data, userId); - - await sutProvider.GetDependency().Received(1).DeleteManyByIdAsync(Arg.Is>(d => d.SequenceEqual(data))); - foreach (var result in results) - { - Assert.Equal("", result.Item2); - } + await sutProvider.Sut.DeleteProjects(data); + await sutProvider.GetDependency() + .Received(1) + .DeleteManyByIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data.Select(d => d.Id)))); } } diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Enums/PermissionType.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Enums/PermissionType.cs index ce718b061..2c8b7f9db 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Enums/PermissionType.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Enums/PermissionType.cs @@ -4,5 +4,5 @@ public enum PermissionType { RunAsAdmin, RunAsUserWithPermission, - RunAsServiceAccountWithPermission + RunAsServiceAccountWithPermission, } diff --git a/src/Api/SecretsManager/Controllers/ProjectsController.cs b/src/Api/SecretsManager/Controllers/ProjectsController.cs index e94da33a6..7ed428ad5 100644 --- a/src/Api/SecretsManager/Controllers/ProjectsController.cs +++ b/src/Api/SecretsManager/Controllers/ProjectsController.cs @@ -6,6 +6,7 @@ using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.AuthorizationRequirements; using Bit.Core.SecretsManager.Commands.Projects.Interfaces; +using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Services; using Bit.Core.Utilities; @@ -127,11 +128,44 @@ public class ProjectsController : Controller } [HttpPost("projects/delete")] - public async Task> BulkDeleteAsync([FromBody] List ids) + public async Task> BulkDeleteAsync( + [FromBody] List ids) { - var userId = _userService.GetProperUserId(User).Value; - var results = await _deleteProjectCommand.DeleteProjects(ids, userId); - var responses = results.Select(r => new BulkDeleteResponseModel(r.Item1.Id, r.Item2)); + var projects = (await _projectRepository.GetManyWithSecretsByIds(ids)).ToList(); + if (!projects.Any() || projects.Count != ids.Count) + { + throw new NotFoundException(); + } + + // Ensure all projects belongs to the same organization + var organizationId = projects.First().OrganizationId; + if (projects.Any(p => p.OrganizationId != organizationId) || + !_currentContext.AccessSecretsManager(organizationId)) + { + throw new NotFoundException(); + } + + var projectsToDelete = new List(); + var results = new List<(Project Project, string Error)>(); + + foreach (var project in projects) + { + var authorizationResult = + await _authorizationService.AuthorizeAsync(User, project, ProjectOperations.Delete); + if (authorizationResult.Succeeded) + { + projectsToDelete.Add(project); + results.Add((project, "")); + } + else + { + results.Add((project, "access denied")); + } + } + + await _deleteProjectCommand.DeleteProjects(projectsToDelete); + + var responses = results.Select(r => new BulkDeleteResponseModel(r.Project.Id, r.Error)); return new ListResponseModel(responses); } } diff --git a/src/Core/SecretsManager/AuthorizationRequirements/ProjectOperationRequirement.cs b/src/Core/SecretsManager/AuthorizationRequirements/ProjectOperationRequirement.cs index ccad661ca..231bed205 100644 --- a/src/Core/SecretsManager/AuthorizationRequirements/ProjectOperationRequirement.cs +++ b/src/Core/SecretsManager/AuthorizationRequirements/ProjectOperationRequirement.cs @@ -10,4 +10,5 @@ public static class ProjectOperations { public static readonly ProjectOperationRequirement Create = new() { Name = nameof(Create) }; public static readonly ProjectOperationRequirement Update = new() { Name = nameof(Update) }; + public static readonly ProjectOperationRequirement Delete = new() { Name = nameof(Delete) }; } diff --git a/src/Core/SecretsManager/Commands/Projects/Interfaces/IDeleteProjectCommand.cs b/src/Core/SecretsManager/Commands/Projects/Interfaces/IDeleteProjectCommand.cs index 64b537bed..f69d37b2e 100644 --- a/src/Core/SecretsManager/Commands/Projects/Interfaces/IDeleteProjectCommand.cs +++ b/src/Core/SecretsManager/Commands/Projects/Interfaces/IDeleteProjectCommand.cs @@ -4,6 +4,6 @@ namespace Bit.Core.SecretsManager.Commands.Projects.Interfaces; public interface IDeleteProjectCommand { - Task>> DeleteProjects(List ids, Guid userId); + Task DeleteProjects(IEnumerable projects); } diff --git a/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs index 66578218c..30287eb95 100644 --- a/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs @@ -248,26 +248,107 @@ public class ProjectsControllerTests [Theory] [BitAutoData] - public async void BulkDeleteProjects_Success(SutProvider sutProvider, List data) + public async void BulkDeleteProjects_NoProjectsFound_ThrowsNotFound( + SutProvider sutProvider, List data) { - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); var ids = data.Select(project => project.Id).ToList(); - var mockResult = data.Select(project => new Tuple(project, "")).ToList(); - - sutProvider.GetDependency().DeleteProjects(ids, default).ReturnsForAnyArgs(mockResult); - - var results = await sutProvider.Sut.BulkDeleteAsync(ids); - await sutProvider.GetDependency().Received(1) - .DeleteProjects(Arg.Is(ids), Arg.Any()); - Assert.Equal(data.Count, results.Data.Count()); + sutProvider.GetDependency().GetManyWithSecretsByIds(Arg.Is(ids)).ReturnsForAnyArgs(new List()); + await Assert.ThrowsAsync(() => sutProvider.Sut.BulkDeleteAsync(ids)); } [Theory] [BitAutoData] - public async void BulkDeleteProjects_NoGuids_ThrowsArgumentNullException( - SutProvider sutProvider) + public async void BulkDeleteProjects_ProjectsFoundMisMatch_ThrowsNotFound( + SutProvider sutProvider, List data, Project mockProject) { - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - await Assert.ThrowsAsync(() => sutProvider.Sut.BulkDeleteAsync(new List())); + data.Add(mockProject); + var ids = data.Select(project => project.Id).ToList(); + sutProvider.GetDependency().GetManyWithSecretsByIds(Arg.Is(ids)).ReturnsForAnyArgs(new List { mockProject }); + await Assert.ThrowsAsync(() => sutProvider.Sut.BulkDeleteAsync(ids)); + } + + [Theory] + [BitAutoData] + public async void BulkDeleteProjects_OrganizationMistMatch_ThrowsNotFound( + SutProvider sutProvider, List data) + { + + var ids = data.Select(project => project.Id).ToList(); + sutProvider.GetDependency().GetManyWithSecretsByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); + await Assert.ThrowsAsync(() => sutProvider.Sut.BulkDeleteAsync(ids)); + } + + [Theory] + [BitAutoData] + public async void BulkDeleteProjects_NoAccessToSecretsManager_ThrowsNotFound( + SutProvider sutProvider, List data) + { + + var ids = data.Select(project => project.Id).ToList(); + var organizationId = data.First().OrganizationId; + foreach (var project in data) + { + project.OrganizationId = organizationId; + } + sutProvider.GetDependency().AccessSecretsManager(Arg.Is(organizationId)).ReturnsForAnyArgs(false); + sutProvider.GetDependency().GetManyWithSecretsByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); + await Assert.ThrowsAsync(() => sutProvider.Sut.BulkDeleteAsync(ids)); + } + + [Theory] + [BitAutoData] + public async void BulkDeleteProjects_ReturnsAccessDeniedForProjectsWithoutAccess_Success( + SutProvider sutProvider, List data) + { + + var ids = data.Select(project => project.Id).ToList(); + var organizationId = data.First().OrganizationId; + foreach (var project in data) + { + project.OrganizationId = organizationId; + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), project, + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); + } + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), data.First(), + Arg.Any>()).Returns(AuthorizationResult.Failed()); + + sutProvider.GetDependency().AccessSecretsManager(Arg.Is(organizationId)).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetManyWithSecretsByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); + var results = await sutProvider.Sut.BulkDeleteAsync(ids); + Assert.Equal(data.Count, results.Data.Count()); + Assert.Equal("access denied", results.Data.First().Error); + + data.Remove(data.First()); + await sutProvider.GetDependency().Received(1) + .DeleteProjects(Arg.Is(AssertHelper.AssertPropertyEqual(data))); + } + + [Theory] + [BitAutoData] + public async void BulkDeleteProjects_Success(SutProvider sutProvider, List data) + { + var ids = data.Select(project => project.Id).ToList(); + var organizationId = data.First().OrganizationId; + foreach (var project in data) + { + project.OrganizationId = organizationId; + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), project, + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); + } + + sutProvider.GetDependency().GetManyWithSecretsByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); + sutProvider.GetDependency().AccessSecretsManager(Arg.Is(organizationId)).ReturnsForAnyArgs(true); + + var results = await sutProvider.Sut.BulkDeleteAsync(ids); + await sutProvider.GetDependency().Received(1) + .DeleteProjects(Arg.Is(AssertHelper.AssertPropertyEqual(data))); + Assert.Equal(data.Count, results.Data.Count()); + foreach (var result in results.Data) + { + Assert.Null(result.Error); + } } }