From 1ee14d93e63f4843f35ca573383ad1de577ce8ba Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Tue, 7 Feb 2023 14:30:22 -0600 Subject: [PATCH] [SM-473] Access Policies - Service Accounts (#2658) * Add service account access policy endpoints * Add unit & integration tests for new endpoints * Fix formatting on response models * Cleanup unit tests --- .../CreateAccessPoliciesCommand.cs | 86 ++++- .../Repositories/AccessPolicyRepository.cs | 246 ++++++------- .../CreateAccessPoliciesCommandTests.cs | 347 ++++++++++++++---- .../Controllers/AccessPoliciesController.cs | 47 ++- .../Request/AccessPoliciesCreateRequest.cs | 69 +++- .../ProjectAccessPoliciesResponseModel.cs | 2 + ...rviceAccountAccessPoliciesResponseModel.cs | 39 ++ .../ICreateAccessPoliciesCommand.cs | 1 + .../Repositories/DatabaseContext.cs | 2 + .../AccessPoliciesControllerTest.cs | 229 ++++++++++++ .../AccessPoliciesControllerTests.cs | 267 +++++++++----- 11 files changed, 1030 insertions(+), 305 deletions(-) create mode 100644 src/Api/SecretsManager/Models/Response/ServiceAccountAccessPoliciesResponseModel.cs diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs index 2b003aae48..ffc39099e4 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs @@ -12,15 +12,18 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand private readonly IAccessPolicyRepository _accessPolicyRepository; private readonly ICurrentContext _currentContext; private readonly IProjectRepository _projectRepository; + private readonly IServiceAccountRepository _serviceAccountRepository; public CreateAccessPoliciesCommand( IAccessPolicyRepository accessPolicyRepository, ICurrentContext currentContext, - IProjectRepository projectRepository) + IProjectRepository projectRepository, + IServiceAccountRepository serviceAccountRepository) { - _projectRepository = projectRepository; _accessPolicyRepository = accessPolicyRepository; _currentContext = currentContext; + _projectRepository = projectRepository; + _serviceAccountRepository = serviceAccountRepository; } public async Task> CreateForProjectAsync(Guid projectId, @@ -32,21 +35,33 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand throw new NotFoundException(); } - var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId); - var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + await CheckPermissionAsync(project.OrganizationId, userId, projectId); + CheckForDistinctAccessPolicies(accessPolicies); + await CheckAccessPoliciesDoNotExistAsync(accessPolicies); - var hasAccess = accessClient switch - { - AccessClientType.NoAccessCheck => true, - AccessClientType.User => await _projectRepository.UserHasWriteAccessToProject(project.Id, userId), - _ => false, - }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + return await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(projectId); + } - if (!hasAccess) + public async Task> CreateForServiceAccountAsync(Guid serviceAccountId, + List accessPolicies, Guid userId) + { + var serviceAccount = await _serviceAccountRepository.GetByIdAsync(serviceAccountId); + if (serviceAccount == null || !_currentContext.AccessSecretsManager(serviceAccount.OrganizationId)) { throw new NotFoundException(); } + await CheckPermissionAsync(serviceAccount.OrganizationId, userId, serviceAccountIdToCheck: serviceAccountId); + CheckForDistinctAccessPolicies(accessPolicies); + await CheckAccessPoliciesDoNotExistAsync(accessPolicies); + + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + return await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(serviceAccountId); + } + + private static void CheckForDistinctAccessPolicies(IReadOnlyCollection accessPolicies) + { var distinctAccessPolicies = accessPolicies.DistinctBy(baseAccessPolicy => { return baseAccessPolicy switch @@ -55,6 +70,9 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand GroupProjectAccessPolicy ap => new Tuple(ap.GroupId, ap.GrantedProjectId), ServiceAccountProjectAccessPolicy ap => new Tuple(ap.ServiceAccountId, ap.GrantedProjectId), + UserServiceAccountAccessPolicy ap => new Tuple(ap.OrganizationUserId, + ap.GrantedServiceAccountId), + GroupServiceAccountAccessPolicy ap => new Tuple(ap.GroupId, ap.GrantedServiceAccountId), _ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)), }; }).ToList(); @@ -63,7 +81,10 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand { throw new BadRequestException("Resources must be unique"); } + } + private async Task CheckAccessPoliciesDoNotExistAsync(List accessPolicies) + { foreach (var accessPolicy in accessPolicies) { if (await _accessPolicyRepository.AccessPolicyExists(accessPolicy)) @@ -71,7 +92,46 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand throw new BadRequestException("Resource already exists"); } } - await _accessPolicyRepository.CreateManyAsync(accessPolicies); - return await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(projectId); + } + + private async Task CheckPermissionAsync(Guid organizationId, + Guid userId, + Guid? projectIdToCheck = null, + Guid? serviceAccountIdToCheck = null) + { + var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + bool hasAccess; + switch (accessClient) + { + case AccessClientType.NoAccessCheck: + hasAccess = true; + break; + case AccessClientType.User: + if (projectIdToCheck != null) + { + hasAccess = await _projectRepository.UserHasWriteAccessToProject(projectIdToCheck.Value, userId); + } + else if (serviceAccountIdToCheck != null) + { + hasAccess = await _serviceAccountRepository.UserHasWriteAccessToServiceAccount( + serviceAccountIdToCheck.Value, + userId); + } + else + { + hasAccess = false; + } + break; + default: + hasAccess = false; + break; + } + + if (!hasAccess) + { + throw new NotFoundException(); + } } } diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs index b61bdcbfd7..8e5015f8b5 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs @@ -16,152 +16,152 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli public async Task> CreateManyAsync(List baseAccessPolicies) { - using (var scope = ServiceScopeFactory.CreateScope()) + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + foreach (var baseAccessPolicy in baseAccessPolicies) { - var dbContext = GetDatabaseContext(scope); - foreach (var baseAccessPolicy in baseAccessPolicies) - { - baseAccessPolicy.SetNewId(); - switch (baseAccessPolicy) - { - case Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy: - { - var entity = - Mapper.Map(accessPolicy); - await dbContext.AddAsync(entity); - break; - } - case Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy: - { - var entity = - Mapper.Map(accessPolicy); - await dbContext.AddAsync(entity); - break; - } - case Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy: - { - var entity = Mapper.Map(accessPolicy); - await dbContext.AddAsync(entity); - break; - } - case Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy: - { - var entity = Mapper.Map(accessPolicy); - await dbContext.AddAsync(entity); - break; - } - case Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy: - { - var entity = Mapper.Map(accessPolicy); - await dbContext.AddAsync(entity); - break; - } - } - } - - await dbContext.SaveChangesAsync(); - return baseAccessPolicies; - } - } - - public async Task AccessPolicyExists(Core.SecretsManager.Entities.BaseAccessPolicy baseAccessPolicy) - { - using (var scope = ServiceScopeFactory.CreateScope()) - { - var dbContext = GetDatabaseContext(scope); + baseAccessPolicy.SetNewId(); switch (baseAccessPolicy) { case Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy: { - var policy = await dbContext.UserProjectAccessPolicy - .Where(c => c.OrganizationUserId == accessPolicy.OrganizationUserId && - c.GrantedProjectId == accessPolicy.GrantedProjectId) - .FirstOrDefaultAsync(); - return policy != null; + var entity = + Mapper.Map(accessPolicy); + await dbContext.AddAsync(entity); + break; + } + case Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy: + { + var entity = + Mapper.Map(accessPolicy); + await dbContext.AddAsync(entity); + break; } case Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy: { - var policy = await dbContext.GroupProjectAccessPolicy - .Where(c => c.GroupId == accessPolicy.GroupId && - c.GrantedProjectId == accessPolicy.GrantedProjectId) - .FirstOrDefaultAsync(); - return policy != null; + var entity = Mapper.Map(accessPolicy); + await dbContext.AddAsync(entity); + break; + } + case Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy: + { + var entity = Mapper.Map(accessPolicy); + await dbContext.AddAsync(entity); + break; } case Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy: { - var policy = await dbContext.ServiceAccountProjectAccessPolicy - .Where(c => c.ServiceAccountId == accessPolicy.ServiceAccountId && - c.GrantedProjectId == accessPolicy.GrantedProjectId) - .FirstOrDefaultAsync(); - return policy != null; + var entity = Mapper.Map(accessPolicy); + await dbContext.AddAsync(entity); + break; } - default: - throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)); } } + + await dbContext.SaveChangesAsync(); + return baseAccessPolicies; + } + + public async Task AccessPolicyExists(Core.SecretsManager.Entities.BaseAccessPolicy baseAccessPolicy) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + switch (baseAccessPolicy) + { + case Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy: + { + var policy = await dbContext.UserProjectAccessPolicy + .Where(c => c.OrganizationUserId == accessPolicy.OrganizationUserId && + c.GrantedProjectId == accessPolicy.GrantedProjectId) + .FirstOrDefaultAsync(); + return policy != null; + } + case Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy: + { + var policy = await dbContext.GroupProjectAccessPolicy + .Where(c => c.GroupId == accessPolicy.GroupId && + c.GrantedProjectId == accessPolicy.GrantedProjectId) + .FirstOrDefaultAsync(); + return policy != null; + } + case Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy: + { + var policy = await dbContext.ServiceAccountProjectAccessPolicy + .Where(c => c.ServiceAccountId == accessPolicy.ServiceAccountId && + c.GrantedProjectId == accessPolicy.GrantedProjectId) + .FirstOrDefaultAsync(); + return policy != null; + } + case Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy: + { + var policy = await dbContext.UserServiceAccountAccessPolicy + .Where(c => c.OrganizationUserId == accessPolicy.OrganizationUserId && + c.GrantedServiceAccountId == accessPolicy.GrantedServiceAccountId) + .FirstOrDefaultAsync(); + return policy != null; + } + case Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy: + { + var policy = await dbContext.GroupServiceAccountAccessPolicy + .Where(c => c.GroupId == accessPolicy.GroupId && + c.GrantedServiceAccountId == accessPolicy.GrantedServiceAccountId) + .FirstOrDefaultAsync(); + return policy != null; + } + default: + throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)); + } } public async Task GetByIdAsync(Guid id) { - using (var scope = ServiceScopeFactory.CreateScope()) - { - var dbContext = GetDatabaseContext(scope); - var entity = await dbContext.AccessPolicies.Where(ap => ap.Id == id) - .Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User) - .Include(ap => ((UserProjectAccessPolicy)ap).GrantedProject) - .Include(ap => ((GroupProjectAccessPolicy)ap).Group) - .Include(ap => ((GroupProjectAccessPolicy)ap).GrantedProject) - .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) - .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).GrantedProject) - .Include(ap => ((UserServiceAccountAccessPolicy)ap).OrganizationUser.User) - .Include(ap => ((UserServiceAccountAccessPolicy)ap).GrantedServiceAccount) - .Include(ap => ((GroupServiceAccountAccessPolicy)ap).Group) - .Include(ap => ((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccount) - .FirstOrDefaultAsync(); + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var entity = await dbContext.AccessPolicies.Where(ap => ap.Id == id) + .Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User) + .Include(ap => ((UserProjectAccessPolicy)ap).GrantedProject) + .Include(ap => ((GroupProjectAccessPolicy)ap).Group) + .Include(ap => ((GroupProjectAccessPolicy)ap).GrantedProject) + .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) + .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).GrantedProject) + .Include(ap => ((UserServiceAccountAccessPolicy)ap).OrganizationUser.User) + .Include(ap => ((UserServiceAccountAccessPolicy)ap).GrantedServiceAccount) + .Include(ap => ((GroupServiceAccountAccessPolicy)ap).Group) + .Include(ap => ((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccount) + .FirstOrDefaultAsync(); - if (entity == null) - { - return null; - } - - return MapToCore(entity); - } + return entity == null ? null : MapToCore(entity); } public async Task ReplaceAsync(Core.SecretsManager.Entities.BaseAccessPolicy baseAccessPolicy) { - using (var scope = ServiceScopeFactory.CreateScope()) + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var entity = await dbContext.AccessPolicies.FindAsync(baseAccessPolicy.Id); + if (entity != null) { - var dbContext = GetDatabaseContext(scope); - var entity = await dbContext.AccessPolicies.FindAsync(baseAccessPolicy.Id); - if (entity != null) - { - dbContext.AccessPolicies.Attach(entity); - entity.Write = baseAccessPolicy.Write; - entity.Read = baseAccessPolicy.Read; - entity.RevisionDate = baseAccessPolicy.RevisionDate; - await dbContext.SaveChangesAsync(); - } + dbContext.AccessPolicies.Attach(entity); + entity.Write = baseAccessPolicy.Write; + entity.Read = baseAccessPolicy.Read; + entity.RevisionDate = baseAccessPolicy.RevisionDate; + await dbContext.SaveChangesAsync(); } } public async Task> GetManyByGrantedProjectIdAsync(Guid id) { - using (var scope = ServiceScopeFactory.CreateScope()) - { - var dbContext = GetDatabaseContext(scope); + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); - var entities = await dbContext.AccessPolicies.Where(ap => - ((UserProjectAccessPolicy)ap).GrantedProjectId == id || - ((GroupProjectAccessPolicy)ap).GrantedProjectId == id || - ((ServiceAccountProjectAccessPolicy)ap).GrantedProjectId == id) - .Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User) - .Include(ap => ((GroupProjectAccessPolicy)ap).Group) - .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) - .ToListAsync(); - - return entities.Select(MapToCore); - } + var entities = await dbContext.AccessPolicies.Where(ap => + ((UserProjectAccessPolicy)ap).GrantedProjectId == id || + ((GroupProjectAccessPolicy)ap).GrantedProjectId == id || + ((ServiceAccountProjectAccessPolicy)ap).GrantedProjectId == id) + .Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User) + .Include(ap => ((GroupProjectAccessPolicy)ap).Group) + .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) + .ToListAsync(); + return entities.Select(MapToCore); } public async Task> GetManyByGrantedServiceAccountIdAsync(Guid id) @@ -181,15 +181,13 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli public async Task DeleteAsync(Guid id) { - using (var scope = ServiceScopeFactory.CreateScope()) + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var entity = await dbContext.AccessPolicies.FindAsync(id); + if (entity != null) { - var dbContext = GetDatabaseContext(scope); - var entity = await dbContext.AccessPolicies.FindAsync(id); - if (entity != null) - { - dbContext.Remove(entity); - await dbContext.SaveChangesAsync(); - } + dbContext.Remove(entity); + await dbContext.SaveChangesAsync(); } } diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs index 451caeffeb..d1c1a29145 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs @@ -17,9 +17,127 @@ namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies; [ProjectCustomize] public class CreateAccessPoliciesCommandTests { + private static List MakeDuplicate(List data, AccessPolicyType accessPolicyType) + { + switch (accessPolicyType) + { + case AccessPolicyType.UserProjectAccessPolicy: + { + var mockAccessPolicy = new UserProjectAccessPolicy + { + OrganizationUserId = Guid.NewGuid(), + GrantedProjectId = Guid.NewGuid(), + }; + data.Add(mockAccessPolicy); + + // Add a duplicate policy + data.Add(mockAccessPolicy); + break; + } + case AccessPolicyType.GroupProjectAccessPolicy: + { + var mockAccessPolicy = new GroupProjectAccessPolicy + { + GroupId = Guid.NewGuid(), + GrantedProjectId = Guid.NewGuid(), + }; + data.Add(mockAccessPolicy); + + // Add a duplicate policy + data.Add(mockAccessPolicy); + break; + } + case AccessPolicyType.ServiceAccountProjectAccessPolicy: + { + var mockAccessPolicy = new ServiceAccountProjectAccessPolicy + { + ServiceAccountId = Guid.NewGuid(), + GrantedProjectId = Guid.NewGuid(), + }; + data.Add(mockAccessPolicy); + + // Add a duplicate policy + data.Add(mockAccessPolicy); + break; + } + case AccessPolicyType.UserServiceAccountAccessPolicy: + { + var mockAccessPolicy = new UserServiceAccountAccessPolicy + { + OrganizationUserId = Guid.NewGuid(), + GrantedServiceAccountId = Guid.NewGuid(), + }; + data.Add(mockAccessPolicy); + + // Add a duplicate policy + data.Add(mockAccessPolicy); + break; + } + case AccessPolicyType.GroupServiceAccountAccessPolicy: + { + var mockAccessPolicy = new GroupServiceAccountAccessPolicy + { + GroupId = Guid.NewGuid(), + GrantedServiceAccountId = Guid.NewGuid(), + }; + data.Add(mockAccessPolicy); + + // Add a duplicate policy + data.Add(mockAccessPolicy); + break; + } + } + + return data; + } + + private static void SetupAdmin(SutProvider sutProvider, Guid organizationId) + { + sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); + } + + private static void SetupUser(SutProvider sutProvider, Guid organizationId) + { + sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); + } + + private static void SetupPermission(SutProvider sutProvider, + PermissionType permissionType, Project project, Guid userId) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, project.OrganizationId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUser(sutProvider, project.OrganizationId); + sutProvider.GetDependency().UserHasWriteAccessToProject(project.Id, userId) + .Returns(true); + break; + } + } + + private static void SetupPermission(SutProvider sutProvider, + PermissionType permissionType, ServiceAccount serviceAccount, Guid userId) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, serviceAccount.OrganizationId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUser(sutProvider, serviceAccount.OrganizationId); + sutProvider.GetDependency() + .UserHasWriteAccessToServiceAccount(serviceAccount.Id, userId).Returns(true); + break; + } + } + [Theory] [BitAutoData] - public async Task CreateAsync_SmNotEnabled_Throws( + public async Task CreateForProject_SmNotEnabled_Throws( Guid userId, Project project, List userProjectAccessPolicies, @@ -42,7 +160,7 @@ public class CreateAccessPoliciesCommandTests [Theory] [BitAutoData] - public async Task CreateAsync_AlreadyExists_Throws_BadRequestException( + public async Task CreateForProject_AlreadyExists_Throws_BadRequestException( Guid userId, Project project, List userProjectAccessPolicies, @@ -55,10 +173,8 @@ public class CreateAccessPoliciesCommandTests data.AddRange(groupProjectAccessPolicies); data.AddRange(serviceAccountProjectAccessPolicies); - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + SetupAdmin(sutProvider, project.OrganizationId); sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); - sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId).Returns(true); - sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) .Returns(true); @@ -69,17 +185,13 @@ public class CreateAccessPoliciesCommandTests } [Theory] - [BitAutoData(true, false, false)] - [BitAutoData(false, true, false)] - [BitAutoData(true, true, false)] - [BitAutoData(false, false, true)] - [BitAutoData(true, false, true)] - [BitAutoData(false, true, true)] - [BitAutoData(true, true, true)] - public async Task CreateAsync_NotUnique_ThrowsException( - bool testUserPolicies, - bool testGroupPolicies, - bool testServiceAccountPolicies, + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] + public async Task CreateForProjectAsync_NotUnique_ThrowsException( + AccessPolicyType accessPolicyType, Guid userId, Project project, List userProjectAccessPolicies, @@ -92,64 +204,24 @@ public class CreateAccessPoliciesCommandTests data.AddRange(userProjectAccessPolicies); data.AddRange(groupProjectAccessPolicies); data.AddRange(serviceAccountProjectAccessPolicies); + data = MakeDuplicate(data, accessPolicyType); - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + SetupAdmin(sutProvider, project.OrganizationId); sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); - sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId).Returns(true); - - if (testUserPolicies) - { - var mockUserPolicy = new UserProjectAccessPolicy - { - OrganizationUserId = Guid.NewGuid(), - GrantedProjectId = Guid.NewGuid(), - }; - data.Add(mockUserPolicy); - - // Add a duplicate policy - data.Add(mockUserPolicy); - } - - if (testGroupPolicies) - { - var mockGroupPolicy = new GroupProjectAccessPolicy - { - GroupId = Guid.NewGuid(), - GrantedProjectId = Guid.NewGuid(), - }; - data.Add(mockGroupPolicy); - - // Add a duplicate policy - data.Add(mockGroupPolicy); - } - - if (testServiceAccountPolicies) - { - var mockServiceAccountPolicy = new ServiceAccountProjectAccessPolicy - { - ServiceAccountId = Guid.NewGuid(), - GrantedProjectId = Guid.NewGuid(), - }; - data.Add(mockServiceAccountPolicy); - - // Add a duplicate policy - data.Add(mockServiceAccountPolicy); - } - - sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) .Returns(true); await Assert.ThrowsAsync(() => sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId)); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateManyAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); } [Theory] [BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async Task CreateAsync_Success( + public async Task CreateForProject_Success( PermissionType permissionType, Guid userId, Project project, @@ -163,18 +235,8 @@ public class CreateAccessPoliciesCommandTests data.AddRange(groupProjectAccessPolicies); data.AddRange(serviceAccountProjectAccessPolicies); - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); - - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId).Returns(true); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency().UserHasWriteAccessToProject(project.Id, userId).Returns(true); - break; - } + SetupPermission(sutProvider, permissionType, project, userId); await sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId); @@ -184,7 +246,7 @@ public class CreateAccessPoliciesCommandTests [Theory] [BitAutoData] - public async Task CreateAsync_User_NoPermission( + public async Task CreateForProject_UserNoPermission_ThrowsNotFound( Guid userId, Project project, List userProjectAccessPolicies, @@ -197,13 +259,148 @@ public class CreateAccessPoliciesCommandTests data.AddRange(groupProjectAccessPolicies); data.AddRange(serviceAccountProjectAccessPolicies); - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); - sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); + SetupUser(sutProvider, project.OrganizationId); sutProvider.GetDependency().UserHasWriteAccessToProject(project.Id, userId).Returns(false); + sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); await Assert.ThrowsAsync(() => sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateManyAsync(default); } + + [Theory] + [BitAutoData] + public async Task CreateForServiceAccount_SmNotEnabled_Throws( + Guid userId, + ServiceAccount serviceAccount, + List userProjectAccessPolicies, + List groupProjectAccessPolicies, + List serviceAccountProjectAccessPolicies, + SutProvider sutProvider) + { + var data = new List(); + data.AddRange(userProjectAccessPolicies); + data.AddRange(groupProjectAccessPolicies); + data.AddRange(serviceAccountProjectAccessPolicies); + + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(false); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async Task CreateForServiceAccount_AlreadyExists_ThrowsBadRequestException( + Guid userId, + ServiceAccount serviceAccount, + List userProjectAccessPolicies, + List groupProjectAccessPolicies, + List serviceAccountProjectAccessPolicies, + SutProvider sutProvider) + { + var data = new List(); + data.AddRange(userProjectAccessPolicies); + data.AddRange(groupProjectAccessPolicies); + data.AddRange(serviceAccountProjectAccessPolicies); + + SetupAdmin(sutProvider, serviceAccount.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount); + sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) + .Returns(true); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] + public async Task CreateForServiceAccount_NotUnique_Throws( + AccessPolicyType accessPolicyType, + Guid userId, + ServiceAccount serviceAccount, + List userProjectAccessPolicies, + List groupProjectAccessPolicies, + List serviceAccountProjectAccessPolicies, + SutProvider sutProvider + ) + { + var data = new List(); + data.AddRange(userProjectAccessPolicies); + data.AddRange(groupProjectAccessPolicies); + data.AddRange(serviceAccountProjectAccessPolicies); + data = MakeDuplicate(data, accessPolicyType); + + SetupAdmin(sutProvider, serviceAccount.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount); + sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) + .Returns(true); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } + + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async Task CreateForServiceAccount_Success( + PermissionType permissionType, + Guid userId, + ServiceAccount serviceAccount, + List userServiceAccountAccessPolicies, + List groupServiceAccountAccessPolicies, + SutProvider sutProvider) + { + var data = new List(); + data.AddRange(userServiceAccountAccessPolicies); + data.AddRange(groupServiceAccountAccessPolicies); + + sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount); + SetupPermission(sutProvider, permissionType, serviceAccount, userId); + + await sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId); + + await sutProvider.GetDependency().Received(1) + .CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data))); + } + + [Theory] + [BitAutoData] + public async Task CreateForServiceAccount_UserWithoutPermission_ThrowsNotFound( + Guid userId, + ServiceAccount serviceAccount, + List userServiceAccountAccessPolicies, + List groupServiceAccountAccessPolicies, + SutProvider sutProvider) + { + var data = new List(); + data.AddRange(userServiceAccountAccessPolicies); + data.AddRange(groupServiceAccountAccessPolicies); + + SetupUser(sutProvider, serviceAccount.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount); + sutProvider.GetDependency() + .UserHasWriteAccessToServiceAccount(serviceAccount.Id, userId).Returns(false); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } } diff --git a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs index 254dd09e24..1707fd33c2 100644 --- a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs +++ b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs @@ -20,13 +20,13 @@ namespace Bit.Api.SecretsManager.Controllers; public class AccessPoliciesController : Controller { private readonly IAccessPolicyRepository _accessPolicyRepository; + private readonly IServiceAccountRepository _serviceAccountRepository; private readonly ICreateAccessPoliciesCommand _createAccessPoliciesCommand; private readonly ICurrentContext _currentContext; private readonly IDeleteAccessPolicyCommand _deleteAccessPolicyCommand; private readonly IGroupRepository _groupRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IProjectRepository _projectRepository; - private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IUpdateAccessPolicyCommand _updateAccessPolicyCommand; private readonly IUserService _userService; @@ -74,6 +74,26 @@ public class AccessPoliciesController : Controller return new ProjectAccessPoliciesResponseModel(results); } + [HttpPost("/service-accounts/{id}/access-policies")] + public async Task CreateServiceAccountAccessPoliciesAsync([FromRoute] Guid id, + [FromBody] AccessPoliciesCreateRequest request) + { + var userId = _userService.GetProperUserId(User).Value; + var policies = request.ToBaseAccessPoliciesForServiceAccount(id); + var results = await _createAccessPoliciesCommand.CreateForServiceAccountAsync(id, policies, userId); + return new ServiceAccountAccessPoliciesResponseModel(results); + } + + [HttpGet("/service-accounts/{id}/access-policies")] + public async Task GetServiceAccountAccessPoliciesAsync([FromRoute] Guid id) + { + var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id); + await CheckUserHasWriteAccessToServiceAccountAsync(serviceAccount); + + var results = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(id); + return new ServiceAccountAccessPoliciesResponseModel(results); + } + [HttpPut("{id}")] public async Task UpdateAccessPolicyAsync([FromRoute] Guid id, [FromBody] AccessPolicyUpdateRequest request) @@ -84,7 +104,9 @@ public class AccessPoliciesController : Controller return result switch { UserProjectAccessPolicy accessPolicy => new UserProjectAccessPolicyResponseModel(accessPolicy), + UserServiceAccountAccessPolicy accessPolicy => new UserServiceAccountAccessPolicyResponseModel(accessPolicy), GroupProjectAccessPolicy accessPolicy => new GroupProjectAccessPolicyResponseModel(accessPolicy), + GroupServiceAccountAccessPolicy accessPolicy => new GroupServiceAccountAccessPolicyResponseModel(accessPolicy), ServiceAccountProjectAccessPolicy accessPolicy => new ServiceAccountProjectAccessPolicyResponseModel( accessPolicy), _ => throw new ArgumentException("Unsupported access policy type provided."), @@ -163,4 +185,27 @@ public class AccessPoliciesController : Controller throw new NotFoundException(); } } + + private async Task CheckUserHasWriteAccessToServiceAccountAsync(ServiceAccount serviceAccount) + { + if (serviceAccount == null || !_currentContext.AccessSecretsManager(serviceAccount.OrganizationId)) + { + throw new NotFoundException(); + } + var userId = _userService.GetProperUserId(User).Value; + var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + var hasAccess = accessClient switch + { + AccessClientType.NoAccessCheck => true, + AccessClientType.User => await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(serviceAccount.Id, userId), + _ => false, + }; + + if (!hasAccess) + { + throw new NotFoundException(); + } + } } diff --git a/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs b/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs index eab1c1dbaf..689a6c59e1 100644 --- a/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs +++ b/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs @@ -13,7 +13,7 @@ public class AccessPoliciesCreateRequest public IEnumerable? ServiceAccountAccessPolicyRequests { get; set; } - public List ToBaseAccessPoliciesForProject(Guid projectId) + public List ToBaseAccessPoliciesForProject(Guid grantedProjectId) { if (UserAccessPolicyRequests == null && GroupAccessPolicyRequests == null && ServiceAccountAccessPolicyRequests == null) { @@ -21,18 +21,55 @@ public class AccessPoliciesCreateRequest } var userAccessPolicies = UserAccessPolicyRequests? - .Select(x => x.ToUserProjectAccessPolicy(projectId)).ToList(); + .Select(x => x.ToUserProjectAccessPolicy(grantedProjectId)).ToList(); var groupAccessPolicies = GroupAccessPolicyRequests? - .Select(x => x.ToGroupProjectAccessPolicy(projectId)).ToList(); + .Select(x => x.ToGroupProjectAccessPolicy(grantedProjectId)).ToList(); var serviceAccountAccessPolicies = ServiceAccountAccessPolicyRequests? - .Select(x => x.ToServiceAccountProjectAccessPolicy(projectId)).ToList(); + .Select(x => x.ToServiceAccountProjectAccessPolicy(grantedProjectId)).ToList(); var policies = new List(); - if (userAccessPolicies != null) { policies.AddRange(userAccessPolicies); } - if (groupAccessPolicies != null) { policies.AddRange(groupAccessPolicies); } - if (serviceAccountAccessPolicies != null) { policies.AddRange(serviceAccountAccessPolicies); } + if (userAccessPolicies != null) + { + policies.AddRange(userAccessPolicies); + } + + if (groupAccessPolicies != null) + { + policies.AddRange(groupAccessPolicies); + } + + if (serviceAccountAccessPolicies != null) + { + policies.AddRange(serviceAccountAccessPolicies); + } + return policies; + } + + public List ToBaseAccessPoliciesForServiceAccount(Guid grantedServiceAccountId) + { + if (UserAccessPolicyRequests == null && GroupAccessPolicyRequests == null) + { + throw new BadRequestException("No creation requests provided."); + } + + var userAccessPolicies = UserAccessPolicyRequests? + .Select(x => x.ToUserServiceAccountAccessPolicy(grantedServiceAccountId)).ToList(); + + var groupAccessPolicies = GroupAccessPolicyRequests? + .Select(x => x.ToGroupServiceAccountAccessPolicy(grantedServiceAccountId)).ToList(); + + var policies = new List(); + if (userAccessPolicies != null) + { + policies.AddRange(userAccessPolicies); + } + + if (groupAccessPolicies != null) + { + policies.AddRange(groupAccessPolicies); + } return policies; } } @@ -74,4 +111,22 @@ public class AccessPolicyRequest Read = Read, Write = Write }; + + public UserServiceAccountAccessPolicy ToUserServiceAccountAccessPolicy(Guid id) => + new() + { + OrganizationUserId = GranteeId, + GrantedServiceAccountId = id, + Read = Read, + Write = Write + }; + + public GroupServiceAccountAccessPolicy ToGroupServiceAccountAccessPolicy(Guid id) => + new() + { + GroupId = GranteeId, + GrantedServiceAccountId = id, + Read = Read, + Write = Write + }; } diff --git a/src/Api/SecretsManager/Models/Response/ProjectAccessPoliciesResponseModel.cs b/src/Api/SecretsManager/Models/Response/ProjectAccessPoliciesResponseModel.cs index f03411cd39..7308d1833c 100644 --- a/src/Api/SecretsManager/Models/Response/ProjectAccessPoliciesResponseModel.cs +++ b/src/Api/SecretsManager/Models/Response/ProjectAccessPoliciesResponseModel.cs @@ -11,6 +11,7 @@ public class ProjectAccessPoliciesResponseModel : ResponseModel : base(_objectName) { foreach (var baseAccessPolicy in baseAccessPolicies) + { switch (baseAccessPolicy) { case UserProjectAccessPolicy accessPolicy: @@ -24,6 +25,7 @@ public class ProjectAccessPoliciesResponseModel : ResponseModel new ServiceAccountProjectAccessPolicyResponseModel(accessPolicy)); break; } + } } public ProjectAccessPoliciesResponseModel() : base(_objectName) diff --git a/src/Api/SecretsManager/Models/Response/ServiceAccountAccessPoliciesResponseModel.cs b/src/Api/SecretsManager/Models/Response/ServiceAccountAccessPoliciesResponseModel.cs new file mode 100644 index 0000000000..6f047cf881 --- /dev/null +++ b/src/Api/SecretsManager/Models/Response/ServiceAccountAccessPoliciesResponseModel.cs @@ -0,0 +1,39 @@ +using Bit.Core.Models.Api; +using Bit.Core.SecretsManager.Entities; + +namespace Bit.Api.SecretsManager.Models.Response; + +public class ServiceAccountAccessPoliciesResponseModel : ResponseModel +{ + private const string _objectName = "serviceAccountAccessPolicies"; + + public ServiceAccountAccessPoliciesResponseModel(IEnumerable baseAccessPolicies) + : base(_objectName) + { + if (baseAccessPolicies == null) + { + return; + } + + foreach (var baseAccessPolicy in baseAccessPolicies) + { + switch (baseAccessPolicy) + { + case UserServiceAccountAccessPolicy accessPolicy: + UserAccessPolicies.Add(new UserServiceAccountAccessPolicyResponseModel(accessPolicy)); + break; + case GroupServiceAccountAccessPolicy accessPolicy: + GroupAccessPolicies.Add(new GroupServiceAccountAccessPolicyResponseModel(accessPolicy)); + break; + } + } + } + + public ServiceAccountAccessPoliciesResponseModel() : base(_objectName) + { + } + + public List UserAccessPolicies { get; set; } = new(); + + public List GroupAccessPolicies { get; set; } = new(); +} diff --git a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs index 29aa34c0f7..b1b0bf563f 100644 --- a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs +++ b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs @@ -5,4 +5,5 @@ namespace Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; public interface ICreateAccessPoliciesCommand { Task> CreateForProjectAsync(Guid projectId, List accessPolicies, Guid userId); + Task> CreateForServiceAccountAsync(Guid serviceAccountId, List accessPolicies, Guid userId); } diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs index 4368c97b4f..9aeb18dc15 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs @@ -21,6 +21,8 @@ public class DatabaseContext : DbContext public DbSet UserProjectAccessPolicy { get; set; } public DbSet GroupProjectAccessPolicy { get; set; } public DbSet ServiceAccountProjectAccessPolicy { get; set; } + public DbSet UserServiceAccountAccessPolicy { get; set; } + public DbSet GroupServiceAccountAccessPolicy { get; set; } public DbSet ApiKeys { get; set; } public DbSet Ciphers { get; set; } public DbSet Collections { get; set; } diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs index 9d48a8d3a5..33cd4d6e52 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs @@ -534,6 +534,235 @@ public class AccessPoliciesControllerTest : IClassFixture Assert.Equal(serviceAccount.Id.ToString(), result!.Data.First(x => x.Id == serviceAccount.Id.ToString()).Id); } + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task CreateServiceAccountAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, orgUser) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var request = new AccessPoliciesCreateRequest + { + UserAccessPolicyRequests = new List + { + new() { GranteeId = orgUser.Id, Read = true, Write = true }, + }, + }; + + var response = + await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task CreateServiceAccountAccessPolicies(PermissionType permissionType) + { + var (org, orgUser) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + var ownerOrgUserId = orgUser.Id; + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var request = new AccessPoliciesCreateRequest + { + UserAccessPolicyRequests = new List + { + new() { GranteeId = orgUser.Id, Read = true, Write = true }, + }, + }; + + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, newOrgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + var accessPolicies = new List + { + new UserServiceAccountAccessPolicy + { + GrantedServiceAccountId = serviceAccount.Id, + OrganizationUserId = newOrgUser.Id, + Read = true, + Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + + request = new AccessPoliciesCreateRequest + { + UserAccessPolicyRequests = new List + { + new() { GranteeId = ownerOrgUserId, Read = true, Write = true }, + }, + }; + } + + + var response = + await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(); + + Assert.NotNull(result); + Assert.Equal(ownerOrgUserId, + result!.UserAccessPolicies.First(ap => ap.OrganizationUserId == ownerOrgUserId).OrganizationUserId); + Assert.True(result.UserAccessPolicies.First().Read); + Assert.True(result.UserAccessPolicies.First().Write); + AssertHelper.AssertRecent(result.UserAccessPolicies.First().RevisionDate); + AssertHelper.AssertRecent(result.UserAccessPolicies.First().CreationDate); + + var createdAccessPolicy = + await _accessPolicyRepository.GetByIdAsync(result.UserAccessPolicies.First().Id); + Assert.NotNull(createdAccessPolicy); + Assert.Equal(result.UserAccessPolicies.First().Read, createdAccessPolicy!.Read); + Assert.Equal(result.UserAccessPolicies.First().Write, createdAccessPolicy.Write); + Assert.Equal(result.UserAccessPolicies.First().Id, createdAccessPolicy.Id); + AssertHelper.AssertRecent(createdAccessPolicy.CreationDate); + AssertHelper.AssertRecent(createdAccessPolicy.RevisionDate); + } + + [Fact] + public async Task CreateServiceAccountAccessPolicies_NoPermission() + { + // Create a new account as a user + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var request = new AccessPoliciesCreateRequest + { + UserAccessPolicyRequests = new List + { + new() { GranteeId = orgUser.Id, Read = true, Write = true }, + }, + }; + + var response = + await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task GetServiceAccountAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + var initData = await SetupAccessPolicyRequest(org.Id); + + var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/access-policies"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetServiceAccountAccessPolicies_ReturnsEmpty() + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var response = await _client.GetAsync($"/service-accounts/{serviceAccount.Id}/access-policies"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(); + + Assert.NotNull(result); + Assert.Empty(result!.UserAccessPolicies); + Assert.Empty(result!.GroupAccessPolicies); + } + + [Fact] + public async Task GetServiceAccountAccessPolicies_NoPermission() + { + // Create a new account as a user + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var initData = await SetupAccessPolicyRequest(orgUser.OrganizationId); + + var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/access-policies"); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task GetServiceAccountAccessPolicies(PermissionType permissionType) + { + var (org, owerOrgUser) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + var initData = await SetupAccessPolicyRequest(org.Id); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + var accessPolicies = new List + { + new UserServiceAccountAccessPolicy + { + GrantedServiceAccountId = initData.ServiceAccountId, + OrganizationUserId = orgUser.Id, + Read = true, + Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + var policies = new List + { + new UserServiceAccountAccessPolicy + { + GrantedServiceAccountId = initData.ServiceAccountId, + OrganizationUserId = owerOrgUser.Id, + Read = true, + Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(policies); + + var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/access-policies"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(); + + Assert.NotNull(result?.UserAccessPolicies); + Assert.NotEmpty(result!.UserAccessPolicies); + Assert.Equal(owerOrgUser.Id, + result.UserAccessPolicies.First(x => x.OrganizationUserId == owerOrgUser.Id).OrganizationUserId); + } + private async Task SetupAccessPolicyRequest(Guid organizationId) { var project = await _projectRepository.CreateAsync(new Project diff --git a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs index 34bfce5427..14b4774677 100644 --- a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs @@ -31,51 +31,63 @@ public class AccessPoliciesControllerTests RunAsUserWithPermission, } - private static void SetupAdmin(SutProvider sutProvider, Project data) + private static void SetupAdmin(SutProvider sutProvider, Guid organizationId) { sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(true); + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); } - private static void SetupUserWithPermission(SutProvider sutProvider, Project data) + private static void SetupUserWithPermission(SutProvider sutProvider, Guid organizationId) { sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(false); + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); sutProvider.GetDependency().OrganizationUser(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) - .ReturnsForAnyArgs(true); } - private static void SetupUserWithoutPermission(SutProvider sutProvider, Project data) + private static void SetupUserWithoutPermission(SutProvider sutProvider, + Guid organizationId) { sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(false); + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); sutProvider.GetDependency().OrganizationUser(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) - .ReturnsForAnyArgs(false); + } + + private static void SetupPermission(SutProvider sutProvider, PermissionType permissionType, Guid orgId) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, orgId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, orgId); + break; + } + } [Theory] [BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async void GetAccessPoliciesByProject_ReturnsEmptyList( + public async void GetProjectAccessPolicies_ReturnsEmptyList( PermissionType permissionType, SutProvider sutProvider, Guid id, Project data) { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + switch (permissionType) { case PermissionType.RunAsAdmin: - SetupAdmin(sutProvider, data); + SetupAdmin(sutProvider, data.OrganizationId); break; case PermissionType.RunAsUserWithPermission: - SetupUserWithPermission(sutProvider, data); + SetupUserWithPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) + .ReturnsForAnyArgs(true); break; } @@ -91,12 +103,15 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData] - public async void GetAccessPoliciesByProject_UserWithoutPermission_Throws( + public async void GetProjectAccessPolicies_UserWithoutPermission_Throws( SutProvider sutProvider, Guid id, Project data) { - SetupUserWithoutPermission(sutProvider, data); + SetupUserWithoutPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) + .ReturnsForAnyArgs(false); await Assert.ThrowsAsync(() => sutProvider.Sut.GetProjectAccessPoliciesAsync(id)); @@ -107,20 +122,23 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async void GetAccessPoliciesByProject_Admin_Success( + public async void GetProjectAccessPolicies_Success( PermissionType permissionType, SutProvider sutProvider, Guid id, Project data, UserProjectAccessPolicy resultAccessPolicy) { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); switch (permissionType) { case PermissionType.RunAsAdmin: - SetupAdmin(sutProvider, data); + SetupAdmin(sutProvider, data.OrganizationId); break; case PermissionType.RunAsUserWithPermission: - SetupUserWithPermission(sutProvider, data); + SetupUserWithPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) + .ReturnsForAnyArgs(true); break; } @@ -139,13 +157,17 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData] - public async void GetAccessPoliciesByProject_ProjectsExist_UserWithoutPermission_Throws( + public async void GetProjectAccessPolicies_ProjectsExist_UserWithoutPermission_Throws( SutProvider sutProvider, Guid id, Project data, UserProjectAccessPolicy resultAccessPolicy) { - SetupUserWithoutPermission(sutProvider, data); + SetupUserWithoutPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) + .ReturnsForAnyArgs(false); + sutProvider.GetDependency().GetManyByGrantedProjectIdAsync(default) .ReturnsForAnyArgs(new List { resultAccessPolicy }); @@ -155,9 +177,117 @@ public class AccessPoliciesControllerTests .GetManyByGrantedProjectIdAsync(Arg.Any()); } + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetServiceAccountAccessPolicies_ReturnsEmptyList( + PermissionType permissionType, + SutProvider sutProvider, + Guid id, ServiceAccount data) + { + sutProvider.GetDependency().GetByIdAsync(data.Id).ReturnsForAnyArgs(data); + + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, data.OrganizationId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency() + .UserHasWriteAccessToServiceAccount(default, default) + .ReturnsForAnyArgs(true); + break; + } + + var result = await sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id); + + await sutProvider.GetDependency().Received(1) + .GetManyByGrantedServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); + + Assert.Empty(result.UserAccessPolicies); + Assert.Empty(result.GroupAccessPolicies); + } + [Theory] [BitAutoData] - public async void CreateAccessPolicies_Success( + public async void GetServiceAccountAccessPolicies_UserWithoutPermission_Throws( + SutProvider sutProvider, + Guid id, + ServiceAccount data) + { + SetupUserWithoutPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency().UserHasWriteAccessToServiceAccount(default, default) + .ReturnsForAnyArgs(false); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .GetManyByGrantedServiceAccountIdAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetServiceAccountAccessPolicies_Success( + PermissionType permissionType, + SutProvider sutProvider, + Guid id, + ServiceAccount data, + UserServiceAccountAccessPolicy resultAccessPolicy) + { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, data.OrganizationId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency() + .UserHasWriteAccessToServiceAccount(default, default) + .ReturnsForAnyArgs(true); + break; + } + + sutProvider.GetDependency().GetManyByGrantedServiceAccountIdAsync(default) + .ReturnsForAnyArgs(new List { resultAccessPolicy }); + + var result = await sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id); + + await sutProvider.GetDependency().Received(1) + .GetManyByGrantedServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); + + Assert.Empty(result.GroupAccessPolicies); + Assert.NotEmpty(result.UserAccessPolicies); + } + + [Theory] + [BitAutoData] + public async void GetServiceAccountAccessPolicies_ServiceAccountExists_UserWithoutPermission_Throws( + SutProvider sutProvider, + Guid id, + ServiceAccount data, + UserServiceAccountAccessPolicy resultAccessPolicy) + { + SetupUserWithoutPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency().UserHasWriteAccessToServiceAccount(default, default) + .ReturnsForAnyArgs(false); + + sutProvider.GetDependency().GetManyByGrantedServiceAccountIdAsync(default) + .ReturnsForAnyArgs(new List { resultAccessPolicy }); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .GetManyByGrantedServiceAccountIdAsync(Arg.Any()); + } + + [Theory] + [BitAutoData] + public async void CreateProjectAccessPolicies_Success( SutProvider sutProvider, Guid id, UserProjectAccessPolicy data, @@ -173,6 +303,25 @@ public class AccessPoliciesControllerTests .CreateForProjectAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } + [Theory] + [BitAutoData] + public async void CreateServiceAccountAccessPolicies_Success( + SutProvider sutProvider, + Guid id, + UserServiceAccountAccessPolicy data, + AccessPoliciesCreateRequest request) + { + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency() + .CreateForServiceAccountAsync(default, default, default) + .ReturnsForAnyArgs(new List { data }); + + await sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request); + + await sutProvider.GetDependency().Received(1) + .CreateForServiceAccountAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + } + [Theory] [BitAutoData] public async void UpdateAccessPolicies_Success( @@ -207,25 +356,12 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async void GetPeoplePotentialGranteesAsync_ReturnsEmptyList( + public async void GetPeoplePotentialGrantees_ReturnsEmptyList( PermissionType permissionType, SutProvider sutProvider, Guid id) { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(true); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - } - + SetupPermission(sutProvider, permissionType, id); var result = await sutProvider.Sut.GetPeoplePotentialGranteesAsync(id); await sutProvider.GetDependency().Received(1) @@ -239,7 +375,7 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData] - public async void GetPeoplePotentialGranteesAsync_UserWithoutPermission_Throws( + public async void GetPeoplePotentialGrantees_UserWithoutPermission_Throws( SutProvider sutProvider, Guid id) { @@ -262,26 +398,13 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async void GetPeoplePotentialGranteesAsync_Success( + public async void GetPeoplePotentialGrantees_Success( PermissionType permissionType, SutProvider sutProvider, Guid id, Group mockGroup) { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(true); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - } - + SetupPermission(sutProvider, permissionType, id); sutProvider.GetDependency().GetManyByOrganizationIdAsync(default) .ReturnsForAnyArgs(new List { mockGroup }); @@ -299,25 +422,12 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async void GetServiceAccountsPotentialGranteesAsync_ReturnsEmptyList( + public async void GetServiceAccountsPotentialGrantees_ReturnsEmptyList( PermissionType permissionType, SutProvider sutProvider, Guid id) { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(true); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - } - + SetupPermission(sutProvider, permissionType, id); var result = await sutProvider.Sut.GetServiceAccountsPotentialGranteesAsync(id); await sutProvider.GetDependency().Received(1) @@ -353,22 +463,9 @@ public class AccessPoliciesControllerTests Guid id, ServiceAccount mockServiceAccount) { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(true); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - } - + SetupPermission(sutProvider, permissionType, id); sutProvider.GetDependency().GetManyByOrganizationIdWriteAccessAsync(default, default, default) - .ReturnsForAnyArgs(new List { mockServiceAccount }); + .ReturnsForAnyArgs(new List { mockServiceAccount }); var result = await sutProvider.Sut.GetServiceAccountsPotentialGranteesAsync(id);