From acc4808509cbd8f533bb5931195add8551a9877c Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:06:33 -0500 Subject: [PATCH] [SM-1256] Add BulkSecretAuthorizationHandler (#4099) * Add AccessToSecretsAsync to the repository * Add BulkSecretAuthorizationHandler * Update controller to use the new authz handler * Add integration test coverage --- .../Secrets/BulkSecretAuthorizationHandler.cs | 63 +++++ .../SecretsManagerCollectionExtensions.cs | 1 + .../Repositories/SecretRepository.cs | 16 ++ .../BulkSecretAuthorizationHandlerTests.cs | 224 ++++++++++++++++++ .../Controllers/SecretsController.cs | 18 +- .../BulkSecretOperationRequirement.cs | 12 + .../Repositories/ISecretRepository.cs | 1 + .../Repositories/Noop/NoopSecretRepository.cs | 6 + .../Controllers/SecretsControllerTests.cs | 183 +++++++++++--- .../Controllers/SecretsControllerTests.cs | 32 +-- 10 files changed, 484 insertions(+), 72 deletions(-) create mode 100644 bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandler.cs create mode 100644 bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandlerTests.cs create mode 100644 src/Core/SecretsManager/AuthorizationRequirements/BulkSecretOperationRequirement.cs diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandler.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandler.cs new file mode 100644 index 000000000..49d6e1fcc --- /dev/null +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandler.cs @@ -0,0 +1,63 @@ +#nullable enable +using Bit.Core.Context; +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.Secrets; + +public class + BulkSecretAuthorizationHandler : AuthorizationHandler> +{ + private readonly IAccessClientQuery _accessClientQuery; + private readonly ICurrentContext _currentContext; + private readonly ISecretRepository _secretRepository; + + public BulkSecretAuthorizationHandler(ICurrentContext currentContext, IAccessClientQuery accessClientQuery, + ISecretRepository secretRepository) + { + _currentContext = currentContext; + _accessClientQuery = accessClientQuery; + _secretRepository = secretRepository; + } + + + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + BulkSecretOperationRequirement requirement, + IReadOnlyList resources) + { + // Ensure all secrets belong to the same organization. + var organizationId = resources[0].OrganizationId; + if (resources.Any(secret => secret.OrganizationId != organizationId) || + !_currentContext.AccessSecretsManager(organizationId)) + { + return; + } + + switch (requirement) + { + case not null when requirement == BulkSecretOperations.ReadAll: + await CanReadAllAsync(context, requirement, resources, organizationId); + break; + default: + throw new ArgumentException("Unsupported operation requirement type provided.", nameof(requirement)); + } + } + + private async Task CanReadAllAsync(AuthorizationHandlerContext context, + BulkSecretOperationRequirement requirement, IReadOnlyList resources, Guid organizationId) + { + var (accessClient, userId) = await _accessClientQuery.GetAccessClientAsync(context.User, organizationId); + + var secretsAccess = + await _secretRepository.AccessToSecretsAsync(resources.Select(s => s.Id), userId, accessClient); + + if (secretsAccess.Count == resources.Count && + secretsAccess.All(a => a.Value.Read)) + { + context.Succeed(requirement); + } + } +} diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs index 970d874f8..24051eec7 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs @@ -43,6 +43,7 @@ public static class SecretsManagerCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs index ae9a5032c..a608fd207 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs @@ -299,6 +299,22 @@ public class SecretRepository : Repository> AccessToSecretsAsync( + IEnumerable ids, + Guid userId, + AccessClientType accessType) + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + + var secrets = dbContext.Secret + .Where(s => ids.Contains(s.Id)); + + var accessQuery = BuildSecretAccessQuery(secrets, userId, accessType); + + return await accessQuery.ToDictionaryAsync(sa => sa.Id, sa => (sa.Read, sa.Write)); + } + public async Task EmptyTrash(DateTime currentDate, uint deleteAfterThisNumberOfDays) { using var scope = ServiceScopeFactory.CreateScope(); diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandlerTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandlerTests.cs new file mode 100644 index 000000000..d7dc11ba7 --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandlerTests.cs @@ -0,0 +1,224 @@ +#nullable enable +using System.Reflection; +using System.Security.Claims; +using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Secrets; +using Bit.Core.Context; +using Bit.Core.Enums; +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.Secrets; + +[SutProviderCustomize] +[ProjectCustomize] +public class BulkSecretAuthorizationHandlerTests +{ + [Fact] + public void BulkSecretOperations_OnlyPublicStatic() + { + var publicStaticFields = typeof(BulkSecretOperations).GetFields(BindingFlags.Public | BindingFlags.Static); + var allFields = typeof(BulkSecretOperations).GetFields(); + Assert.Equal(publicStaticFields.Length, allFields.Length); + } + + [Theory] + [BitAutoData] + public async Task Handler_MisMatchedOrganizations_DoesNotSucceed( + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = BulkSecretOperations.ReadAll; + resources[0].OrganizationId = Guid.NewGuid(); + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()) + .ReturnsForAnyArgs(true); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData] + public async Task Handler_NoAccessToSecretsManager_DoesNotSucceed( + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = BulkSecretOperations.ReadAll; + resources = SetSameOrganization(resources); + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()) + .ReturnsForAnyArgs(false); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData] + public async Task Handler_UnsupportedSecretOperationRequirement_Throws( + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = new BulkSecretOperationRequirement(); + resources = SetSameOrganization(resources); + SetupUserSubstitutes(sutProvider, AccessClientType.User, resources.First().OrganizationId); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await Assert.ThrowsAsync(() => sutProvider.Sut.HandleAsync(authzContext)); + } + + [Theory] + [BitAutoData(AccessClientType.User)] + [BitAutoData(AccessClientType.NoAccessCheck)] + [BitAutoData(AccessClientType.ServiceAccount)] + public async Task Handler_NoAccessToSecrets_DoesNotSucceed( + AccessClientType accessClientType, + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = BulkSecretOperations.ReadAll; + resources = SetSameOrganization(resources); + var secretIds = + SetupSecretAccessRequest(sutProvider, resources, accessClientType, resources.First().OrganizationId); + sutProvider.GetDependency() + .AccessToSecretsAsync(Arg.Any>(), Arg.Any(), Arg.Any()) + .Returns(secretIds.ToDictionary(id => id, _ => (false, false))); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessClientType.User)] + [BitAutoData(AccessClientType.NoAccessCheck)] + [BitAutoData(AccessClientType.ServiceAccount)] + public async Task Handler_HasAccessToSomeSecrets_DoesNotSucceed( + AccessClientType accessClientType, + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = BulkSecretOperations.ReadAll; + resources = SetSameOrganization(resources); + var secretIds = + SetupSecretAccessRequest(sutProvider, resources, accessClientType, resources.First().OrganizationId); + + var accessResult = secretIds.ToDictionary(secretId => secretId, _ => (false, false)); + accessResult[secretIds.First()] = (true, true); + sutProvider.GetDependency() + .AccessToSecretsAsync(Arg.Any>(), Arg.Any(), Arg.Any()) + .Returns(accessResult); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessClientType.User)] + [BitAutoData(AccessClientType.NoAccessCheck)] + [BitAutoData(AccessClientType.ServiceAccount)] + public async Task Handler_PartialAccessReturn_DoesNotSucceed( + AccessClientType accessClientType, + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = BulkSecretOperations.ReadAll; + resources = SetSameOrganization(resources); + var secretIds = + SetupSecretAccessRequest(sutProvider, resources, accessClientType, resources.First().OrganizationId); + + var accessResult = secretIds.ToDictionary(secretId => secretId, _ => (false, false)); + accessResult.Remove(secretIds.First()); + sutProvider.GetDependency() + .AccessToSecretsAsync(Arg.Any>(), Arg.Any(), Arg.Any()) + .Returns(accessResult); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessClientType.User)] + [BitAutoData(AccessClientType.NoAccessCheck)] + [BitAutoData(AccessClientType.ServiceAccount)] + public async Task Handler_HasAccessToAllSecrets_Success( + AccessClientType accessClientType, + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = BulkSecretOperations.ReadAll; + resources = SetSameOrganization(resources); + var secretIds = + SetupSecretAccessRequest(sutProvider, resources, accessClientType, resources.First().OrganizationId); + + var accessResult = secretIds.ToDictionary(secretId => secretId, _ => (true, true)); + sutProvider.GetDependency() + .AccessToSecretsAsync(Arg.Any>(), Arg.Any(), Arg.Any()) + .Returns(accessResult); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.True(authzContext.HasSucceeded); + } + + private static List SetSameOrganization(List secrets) + { + var organizationId = secrets.First().OrganizationId; + foreach (var secret in secrets) + { + secret.OrganizationId = organizationId; + } + + return secrets; + } + + private static void SetupUserSubstitutes( + SutProvider sutProvider, + AccessClientType accessClientType, + Guid organizationId, + Guid userId = new()) + { + sutProvider.GetDependency().AccessSecretsManager(organizationId) + .Returns(true); + sutProvider.GetDependency().GetAccessClientAsync(default, organizationId) + .ReturnsForAnyArgs((accessClientType, userId)); + } + + private static List SetupSecretAccessRequest( + SutProvider sutProvider, + IEnumerable resources, + AccessClientType accessClientType, + Guid organizationId, + Guid userId = new()) + { + SetupUserSubstitutes(sutProvider, accessClientType, organizationId, userId); + return resources.Select(s => s.Id).ToList(); + } +} diff --git a/src/Api/SecretsManager/Controllers/SecretsController.cs b/src/Api/SecretsManager/Controllers/SecretsController.cs index 34c6a9723..8e93f3d79 100644 --- a/src/Api/SecretsManager/Controllers/SecretsController.cs +++ b/src/Api/SecretsManager/Controllers/SecretsController.cs @@ -260,25 +260,13 @@ public class SecretsController : Controller throw new NotFoundException(); } - // Ensure all secrets belong to the same organization. - var organizationId = secrets.First().OrganizationId; - if (secrets.Any(secret => secret.OrganizationId != organizationId) || - !_currentContext.AccessSecretsManager(organizationId)) + var authorizationResult = await _authorizationService.AuthorizeAsync(User, secrets, BulkSecretOperations.ReadAll); + if (!authorizationResult.Succeeded) { throw new NotFoundException(); } - - foreach (var secret in secrets) - { - var authorizationResult = await _authorizationService.AuthorizeAsync(User, secret, SecretOperations.Read); - if (!authorizationResult.Succeeded) - { - throw new NotFoundException(); - } - } - - await LogSecretsRetrievalAsync(organizationId, secrets); + await LogSecretsRetrievalAsync(secrets.First().OrganizationId, secrets); var responses = secrets.Select(s => new BaseSecretResponseModel(s)); return new ListResponseModel(responses); diff --git a/src/Core/SecretsManager/AuthorizationRequirements/BulkSecretOperationRequirement.cs b/src/Core/SecretsManager/AuthorizationRequirements/BulkSecretOperationRequirement.cs new file mode 100644 index 000000000..f0ab78a83 --- /dev/null +++ b/src/Core/SecretsManager/AuthorizationRequirements/BulkSecretOperationRequirement.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Authorization.Infrastructure; + +namespace Bit.Core.SecretsManager.AuthorizationRequirements; + +public class BulkSecretOperationRequirement : OperationAuthorizationRequirement +{ +} + +public static class BulkSecretOperations +{ + public static readonly BulkSecretOperationRequirement ReadAll = new() { Name = nameof(ReadAll) }; +} diff --git a/src/Core/SecretsManager/Repositories/ISecretRepository.cs b/src/Core/SecretsManager/Repositories/ISecretRepository.cs index 8492bac50..693baf85c 100644 --- a/src/Core/SecretsManager/Repositories/ISecretRepository.cs +++ b/src/Core/SecretsManager/Repositories/ISecretRepository.cs @@ -21,6 +21,7 @@ public interface ISecretRepository Task RestoreManyByIdAsync(IEnumerable ids); Task> ImportAsync(IEnumerable secrets); Task<(bool Read, bool Write)> AccessToSecretAsync(Guid id, Guid userId, AccessClientType accessType); + Task> AccessToSecretsAsync(IEnumerable ids, Guid userId, AccessClientType accessType); Task EmptyTrash(DateTime nowTime, uint deleteAfterThisNumberOfDays); Task GetSecretsCountByOrganizationIdAsync(Guid organizationId); } diff --git a/src/Core/SecretsManager/Repositories/Noop/NoopSecretRepository.cs b/src/Core/SecretsManager/Repositories/Noop/NoopSecretRepository.cs index 0448bbaf2..ba1d3ccb0 100644 --- a/src/Core/SecretsManager/Repositories/Noop/NoopSecretRepository.cs +++ b/src/Core/SecretsManager/Repositories/Noop/NoopSecretRepository.cs @@ -81,6 +81,12 @@ public class NoopSecretRepository : ISecretRepository return Task.FromResult((false, false)); } + public Task> AccessToSecretsAsync(IEnumerable ids, + Guid userId, AccessClientType accessType) + { + return Task.FromResult(null as Dictionary); + } + public Task EmptyTrash(DateTime nowTime, uint deleteAfterThisNumberOfDays) { return Task.FromResult(0); diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsControllerTests.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsControllerTests.cs index 23adbff4e..be95c0dc1 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsControllerTests.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsControllerTests.cs @@ -741,44 +741,83 @@ public class SecretsControllerTests : IClassFixture, IAsy Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } - [Theory] - [InlineData(PermissionType.RunAsAdmin)] - [InlineData(PermissionType.RunAsUserWithPermission)] - public async Task GetSecretsByIds_Success(PermissionType permissionType) + [Fact] + public async Task GetSecretsByIds_SecretsNotInTheSameOrganization_NotFound() { var (org, _) = await _organizationHelper.Initialize(true, true, true); await _loginHelper.LoginAsync(_email); - - var (project, secretIds) = await CreateSecretsAsync(org.Id); - - if (permissionType == PermissionType.RunAsUserWithPermission) - { - var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); - await _loginHelper.LoginAsync(email); - - var accessPolicies = new List - { - new UserProjectAccessPolicy - { - GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true, - }, - }; - await _accessPolicyRepository.CreateManyAsync(accessPolicies); - } - else - { - var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.Admin, true); - await _loginHelper.LoginAsync(email); - } + var otherOrg = await _organizationHelper.CreateSmOrganizationAsync(); + var (_, secretIds) = await CreateSecretsAsync(org.Id); + var (_, diffOrgSecrets) = await CreateSecretsAsync(otherOrg.Id, 1); + secretIds.AddRange(diffOrgSecrets); var request = new GetSecretsRequestModel { Ids = secretIds }; + var response = await _client.PostAsJsonAsync("/secrets/get-by-ids", request); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task GetSecretsByIds_SecretsNonExistent_NotFound(bool partial) + { + var (org, _) = await _organizationHelper.Initialize(true, true, true); + await _loginHelper.LoginAsync(_email); + var ids = new List(); + + if (partial) + { + var (_, secretIds) = await CreateSecretsAsync(org.Id); + ids = secretIds; + ids.Add(Guid.NewGuid()); + } + + var request = new GetSecretsRequestModel { Ids = ids }; + + var response = await _client.PostAsJsonAsync("/secrets/get-by-ids", request); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(true, false)] + [InlineData(true, true)] + [InlineData(false, false)] + [InlineData(false, true)] + public async Task GetSecretsByIds_NoAccess_NotFound(bool runAsServiceAccount, bool partialAccess) + { + var (org, _) = await _organizationHelper.Initialize(true, true, true); + + var request = await SetupNoAccessRequestAsync(org.Id, runAsServiceAccount, partialAccess); + + var response = await _client.PostAsJsonAsync("/secrets/get-by-ids", request); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + [InlineData(PermissionType.RunAsServiceAccountWithPermission)] + public async Task GetSecretsByIds_Success(PermissionType permissionType) + { + var (org, _) = await _organizationHelper.Initialize(true, true, true); + await _loginHelper.LoginAsync(_email); + var request = await SetupGetSecretsByIdsRequestAsync(org.Id, permissionType); + var response = await _client.PostAsJsonAsync("/secrets/get-by-ids", request); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync>(); + Assert.NotNull(result); Assert.NotEmpty(result.Data); - Assert.Equal(secretIds.Count, result.Data.Count()); + Assert.Equal(request.Ids.Count(), result.Data.Count()); + Assert.All(result.Data, data => Assert.Equal(_mockEncryptedString, data.Value)); + Assert.All(result.Data, data => Assert.Equal(_mockEncryptedString, data.Key)); + Assert.All(result.Data, data => Assert.Equal(_mockEncryptedString, data.Note)); + Assert.All(result.Data, data => Assert.Equal(org.Id, data.OrganizationId)); } @@ -1161,4 +1200,94 @@ public class SecretsControllerTests : IClassFixture, IAsy return (secret, request); } + + private async Task SetupGetSecretsByIdsRequestAsync(Guid organizationId, + PermissionType permissionType) + { + var (project, secretIds) = await CreateSecretsAsync(organizationId); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await _loginHelper.LoginAsync(email); + + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true + } + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + if (permissionType == PermissionType.RunAsServiceAccountWithPermission) + { + var apiKeyDetails = await _organizationHelper.CreateNewServiceAccountApiKeyAsync(); + await _loginHelper.LoginWithApiKeyAsync(apiKeyDetails); + + var accessPolicies = new List + { + new ServiceAccountProjectAccessPolicy + { + GrantedProjectId = project.Id, + ServiceAccountId = apiKeyDetails.ApiKey.ServiceAccountId, + Read = true, + Write = true + } + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + return new GetSecretsRequestModel { Ids = secretIds }; + } + + private async Task SetupNoAccessRequestAsync(Guid organizationId, bool runAsServiceAccount, + bool partialAccess) + { + var (_, secretIds) = await CreateSecretsAsync(organizationId); + + if (runAsServiceAccount) + { + var apiKeyDetails = await _organizationHelper.CreateNewServiceAccountApiKeyAsync(); + await _loginHelper.LoginWithApiKeyAsync(apiKeyDetails); + + if (partialAccess) + { + var accessPolicies = new List + { + new ServiceAccountSecretAccessPolicy + { + GrantedSecretId = secretIds[0], + ServiceAccountId = apiKeyDetails.ApiKey.ServiceAccountId, + Read = true, + Write = true + } + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + } + else + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await _loginHelper.LoginAsync(email); + + if (partialAccess) + { + var accessPolicies = new List + { + new UserSecretAccessPolicy + { + GrantedSecretId = secretIds[0], + OrganizationUserId = orgUser.Id, + Read = true, + Write = true + } + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + } + + return new GetSecretsRequestModel { Ids = secretIds }; + } } diff --git a/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs index 3eea25b39..4fb2c4b7f 100644 --- a/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs @@ -412,30 +412,6 @@ public class SecretsControllerTests await Assert.ThrowsAsync(() => sutProvider.Sut.GetSecretsByIdsAsync(request)); } - [Theory] - [BitAutoData] - public async Task GetSecretsByIds_OrganizationMisMatch_ThrowsNotFound(SutProvider sutProvider, - List data) - { - var (ids, request) = BuildGetSecretsRequestModel(data); - sutProvider.GetDependency().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); - await Assert.ThrowsAsync(() => sutProvider.Sut.GetSecretsByIdsAsync(request)); - } - - [Theory] - [BitAutoData] - public async Task GetSecretsByIds_NoAccessToSecretsManager_ThrowsNotFound( - SutProvider sutProvider, List data) - { - var (ids, request) = BuildGetSecretsRequestModel(data); - var organizationId = SetOrganizations(ref data); - - sutProvider.GetDependency().AccessSecretsManager(Arg.Is(organizationId)) - .ReturnsForAnyArgs(false); - sutProvider.GetDependency().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); - await Assert.ThrowsAsync(() => sutProvider.Sut.GetSecretsByIdsAsync(request)); - } - [Theory] [BitAutoData] public async Task GetSecretsByIds_AccessDenied_ThrowsNotFound(SutProvider sutProvider, @@ -445,10 +421,8 @@ public class SecretsControllerTests var organizationId = SetOrganizations(ref data); sutProvider.GetDependency().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); - sutProvider.GetDependency().AccessSecretsManager(Arg.Is(organizationId)) - .ReturnsForAnyArgs(true); sutProvider.GetDependency() - .AuthorizeAsync(Arg.Any(), data.First(), + .AuthorizeAsync(Arg.Any(), data, Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Failed()); await Assert.ThrowsAsync(() => sutProvider.Sut.GetSecretsByIdsAsync(request)); @@ -462,10 +436,8 @@ public class SecretsControllerTests var organizationId = SetOrganizations(ref data); sutProvider.GetDependency().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); - sutProvider.GetDependency().AccessSecretsManager(Arg.Is(organizationId)) - .ReturnsForAnyArgs(true); sutProvider.GetDependency() - .AuthorizeAsync(Arg.Any(), data.First(), + .AuthorizeAsync(Arg.Any(), data, Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); var results = await sutProvider.Sut.GetSecretsByIdsAsync(request);