1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-25 12:45:18 +01:00

[SM-429] Add permission checks to access policy endpoints (#2628)

* Add permission checks to access policy endpoints

* Fix unit tests

* Add service account grant permission checks

* Add service account grant tests

* Add new endpoint unit tests

* Cleanup unit tests add integration tests

* User permission enum in create tests

* Swap to NotFoundException for access checks

* Add filter for potential grantees

* Add in AccessSecretsManager check and test it

* Add code review updates

* Code review updates

* Refactor potential grantees endpoint

* Code review updates
This commit is contained in:
Thomas Avery 2023-02-06 11:26:06 -06:00 committed by GitHub
parent 9110efa44e
commit cf669286ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1710 additions and 146 deletions

View File

@ -1,4 +1,6 @@
using Bit.Core.Exceptions; using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
@ -8,22 +10,52 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand
{ {
private readonly IAccessPolicyRepository _accessPolicyRepository; private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly ICurrentContext _currentContext;
private readonly IProjectRepository _projectRepository;
public CreateAccessPoliciesCommand(IAccessPolicyRepository accessPolicyRepository) public CreateAccessPoliciesCommand(
IAccessPolicyRepository accessPolicyRepository,
ICurrentContext currentContext,
IProjectRepository projectRepository)
{ {
_projectRepository = projectRepository;
_accessPolicyRepository = accessPolicyRepository; _accessPolicyRepository = accessPolicyRepository;
_currentContext = currentContext;
} }
public async Task<List<BaseAccessPolicy>> CreateAsync(List<BaseAccessPolicy> accessPolicies) public async Task<IEnumerable<BaseAccessPolicy>> CreateForProjectAsync(Guid projectId,
List<BaseAccessPolicy> accessPolicies, Guid userId)
{ {
var project = await _projectRepository.GetByIdAsync(projectId);
if (project == null || !_currentContext.AccessSecretsManager(project.OrganizationId))
{
throw new NotFoundException();
}
var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var hasAccess = accessClient switch
{
AccessClientType.NoAccessCheck => true,
AccessClientType.User => await _projectRepository.UserHasWriteAccessToProject(project.Id, userId),
_ => false,
};
if (!hasAccess)
{
throw new NotFoundException();
}
var distinctAccessPolicies = accessPolicies.DistinctBy(baseAccessPolicy => var distinctAccessPolicies = accessPolicies.DistinctBy(baseAccessPolicy =>
{ {
return baseAccessPolicy switch return baseAccessPolicy switch
{ {
UserProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId, ap.GrantedProjectId), UserProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId, ap.GrantedProjectId),
GroupProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedProjectId), GroupProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedProjectId),
ServiceAccountProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.ServiceAccountId, ap.GrantedProjectId), ServiceAccountProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.ServiceAccountId,
_ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)) ap.GrantedProjectId),
_ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)),
}; };
}).ToList(); }).ToList();
@ -39,7 +71,7 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand
throw new BadRequestException("Resource already exists"); throw new BadRequestException("Resource already exists");
} }
} }
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
return await _accessPolicyRepository.CreateManyAsync(accessPolicies); return await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(projectId);
} }
} }

View File

@ -1,5 +1,8 @@
using Bit.Core.Exceptions; using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
@ -7,14 +10,23 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
public class DeleteAccessPolicyCommand : IDeleteAccessPolicyCommand public class DeleteAccessPolicyCommand : IDeleteAccessPolicyCommand
{ {
private readonly IAccessPolicyRepository _accessPolicyRepository; private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly ICurrentContext _currentContext;
private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;
public DeleteAccessPolicyCommand(IAccessPolicyRepository accessPolicyRepository) public DeleteAccessPolicyCommand(
IAccessPolicyRepository accessPolicyRepository,
ICurrentContext currentContext,
IProjectRepository projectRepository,
IServiceAccountRepository serviceAccountRepository)
{ {
_projectRepository = projectRepository;
_serviceAccountRepository = serviceAccountRepository;
_accessPolicyRepository = accessPolicyRepository; _accessPolicyRepository = accessPolicyRepository;
_currentContext = currentContext;
} }
public async Task DeleteAsync(Guid id, Guid userId)
public async Task DeleteAsync(Guid id)
{ {
var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id); var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id);
if (accessPolicy == null) if (accessPolicy == null)
@ -22,6 +34,74 @@ public class DeleteAccessPolicyCommand : IDeleteAccessPolicyCommand
throw new NotFoundException(); throw new NotFoundException();
} }
if (!await IsAllowedToDeleteAsync(accessPolicy, userId))
{
throw new NotFoundException();
}
await _accessPolicyRepository.DeleteAsync(id); await _accessPolicyRepository.DeleteAsync(id);
} }
private async Task<bool> IsAllowedToDeleteAsync(BaseAccessPolicy baseAccessPolicy, Guid userId) =>
baseAccessPolicy switch
{
UserProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId, userId,
ap.GrantedProjectId),
GroupProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId, userId,
ap.GrantedProjectId),
ServiceAccountProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId,
userId, ap.GrantedProjectId),
UserServiceAccountAccessPolicy ap => await HasPermissionAsync(
ap.GrantedServiceAccount!.OrganizationId,
userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId),
GroupServiceAccountAccessPolicy ap => await HasPermissionAsync(
ap.GrantedServiceAccount!.OrganizationId,
userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId),
_ => throw new ArgumentException("Unsupported access policy type provided."),
};
private async Task<bool> HasPermissionAsync(
Guid organizationId,
Guid userId,
Guid? projectIdToCheck = null,
Guid? serviceAccountIdToCheck = null)
{
if (!_currentContext.AccessSecretsManager(organizationId))
{
return false;
}
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
bool hasAccess;
switch (accessClient)
{
case AccessClientType.NoAccessCheck:
hasAccess = true;
break;
case AccessClientType.User:
if (projectIdToCheck.HasValue)
{
hasAccess = await _projectRepository.UserHasWriteAccessToProject(projectIdToCheck.Value, userId);
}
else if (serviceAccountIdToCheck.HasValue)
{
hasAccess =
await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(
serviceAccountIdToCheck.Value, userId);
}
else
{
throw new ArgumentException("No ID to check provided.");
}
break;
default:
hasAccess = false;
break;
}
return hasAccess;
}
} }

View File

@ -1,4 +1,6 @@
using Bit.Core.Exceptions; using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
@ -8,13 +10,23 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
public class UpdateAccessPolicyCommand : IUpdateAccessPolicyCommand public class UpdateAccessPolicyCommand : IUpdateAccessPolicyCommand
{ {
private readonly IAccessPolicyRepository _accessPolicyRepository; private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly ICurrentContext _currentContext;
private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;
public UpdateAccessPolicyCommand(IAccessPolicyRepository accessPolicyRepository) public UpdateAccessPolicyCommand(
IAccessPolicyRepository accessPolicyRepository,
ICurrentContext currentContext,
IProjectRepository projectRepository,
IServiceAccountRepository serviceAccountRepository)
{ {
_accessPolicyRepository = accessPolicyRepository; _accessPolicyRepository = accessPolicyRepository;
_currentContext = currentContext;
_projectRepository = projectRepository;
_serviceAccountRepository = serviceAccountRepository;
} }
public async Task<BaseAccessPolicy> UpdateAsync(Guid id, bool read, bool write) public async Task<BaseAccessPolicy> UpdateAsync(Guid id, bool read, bool write, Guid userId)
{ {
var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id); var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id);
if (accessPolicy == null) if (accessPolicy == null)
@ -22,11 +34,78 @@ public class UpdateAccessPolicyCommand : IUpdateAccessPolicyCommand
throw new NotFoundException(); throw new NotFoundException();
} }
if (!await IsAllowedToUpdateAsync(accessPolicy, userId))
{
throw new NotFoundException();
}
accessPolicy.Read = read; accessPolicy.Read = read;
accessPolicy.Write = write; accessPolicy.Write = write;
accessPolicy.RevisionDate = DateTime.UtcNow; accessPolicy.RevisionDate = DateTime.UtcNow;
await _accessPolicyRepository.ReplaceAsync(accessPolicy); await _accessPolicyRepository.ReplaceAsync(accessPolicy);
return accessPolicy; return accessPolicy;
} }
private async Task<bool> IsAllowedToUpdateAsync(BaseAccessPolicy baseAccessPolicy, Guid userId) =>
baseAccessPolicy switch
{
UserProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId, userId,
ap.GrantedProjectId),
GroupProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId, userId,
ap.GrantedProjectId),
ServiceAccountProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId,
userId, ap.GrantedProjectId),
UserServiceAccountAccessPolicy ap => await HasPermissionsAsync(
ap.GrantedServiceAccount!.OrganizationId,
userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId),
GroupServiceAccountAccessPolicy ap => await HasPermissionsAsync(
ap.GrantedServiceAccount!.OrganizationId,
userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId),
_ => throw new ArgumentException("Unsupported access policy type provided."),
};
private async Task<bool> HasPermissionsAsync(
Guid organizationId,
Guid userId,
Guid? projectIdToCheck = null,
Guid? serviceAccountIdToCheck = null)
{
if (!_currentContext.AccessSecretsManager(organizationId))
{
return false;
}
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
bool hasAccess;
switch (accessClient)
{
case AccessClientType.NoAccessCheck:
hasAccess = true;
break;
case AccessClientType.User:
if (projectIdToCheck.HasValue)
{
hasAccess = await _projectRepository.UserHasWriteAccessToProject(projectIdToCheck.Value, userId);
}
else if (serviceAccountIdToCheck.HasValue)
{
hasAccess =
await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(
serviceAccountIdToCheck.Value, userId);
}
else
{
throw new ArgumentException("No ID to check provided.");
}
break;
default:
hasAccess = false;
break;
}
return hasAccess;
}
} }

