diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Projects/ProjectAuthorizationHandler.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Projects/ProjectAuthorizationHandler.cs new file mode 100644 index 0000000000..0d898a7047 --- /dev/null +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Projects/ProjectAuthorizationHandler.cs @@ -0,0 +1,88 @@ +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.SecretsManager.AuthorizationRequirements; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Services; +using Microsoft.AspNetCore.Authorization; + +namespace Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Projects; + +public class ProjectAuthorizationHandler : AuthorizationHandler +{ + private readonly ICurrentContext _currentContext; + private readonly IProjectRepository _projectRepository; + private readonly IUserService _userService; + + public ProjectAuthorizationHandler(ICurrentContext currentContext, IUserService userService, + IProjectRepository projectRepository) + { + _currentContext = currentContext; + _userService = userService; + _projectRepository = projectRepository; + } + + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + ProjectOperationRequirement requirement, + Project resource) + { + if (!_currentContext.AccessSecretsManager(resource.OrganizationId)) + { + return; + } + + switch (requirement) + { + case not null when requirement == ProjectOperations.Create: + await CanCreateProjectAsync(context, requirement, resource); + break; + case not null when requirement == ProjectOperations.Update: + await CanUpdateProjectAsync(context, requirement, resource); + break; + default: + throw new ArgumentException("Unsupported project operation requirement type provided.", nameof(requirement)); + } + } + + private async Task CanCreateProjectAsync(AuthorizationHandlerContext context, + ProjectOperationRequirement requirement, Project resource) + { + var accessClient = await GetAccessClientAsync(resource.OrganizationId); + var hasAccess = accessClient switch + { + AccessClientType.NoAccessCheck => true, + AccessClientType.User => true, + AccessClientType.ServiceAccount => false, + _ => false, + }; + + if (hasAccess) + { + context.Succeed(requirement); + } + } + + private async Task CanUpdateProjectAsync(AuthorizationHandlerContext context, + ProjectOperationRequirement requirement, Project resource) + { + var accessClient = await GetAccessClientAsync(resource.OrganizationId); + if (accessClient == AccessClientType.ServiceAccount) + { + return; + } + + var userId = _userService.GetProperUserId(context.User).Value; + var access = await _projectRepository.AccessToProjectAsync(resource.Id, userId, accessClient); + + if (access.Write) + { + context.Succeed(requirement); + } + } + + private async Task GetAccessClientAsync(Guid organizationId) + { + var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); + return AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + } +} diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/CreateProjectCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/CreateProjectCommand.cs index 055fda0c8b..91bfc2960b 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/CreateProjectCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/CreateProjectCommand.cs @@ -14,8 +14,7 @@ public class CreateProjectCommand : ICreateProjectCommand public CreateProjectCommand( IAccessPolicyRepository accessPolicyRepository, IOrganizationUserRepository organizationUserRepository, - IProjectRepository projectRepository - ) + IProjectRepository projectRepository) { _accessPolicyRepository = accessPolicyRepository; _organizationUserRepository = organizationUserRepository; diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/UpdateProjectCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/UpdateProjectCommand.cs index d03dcf9544..4770066c70 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/UpdateProjectCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/UpdateProjectCommand.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.Projects.Interfaces; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -10,15 +8,13 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Projects; public class UpdateProjectCommand : IUpdateProjectCommand { private readonly IProjectRepository _projectRepository; - private readonly ICurrentContext _currentContext; - public UpdateProjectCommand(IProjectRepository projectRepository, ICurrentContext currentContext) + public UpdateProjectCommand(IProjectRepository projectRepository) { _projectRepository = projectRepository; - _currentContext = currentContext; } - public async Task UpdateAsync(Project updatedProject, Guid userId) + public async Task UpdateAsync(Project updatedProject) { var project = await _projectRepository.GetByIdAsync(updatedProject.Id); if (project == null) @@ -26,20 +22,6 @@ public class UpdateProjectCommand : IUpdateProjectCommand throw new NotFoundException(); } - if (!_currentContext.AccessSecretsManager(project.OrganizationId)) - { - throw new NotFoundException(); - } - - var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId); - var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); - - var access = await _projectRepository.AccessToProjectAsync(updatedProject.Id, userId, accessClient); - if (!access.Write || accessClient == AccessClientType.ServiceAccount) - { - throw new NotFoundException(); - } - project.Name = updatedProject.Name; project.RevisionDate = DateTime.UtcNow; diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs index 7922dd0341..149bc690af 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.Commands.AccessPolicies; +using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Projects; +using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; using Bit.Commercial.Core.SecretsManager.Commands.AccessTokens; using Bit.Commercial.Core.SecretsManager.Commands.Porting; using Bit.Commercial.Core.SecretsManager.Commands.Projects; @@ -12,6 +13,7 @@ using Bit.Core.SecretsManager.Commands.Projects.Interfaces; using Bit.Core.SecretsManager.Commands.Secrets.Interfaces; using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces; using Bit.Core.SecretsManager.Commands.Trash.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; namespace Bit.Commercial.Core.SecretsManager; @@ -20,6 +22,7 @@ public static class SecretsManagerCollectionExtensions { public static void AddSecretsManagerServices(this IServiceCollection services) { + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); 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 a05140fb01..98021ecb2b 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs @@ -125,7 +125,7 @@ public class ProjectRepository : Repository ProjectsAreInOrganization(List projectIds, Guid organizationId) 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 new file mode 100644 index 0000000000..3e9b73256a --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Projects/ProjectAuthorizationHandlerTests.cs @@ -0,0 +1,245 @@ +using System.Reflection; +using System.Security.Claims; +using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Projects; +using Bit.Commercial.Core.Test.SecretsManager.Enums; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Identity; +using Bit.Core.SecretsManager.AuthorizationRequirements; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Services; +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.Projects; + +[SutProviderCustomize] +[ProjectCustomize] +public class ProjectAuthorizationHandlerTests +{ + private static void SetupPermission(SutProvider sutProvider, + PermissionType permissionType, Guid organizationId) + { + sutProvider.GetDependency().AccessSecretsManager(organizationId) + .Returns(true); + + sutProvider.GetDependency().ClientType + .Returns(ClientType.User); + + switch (permissionType) + { + case PermissionType.RunAsAdmin: + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); + break; + case PermissionType.RunAsUserWithPermission: + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); + break; + default: + throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null); + } + } + + [Fact] + public void ProjectOperations_OnlyPublicStatic() + { + var publicStaticFields = typeof(ProjectOperations).GetFields(BindingFlags.Public | BindingFlags.Static); + var allFields = typeof(ProjectOperations).GetFields(); + Assert.Equal(publicStaticFields.Length, allFields.Length); + } + + [Theory] + [BitAutoData] + public async Task Handler_UnsupportedProjectOperationRequirement_Throws( + SutProvider sutProvider, Project project, ClaimsPrincipal claimsPrincipal) + { + sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId) + .Returns(true); + var requirement = new ProjectOperationRequirement(); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, project); + + + await Assert.ThrowsAsync(() => sutProvider.Sut.HandleAsync(authzContext)); + } + + [Theory] + [BitAutoData] + public async Task Handler_SupportedProjectOperationRequirement_DoesNotThrow( + SutProvider sutProvider, Project project, ClaimsPrincipal claimsPrincipal) + { + sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId) + .Returns(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(new Guid()); + + var requirements = typeof(ProjectOperations).GetFields(BindingFlags.Public | BindingFlags.Static) + .Select(i => (ProjectOperationRequirement)i.GetValue(null)); + + foreach (var req in requirements) + { + var authzContext = new AuthorizationHandlerContext(new List { req }, + claimsPrincipal, project); + + await sutProvider.Sut.HandleAsync(authzContext); + } + } + + [Theory] + [BitAutoData] + public async Task CanCreateProject_AccessToSecretsManagerFalse_DoesNotSucceed( + SutProvider sutProvider, Project project, ClaimsPrincipal claimsPrincipal) + { + sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId) + .Returns(false); + var requirement = ProjectOperations.Create; + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, project); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(ClientType.ServiceAccount)] + [BitAutoData(ClientType.Organization)] + public async Task CanCreateProject_NotSupportedClientTypes_DoesNotSucceed(ClientType clientType, + SutProvider sutProvider, Project project, ClaimsPrincipal claimsPrincipal) + { + sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId) + .Returns(true); + sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId) + .Returns(false); + sutProvider.GetDependency().ClientType + .Returns(clientType); + var requirement = ProjectOperations.Create; + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, project); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async Task CanCreateProject_Success(PermissionType permissionType, + SutProvider sutProvider, Project project, ClaimsPrincipal claimsPrincipal) + { + SetupPermission(sutProvider, permissionType, project.OrganizationId); + var requirement = ProjectOperations.Create; + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, project); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.True(authzContext.HasSucceeded); + } + + + [Theory] + [BitAutoData] + public async Task CanUpdateProject_AccessToSecretsManagerFalse_DoesNotSucceed( + SutProvider sutProvider, Project project, ClaimsPrincipal claimsPrincipal) + { + sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId) + .Returns(false); + var requirement = ProjectOperations.Update; + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, project); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData] + public async Task CanUpdateProject_NullResource_DoesNotSucceed( + SutProvider sutProvider, Project project, ClaimsPrincipal claimsPrincipal, + Guid userId) + { + sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId) + .Returns(true); + SetupPermission(sutProvider, PermissionType.RunAsAdmin, project.OrganizationId); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(userId); + sutProvider.GetDependency() + .AccessToProjectAsync(project.Id, userId, Arg.Any()) + .Returns((true, true)); + var requirement = ProjectOperations.Update; + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, null); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData] + public async Task CanUpdateProject_NotSupportedClientType_DoesNotSucceed( + SutProvider sutProvider, Project project, ClaimsPrincipal claimsPrincipal) + { + sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId) + .Returns(true); + sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId).Returns(false); + sutProvider.GetDependency().ClientType + .Returns(ClientType.ServiceAccount); + var requirement = ProjectOperations.Update; + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, project); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, false)] + public async Task CanUpdateProject_ShouldNotSucceed(PermissionType permissionType, bool read, bool write, + SutProvider sutProvider, Project project, ClaimsPrincipal claimsPrincipal, + Guid userId) + { + SetupPermission(sutProvider, permissionType, project.OrganizationId); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(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.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin, true, true)] + [BitAutoData(PermissionType.RunAsAdmin, false, true)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, true)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, true)] + public async Task CanUpdateProject_Success(PermissionType permissionType, bool read, bool write, + SutProvider sutProvider, Project project, ClaimsPrincipal claimsPrincipal, + Guid userId) + { + SetupPermission(sutProvider, permissionType, project.OrganizationId); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(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.True(authzContext.HasSucceeded); + } +} diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommandTests.cs similarity index 99% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommandTests.cs index 1105f2a5a3..f4a3852f76 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommandTests.cs @@ -11,7 +11,7 @@ using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessPolicies; [SutProviderCustomize] [ProjectCustomize] diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/DeleteAccessPolicyCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommandTests.cs similarity index 99% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/DeleteAccessPolicyCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommandTests.cs index 5c98812ce6..11814f5774 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/DeleteAccessPolicyCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommandTests.cs @@ -13,7 +13,7 @@ using NSubstitute; using NSubstitute.ReturnsExtensions; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessPolicies; [SutProviderCustomize] [ProjectCustomize] diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/UpdateAccessPolicyCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommandTests.cs similarity index 99% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/UpdateAccessPolicyCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommandTests.cs index b027f47b5d..a5892d142e 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/UpdateAccessPolicyCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommandTests.cs @@ -13,7 +13,7 @@ using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessPolicies; [SutProviderCustomize] [ProjectCustomize] diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessTokens/CreateAccessTokenCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessTokens/CreateAccessTokenCommandTests.cs similarity index 97% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessTokens/CreateAccessTokenCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessTokens/CreateAccessTokenCommandTests.cs index de34e77392..e425238c90 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessTokens/CreateAccessTokenCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/AccessTokens/CreateAccessTokenCommandTests.cs @@ -9,7 +9,7 @@ using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.AccessTokens; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessTokens; [SutProviderCustomize] public class CreateServiceAccountCommandTests diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/CreateProjectCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Projects/CreateProjectCommandTests.cs similarity index 94% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/CreateProjectCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Projects/CreateProjectCommandTests.cs index 3739d4699f..9223e56d55 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/CreateProjectCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Projects/CreateProjectCommandTests.cs @@ -9,7 +9,7 @@ using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.Projects; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Projects; [SutProviderCustomize] [ProjectCustomize] diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/DeleteProjectCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Projects/DeleteProjectCommandTests.cs similarity index 98% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/DeleteProjectCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Projects/DeleteProjectCommandTests.cs index 9361c2a0ce..dbccc9a961 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/DeleteProjectCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Projects/DeleteProjectCommandTests.cs @@ -10,7 +10,7 @@ using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.Projects; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Projects; [SutProviderCustomize] public class DeleteProjectCommandTests diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Projects/UpdateProjectCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Projects/UpdateProjectCommandTests.cs new file mode 100644 index 0000000000..d82e552ba5 --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Projects/UpdateProjectCommandTests.cs @@ -0,0 +1,43 @@ +using Bit.Commercial.Core.SecretsManager.Commands.Projects; +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 NSubstitute; +using NSubstitute.ReturnsExtensions; +using Xunit; + +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Projects; + +[SutProviderCustomize] +[ProjectCustomize] +public class UpdateProjectCommandTests +{ + [Theory] + [BitAutoData] + public async Task UpdateAsync_Throws_NotFoundException(Project project, SutProvider sutProvider) + { + sutProvider.GetDependency().GetByIdAsync(project.Id).ReturnsNull(); + + await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(project)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); + } + + [Theory] + [BitAutoData] + public async Task UpdateAsync_Success(Project project, SutProvider sutProvider) + { + sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); + + var updatedProject = new Project { Id = project.Id, Name = "newName" }; + var result = await sutProvider.Sut.UpdateAsync(updatedProject); + + Assert.NotNull(result); + Assert.Equal("newName", result.Name); + + await sutProvider.GetDependency().ReceivedWithAnyArgs(1).ReplaceAsync(default); + } +} diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/CreateSecretCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Secrets/CreateSecretCommandTests.cs similarity index 97% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/CreateSecretCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Secrets/CreateSecretCommandTests.cs index 6cec64337e..bf2605ba20 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/CreateSecretCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Secrets/CreateSecretCommandTests.cs @@ -11,7 +11,7 @@ using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.Secrets; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Secrets; [SutProviderCustomize] [SecretCustomize] diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/DeleteSecretCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Secrets/DeleteSecretCommandTests.cs similarity index 98% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/DeleteSecretCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Secrets/DeleteSecretCommandTests.cs index 4ff4793a2e..c3183e9064 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/DeleteSecretCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Secrets/DeleteSecretCommandTests.cs @@ -12,7 +12,7 @@ using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.Secrets; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Secrets; [SutProviderCustomize] [ProjectCustomize] diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/UpdateSecretCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Secrets/UpdateSecretCommandTests.cs similarity index 99% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/UpdateSecretCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Secrets/UpdateSecretCommandTests.cs index b908145138..fff5d1a936 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/UpdateSecretCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Secrets/UpdateSecretCommandTests.cs @@ -13,7 +13,7 @@ using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.Secrets; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Secrets; [SutProviderCustomize] [SecretCustomize] diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/ServiceAccounts/CreateServiceAccountCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/ServiceAccounts/CreateServiceAccountCommandTests.cs similarity index 93% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/ServiceAccounts/CreateServiceAccountCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/ServiceAccounts/CreateServiceAccountCommandTests.cs index 7acd315651..85a347ff2b 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/ServiceAccounts/CreateServiceAccountCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/ServiceAccounts/CreateServiceAccountCommandTests.cs @@ -9,7 +9,7 @@ using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.ServiceAccounts; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.ServiceAccounts; [SutProviderCustomize] public class CreateServiceAccountCommandTests diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/ServiceAccounts/RevokeAccessTokenCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/ServiceAccounts/RevokeAccessTokenCommandTests.cs similarity index 94% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/ServiceAccounts/RevokeAccessTokenCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/ServiceAccounts/RevokeAccessTokenCommandTests.cs index 5f077c4ace..2bf006165e 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/ServiceAccounts/RevokeAccessTokenCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/ServiceAccounts/RevokeAccessTokenCommandTests.cs @@ -6,7 +6,7 @@ using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.ServiceAccounts; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.ServiceAccounts; [SutProviderCustomize] public class RevokeAccessTokenCommandTests diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/ServiceAccounts/UpdateServiceAccountCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/ServiceAccounts/UpdateServiceAccountCommandTests.cs similarity index 98% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/ServiceAccounts/UpdateServiceAccountCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/ServiceAccounts/UpdateServiceAccountCommandTests.cs index 3a06eac474..488138a4ec 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/ServiceAccounts/UpdateServiceAccountCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/ServiceAccounts/UpdateServiceAccountCommandTests.cs @@ -9,7 +9,7 @@ using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.ServiceAccounts; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.ServiceAccounts; [SutProviderCustomize] public class UpdateServiceAccountCommandTests diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/EmptyTrashCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Trash/EmptyTrashCommandTests.cs similarity index 96% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/EmptyTrashCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Trash/EmptyTrashCommandTests.cs index 603e5fcf4a..44562a82d8 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/EmptyTrashCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Trash/EmptyTrashCommandTests.cs @@ -8,7 +8,7 @@ using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.Trash; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Trash; [SutProviderCustomize] [ProjectCustomize] diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/RestoreTrashCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Trash/RestoreTrashCommandTests.cs similarity index 96% rename from bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/RestoreTrashCommandTests.cs rename to bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Trash/RestoreTrashCommandTests.cs index 1054ad154f..7dc8c1fd03 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/RestoreTrashCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Trash/RestoreTrashCommandTests.cs @@ -8,7 +8,7 @@ using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -namespace Bit.Commercial.Core.Test.SecretsManager.Trash; +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Trash; [SutProviderCustomize] [ProjectCustomize] diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/UpdateProjectCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/UpdateProjectCommandTests.cs deleted file mode 100644 index f105f5cbeb..0000000000 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/UpdateProjectCommandTests.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Bit.Commercial.Core.SecretsManager.Commands.Projects; -using Bit.Core.Context; -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.Projects; - -[SutProviderCustomize] -[ProjectCustomize] -public class UpdateProjectCommandTests -{ - [Theory] - [BitAutoData] - public async Task UpdateAsync_Throws_NotFoundException(Project project, Guid userId, SutProvider sutProvider) - { - sutProvider.GetDependency().GetByIdAsync(project.Id).ReturnsNull(); - - await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(project, userId)); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); - } - - [Theory] - [BitAutoData] - public async Task UpdateAsync_Admin_Succeeds(Project project, Guid userId, SutProvider sutProvider) - { - sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); - sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId).Returns(true); - sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId).Returns(true); - sutProvider.GetDependency().AccessToProjectAsync(project.Id, userId, AccessClientType.NoAccessCheck) - .Returns((true, true)); - - var project2 = new Project { Id = project.Id, Name = "newName" }; - var result = await sutProvider.Sut.UpdateAsync(project2, userId); - - Assert.NotNull(result); - Assert.Equal("newName", result.Name); - AssertHelper.AssertRecent(result.RevisionDate); - - await sutProvider.GetDependency().ReceivedWithAnyArgs(1).ReplaceAsync(default); - } - - [Theory] - [BitAutoData] - public async Task UpdateAsync_User_NoAccess(Project project, Guid userId, SutProvider sutProvider) - { - sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); - sutProvider.GetDependency().AccessToProjectAsync(project.Id, userId, AccessClientType.User) - .Returns((false, false)); - sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId).Returns(true); - - await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(project, userId)); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); - } - - [Theory] - [BitAutoData] - public async Task UpdateAsync_User_Success(Project project, Guid userId, SutProvider sutProvider) - { - sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); - sutProvider.GetDependency().AccessToProjectAsync(project.Id, userId, AccessClientType.User) - .Returns((true, true)); - sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId).Returns(true); - - var project2 = new Project { Id = project.Id, Name = "newName" }; - var result = await sutProvider.Sut.UpdateAsync(project2, userId); - - Assert.NotNull(result); - Assert.Equal("newName", result.Name); - - await sutProvider.GetDependency().ReceivedWithAnyArgs(1).ReplaceAsync(default); - } -} diff --git a/src/Api/SecretsManager/Controllers/ProjectsController.cs b/src/Api/SecretsManager/Controllers/ProjectsController.cs index b4f9983bd7..960f750952 100644 --- a/src/Api/SecretsManager/Controllers/ProjectsController.cs +++ b/src/Api/SecretsManager/Controllers/ProjectsController.cs @@ -4,6 +4,7 @@ using Bit.Api.SecretsManager.Models.Response; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.SecretsManager.AuthorizationRequirements; using Bit.Core.SecretsManager.Commands.Projects.Interfaces; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Services; @@ -22,6 +23,7 @@ public class ProjectsController : Controller private readonly ICreateProjectCommand _createProjectCommand; private readonly IUpdateProjectCommand _updateProjectCommand; private readonly IDeleteProjectCommand _deleteProjectCommand; + private readonly IAuthorizationService _authorizationService; public ProjectsController( ICurrentContext currentContext, @@ -29,7 +31,8 @@ public class ProjectsController : Controller IProjectRepository projectRepository, ICreateProjectCommand createProjectCommand, IUpdateProjectCommand updateProjectCommand, - IDeleteProjectCommand deleteProjectCommand) + IDeleteProjectCommand deleteProjectCommand, + IAuthorizationService authorizationService) { _currentContext = currentContext; _userService = userService; @@ -37,6 +40,7 @@ public class ProjectsController : Controller _createProjectCommand = createProjectCommand; _updateProjectCommand = updateProjectCommand; _deleteProjectCommand = deleteProjectCommand; + _authorizationService = authorizationService; } [HttpGet("organizations/{organizationId}/projects")] @@ -58,26 +62,37 @@ public class ProjectsController : Controller } [HttpPost("organizations/{organizationId}/projects")] - public async Task CreateAsync([FromRoute] Guid organizationId, [FromBody] ProjectCreateRequestModel createRequest) + public async Task CreateAsync([FromRoute] Guid organizationId, + [FromBody] ProjectCreateRequestModel createRequest) { - if (!_currentContext.AccessSecretsManager(organizationId)) + var project = createRequest.ToProject(organizationId); + var authorizationResult = + await _authorizationService.AuthorizeAsync(User, project, ProjectOperations.Create); + if (!authorizationResult.Succeeded) { throw new NotFoundException(); } var userId = _userService.GetProperUserId(User).Value; - var result = await _createProjectCommand.CreateAsync(createRequest.ToProject(organizationId), userId); + var result = await _createProjectCommand.CreateAsync(project, userId); // Creating a project means you have read & write permission. return new ProjectResponseModel(result, true, true); } [HttpPut("projects/{id}")] - public async Task UpdateAsync([FromRoute] Guid id, [FromBody] ProjectUpdateRequestModel updateRequest) + public async Task UpdateAsync([FromRoute] Guid id, + [FromBody] ProjectUpdateRequestModel updateRequest) { - var userId = _userService.GetProperUserId(User).Value; + var project = await _projectRepository.GetByIdAsync(id); + var authorizationResult = + await _authorizationService.AuthorizeAsync(User, project, ProjectOperations.Update); + if (!authorizationResult.Succeeded) + { + throw new NotFoundException(); + } - var result = await _updateProjectCommand.UpdateAsync(updateRequest.ToProject(id), userId); + var result = await _updateProjectCommand.UpdateAsync(updateRequest.ToProject(id)); // Updating a project means you have read & write permission. return new ProjectResponseModel(result, true, true); diff --git a/src/Api/SecretsManager/Models/Request/ProjectCreateRequestModel.cs b/src/Api/SecretsManager/Models/Request/ProjectCreateRequestModel.cs index fcf64271f8..65c4d244d2 100644 --- a/src/Api/SecretsManager/Models/Request/ProjectCreateRequestModel.cs +++ b/src/Api/SecretsManager/Models/Request/ProjectCreateRequestModel.cs @@ -12,7 +12,7 @@ public class ProjectCreateRequestModel public Project ToProject(Guid organizationId) { - return new Project() + return new Project { OrganizationId = organizationId, Name = Name, diff --git a/src/Api/SecretsManager/Models/Request/ProjectUpdateRequestModel.cs b/src/Api/SecretsManager/Models/Request/ProjectUpdateRequestModel.cs index e552a4d05b..4490e02c4e 100644 --- a/src/Api/SecretsManager/Models/Request/ProjectUpdateRequestModel.cs +++ b/src/Api/SecretsManager/Models/Request/ProjectUpdateRequestModel.cs @@ -12,11 +12,10 @@ public class ProjectUpdateRequestModel public Project ToProject(Guid id) { - return new Project() + return new Project { Id = id, Name = Name, }; } } - diff --git a/src/Core/SecretsManager/AuthorizationRequirements/ProjectOperationRequirement.cs b/src/Core/SecretsManager/AuthorizationRequirements/ProjectOperationRequirement.cs new file mode 100644 index 0000000000..ccad661ca4 --- /dev/null +++ b/src/Core/SecretsManager/AuthorizationRequirements/ProjectOperationRequirement.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Authorization.Infrastructure; + +namespace Bit.Core.SecretsManager.AuthorizationRequirements; + +public class ProjectOperationRequirement : OperationAuthorizationRequirement +{ +} + +public static class ProjectOperations +{ + public static readonly ProjectOperationRequirement Create = new() { Name = nameof(Create) }; + public static readonly ProjectOperationRequirement Update = new() { Name = nameof(Update) }; +} diff --git a/src/Core/SecretsManager/Commands/Projects/Interfaces/IUpdateProjectCommand.cs b/src/Core/SecretsManager/Commands/Projects/Interfaces/IUpdateProjectCommand.cs index 0268e43d2a..cdcf0e0a43 100644 --- a/src/Core/SecretsManager/Commands/Projects/Interfaces/IUpdateProjectCommand.cs +++ b/src/Core/SecretsManager/Commands/Projects/Interfaces/IUpdateProjectCommand.cs @@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.Projects.Interfaces; public interface IUpdateProjectCommand { - Task UpdateAsync(Project updatedProject, Guid userId); + Task UpdateAsync(Project updatedProject); } diff --git a/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs index ab2643f3f6..951875b1b4 100644 --- a/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.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; @@ -13,6 +14,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 Xunit; @@ -102,10 +104,18 @@ public class ProjectsControllerTests [Theory] [BitAutoData] - public async void Create_SmNotEnabled_Throws(SutProvider sutProvider, Guid orgId, - ProjectCreateRequestModel data) + public async void Create_NoAccess_Throws(SutProvider sutProvider, + Guid orgId, ProjectCreateRequestModel data) { - sutProvider.GetDependency().AccessSecretsManager(orgId).Returns(false); + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), data.ToProject(orgId), + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Failed()); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + + var resultProject = data.ToProject(orgId); + + sutProvider.GetDependency().CreateAsync(default, default) + .ReturnsForAnyArgs(resultProject); await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(orgId, data)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() @@ -113,22 +123,17 @@ public class ProjectsControllerTests } [Theory] - [BitAutoData(PermissionType.RunAsAdmin)] - [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async void Create_Success(PermissionType permissionType, SutProvider sutProvider, + [BitAutoData] + public async void Create_Success(SutProvider sutProvider, Guid orgId, ProjectCreateRequestModel data) { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - SetupAdmin(sutProvider, orgId); - break; - case PermissionType.RunAsUserWithPermission: - SetupUserWithPermission(sutProvider, orgId); - break; - } + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), data.ToProject(orgId), + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); var resultProject = data.ToProject(orgId); + sutProvider.GetDependency().CreateAsync(default, default) .ReturnsForAnyArgs(resultProject); @@ -139,29 +144,44 @@ public class ProjectsControllerTests } [Theory] - [BitAutoData(PermissionType.RunAsAdmin)] - [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async void Update_Success(PermissionType permissionType, SutProvider sutProvider, - Guid orgId, ProjectUpdateRequestModel data) + [BitAutoData] + public async void Update_NoAccess_Throws(SutProvider sutProvider, + Guid userId, ProjectUpdateRequestModel data, Project existingProject) { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - SetupAdmin(sutProvider, orgId); - break; - case PermissionType.RunAsUserWithPermission: - SetupUserWithPermission(sutProvider, orgId); - break; - } + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), data.ToProject(existingProject.Id), + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Failed()); + sutProvider.GetDependency().GetByIdAsync(existingProject.Id).ReturnsForAnyArgs(existingProject); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(userId); - var resultProject = data.ToProject(orgId); - sutProvider.GetDependency().UpdateAsync(default, default) + var resultProject = data.ToProject(existingProject.Id); + sutProvider.GetDependency().UpdateAsync(default) .ReturnsForAnyArgs(resultProject); - await sutProvider.Sut.UpdateAsync(orgId, data); + await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(existingProject.Id, data)); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .UpdateAsync(Arg.Any()); + } + + [Theory] + [BitAutoData] + public async void Update_Success(SutProvider sutProvider, + Guid userId, ProjectUpdateRequestModel data, Project existingProject) + { + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), data.ToProject(existingProject.Id), + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); + sutProvider.GetDependency().GetByIdAsync(existingProject.Id).ReturnsForAnyArgs(existingProject); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(userId); + + var resultProject = data.ToProject(existingProject.Id); + sutProvider.GetDependency().UpdateAsync(default) + .ReturnsForAnyArgs(resultProject); + + await sutProvider.Sut.UpdateAsync(existingProject.Id, data); await sutProvider.GetDependency().Received(1) - .UpdateAsync(Arg.Any(), Arg.Any()); + .UpdateAsync(Arg.Any()); } [Theory]