View File

@ -108,8 +108,15 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
var dbContext = GetDatabaseContext(scope); var dbContext = GetDatabaseContext(scope);
var entity = await dbContext.AccessPolicies.Where(ap => ap.Id == id) var entity = await dbContext.AccessPolicies.Where(ap => ap.Id == id)
.Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User) .Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User)
.Include(ap => ((UserProjectAccessPolicy)ap).GrantedProject)
.Include(ap => ((GroupProjectAccessPolicy)ap).Group) .Include(ap => ((GroupProjectAccessPolicy)ap).Group)
.Include(ap => ((GroupProjectAccessPolicy)ap).GrantedProject)
.Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) .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(); .FirstOrDefaultAsync();
if (entity == null) if (entity == null)

View File

@ -54,6 +54,23 @@ public class ServiceAccountRepository : Repository<Core.SecretsManager.Entities.
return await query.AnyAsync(); return await query.AnyAsync();
} }
public async Task<IEnumerable<Core.SecretsManager.Entities.ServiceAccount>> GetManyByOrganizationIdWriteAccessAsync(Guid organizationId, Guid userId, AccessClientType accessType)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var query = dbContext.ServiceAccount.Where(c => c.OrganizationId == organizationId);
query = accessType switch
{
AccessClientType.NoAccessCheck => query,
AccessClientType.User => query.Where(UserHasWriteAccessToServiceAccount(userId)),
_ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null),
};
var serviceAccounts = await query.OrderBy(c => c.RevisionDate).ToListAsync();
return Mapper.Map<List<Core.SecretsManager.Entities.ServiceAccount>>(serviceAccounts);
}
private static Expression<Func<ServiceAccount, bool>> UserHasReadAccessToServiceAccount(Guid userId) => sa => private static Expression<Func<ServiceAccount, bool>> UserHasReadAccessToServiceAccount(Guid userId) => sa =>
sa.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Read) || sa.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Read) ||
sa.GroupAccessPolicies.Any(ap => ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Read)); sa.GroupAccessPolicies.Any(ap => ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Read));

View File

@ -1,7 +1,10 @@
using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
using Bit.Commercial.Core.Test.SecretsManager.Enums;
using Bit.Core.Context;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers; using Bit.Test.Common.Helpers;
@ -11,29 +14,14 @@ using Xunit;
namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies; namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies;
[SutProviderCustomize] [SutProviderCustomize]
[ProjectCustomize]
public class CreateAccessPoliciesCommandTests public class CreateAccessPoliciesCommandTests
{ {
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task CreateAsync_CallsCreate(List<UserProjectAccessPolicy> userProjectAccessPolicies, public async Task CreateAsync_SmNotEnabled_Throws(
List<GroupProjectAccessPolicy> groupProjectAccessPolicies, Guid userId,
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies, Project project,
SutProvider<CreateAccessPoliciesCommand> sutProvider)
{
var data = new List<BaseAccessPolicy>();
data.AddRange(userProjectAccessPolicies);
data.AddRange(groupProjectAccessPolicies);
data.AddRange(serviceAccountProjectAccessPolicies);
await sutProvider.Sut.CreateAsync(data);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
.CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data)));
}
[Theory]
[BitAutoData]
public async Task CreateAsync_AlreadyExists_Throws_BadRequestException(
List<UserProjectAccessPolicy> userProjectAccessPolicies, List<UserProjectAccessPolicy> userProjectAccessPolicies,
List<GroupProjectAccessPolicy> groupProjectAccessPolicies, List<GroupProjectAccessPolicy> groupProjectAccessPolicies,
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies, List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies,
@ -44,14 +32,41 @@ public class CreateAccessPoliciesCommandTests
data.AddRange(groupProjectAccessPolicies); data.AddRange(groupProjectAccessPolicies);
data.AddRange(serviceAccountProjectAccessPolicies); data.AddRange(serviceAccountProjectAccessPolicies);
sutProvider.GetDependency<IAccessPolicyRepository>().AccessPolicyExists(Arg.Any<BaseAccessPolicy>()) sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(false);
.Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateAsync(data)); await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().CreateManyAsync(default); await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().CreateManyAsync(default);
} }
[Theory]
[BitAutoData]
public async Task CreateAsync_AlreadyExists_Throws_BadRequestException(
Guid userId,
Project project,
List<UserProjectAccessPolicy> userProjectAccessPolicies,
List<GroupProjectAccessPolicy> groupProjectAccessPolicies,
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies,
SutProvider<CreateAccessPoliciesCommand> sutProvider)
{
var data = new List<BaseAccessPolicy>();
data.AddRange(userProjectAccessPolicies);
data.AddRange(groupProjectAccessPolicies);
data.AddRange(serviceAccountProjectAccessPolicies);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(project.OrganizationId).Returns(true);
sutProvider.GetDependency<IAccessPolicyRepository>().AccessPolicyExists(Arg.Any<BaseAccessPolicy>())
.Returns(true);
await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().CreateManyAsync(default);
}
[Theory] [Theory]
[BitAutoData(true, false, false)] [BitAutoData(true, false, false)]
@ -65,6 +80,8 @@ public class CreateAccessPoliciesCommandTests
bool testUserPolicies, bool testUserPolicies,
bool testGroupPolicies, bool testGroupPolicies,
bool testServiceAccountPolicies, bool testServiceAccountPolicies,
Guid userId,
Project project,
List<UserProjectAccessPolicy> userProjectAccessPolicies, List<UserProjectAccessPolicy> userProjectAccessPolicies,
List<GroupProjectAccessPolicy> groupProjectAccessPolicies, List<GroupProjectAccessPolicy> groupProjectAccessPolicies,
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies, List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies,
@ -76,12 +93,16 @@ public class CreateAccessPoliciesCommandTests
data.AddRange(groupProjectAccessPolicies); data.AddRange(groupProjectAccessPolicies);
data.AddRange(serviceAccountProjectAccessPolicies); data.AddRange(serviceAccountProjectAccessPolicies);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(project.OrganizationId).Returns(true);
if (testUserPolicies) if (testUserPolicies)
{ {
var mockUserPolicy = new UserProjectAccessPolicy var mockUserPolicy = new UserProjectAccessPolicy
{ {
OrganizationUserId = Guid.NewGuid(), OrganizationUserId = Guid.NewGuid(),
GrantedProjectId = Guid.NewGuid() GrantedProjectId = Guid.NewGuid(),
}; };
data.Add(mockUserPolicy); data.Add(mockUserPolicy);
@ -94,7 +115,7 @@ public class CreateAccessPoliciesCommandTests
var mockGroupPolicy = new GroupProjectAccessPolicy var mockGroupPolicy = new GroupProjectAccessPolicy
{ {
GroupId = Guid.NewGuid(), GroupId = Guid.NewGuid(),
GrantedProjectId = Guid.NewGuid() GrantedProjectId = Guid.NewGuid(),
}; };
data.Add(mockGroupPolicy); data.Add(mockGroupPolicy);
@ -107,7 +128,7 @@ public class CreateAccessPoliciesCommandTests
var mockServiceAccountPolicy = new ServiceAccountProjectAccessPolicy var mockServiceAccountPolicy = new ServiceAccountProjectAccessPolicy
{ {
ServiceAccountId = Guid.NewGuid(), ServiceAccountId = Guid.NewGuid(),
GrantedProjectId = Guid.NewGuid() GrantedProjectId = Guid.NewGuid(),
}; };
data.Add(mockServiceAccountPolicy); data.Add(mockServiceAccountPolicy);
@ -119,7 +140,69 @@ public class CreateAccessPoliciesCommandTests
sutProvider.GetDependency<IAccessPolicyRepository>().AccessPolicyExists(Arg.Any<BaseAccessPolicy>()) sutProvider.GetDependency<IAccessPolicyRepository>().AccessPolicyExists(Arg.Any<BaseAccessPolicy>())
.Returns(true); .Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateAsync(data)); await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().CreateManyAsync(default);
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async Task CreateAsync_Success(
PermissionType permissionType,
Guid userId,
Project project,
List<UserProjectAccessPolicy> userProjectAccessPolicies,
List<GroupProjectAccessPolicy> groupProjectAccessPolicies,
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies,
SutProvider<CreateAccessPoliciesCommand> sutProvider)
{
var data = new List<BaseAccessPolicy>();
data.AddRange(userProjectAccessPolicies);
data.AddRange(groupProjectAccessPolicies);
data.AddRange(serviceAccountProjectAccessPolicies);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(project.OrganizationId).Returns(true);
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(project.Id, userId).Returns(true);
break;
}
await sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
.CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data)));
}
[Theory]
[BitAutoData]
public async Task CreateAsync_User_NoPermission(
Guid userId,
Project project,
List<UserProjectAccessPolicy> userProjectAccessPolicies,
List<GroupProjectAccessPolicy> groupProjectAccessPolicies,
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies,
SutProvider<CreateAccessPoliciesCommand> sutProvider)
{
var data = new List<BaseAccessPolicy>();
data.AddRange(userProjectAccessPolicies);
data.AddRange(groupProjectAccessPolicies);
data.AddRange(serviceAccountProjectAccessPolicies);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(project.Id, userId).Returns(false);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().CreateManyAsync(default); await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().CreateManyAsync(default);
} }

View File

@ -1,7 +1,11 @@
using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
using Bit.Commercial.Core.Test.SecretsManager.Enums;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute; using NSubstitute;
@ -11,28 +15,216 @@ using Xunit;
namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies; namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies;
[SutProviderCustomize] [SutProviderCustomize]
[ProjectCustomize]
public class DeleteAccessPolicyCommandTests public class DeleteAccessPolicyCommandTests
{ {
private static void SetupPermission(SutProvider<DeleteAccessPolicyCommand> sutProvider,
PermissionType permissionType, Project grantedProject, Guid userId)
{
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(grantedProject.OrganizationId)
.Returns(true);
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(grantedProject.Id, userId)
.Returns(true);
break;
default:
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
}
}
private static void SetupPermission(SutProvider<DeleteAccessPolicyCommand> sutProvider,
PermissionType permissionType, ServiceAccount grantedServiceAccount, Guid userId)
{
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(grantedServiceAccount.OrganizationId)
.Returns(true);
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<IServiceAccountRepository>()
.UserHasWriteAccessToServiceAccount(grantedServiceAccount.Id, userId)
.Returns(true);
break;
default:
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
}
}
private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, Guid data,
Project grantedProject, Group mockGroup, ServiceAccount mockServiceAccount) =>
accessPolicyType switch
{
AccessPolicyType.UserProjectAccessPolicy => new UserProjectAccessPolicy
{
Id = data,
GrantedProjectId = grantedProject.Id,
GrantedProject = grantedProject,
},
AccessPolicyType.GroupProjectAccessPolicy => new GroupProjectAccessPolicy
{
Id = data,
GrantedProjectId = grantedProject.Id,
Group = mockGroup,
GrantedProject = grantedProject,
},
AccessPolicyType.ServiceAccountProjectAccessPolicy => new ServiceAccountProjectAccessPolicy
{
Id = data,
GrantedProjectId = grantedProject.Id,
ServiceAccount = mockServiceAccount,
GrantedProject = grantedProject,
},
_ => null,
};
private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, Guid data,
ServiceAccount grantedServiceAccount, Group mockGroup) =>
accessPolicyType switch
{
AccessPolicyType.UserServiceAccountAccessPolicy => new UserServiceAccountAccessPolicy
{
Id = data,
GrantedServiceAccountId = grantedServiceAccount.Id,
GrantedServiceAccount = grantedServiceAccount,
},
AccessPolicyType.GroupServiceAccountAccessPolicy => new GroupServiceAccountAccessPolicy
{
Id = data,
GrantedServiceAccountId = grantedServiceAccount.Id,
Group = mockGroup,
GrantedServiceAccount = grantedServiceAccount,
},
_ => null,
};
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task DeleteAccessPolicy_Throws_NotFoundException(Guid data, public async Task DeleteAccessPolicy_Throws_NotFoundException(Guid data, Guid userId,
SutProvider<DeleteAccessPolicyCommand> sutProvider) SutProvider<DeleteAccessPolicyCommand> sutProvider)
{ {
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).ReturnsNull(); sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).ReturnsNull();
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAsync(data)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAsync(data, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().DeleteAsync(default); await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().DeleteAsync(default);
} }
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task DeleteAccessPolicy_Success(Guid data, public async Task DeleteAccessPolicy_SmNotEnabled_Throws_NotFoundException(Guid data, Guid userId,
SutProvider<DeleteAccessPolicyCommand> sutProvider) SutProvider<DeleteAccessPolicyCommand> sutProvider)
{ {
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data) sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(false);
.Returns(new UserProjectAccessPolicy { Id = data }); sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).ReturnsNull();
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAsync(data, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().DeleteAsync(default);
}
await sutProvider.Sut.DeleteAsync(data); [Theory]
[BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission)]
[BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission)]
[BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission)]
public async Task DeleteAccessPolicy_ProjectGrants_PermissionsCheck_Success(
AccessPolicyType accessPolicyType,
PermissionType permissionType,
Guid data,
Guid userId,
Project grantedProject,
Group mockGroup,
ServiceAccount mockServiceAccount,
SutProvider<DeleteAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn =
CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount);
SetupPermission(sutProvider, permissionType, grantedProject, userId);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data)
.Returns(policyToReturn);
await sutProvider.Sut.DeleteAsync(data, userId);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1).DeleteAsync(Arg.Is(data)); await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1).DeleteAsync(Arg.Is(data));
} }
[Theory]
[BitAutoData(AccessPolicyType.UserProjectAccessPolicy)]
[BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)]
[BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)]
public async Task DeleteAccessPolicy_UserProjectAccessPolicy_PermissionsCheck_ThrowsNotAuthorized(
AccessPolicyType accessPolicyType,
Guid data,
Guid userId,
Group mockGroup,
ServiceAccount mockServiceAccount,
Project grantedProject,
SutProvider<DeleteAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn =
CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data)
.Returns(policyToReturn);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.DeleteAsync(data, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().DeleteAsync(default);
}
[Theory]
[BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)]
[BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)]
public async Task DeleteAccessPolicy_ServiceAccountGrants_PermissionsCheck_Success(
AccessPolicyType accessPolicyType,
PermissionType permissionType,
Guid data,
Guid userId,
ServiceAccount grantedServiceAccount,
Group mockGroup,
SutProvider<DeleteAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn = CreatePolicyToReturn(accessPolicyType, data, grantedServiceAccount, mockGroup);
SetupPermission(sutProvider, permissionType, grantedServiceAccount, userId);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data)
.Returns(policyToReturn);
await sutProvider.Sut.DeleteAsync(data, userId);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1).DeleteAsync(Arg.Is(data));
}
[Theory]
[BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)]
[BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)]
public async Task DeleteAccessPolicy_ServiceAccountGrants_PermissionsCheck_Throws(
AccessPolicyType accessPolicyType,
Guid data,
Guid userId,
ServiceAccount grantedServiceAccount,
Group mockGroup,
SutProvider<DeleteAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn = CreatePolicyToReturn(accessPolicyType, data, grantedServiceAccount, mockGroup);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data)
.Returns(policyToReturn);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.DeleteAsync(data, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().DeleteAsync(default);
}
} }

View File

@ -1,7 +1,11 @@
using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
using Bit.Commercial.Core.Test.SecretsManager.Enums;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers; using Bit.Test.Common.Helpers;
@ -11,30 +15,246 @@ using Xunit;
namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies; namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies;
[SutProviderCustomize] [SutProviderCustomize]
[ProjectCustomize]
public class UpdateAccessPolicyCommandTests public class UpdateAccessPolicyCommandTests
{ {
private static void SetupPermission(SutProvider<UpdateAccessPolicyCommand> sutProvider,
PermissionType permissionType, Project grantedProject, Guid userId)
{
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(grantedProject.OrganizationId)
.Returns(true);
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(grantedProject.Id, userId)
.Returns(true);
break;
default:
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
}
}
private static void SetupPermission(SutProvider<UpdateAccessPolicyCommand> sutProvider,
PermissionType permissionType, ServiceAccount grantedServiceAccount, Guid userId)
{
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(grantedServiceAccount.OrganizationId)
.Returns(true);
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<IServiceAccountRepository>()
.UserHasWriteAccessToServiceAccount(grantedServiceAccount.Id, userId)
.Returns(true);
break;
default:
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
}
}
private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType,
ServiceAccount grantedServiceAccount, Guid data, Group mockGroup)
{
switch (accessPolicyType)
{
case AccessPolicyType.UserServiceAccountAccessPolicy:
return
new UserServiceAccountAccessPolicy
{
Id = data,
Read = true,
Write = true,
GrantedServiceAccountId = grantedServiceAccount.Id,
GrantedServiceAccount = grantedServiceAccount,
};
case AccessPolicyType.GroupServiceAccountAccessPolicy:
mockGroup.OrganizationId = grantedServiceAccount.OrganizationId;
return new GroupServiceAccountAccessPolicy
{
Id = data,
GrantedServiceAccountId = grantedServiceAccount.Id,
GrantedServiceAccount = grantedServiceAccount,
Read = true,
Write = true,
Group = mockGroup,
};
default:
throw new ArgumentOutOfRangeException(nameof(accessPolicyType), accessPolicyType, null);
}
}
private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, Guid data,
Project grantedProject, Group mockGroup, ServiceAccount mockServiceAccount)
{
switch (accessPolicyType)
{
case AccessPolicyType.UserProjectAccessPolicy:
return
new UserProjectAccessPolicy
{
Id = data,
Read = true,
Write = true,
GrantedProjectId = grantedProject.Id,
GrantedProject = grantedProject,
};
case AccessPolicyType.GroupProjectAccessPolicy:
mockGroup.OrganizationId = grantedProject.OrganizationId;
return
new GroupProjectAccessPolicy
{
Id = data,
GrantedProjectId = grantedProject.Id,
Read = true,
Write = true,
Group = mockGroup,
GrantedProject = grantedProject,
};
case AccessPolicyType.ServiceAccountProjectAccessPolicy:
mockServiceAccount.OrganizationId = grantedProject.OrganizationId;
return new ServiceAccountProjectAccessPolicy
{
Id = data,
GrantedProjectId = grantedProject.Id,
Read = true,
Write = true,
ServiceAccount = mockServiceAccount,
GrantedProject = grantedProject,
};
default:
throw new ArgumentOutOfRangeException(nameof(accessPolicyType), accessPolicyType, null);
}
}
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task UpdateAsync_Throws_NotFoundException(Guid data, bool read, bool write, public async Task UpdateAsync_Throws_NotFoundException(Guid data, bool read, bool write, Guid userId,
SutProvider<UpdateAccessPolicyCommand> sutProvider) SutProvider<UpdateAccessPolicyCommand> sutProvider)
{ {
var exception = sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, read, write)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, read, write, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default); await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
} }
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task UpdateAsync_Calls_Replace(Guid data, bool read, bool write, public async Task UpdateAsync_SmNotEnabled_Throws_NotFoundException(Guid data, bool read, bool write, Guid userId,
SutProvider<UpdateAccessPolicyCommand> sutProvider) SutProvider<UpdateAccessPolicyCommand> sutProvider)
{ {
var existingPolicy = new UserProjectAccessPolicy { Id = data, Read = true, Write = true }; sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(false);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).Returns(existingPolicy); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, read, write, userId));
var result = await sutProvider.Sut.UpdateAsync(data, read, write); await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1).ReplaceAsync(existingPolicy); }
[Theory]
[BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission)]
[BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission)]
[BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission)]
public async Task UpdateAsync_ProjectGrants_PermissionsCheck_Success(
AccessPolicyType accessPolicyType,
PermissionType permissionType,
Guid data,
bool read,
bool write,
Guid userId,
Project grantedProject,
Group mockGroup,
ServiceAccount mockServiceAccount,
SutProvider<UpdateAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn =
CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount);
SetupPermission(sutProvider, permissionType, grantedProject, userId);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).Returns(policyToReturn);
var result = await sutProvider.Sut.UpdateAsync(data, read, write, userId);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1).ReplaceAsync(policyToReturn);
AssertHelper.AssertRecent(result.RevisionDate); AssertHelper.AssertRecent(result.RevisionDate);
Assert.Equal(read, result.Read); Assert.Equal(read, result.Read);
Assert.Equal(write, result.Write); Assert.Equal(write, result.Write);
} }
[Theory]
[BitAutoData(AccessPolicyType.UserProjectAccessPolicy)]
[BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)]
[BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)]
public async Task UpdateAsync_ProjectGrants_PermissionsCheck_Throws(
AccessPolicyType accessPolicyType,
Guid data,
bool read,
bool write,
Guid userId,
Project grantedProject,
Group mockGroup,
ServiceAccount mockServiceAccount,
SutProvider<UpdateAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn =
CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).Returns(policyToReturn);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.UpdateAsync(data, read, write, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
[Theory]
[BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)]
[BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)]
public async Task UpdateAsync_ServiceAccountGrants_PermissionsCheck_Success(
AccessPolicyType accessPolicyType,
PermissionType permissionType,
Guid data,
bool read,
bool write,
Guid userId,
ServiceAccount grantedServiceAccount,
Group mockGroup,
SutProvider<UpdateAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn = CreatePolicyToReturn(accessPolicyType, grantedServiceAccount, data, mockGroup);
SetupPermission(sutProvider, permissionType, grantedServiceAccount, userId);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).Returns(policyToReturn);
var result = await sutProvider.Sut.UpdateAsync(data, read, write, userId);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1).ReplaceAsync(policyToReturn);
AssertHelper.AssertRecent(result.RevisionDate);
Assert.Equal(read, result.Read);
Assert.Equal(write, result.Write);
}
[Theory]
[BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)]
[BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)]
public async Task UpdateAsync_ServiceAccountGrants_PermissionsCheck_Throws(
AccessPolicyType accessPolicyType,
Guid data,
bool read,
bool write,
Guid userId,
ServiceAccount grantedServiceAccount,
Group mockGroup,
SutProvider<UpdateAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn = CreatePolicyToReturn(accessPolicyType, grantedServiceAccount, data, mockGroup);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).Returns(policyToReturn);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.UpdateAsync(data, read, write, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
} }

View File

@ -0,0 +1,11 @@
namespace Bit.Commercial.Core.Test.SecretsManager.Enums;
public enum AccessPolicyType
{
UserProjectAccessPolicy,
GroupProjectAccessPolicy,
ServiceAccountProjectAccessPolicy,
UserServiceAccountAccessPolicy,
GroupServiceAccountAccessPolicy,
}

View File

@ -0,0 +1,7 @@
namespace Bit.Commercial.Core.Test.SecretsManager.Enums;
public enum PermissionType
{
RunAsAdmin,
RunAsUserWithPermission,
}

View File

@ -1,27 +1,53 @@
using Bit.Api.SecretsManager.Models.Request; using Bit.Api.Models.Response;
using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.SecretsManager.Models.Response; using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.SecretsManager.Controllers; namespace Bit.Api.SecretsManager.Controllers;
[SecretsManager] [SecretsManager]
[Authorize("secrets")]
[Route("access-policies")] [Route("access-policies")]
public class AccessPoliciesController : Controller public class AccessPoliciesController : Controller
{ {
private readonly IAccessPolicyRepository _accessPolicyRepository; private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly ICreateAccessPoliciesCommand _createAccessPoliciesCommand; private readonly ICreateAccessPoliciesCommand _createAccessPoliciesCommand;
private readonly ICurrentContext _currentContext;
private readonly IDeleteAccessPolicyCommand _deleteAccessPolicyCommand; 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 IUpdateAccessPolicyCommand _updateAccessPolicyCommand;
private readonly IUserService _userService;
public AccessPoliciesController( public AccessPoliciesController(
IUserService userService,
ICurrentContext currentContext,
IAccessPolicyRepository accessPolicyRepository, IAccessPolicyRepository accessPolicyRepository,
IServiceAccountRepository serviceAccountRepository,
IGroupRepository groupRepository,
IProjectRepository projectRepository,
IOrganizationUserRepository organizationUserRepository,
ICreateAccessPoliciesCommand createAccessPoliciesCommand, ICreateAccessPoliciesCommand createAccessPoliciesCommand,
IDeleteAccessPolicyCommand deleteAccessPolicyCommand, IDeleteAccessPolicyCommand deleteAccessPolicyCommand,
IUpdateAccessPolicyCommand updateAccessPolicyCommand) IUpdateAccessPolicyCommand updateAccessPolicyCommand)
{ {
_userService = userService;
_currentContext = currentContext;
_serviceAccountRepository = serviceAccountRepository;
_projectRepository = projectRepository;
_groupRepository = groupRepository;
_organizationUserRepository = organizationUserRepository;
_accessPolicyRepository = accessPolicyRepository; _accessPolicyRepository = accessPolicyRepository;
_createAccessPoliciesCommand = createAccessPoliciesCommand; _createAccessPoliciesCommand = createAccessPoliciesCommand;
_deleteAccessPolicyCommand = deleteAccessPolicyCommand; _deleteAccessPolicyCommand = deleteAccessPolicyCommand;
@ -32,14 +58,18 @@ public class AccessPoliciesController : Controller
public async Task<ProjectAccessPoliciesResponseModel> CreateProjectAccessPoliciesAsync([FromRoute] Guid id, public async Task<ProjectAccessPoliciesResponseModel> CreateProjectAccessPoliciesAsync([FromRoute] Guid id,
[FromBody] AccessPoliciesCreateRequest request) [FromBody] AccessPoliciesCreateRequest request)
{ {
var userId = _userService.GetProperUserId(User).Value;
var policies = request.ToBaseAccessPoliciesForProject(id); var policies = request.ToBaseAccessPoliciesForProject(id);
var results = await _createAccessPoliciesCommand.CreateAsync(policies); var results = await _createAccessPoliciesCommand.CreateForProjectAsync(id, policies, userId);
return new ProjectAccessPoliciesResponseModel(results); return new ProjectAccessPoliciesResponseModel(results);
} }
[HttpGet("/projects/{id}/access-policies")] [HttpGet("/projects/{id}/access-policies")]
public async Task<ProjectAccessPoliciesResponseModel> GetProjectAccessPoliciesAsync([FromRoute] Guid id) public async Task<ProjectAccessPoliciesResponseModel> GetProjectAccessPoliciesAsync([FromRoute] Guid id)
{ {
var project = await _projectRepository.GetByIdAsync(id);
await CheckUserHasWriteAccessToProjectAsync(project);
var results = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(id); var results = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(id);
return new ProjectAccessPoliciesResponseModel(results); return new ProjectAccessPoliciesResponseModel(results);
} }
@ -48,7 +78,8 @@ public class AccessPoliciesController : Controller
public async Task<BaseAccessPolicyResponseModel> UpdateAccessPolicyAsync([FromRoute] Guid id, public async Task<BaseAccessPolicyResponseModel> UpdateAccessPolicyAsync([FromRoute] Guid id,
[FromBody] AccessPolicyUpdateRequest request) [FromBody] AccessPolicyUpdateRequest request)
{ {
var result = await _updateAccessPolicyCommand.UpdateAsync(id, request.Read, request.Write); var userId = _userService.GetProperUserId(User).Value;
var result = await _updateAccessPolicyCommand.UpdateAsync(id, request.Read, request.Write, userId);
return result switch return result switch
{ {
@ -56,13 +87,80 @@ public class AccessPoliciesController : Controller
GroupProjectAccessPolicy accessPolicy => new GroupProjectAccessPolicyResponseModel(accessPolicy), GroupProjectAccessPolicy accessPolicy => new GroupProjectAccessPolicyResponseModel(accessPolicy),
ServiceAccountProjectAccessPolicy accessPolicy => new ServiceAccountProjectAccessPolicyResponseModel( ServiceAccountProjectAccessPolicy accessPolicy => new ServiceAccountProjectAccessPolicyResponseModel(
accessPolicy), accessPolicy),
_ => throw new ArgumentException("Unsupported access policy type provided.") _ => throw new ArgumentException("Unsupported access policy type provided."),
}; };
} }
[HttpDelete("{id}")] [HttpDelete("{id}")]
public async Task DeleteAccessPolicyAsync([FromRoute] Guid id) public async Task DeleteAccessPolicyAsync([FromRoute] Guid id)
{ {
await _deleteAccessPolicyCommand.DeleteAsync(id); var userId = _userService.GetProperUserId(User).Value;
await _deleteAccessPolicyCommand.DeleteAsync(id, userId);
}
[HttpGet("/organizations/{id}/access-policies/people/potential-grantees")]
public async Task<ListResponseModel<PotentialGranteeResponseModel>> GetPeoplePotentialGranteesAsync([FromRoute] Guid id)
{
if (!_currentContext.AccessSecretsManager(id))
{
throw new NotFoundException();
}
var groups = await _groupRepository.GetManyByOrganizationIdAsync(id);
var groupResponses = groups.Select(g => new PotentialGranteeResponseModel(g));
var organizationUsers =
await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id);
var userResponses = organizationUsers
.Where(user => user.AccessSecretsManager)
.Select(userDetails => new PotentialGranteeResponseModel(userDetails));
return new ListResponseModel<PotentialGranteeResponseModel>(userResponses.Concat(groupResponses));
}
[HttpGet("/organizations/{id}/access-policies/service-accounts/potential-grantees")]
public async Task<ListResponseModel<PotentialGranteeResponseModel>> GetServiceAccountsPotentialGranteesAsync([FromRoute] Guid id)
{
if (!_currentContext.AccessSecretsManager(id))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User).Value;
var orgAdmin = await _currentContext.OrganizationAdmin(id);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var serviceAccounts =
await _serviceAccountRepository.GetManyByOrganizationIdWriteAccessAsync(id,
userId,
accessClient);
var serviceAccountResponses =
serviceAccounts.Select(serviceAccount => new PotentialGranteeResponseModel(serviceAccount));
return new ListResponseModel<PotentialGranteeResponseModel>(serviceAccountResponses);
}
private async Task CheckUserHasWriteAccessToProjectAsync(Project project)
{
if (project == null || !_currentContext.AccessSecretsManager(project.OrganizationId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User).Value;
var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var hasAccess = accessClient switch
{
AccessClientType.NoAccessCheck => true,
AccessClientType.User => await _projectRepository.UserHasWriteAccessToProject(project.Id, userId),
_ => false,
};
if (!hasAccess)
{
throw new NotFoundException();
}
} }
} }

View File

@ -0,0 +1,62 @@
using Bit.Core.Entities;
using Bit.Core.Models.Api;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.SecretsManager.Entities;
namespace Bit.Api.SecretsManager.Models.Response;
public class PotentialGranteeResponseModel : ResponseModel
{
private const string _objectName = "potentialGrantee";
public PotentialGranteeResponseModel(Group group)
: base(_objectName)
{
if (group == null)
{
throw new ArgumentNullException(nameof(group));
}
Id = group.Id.ToString();
Name = group.Name;
Type = "group";
}
public PotentialGranteeResponseModel(OrganizationUserUserDetails user)
: base(_objectName)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
Id = user.Id.ToString();
Name = user.Name;
Email = user.Email;
Type = "user";
}
public PotentialGranteeResponseModel(ServiceAccount serviceAccount)
: base(_objectName)
{
if (serviceAccount == null)
{
throw new ArgumentNullException(nameof(serviceAccount));
}
Id = serviceAccount.Id.ToString();
Name = serviceAccount.Name;
Type = "serviceAccount";
}
public PotentialGranteeResponseModel() : base(_objectName)
{
}
public string Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public string? Email { get; set; }
}

View File

@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
public interface ICreateAccessPoliciesCommand public interface ICreateAccessPoliciesCommand
{ {
Task<List<BaseAccessPolicy>> CreateAsync(List<BaseAccessPolicy> accessPolicies); Task<IEnumerable<BaseAccessPolicy>> CreateForProjectAsync(Guid projectId, List<BaseAccessPolicy> accessPolicies, Guid userId);
} }

View File

@ -2,5 +2,5 @@
public interface IDeleteAccessPolicyCommand public interface IDeleteAccessPolicyCommand
{ {
Task DeleteAsync(Guid id); Task DeleteAsync(Guid id, Guid userId);
} }

View File

@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
public interface IUpdateAccessPolicyCommand public interface IUpdateAccessPolicyCommand
{ {
public Task<BaseAccessPolicy> UpdateAsync(Guid id, bool read, bool write); public Task<BaseAccessPolicy> UpdateAsync(Guid id, bool read, bool write, Guid userId);
} }

View File

@ -24,34 +24,39 @@ public abstract class BaseAccessPolicy
public class UserProjectAccessPolicy : BaseAccessPolicy public class UserProjectAccessPolicy : BaseAccessPolicy
{ {
public Guid? OrganizationUserId { get; set; } public Guid? OrganizationUserId { get; set; }
public Guid? GrantedProjectId { get; set; }
public User? User { get; set; } public User? User { get; set; }
public Guid? GrantedProjectId { get; set; }
public Project? GrantedProject { get; set; }
} }
public class UserServiceAccountAccessPolicy : BaseAccessPolicy public class UserServiceAccountAccessPolicy : BaseAccessPolicy
{ {
public Guid? OrganizationUserId { get; set; } public Guid? OrganizationUserId { get; set; }
public Guid? GrantedServiceAccountId { get; set; }
public User? User { get; set; } public User? User { get; set; }
public Guid? GrantedServiceAccountId { get; set; }
public ServiceAccount? GrantedServiceAccount { get; set; }
} }
public class GroupProjectAccessPolicy : BaseAccessPolicy public class GroupProjectAccessPolicy : BaseAccessPolicy
{ {
public Guid? GroupId { get; set; } public Guid? GroupId { get; set; }
public Guid? GrantedProjectId { get; set; }
public Group? Group { get; set; } public Group? Group { get; set; }
public Guid? GrantedProjectId { get; set; }
public Project? GrantedProject { get; set; }
} }
public class GroupServiceAccountAccessPolicy : BaseAccessPolicy public class GroupServiceAccountAccessPolicy : BaseAccessPolicy
{ {
public Guid? GroupId { get; set; } public Guid? GroupId { get; set; }
public Guid? GrantedServiceAccountId { get; set; }
public Group? Group { get; set; } public Group? Group { get; set; }
public Guid? GrantedServiceAccountId { get; set; }
public ServiceAccount? GrantedServiceAccount { get; set; }
} }
public class ServiceAccountProjectAccessPolicy : BaseAccessPolicy public class ServiceAccountProjectAccessPolicy : BaseAccessPolicy
{ {
public Guid? ServiceAccountId { get; set; } public Guid? ServiceAccountId { get; set; }
public Guid? GrantedProjectId { get; set; }
public ServiceAccount? ServiceAccount { get; set; } public ServiceAccount? ServiceAccount { get; set; }
public Guid? GrantedProjectId { get; set; }
public Project? GrantedProject { get; set; }
} }

View File

@ -11,4 +11,5 @@ public interface IServiceAccountRepository
Task ReplaceAsync(ServiceAccount serviceAccount); Task ReplaceAsync(ServiceAccount serviceAccount);
Task<bool> UserHasReadAccessToServiceAccount(Guid id, Guid userId); Task<bool> UserHasReadAccessToServiceAccount(Guid id, Guid userId);
Task<bool> UserHasWriteAccessToServiceAccount(Guid id, Guid userId); Task<bool> UserHasWriteAccessToServiceAccount(Guid id, Guid userId);
Task<IEnumerable<ServiceAccount>> GetManyByOrganizationIdWriteAccessAsync(Guid organizationId, Guid userId, AccessClientType accessType);
} }

View File

@ -1,9 +1,11 @@
using System.Net.Http.Headers; using System.Net;
using System.Net.Http.Headers;
using Bit.Api.IntegrationTest.Factories; using Bit.Api.IntegrationTest.Factories;
using Bit.Api.IntegrationTest.Helpers; using Bit.Api.IntegrationTest.SecretsManager.Enums;
using Bit.Api.Models.Response;
using Bit.Api.SecretsManager.Models.Request; using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.SecretsManager.Models.Response; using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Entities; using Bit.Core.Enums;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
using Bit.Test.Common.Helpers; using Bit.Test.Common.Helpers;
@ -13,16 +15,17 @@ namespace Bit.Api.IntegrationTest.SecretsManager.Controllers;
public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>, IAsyncLifetime public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
{ {
private const string _mockEncryptedString =
"2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=";
private readonly IAccessPolicyRepository _accessPolicyRepository; private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly HttpClient _client; private readonly HttpClient _client;
private readonly ApiApplicationFactory _factory; private readonly ApiApplicationFactory _factory;
private const string _mockEncryptedString = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=";
private readonly IProjectRepository _projectRepository; private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IServiceAccountRepository _serviceAccountRepository;
private Organization _organization = null!; private string _email = null!;
private SecretsManagerOrganizationHelper _organizationHelper = null!;
public AccessPoliciesControllerTest(ApiApplicationFactory factory) public AccessPoliciesControllerTest(ApiApplicationFactory factory)
{ {
@ -35,46 +38,105 @@ public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>
public async Task InitializeAsync() public async Task InitializeAsync()
{ {
var ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com"; _email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
var tokens = await _factory.LoginWithNewAccount(ownerEmail); await _factory.LoginWithNewAccount(_email);
var (organization, _) = _organizationHelper = new SecretsManagerOrganizationHelper(_factory, _email);
await OrganizationTestHelpers.SignUpAsync(_factory, ownerEmail: ownerEmail, billingEmail: ownerEmail);
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token);
_organization = organization;
} }
public Task DisposeAsync() => Task.CompletedTask; public Task DisposeAsync()
{
_client.Dispose();
return Task.CompletedTask;
}
[Fact] private async Task LoginAsync(string email)
public async Task CreateProjectAccessPolicies()
{ {
var initialProject = await _projectRepository.CreateAsync(new Project var tokens = await _factory.LoginAsync(email);
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token);
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task CreateProjectAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
{ {
OrganizationId = _organization.Id, var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
Name = _mockEncryptedString await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
}); });
var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{ {
OrganizationId = _organization.Id, OrganizationId = org.Id,
Name = _mockEncryptedString Name = _mockEncryptedString,
}); });
var request = new AccessPoliciesCreateRequest var request = new AccessPoliciesCreateRequest
{ {
ServiceAccountAccessPolicyRequests = new List<AccessPolicyRequest> ServiceAccountAccessPolicyRequests = new List<AccessPolicyRequest>
{ {
new() { GranteeId = initialServiceAccount.Id, Read = true, Write = true } new() { GranteeId = serviceAccount.Id, Read = true, Write = true },
} },
}; };
var response = await _client.PostAsJsonAsync($"/projects/{initialProject.Id}/access-policies", request); var response = await _client.PostAsJsonAsync($"/projects/{project.Id}/access-policies", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateProjectAccessPolicies(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
if (permissionType == PermissionType.RunAsUserWithPermission)
{
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var accessPolicies = new List<BaseAccessPolicy>
{
new UserProjectAccessPolicy
{
GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true,
},
};
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
}
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var request = new AccessPoliciesCreateRequest
{
ServiceAccountAccessPolicyRequests = new List<AccessPolicyRequest>
{
new() { GranteeId = serviceAccount.Id, Read = true, Write = true },
},
};
var response = await _client.PostAsJsonAsync($"/projects/{project.Id}/access-policies", request);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ProjectAccessPoliciesResponseModel>(); var result = await response.Content.ReadFromJsonAsync<ProjectAccessPoliciesResponseModel>();
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal(initialServiceAccount.Id, result!.ServiceAccountAccessPolicies.First().ServiceAccountId); Assert.Equal(serviceAccount.Id, result!.ServiceAccountAccessPolicies.First().ServiceAccountId);
Assert.True(result.ServiceAccountAccessPolicies.First().Read); Assert.True(result.ServiceAccountAccessPolicies.First().Read);
Assert.True(result.ServiceAccountAccessPolicies.First().Write); Assert.True(result.ServiceAccountAccessPolicies.First().Write);
AssertHelper.AssertRecent(result.ServiceAccountAccessPolicies.First().RevisionDate); AssertHelper.AssertRecent(result.ServiceAccountAccessPolicies.First().RevisionDate);
@ -91,15 +153,103 @@ public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>
} }
[Fact] [Fact]
public async Task UpdateAccessPolicy() public async Task CreateProjectAccessPolicies_NoPermission()
{ {
var initData = await SetupAccessPolicyRequest(); // 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 project = await _projectRepository.CreateAsync(new Project
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var request = new AccessPoliciesCreateRequest
{
ServiceAccountAccessPolicyRequests = new List<AccessPolicyRequest>
{
new() { GranteeId = serviceAccount.Id, Read = true, Write = true },
},
};
var response = await _client.PostAsJsonAsync($"/projects/{project.Id}/access-policies", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task UpdateAccessPolicy_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
const bool expectedRead = true; const bool expectedRead = true;
const bool expectedWrite = false; const bool expectedWrite = false;
var request = new AccessPolicyUpdateRequest { Read = expectedRead, Write = expectedWrite }; var request = new AccessPolicyUpdateRequest { Read = expectedRead, Write = expectedWrite };
var response = await _client.PutAsJsonAsync($"/access-policies/{initData.InitialAccessPolicyId}", request); var response = await _client.PutAsJsonAsync($"/access-policies/{initData.AccessPolicyId}", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task UpdateAccessPolicy_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);
const bool expectedRead = true;
const bool expectedWrite = false;
var request = new AccessPolicyUpdateRequest { Read = expectedRead, Write = expectedWrite };
var response = await _client.PutAsJsonAsync($"/access-policies/{initData.AccessPolicyId}", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task UpdateAccessPolicy(PermissionType permissionType)
{
var (org, _) = 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<BaseAccessPolicy>
{
new UserProjectAccessPolicy
{
GrantedProjectId = initData.ProjectId, OrganizationUserId = orgUser.Id, Read = true, Write = true,
},
};
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
}
const bool expectedRead = true;
const bool expectedWrite = false;
var request = new AccessPolicyUpdateRequest { Read = expectedRead, Write = expectedWrite };
var response = await _client.PutAsJsonAsync($"/access-policies/{initData.AccessPolicyId}", request);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ServiceAccountProjectAccessPolicyResponseModel>(); var result = await response.Content.ReadFromJsonAsync<ServiceAccountProjectAccessPolicyResponseModel>();
@ -116,29 +266,78 @@ public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>
AssertHelper.AssertRecent(updatedAccessPolicy.RevisionDate); AssertHelper.AssertRecent(updatedAccessPolicy.RevisionDate);
} }
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task DeleteAccessPolicy_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.DeleteAsync($"/access-policies/{initData.AccessPolicyId}");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact] [Fact]
public async Task DeleteAccessPolicy() public async Task DeleteAccessPolicy_NoPermission()
{ {
var initData = await SetupAccessPolicyRequest(); // 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 response = await _client.DeleteAsync($"/access-policies/{initData.InitialAccessPolicyId}"); var initData = await SetupAccessPolicyRequest(orgUser.OrganizationId);
var response = await _client.DeleteAsync($"/access-policies/{initData.AccessPolicyId}");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task DeleteAccessPolicy(PermissionType permissionType)
{
var (org, _) = 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<BaseAccessPolicy>
{
new UserProjectAccessPolicy
{
GrantedProjectId = initData.ProjectId, OrganizationUserId = orgUser.Id, Read = true, Write = true,
},
};
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
}
var response = await _client.DeleteAsync($"/access-policies/{initData.AccessPolicyId}");
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var test = await _accessPolicyRepository.GetByIdAsync(initData.InitialAccessPolicyId); var test = await _accessPolicyRepository.GetByIdAsync(initData.AccessPolicyId);
Assert.Null(test); Assert.Null(test);
} }
[Fact] [Fact]
public async Task GetProjectAccessPolicies_ReturnsEmpty() public async Task GetProjectAccessPolicies_ReturnsEmpty()
{ {
var initialProject = await _projectRepository.CreateAsync(new Project var (org, _) = await _organizationHelper.Initialize(true, true);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project
{ {
OrganizationId = _organization.Id, OrganizationId = org.Id,
Name = _mockEncryptedString, Name = _mockEncryptedString,
}); });
var response = await _client.GetAsync($"/projects/{initialProject.Id}/access-policies"); var response = await _client.GetAsync($"/projects/{project.Id}/access-policies");
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ProjectAccessPoliciesResponseModel>(); var result = await response.Content.ReadFromJsonAsync<ProjectAccessPoliciesResponseModel>();
@ -149,12 +348,59 @@ public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>
Assert.Empty(result!.ServiceAccountAccessPolicies); Assert.Empty(result!.ServiceAccountAccessPolicies);
} }
[Fact] [Theory]
public async Task GetProjectAccessPolicies() [InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetProjectAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
{ {
var initData = await SetupAccessPolicyRequest(); var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
var response = await _client.GetAsync($"/projects/{initData.InitialProjectId}/access-policies"); var response = await _client.GetAsync($"/projects/{initData.ProjectId}/access-policies");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetProjectAccessPolicies_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($"/projects/{initData.ProjectId}/access-policies");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetProjectAccessPolicies(PermissionType permissionType)
{
var (org, _) = 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<BaseAccessPolicy>
{
new UserProjectAccessPolicy
{
GrantedProjectId = initData.ProjectId, OrganizationUserId = orgUser.Id, Read = true, Write = true,
},
};
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
}
var response = await _client.GetAsync($"/projects/{initData.ProjectId}/access-policies");
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ProjectAccessPoliciesResponseModel>(); var result = await response.Content.ReadFromJsonAsync<ProjectAccessPoliciesResponseModel>();
@ -163,44 +409,166 @@ public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>
Assert.Single(result!.ServiceAccountAccessPolicies); Assert.Single(result!.ServiceAccountAccessPolicies);
} }
private async Task<RequestSetupData> SetupAccessPolicyRequest() [Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetPeoplePotentialGrantees_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
{ {
var initialProject = await _projectRepository.CreateAsync(new Project var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
await LoginAsync(_email);
var response =
await _client.GetAsync(
$"/organizations/{org.Id}/access-policies/people/potential-grantees");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetPeoplePotentialGrantees_Success(PermissionType permissionType)
{ {
OrganizationId = _organization.Id, var (org, _) = await _organizationHelper.Initialize(true, true);
await LoginAsync(_email);
if (permissionType == PermissionType.RunAsUserWithPermission)
{
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
}
var response =
await _client.GetAsync(
$"/organizations/{org.Id}/access-policies/people/potential-grantees");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ListResponseModel<PotentialGranteeResponseModel>>();
Assert.NotNull(result?.Data);
Assert.NotEmpty(result!.Data);
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetServiceAccountPotentialGrantees_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
await LoginAsync(_email);
var response =
await _client.GetAsync(
$"/organizations/{org.Id}/access-policies/service-accounts/potential-grantees");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetServiceAccountPotentialGrantees_OnlyReturnsServiceAccountsWithWriteAccess()
{
// 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, Name = _mockEncryptedString,
}); });
var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
var response =
await _client.GetAsync(
$"/organizations/{org.Id}/access-policies/service-accounts/potential-grantees");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ListResponseModel<PotentialGranteeResponseModel>>();
Assert.NotNull(result?.Data);
Assert.Empty(result!.Data);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetServiceAccountsPotentialGrantees_Success(PermissionType permissionType)
{ {
OrganizationId = _organization.Id, var (org, _) = await _organizationHelper.Initialize(true, true);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString, Name = _mockEncryptedString,
}); });
var initialAccessPolicy = await _accessPolicyRepository.CreateManyAsync( if (permissionType == PermissionType.RunAsUserWithPermission)
{
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
await _accessPolicyRepository.CreateManyAsync(
new List<BaseAccessPolicy>
{
new UserServiceAccountAccessPolicy
{
GrantedServiceAccountId = serviceAccount.Id,
OrganizationUserId = orgUser.Id,
Read = true,
Write = true,
},
});
}
var response =
await _client.GetAsync(
$"/organizations/{org.Id}/access-policies/service-accounts/potential-grantees");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ListResponseModel<PotentialGranteeResponseModel>>();
Assert.NotNull(result?.Data);
Assert.NotEmpty(result!.Data);
Assert.Equal(serviceAccount.Id.ToString(), result!.Data.First(x => x.Id == serviceAccount.Id.ToString()).Id);
}
private async Task<RequestSetupData> SetupAccessPolicyRequest(Guid organizationId)
{
var project = await _projectRepository.CreateAsync(new Project
{
OrganizationId = organizationId,
Name = _mockEncryptedString,
});
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = organizationId,
Name = _mockEncryptedString,
});
var accessPolicy = await _accessPolicyRepository.CreateManyAsync(
new List<BaseAccessPolicy> new List<BaseAccessPolicy>
{ {
new ServiceAccountProjectAccessPolicy new ServiceAccountProjectAccessPolicy
{ {
Read = true, Read = true, Write = true, ServiceAccountId = serviceAccount.Id, GrantedProjectId = project.Id,
Write = true, },
ServiceAccountId = initialServiceAccount.Id,
GrantedProjectId = initialProject.Id,
}
}); });
return new RequestSetupData return new RequestSetupData
{ {
InitialProjectId = initialProject.Id, ProjectId = project.Id,
InitialServiceAccountId = initialServiceAccount.Id, ServiceAccountId = serviceAccount.Id,
InitialAccessPolicyId = initialAccessPolicy.First().Id, AccessPolicyId = accessPolicy.First().Id,
}; };
} }
private class RequestSetupData private class RequestSetupData
{ {
public Guid InitialProjectId { get; set; } public Guid ProjectId { get; set; }
public Guid InitialAccessPolicyId { get; set; } public Guid AccessPolicyId { get; set; }
public Guid InitialServiceAccountId { get; set; } public Guid ServiceAccountId { get; set; }
} }
} }

View File

@ -0,0 +1,7 @@
namespace Bit.Api.IntegrationTest.SecretsManager.Enums;
public enum PermissionType
{
RunAsAdmin,
RunAsUserWithPermission,
}

View File

@ -1,8 +1,15 @@
using Bit.Api.SecretsManager.Controllers; using Bit.Api.SecretsManager.Controllers;
using Bit.Api.SecretsManager.Models.Request; using Bit.Api.SecretsManager.Models.Request;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers; using Bit.Test.Common.Helpers;
@ -14,14 +21,64 @@ namespace Bit.Api.Test.SecretsManager.Controllers;
[ControllerCustomize(typeof(AccessPoliciesController))] [ControllerCustomize(typeof(AccessPoliciesController))]
[SutProviderCustomize] [SutProviderCustomize]
[ProjectCustomize]
[JsonDocumentCustomize] [JsonDocumentCustomize]
public class AccessPoliciesControllerTests public class AccessPoliciesControllerTests
{ {
[Theory] public enum PermissionType
[BitAutoData]
public async void GetAccessPoliciesByProject_ReturnsEmptyList(SutProvider<AccessPoliciesController> sutProvider,
Guid id)
{ {
RunAsAdmin,
RunAsUserWithPermission,
}
private static void SetupAdmin(SutProvider<AccessPoliciesController> sutProvider, Project data)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(default).ReturnsForAnyArgs(data);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(true);
}
private static void SetupUserWithPermission(SutProvider<AccessPoliciesController> sutProvider, Project data)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(default).ReturnsForAnyArgs(data);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(default).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(default, default)
.ReturnsForAnyArgs(true);
}
private static void SetupUserWithoutPermission(SutProvider<AccessPoliciesController> sutProvider, Project data)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(default).ReturnsForAnyArgs(data);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(default).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(default, default)
.ReturnsForAnyArgs(false);
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async void GetAccessPoliciesByProject_ReturnsEmptyList(
PermissionType permissionType,
SutProvider<AccessPoliciesController> sutProvider,
Guid id, Project data)
{
switch (permissionType)
{
case PermissionType.RunAsAdmin:
SetupAdmin(sutProvider, data);
break;
case PermissionType.RunAsUserWithPermission:
SetupUserWithPermission(sutProvider, data);
break;
}
var result = await sutProvider.Sut.GetProjectAccessPoliciesAsync(id); var result = await sutProvider.Sut.GetProjectAccessPoliciesAsync(id);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1) await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
@ -34,9 +91,39 @@ public class AccessPoliciesControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async void GetAccessPoliciesByProject_Success(SutProvider<AccessPoliciesController> sutProvider, Guid id, public async void GetAccessPoliciesByProject_UserWithoutPermission_Throws(
SutProvider<AccessPoliciesController> sutProvider,
Guid id,
Project data)
{
SetupUserWithoutPermission(sutProvider, data);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetProjectAccessPoliciesAsync(id));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
.GetManyByGrantedProjectIdAsync(Arg.Any<Guid>());
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async void GetAccessPoliciesByProject_Admin_Success(
PermissionType permissionType,
SutProvider<AccessPoliciesController> sutProvider,
Guid id,
Project data,
UserProjectAccessPolicy resultAccessPolicy) UserProjectAccessPolicy resultAccessPolicy)
{ {
switch (permissionType)
{
case PermissionType.RunAsAdmin:
SetupAdmin(sutProvider, data);
break;
case PermissionType.RunAsUserWithPermission:
SetupUserWithPermission(sutProvider, data);
break;
}
sutProvider.GetDependency<IAccessPolicyRepository>().GetManyByGrantedProjectIdAsync(default) sutProvider.GetDependency<IAccessPolicyRepository>().GetManyByGrantedProjectIdAsync(default)
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { resultAccessPolicy }); .ReturnsForAnyArgs(new List<BaseAccessPolicy> { resultAccessPolicy });
@ -52,36 +139,244 @@ public class AccessPoliciesControllerTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async void CreateAccessPolicies_Success(SutProvider<AccessPoliciesController> sutProvider, Guid id, public async void GetAccessPoliciesByProject_ProjectsExist_UserWithoutPermission_Throws(
UserProjectAccessPolicy data, AccessPoliciesCreateRequest request) SutProvider<AccessPoliciesController> sutProvider,
Guid id,
Project data,
UserProjectAccessPolicy resultAccessPolicy)
{ {
sutProvider.GetDependency<ICreateAccessPoliciesCommand>().CreateAsync(default) SetupUserWithoutPermission(sutProvider, data);
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data }); sutProvider.GetDependency<IAccessPolicyRepository>().GetManyByGrantedProjectIdAsync(default)
var result = await sutProvider.Sut.CreateProjectAccessPoliciesAsync(id, request); .ReturnsForAnyArgs(new List<BaseAccessPolicy> { resultAccessPolicy });
await sutProvider.GetDependency<ICreateAccessPoliciesCommand>().Received(1)
.CreateAsync(Arg.Any<List<BaseAccessPolicy>>());
}
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetProjectAccessPoliciesAsync(id));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
.GetManyByGrantedProjectIdAsync(Arg.Any<Guid>());
}
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async void UpdateAccessPolicies_Success(SutProvider<AccessPoliciesController> sutProvider, Guid id, public async void CreateAccessPolicies_Success(
UserProjectAccessPolicy data, AccessPolicyUpdateRequest request) SutProvider<AccessPoliciesController> sutProvider,
Guid id,
UserProjectAccessPolicy data,
AccessPoliciesCreateRequest request)
{ {
sutProvider.GetDependency<IUpdateAccessPolicyCommand>().UpdateAsync(default, default, default) sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
sutProvider.GetDependency<ICreateAccessPoliciesCommand>().CreateForProjectAsync(default, default, default)
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data });
await sutProvider.Sut.CreateProjectAccessPoliciesAsync(id, request);
await sutProvider.GetDependency<ICreateAccessPoliciesCommand>().Received(1)
.CreateForProjectAsync(Arg.Any<Guid>(), Arg.Any<List<BaseAccessPolicy>>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData]
public async void UpdateAccessPolicies_Success(
SutProvider<AccessPoliciesController> sutProvider,
Guid id,
UserProjectAccessPolicy data,
AccessPolicyUpdateRequest request)
{
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
sutProvider.GetDependency<IUpdateAccessPolicyCommand>().UpdateAsync(default, default, default, default)
.ReturnsForAnyArgs(data); .ReturnsForAnyArgs(data);
var result = await sutProvider.Sut.UpdateAccessPolicyAsync(id, request);
await sutProvider.Sut.UpdateAccessPolicyAsync(id, request);
await sutProvider.GetDependency<IUpdateAccessPolicyCommand>().Received(1) await sutProvider.GetDependency<IUpdateAccessPolicyCommand>().Received(1)
.UpdateAsync(Arg.Any<Guid>(), Arg.Is(request.Read), Arg.Is(request.Write)); .UpdateAsync(Arg.Any<Guid>(), Arg.Is(request.Read), Arg.Is(request.Write), Arg.Any<Guid>());
} }
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async void DeleteAccessPolicies_Success(SutProvider<AccessPoliciesController> sutProvider, Guid id) public async void DeleteAccessPolicies_Success(SutProvider<AccessPoliciesController> sutProvider, Guid id)
{ {
sutProvider.GetDependency<IDeleteAccessPolicyCommand>().DeleteAsync(default).ReturnsNull(); sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
sutProvider.GetDependency<IDeleteAccessPolicyCommand>().DeleteAsync(default, default).ReturnsNull();
await sutProvider.Sut.DeleteAccessPolicyAsync(id); await sutProvider.Sut.DeleteAccessPolicyAsync(id);
await sutProvider.GetDependency<IDeleteAccessPolicyCommand>().Received(1) await sutProvider.GetDependency<IDeleteAccessPolicyCommand>().Received(1)
.DeleteAsync(Arg.Any<Guid>()); .DeleteAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async void GetPeoplePotentialGranteesAsync_ReturnsEmptyList(
PermissionType permissionType,
SutProvider<AccessPoliciesController> sutProvider,
Guid id)
{
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(id).Returns(true);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(id).Returns(false);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
break;
}
var result = await sutProvider.Sut.GetPeoplePotentialGranteesAsync(id);
await sutProvider.GetDependency<IGroupRepository>().Received(1)
.GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)));
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
.GetManyDetailsByOrganizationAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)));
Assert.Empty(result.Data);
}
[Theory]
[BitAutoData]
public async void GetPeoplePotentialGranteesAsync_UserWithoutPermission_Throws(
SutProvider<AccessPoliciesController> sutProvider,
Guid id)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(id).Returns(false);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(false);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetPeoplePotentialGranteesAsync(id));
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs()
.GetManyByOrganizationIdAsync(Arg.Any<Guid>());
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
.GetManyDetailsByOrganizationAsync(Arg.Any<Guid>());
await sutProvider.GetDependency<IServiceAccountRepository>().DidNotReceiveWithAnyArgs()
.GetManyByOrganizationIdWriteAccessAsync(Arg.Any<Guid>(), Arg.Any<Guid>(), Arg.Any<AccessClientType>());
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async void GetPeoplePotentialGranteesAsync_Success(
PermissionType permissionType,
SutProvider<AccessPoliciesController> sutProvider,
Guid id,
Group mockGroup)
{
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(id).Returns(true);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(id).Returns(false);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
break;
}
sutProvider.GetDependency<IGroupRepository>().GetManyByOrganizationIdAsync(default)
.ReturnsForAnyArgs(new List<Group> { mockGroup });
var result = await sutProvider.Sut.GetPeoplePotentialGranteesAsync(id);
await sutProvider.GetDependency<IGroupRepository>().Received(1)
.GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)));
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
.GetManyDetailsByOrganizationAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)));
Assert.NotEmpty(result.Data);
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async void GetServiceAccountsPotentialGranteesAsync_ReturnsEmptyList(
PermissionType permissionType,
SutProvider<AccessPoliciesController> sutProvider,
Guid id)
{
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(id).Returns(true);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(id).Returns(false);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
break;
}
var result = await sutProvider.Sut.GetServiceAccountsPotentialGranteesAsync(id);
await sutProvider.GetDependency<IServiceAccountRepository>().Received(1)
.GetManyByOrganizationIdWriteAccessAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)),
Arg.Is(AssertHelper.AssertPropertyEqual(id)),
Arg.Any<AccessClientType>());
Assert.Empty(result.Data);
}
[Theory]
[BitAutoData]
public async void GetServiceAccountsPotentialGranteesAsync_UserWithoutPermission_Throws(
SutProvider<AccessPoliciesController> sutProvider,
Guid id)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(id).Returns(false);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(false);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetServiceAccountsPotentialGranteesAsync(id));
await sutProvider.GetDependency<IServiceAccountRepository>().DidNotReceiveWithAnyArgs()
.GetManyByOrganizationIdWriteAccessAsync(Arg.Any<Guid>(), Arg.Any<Guid>(), Arg.Any<AccessClientType>());
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async void GetServiceAccountsPotentialGranteesAsync_Success(
PermissionType permissionType,
SutProvider<AccessPoliciesController> sutProvider,
Guid id,
ServiceAccount mockServiceAccount)
{
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(id).Returns(true);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(id).Returns(false);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
break;
}
sutProvider.GetDependency<IServiceAccountRepository>().GetManyByOrganizationIdWriteAccessAsync(default, default, default)
.ReturnsForAnyArgs(new List<ServiceAccount> { mockServiceAccount });
var result = await sutProvider.Sut.GetServiceAccountsPotentialGranteesAsync(id);
await sutProvider.GetDependency<IServiceAccountRepository>().Received(1)
.GetManyByOrganizationIdWriteAccessAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)),
Arg.Is(AssertHelper.AssertPropertyEqual(id)),
Arg.Any<AccessClientType>());
Assert.NotEmpty(result.Data);
} }
} }