Browse Source
The purpose of this PR is to create server endpoints for creating, reading, updating, and deleting access policies for projects.pull/2605/head
24 changed files with 1133 additions and 63 deletions
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Core.SecretManagerFeatures.AccessPolicies.Interfaces; |
||||
|
||||
namespace Bit.Commercial.Core.SecretManagerFeatures.AccessPolicies; |
||||
|
||||
public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand |
||||
{ |
||||
private readonly IAccessPolicyRepository _accessPolicyRepository; |
||||
|
||||
public CreateAccessPoliciesCommand(IAccessPolicyRepository accessPolicyRepository) |
||||
{ |
||||
_accessPolicyRepository = accessPolicyRepository; |
||||
} |
||||
|
||||
public async Task<List<BaseAccessPolicy>> CreateAsync(List<BaseAccessPolicy> accessPolicies) |
||||
{ |
||||
var distinctAccessPolicies = accessPolicies.DistinctBy(baseAccessPolicy => |
||||
{ |
||||
return baseAccessPolicy switch |
||||
{ |
||||
UserProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId, ap.GrantedProjectId), |
||||
GroupProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedProjectId), |
||||
ServiceAccountProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.ServiceAccountId, ap.GrantedProjectId), |
||||
_ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)) |
||||
}; |
||||
}).ToList(); |
||||
|
||||
if (accessPolicies.Count != distinctAccessPolicies.Count) |
||||
{ |
||||
throw new BadRequestException("Resources must be unique"); |
||||
} |
||||
|
||||
foreach (var accessPolicy in accessPolicies) |
||||
{ |
||||
if (await _accessPolicyRepository.AccessPolicyExists(accessPolicy)) |
||||
{ |
||||
throw new BadRequestException("Resource already exists"); |
||||
} |
||||
} |
||||
|
||||
return await _accessPolicyRepository.CreateManyAsync(accessPolicies); |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Core.SecretManagerFeatures.AccessPolicies.Interfaces; |
||||
|
||||
namespace Bit.Commercial.Core.SecretManagerFeatures.AccessPolicies; |
||||
|
||||
public class DeleteAccessPolicyCommand : IDeleteAccessPolicyCommand |
||||
{ |
||||
private readonly IAccessPolicyRepository _accessPolicyRepository; |
||||
|
||||
public DeleteAccessPolicyCommand(IAccessPolicyRepository accessPolicyRepository) |
||||
{ |
||||
_accessPolicyRepository = accessPolicyRepository; |
||||
} |
||||
|
||||
|
||||
public async Task DeleteAsync(Guid id) |
||||
{ |
||||
var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id); |
||||
if (accessPolicy == null) |
||||
{ |
||||
throw new NotFoundException(); |
||||
} |
||||
|
||||
await _accessPolicyRepository.DeleteAsync(id); |
||||
} |
||||
} |
||||
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Core.SecretManagerFeatures.AccessPolicies.Interfaces; |
||||
|
||||
namespace Bit.Commercial.Core.SecretManagerFeatures.AccessPolicies; |
||||
|
||||
public class UpdateAccessPolicyCommand : IUpdateAccessPolicyCommand |
||||
{ |
||||
private readonly IAccessPolicyRepository _accessPolicyRepository; |
||||
|
||||
public UpdateAccessPolicyCommand(IAccessPolicyRepository accessPolicyRepository) |
||||
{ |
||||
_accessPolicyRepository = accessPolicyRepository; |
||||
} |
||||
|
||||
public async Task<BaseAccessPolicy> UpdateAsync(Guid id, bool read, bool write) |
||||
{ |
||||
var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id); |
||||
if (accessPolicy == null) |
||||
{ |
||||
throw new NotFoundException(); |
||||
} |
||||
|
||||
accessPolicy.Read = read; |
||||
accessPolicy.Write = write; |
||||
accessPolicy.RevisionDate = DateTime.UtcNow; |
||||
|
||||
await _accessPolicyRepository.ReplaceAsync(accessPolicy); |
||||
return accessPolicy; |
||||
} |
||||
} |
||||
@ -0,0 +1,171 @@
@@ -0,0 +1,171 @@
|
||||
using AutoMapper; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Infrastructure.EntityFramework.Models; |
||||
using Bit.Infrastructure.EntityFramework.Repositories; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using Microsoft.Extensions.DependencyInjection; |
||||
|
||||
namespace Bit.Commercial.Infrastructure.EntityFramework.Repositories; |
||||
|
||||
public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPolicyRepository |
||||
{ |
||||
public AccessPolicyRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) : base(serviceScopeFactory, |
||||
mapper) |
||||
{ |
||||
} |
||||
|
||||
public async Task<List<Core.Entities.BaseAccessPolicy>> CreateManyAsync(List<Core.Entities.BaseAccessPolicy> baseAccessPolicies) |
||||
{ |
||||
using (var scope = ServiceScopeFactory.CreateScope()) |
||||
{ |
||||
var dbContext = GetDatabaseContext(scope); |
||||
foreach (var baseAccessPolicy in baseAccessPolicies) |
||||
{ |
||||
baseAccessPolicy.SetNewId(); |
||||
switch (baseAccessPolicy) |
||||
{ |
||||
case Core.Entities.UserProjectAccessPolicy accessPolicy: |
||||
{ |
||||
var entity = |
||||
Mapper.Map<UserProjectAccessPolicy>(accessPolicy); |
||||
await dbContext.AddAsync(entity); |
||||
break; |
||||
} |
||||
case Core.Entities.GroupProjectAccessPolicy accessPolicy: |
||||
{ |
||||
var entity = Mapper.Map<GroupProjectAccessPolicy>(accessPolicy); |
||||
await dbContext.AddAsync(entity); |
||||
break; |
||||
} |
||||
case Core.Entities.ServiceAccountProjectAccessPolicy accessPolicy: |
||||
{ |
||||
var entity = Mapper.Map<ServiceAccountProjectAccessPolicy>(accessPolicy); |
||||
await dbContext.AddAsync(entity); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
await dbContext.SaveChangesAsync(); |
||||
return baseAccessPolicies; |
||||
} |
||||
} |
||||
|
||||
public async Task<bool> AccessPolicyExists(Core.Entities.BaseAccessPolicy baseAccessPolicy) |
||||
{ |
||||
using (var scope = ServiceScopeFactory.CreateScope()) |
||||
{ |
||||
var dbContext = GetDatabaseContext(scope); |
||||
switch (baseAccessPolicy) |
||||
{ |
||||
case Core.Entities.UserProjectAccessPolicy accessPolicy: |
||||
{ |
||||
var policy = await dbContext.UserProjectAccessPolicy |
||||
.Where(c => c.OrganizationUserId == accessPolicy.OrganizationUserId && |
||||
c.GrantedProjectId == accessPolicy.GrantedProjectId) |
||||
.FirstOrDefaultAsync(); |
||||
return policy != null; |
||||
} |
||||
case Core.Entities.GroupProjectAccessPolicy accessPolicy: |
||||
{ |
||||
var policy = await dbContext.GroupProjectAccessPolicy |
||||
.Where(c => c.GroupId == accessPolicy.GroupId && |
||||
c.GrantedProjectId == accessPolicy.GrantedProjectId) |
||||
.FirstOrDefaultAsync(); |
||||
return policy != null; |
||||
} |
||||
case Core.Entities.ServiceAccountProjectAccessPolicy accessPolicy: |
||||
{ |
||||
var policy = await dbContext.ServiceAccountProjectAccessPolicy |
||||
.Where(c => c.ServiceAccountId == accessPolicy.ServiceAccountId && |
||||
c.GrantedProjectId == accessPolicy.GrantedProjectId) |
||||
.FirstOrDefaultAsync(); |
||||
return policy != null; |
||||
} |
||||
default: |
||||
throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public async Task<Core.Entities.BaseAccessPolicy?> GetByIdAsync(Guid id) |
||||
{ |
||||
using (var scope = ServiceScopeFactory.CreateScope()) |
||||
{ |
||||
var dbContext = GetDatabaseContext(scope); |
||||
var entity = await dbContext.AccessPolicies.Where(ap => ap.Id == id) |
||||
.Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User) |
||||
.Include(ap => ((GroupProjectAccessPolicy)ap).Group) |
||||
.Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) |
||||
.FirstOrDefaultAsync(); |
||||
|
||||
if (entity == null) |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
return MapToCore(entity); |
||||
} |
||||
} |
||||
|
||||
public async Task ReplaceAsync(Core.Entities.BaseAccessPolicy baseAccessPolicy) |
||||
{ |
||||
using (var scope = ServiceScopeFactory.CreateScope()) |
||||
{ |
||||
var dbContext = GetDatabaseContext(scope); |
||||
var entity = await dbContext.AccessPolicies.FindAsync(baseAccessPolicy.Id); |
||||
if (entity != null) |
||||
{ |
||||
dbContext.AccessPolicies.Attach(entity); |
||||
entity.Write = baseAccessPolicy.Write; |
||||
entity.Read = baseAccessPolicy.Read; |
||||
entity.RevisionDate = baseAccessPolicy.RevisionDate; |
||||
await dbContext.SaveChangesAsync(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public async Task<IEnumerable<Core.Entities.BaseAccessPolicy>?> GetManyByProjectId(Guid id) |
||||
{ |
||||
using (var scope = ServiceScopeFactory.CreateScope()) |
||||
{ |
||||
var dbContext = GetDatabaseContext(scope); |
||||
|
||||
var entities = await dbContext.AccessPolicies.Where(ap => |
||||
((UserProjectAccessPolicy)ap).GrantedProjectId == id || |
||||
((GroupProjectAccessPolicy)ap).GrantedProjectId == id || |
||||
((ServiceAccountProjectAccessPolicy)ap).GrantedProjectId == id) |
||||
.Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User) |
||||
.Include(ap => ((GroupProjectAccessPolicy)ap).Group) |
||||
.Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) |
||||
.ToListAsync(); |
||||
|
||||
return !entities.Any() ? null : entities.Select(MapToCore); |
||||
} |
||||
} |
||||
|
||||
public async Task DeleteAsync(Guid id) |
||||
{ |
||||
using (var scope = ServiceScopeFactory.CreateScope()) |
||||
{ |
||||
var dbContext = GetDatabaseContext(scope); |
||||
var entity = await dbContext.AccessPolicies.FindAsync(id); |
||||
if (entity != null) |
||||
{ |
||||
dbContext.Remove(entity); |
||||
await dbContext.SaveChangesAsync(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private Core.Entities.BaseAccessPolicy MapToCore(BaseAccessPolicy baseAccessPolicyEntity) |
||||
{ |
||||
return baseAccessPolicyEntity switch |
||||
{ |
||||
UserProjectAccessPolicy ap => Mapper.Map<Core.Entities.UserProjectAccessPolicy>(ap), |
||||
GroupProjectAccessPolicy ap => Mapper.Map<Core.Entities.GroupProjectAccessPolicy>(ap), |
||||
ServiceAccountProjectAccessPolicy ap => Mapper.Map<Core.Entities.ServiceAccountProjectAccessPolicy>(ap), |
||||
_ => throw new ArgumentException("Unsupported access policy type") |
||||
}; |
||||
} |
||||
} |
||||
@ -0,0 +1,126 @@
@@ -0,0 +1,126 @@
|
||||
using Bit.Commercial.Core.SecretManagerFeatures.AccessPolicies; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Test.Common.AutoFixture; |
||||
using Bit.Test.Common.AutoFixture.Attributes; |
||||
using Bit.Test.Common.Helpers; |
||||
using NSubstitute; |
||||
using Xunit; |
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretManagerFeatures.AccessPolicies; |
||||
|
||||
[SutProviderCustomize] |
||||
public class CreateAccessPoliciesCommandTests |
||||
{ |
||||
[Theory] |
||||
[BitAutoData] |
||||
public async Task CreateAsync_CallsCreate(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); |
||||
|
||||
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<GroupProjectAccessPolicy> groupProjectAccessPolicies, |
||||
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies, |
||||
SutProvider<CreateAccessPoliciesCommand> sutProvider) |
||||
{ |
||||
var data = new List<BaseAccessPolicy>(); |
||||
data.AddRange(userProjectAccessPolicies); |
||||
data.AddRange(groupProjectAccessPolicies); |
||||
data.AddRange(serviceAccountProjectAccessPolicies); |
||||
|
||||
sutProvider.GetDependency<IAccessPolicyRepository>().AccessPolicyExists(Arg.Any<BaseAccessPolicy>()) |
||||
.Returns(true); |
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateAsync(data)); |
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().CreateManyAsync(default); |
||||
} |
||||
|
||||
|
||||
[Theory] |
||||
[BitAutoData(true, false, false)] |
||||
[BitAutoData(false, true, false)] |
||||
[BitAutoData(true, true, false)] |
||||
[BitAutoData(false, false, true)] |
||||
[BitAutoData(true, false, true)] |
||||
[BitAutoData(false, true, true)] |
||||
[BitAutoData(true, true, true)] |
||||
public async Task CreateAsync_NotUnique_ThrowsException( |
||||
bool testUserPolicies, |
||||
bool testGroupPolicies, |
||||
bool testServiceAccountPolicies, |
||||
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); |
||||
|
||||
if (testUserPolicies) |
||||
{ |
||||
var mockUserPolicy = new UserProjectAccessPolicy |
||||
{ |
||||
OrganizationUserId = Guid.NewGuid(), |
||||
GrantedProjectId = Guid.NewGuid() |
||||
}; |
||||
data.Add(mockUserPolicy); |
||||
|
||||
// Add a duplicate policy |
||||
data.Add(mockUserPolicy); |
||||
} |
||||
|
||||
if (testGroupPolicies) |
||||
{ |
||||
var mockGroupPolicy = new GroupProjectAccessPolicy |
||||
{ |
||||
GroupId = Guid.NewGuid(), |
||||
GrantedProjectId = Guid.NewGuid() |
||||
}; |
||||
data.Add(mockGroupPolicy); |
||||
|
||||
// Add a duplicate policy |
||||
data.Add(mockGroupPolicy); |
||||
} |
||||
|
||||
if (testServiceAccountPolicies) |
||||
{ |
||||
var mockServiceAccountPolicy = new ServiceAccountProjectAccessPolicy |
||||
{ |
||||
ServiceAccountId = Guid.NewGuid(), |
||||
GrantedProjectId = Guid.NewGuid() |
||||
}; |
||||
data.Add(mockServiceAccountPolicy); |
||||
|
||||
// Add a duplicate policy |
||||
data.Add(mockServiceAccountPolicy); |
||||
} |
||||
|
||||
|
||||
sutProvider.GetDependency<IAccessPolicyRepository>().AccessPolicyExists(Arg.Any<BaseAccessPolicy>()) |
||||
.Returns(true); |
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateAsync(data)); |
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().CreateManyAsync(default); |
||||
} |
||||
} |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
using Bit.Commercial.Core.SecretManagerFeatures.AccessPolicies; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Test.Common.AutoFixture; |
||||
using Bit.Test.Common.AutoFixture.Attributes; |
||||
using NSubstitute; |
||||
using NSubstitute.ReturnsExtensions; |
||||
using Xunit; |
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretManagerFeatures.AccessPolicies; |
||||
|
||||
[SutProviderCustomize] |
||||
public class DeleteAccessPolicyCommandTests |
||||
{ |
||||
[Theory] |
||||
[BitAutoData] |
||||
public async Task DeleteAccessPolicy_Throws_NotFoundException(Guid data, |
||||
SutProvider<DeleteAccessPolicyCommand> sutProvider) |
||||
{ |
||||
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).ReturnsNull(); |
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAsync(data)); |
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().DeleteAsync(default); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
public async Task DeleteAccessPolicy_Success(Guid data, |
||||
SutProvider<DeleteAccessPolicyCommand> sutProvider) |
||||
{ |
||||
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data) |
||||
.Returns(new UserProjectAccessPolicy { Id = data }); |
||||
|
||||
await sutProvider.Sut.DeleteAsync(data); |
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1).DeleteAsync(Arg.Is(data)); |
||||
} |
||||
} |
||||
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
using Bit.Commercial.Core.SecretManagerFeatures.AccessPolicies; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Test.Common.AutoFixture; |
||||
using Bit.Test.Common.AutoFixture.Attributes; |
||||
using Bit.Test.Common.Helpers; |
||||
using NSubstitute; |
||||
using Xunit; |
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretManagerFeatures.AccessPolicies; |
||||
|
||||
[SutProviderCustomize] |
||||
public class UpdateAccessPolicyCommandTests |
||||
{ |
||||
[Theory] |
||||
[BitAutoData] |
||||
public async Task UpdateAsync_Throws_NotFoundException(Guid data, bool read, bool write, |
||||
SutProvider<UpdateAccessPolicyCommand> sutProvider) |
||||
{ |
||||
var exception = |
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, read, write)); |
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
public async Task UpdateAsync_Calls_Replace(Guid data, bool read, bool write, |
||||
SutProvider<UpdateAccessPolicyCommand> sutProvider) |
||||
{ |
||||
var existingPolicy = new UserProjectAccessPolicy { Id = data, Read = true, Write = true }; |
||||
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).Returns(existingPolicy); |
||||
var result = await sutProvider.Sut.UpdateAsync(data, read, write); |
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1).ReplaceAsync(existingPolicy); |
||||
|
||||
AssertHelper.AssertRecent(result.RevisionDate); |
||||
Assert.Equal(read, result.Read); |
||||
Assert.Equal(write, result.Write); |
||||
} |
||||
} |
||||
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
using Bit.Api.SecretManagerFeatures.Models.Request; |
||||
using Bit.Api.SecretManagerFeatures.Models.Response; |
||||
using Bit.Api.Utilities; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Core.SecretManagerFeatures.AccessPolicies.Interfaces; |
||||
using Microsoft.AspNetCore.Mvc; |
||||
|
||||
namespace Bit.Api.Controllers; |
||||
|
||||
[SecretsManager] |
||||
[Route("access-policies")] |
||||
public class AccessPoliciesController : Controller |
||||
{ |
||||
private readonly IAccessPolicyRepository _accessPolicyRepository; |
||||
private readonly ICreateAccessPoliciesCommand _createAccessPoliciesCommand; |
||||
private readonly IDeleteAccessPolicyCommand _deleteAccessPolicyCommand; |
||||
private readonly IUpdateAccessPolicyCommand _updateAccessPolicyCommand; |
||||
|
||||
public AccessPoliciesController( |
||||
IAccessPolicyRepository accessPolicyRepository, |
||||
ICreateAccessPoliciesCommand createAccessPoliciesCommand, |
||||
IDeleteAccessPolicyCommand deleteAccessPolicyCommand, |
||||
IUpdateAccessPolicyCommand updateAccessPolicyCommand) |
||||
{ |
||||
_accessPolicyRepository = accessPolicyRepository; |
||||
_createAccessPoliciesCommand = createAccessPoliciesCommand; |
||||
_deleteAccessPolicyCommand = deleteAccessPolicyCommand; |
||||
_updateAccessPolicyCommand = updateAccessPolicyCommand; |
||||
} |
||||
|
||||
[HttpPost("/projects/{id}/access-policies")] |
||||
public async Task<ProjectAccessPoliciesResponseModel> CreateProjectAccessPoliciesAsync([FromRoute] Guid id, |
||||
[FromBody] AccessPoliciesCreateRequest request) |
||||
{ |
||||
var policies = request.ToBaseAccessPoliciesForProject(id); |
||||
var results = await _createAccessPoliciesCommand.CreateAsync(policies); |
||||
return new ProjectAccessPoliciesResponseModel(results); |
||||
} |
||||
|
||||
[HttpGet("/projects/{id}/access-policies")] |
||||
public async Task<ProjectAccessPoliciesResponseModel> GetProjectAccessPoliciesAsync([FromRoute] Guid id) |
||||
{ |
||||
var results = await _accessPolicyRepository.GetManyByProjectId(id); |
||||
return new ProjectAccessPoliciesResponseModel(results); |
||||
} |
||||
|
||||
[HttpPut("{id}")] |
||||
public async Task<BaseAccessPolicyResponseModel> UpdateAccessPolicyAsync([FromRoute] Guid id, |
||||
[FromBody] AccessPolicyUpdateRequest request) |
||||
{ |
||||
var result = await _updateAccessPolicyCommand.UpdateAsync(id, request.Read, request.Write); |
||||
|
||||
return result switch |
||||
{ |
||||
UserProjectAccessPolicy accessPolicy => new UserProjectAccessPolicyResponseModel(accessPolicy), |
||||
GroupProjectAccessPolicy accessPolicy => new GroupProjectAccessPolicyResponseModel(accessPolicy), |
||||
ServiceAccountProjectAccessPolicy accessPolicy => new ServiceAccountProjectAccessPolicyResponseModel( |
||||
accessPolicy), |
||||
_ => throw new ArgumentException("Unsupported access policy type provided.") |
||||
}; |
||||
} |
||||
|
||||
[HttpDelete("{id}")] |
||||
public async Task DeleteAccessPolicyAsync([FromRoute] Guid id) |
||||
{ |
||||
await _deleteAccessPolicyCommand.DeleteAsync(id); |
||||
} |
||||
} |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
#nullable enable |
||||
using System.ComponentModel.DataAnnotations; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Exceptions; |
||||
|
||||
namespace Bit.Api.SecretManagerFeatures.Models.Request; |
||||
|
||||
public class AccessPoliciesCreateRequest |
||||
{ |
||||
public IEnumerable<AccessPolicyRequest>? UserAccessPolicyRequests { get; set; } |
||||
|
||||
public IEnumerable<AccessPolicyRequest>? GroupAccessPolicyRequests { get; set; } |
||||
|
||||
public IEnumerable<AccessPolicyRequest>? ServiceAccountAccessPolicyRequests { get; set; } |
||||
|
||||
public List<BaseAccessPolicy> ToBaseAccessPoliciesForProject(Guid projectId) |
||||
{ |
||||
if (UserAccessPolicyRequests == null && GroupAccessPolicyRequests == null && ServiceAccountAccessPolicyRequests == null) |
||||
{ |
||||
throw new BadRequestException("No creation requests provided."); |
||||
} |
||||
|
||||
var userAccessPolicies = UserAccessPolicyRequests? |
||||
.Select(x => x.ToUserProjectAccessPolicy(projectId)).ToList(); |
||||
|
||||
var groupAccessPolicies = GroupAccessPolicyRequests? |
||||
.Select(x => x.ToGroupProjectAccessPolicy(projectId)).ToList(); |
||||
|
||||
var serviceAccountAccessPolicies = ServiceAccountAccessPolicyRequests? |
||||
.Select(x => x.ToServiceAccountProjectAccessPolicy(projectId)).ToList(); |
||||
|
||||
var policies = new List<BaseAccessPolicy>(); |
||||
if (userAccessPolicies != null) { policies.AddRange(userAccessPolicies); } |
||||
if (groupAccessPolicies != null) { policies.AddRange(groupAccessPolicies); } |
||||
if (serviceAccountAccessPolicies != null) { policies.AddRange(serviceAccountAccessPolicies); } |
||||
return policies; |
||||
} |
||||
} |
||||
|
||||
public class AccessPolicyRequest |
||||
{ |
||||
[Required] |
||||
public Guid GranteeId { get; set; } |
||||
|
||||
[Required] |
||||
public bool Read { get; set; } |
||||
|
||||
[Required] |
||||
public bool Write { get; set; } |
||||
|
||||
public UserProjectAccessPolicy ToUserProjectAccessPolicy(Guid projectId) => |
||||
new() |
||||
{ |
||||
OrganizationUserId = GranteeId, |
||||
GrantedProjectId = projectId, |
||||
Read = Read, |
||||
Write = Write |
||||
}; |
||||
|
||||
public GroupProjectAccessPolicy ToGroupProjectAccessPolicy(Guid projectId) => |
||||
new() |
||||
{ |
||||
GroupId = GranteeId, |
||||
GrantedProjectId = projectId, |
||||
Read = Read, |
||||
Write = Write |
||||
}; |
||||
|
||||
public ServiceAccountProjectAccessPolicy ToServiceAccountProjectAccessPolicy(Guid projectId) => |
||||
new() |
||||
{ |
||||
ServiceAccountId = GranteeId, |
||||
GrantedProjectId = projectId, |
||||
Read = Read, |
||||
Write = Write |
||||
}; |
||||
} |
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations; |
||||
|
||||
namespace Bit.Api.SecretManagerFeatures.Models.Request; |
||||
|
||||
public class AccessPolicyUpdateRequest |
||||
{ |
||||
[Required] |
||||
public bool Read { get; set; } |
||||
|
||||
[Required] |
||||
public bool Write { get; set; } |
||||
} |
||||
@ -0,0 +1,128 @@
@@ -0,0 +1,128 @@
|
||||
#nullable enable |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Models.Api; |
||||
|
||||
namespace Bit.Api.SecretManagerFeatures.Models.Response; |
||||
|
||||
public abstract class BaseAccessPolicyResponseModel : ResponseModel |
||||
{ |
||||
protected BaseAccessPolicyResponseModel(BaseAccessPolicy baseAccessPolicy, string obj) : base(obj) |
||||
{ |
||||
Id = baseAccessPolicy.Id; |
||||
Read = baseAccessPolicy.Read; |
||||
Write = baseAccessPolicy.Write; |
||||
CreationDate = baseAccessPolicy.CreationDate; |
||||
RevisionDate = baseAccessPolicy.RevisionDate; |
||||
} |
||||
|
||||
public Guid Id { get; set; } |
||||
public bool Read { get; set; } |
||||
public bool Write { get; set; } |
||||
public DateTime CreationDate { get; set; } |
||||
public DateTime RevisionDate { get; set; } |
||||
} |
||||
|
||||
public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel |
||||
{ |
||||
private const string _objectName = "userProjectAccessPolicy"; |
||||
|
||||
public UserProjectAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy) : base(accessPolicy, _objectName) |
||||
{ |
||||
OrganizationUserId = accessPolicy.OrganizationUserId; |
||||
GrantedProjectId = accessPolicy.GrantedProjectId; |
||||
OrganizationUserName = accessPolicy.User?.Name; |
||||
} |
||||
|
||||
public UserProjectAccessPolicyResponseModel() : base(new UserProjectAccessPolicy(), _objectName) |
||||
{ |
||||
} |
||||
|
||||
public Guid? OrganizationUserId { get; set; } |
||||
public string? OrganizationUserName { get; set; } |
||||
public Guid? GrantedProjectId { get; set; } |
||||
} |
||||
|
||||
public class UserServiceAccountAccessPolicyResponseModel : BaseAccessPolicyResponseModel |
||||
{ |
||||
private const string _objectName = "userServiceAccountAccessPolicy"; |
||||
|
||||
public UserServiceAccountAccessPolicyResponseModel(UserServiceAccountAccessPolicy accessPolicy) |
||||
: base(accessPolicy, _objectName) |
||||
{ |
||||
OrganizationUserId = accessPolicy.OrganizationUserId; |
||||
GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId; |
||||
OrganizationUserName = accessPolicy.User?.Name; |
||||
} |
||||
|
||||
public UserServiceAccountAccessPolicyResponseModel() : base(new UserServiceAccountAccessPolicy(), _objectName) |
||||
{ |
||||
} |
||||
|
||||
public Guid? OrganizationUserId { get; set; } |
||||
public string? OrganizationUserName { get; set; } |
||||
public Guid? GrantedServiceAccountId { get; set; } |
||||
} |
||||
|
||||
public class GroupProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel |
||||
{ |
||||
private const string _objectName = "groupProjectAccessPolicy"; |
||||
|
||||
public GroupProjectAccessPolicyResponseModel(GroupProjectAccessPolicy accessPolicy) |
||||
: base(accessPolicy, _objectName) |
||||
{ |
||||
GroupId = accessPolicy.GroupId; |
||||
GrantedProjectId = accessPolicy.GrantedProjectId; |
||||
GroupName = accessPolicy.Group?.Name; |
||||
} |
||||
|
||||
public GroupProjectAccessPolicyResponseModel() : base(new GroupProjectAccessPolicy(), _objectName) |
||||
{ |
||||
} |
||||
|
||||
public Guid? GroupId { get; set; } |
||||
public string? GroupName { get; set; } |
||||
public Guid? GrantedProjectId { get; set; } |
||||
} |
||||
|
||||
public class GroupServiceAccountAccessPolicyResponseModel : BaseAccessPolicyResponseModel |
||||
{ |
||||
private const string _objectName = "groupServiceAccountAccessPolicy"; |
||||
|
||||
public GroupServiceAccountAccessPolicyResponseModel(GroupServiceAccountAccessPolicy accessPolicy) |
||||
: base(accessPolicy, _objectName) |
||||
{ |
||||
GroupId = accessPolicy.GroupId; |
||||
GroupName = accessPolicy.Group?.Name; |
||||
GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId; |
||||
} |
||||
|
||||
public GroupServiceAccountAccessPolicyResponseModel() : base(new GroupServiceAccountAccessPolicy(), _objectName) |
||||
{ |
||||
} |
||||
|
||||
public Guid? GroupId { get; set; } |
||||
public string? GroupName { get; set; } |
||||
public Guid? GrantedServiceAccountId { get; set; } |
||||
} |
||||
|
||||
public class ServiceAccountProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel |
||||
{ |
||||
private const string _objectName = "serviceAccountProjectAccessPolicy"; |
||||
|
||||
public ServiceAccountProjectAccessPolicyResponseModel(ServiceAccountProjectAccessPolicy accessPolicy) |
||||
: base(accessPolicy, _objectName) |
||||
{ |
||||
ServiceAccountId = accessPolicy.ServiceAccountId; |
||||
GrantedProjectId = accessPolicy.GrantedProjectId; |
||||
ServiceAccountName = accessPolicy.ServiceAccount?.Name; |
||||
} |
||||
|
||||
public ServiceAccountProjectAccessPolicyResponseModel() |
||||
: base(new ServiceAccountProjectAccessPolicy(), _objectName) |
||||
{ |
||||
} |
||||
|
||||
public Guid? ServiceAccountId { get; set; } |
||||
public string? ServiceAccountName { get; set; } |
||||
public Guid? GrantedProjectId { get; set; } |
||||
} |
||||
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Models.Api; |
||||
|
||||
namespace Bit.Api.SecretManagerFeatures.Models.Response; |
||||
|
||||
public class ProjectAccessPoliciesResponseModel : ResponseModel |
||||
{ |
||||
private const string _objectName = "projectAccessPolicies"; |
||||
|
||||
public ProjectAccessPoliciesResponseModel(IEnumerable<BaseAccessPolicy> baseAccessPolicies) |
||||
: base(_objectName) |
||||
{ |
||||
if (baseAccessPolicies == null) |
||||
{ |
||||
return; |
||||
} |
||||
|
||||
foreach (var baseAccessPolicy in baseAccessPolicies) |
||||
switch (baseAccessPolicy) |
||||
{ |
||||
case UserProjectAccessPolicy accessPolicy: |
||||
UserAccessPolicies.Add(new UserProjectAccessPolicyResponseModel(accessPolicy)); |
||||
break; |
||||
case GroupProjectAccessPolicy accessPolicy: |
||||
GroupAccessPolicies.Add(new GroupProjectAccessPolicyResponseModel(accessPolicy)); |
||||
break; |
||||
case ServiceAccountProjectAccessPolicy accessPolicy: |
||||
ServiceAccountAccessPolicies.Add( |
||||
new ServiceAccountProjectAccessPolicyResponseModel(accessPolicy)); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
public ProjectAccessPoliciesResponseModel() : base(_objectName) |
||||
{ |
||||
} |
||||
|
||||
public List<UserProjectAccessPolicyResponseModel> UserAccessPolicies { get; set; } = new(); |
||||
|
||||
public List<GroupProjectAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = new(); |
||||
|
||||
public List<ServiceAccountProjectAccessPolicyResponseModel> ServiceAccountAccessPolicies { get; set; } = new(); |
||||
} |
||||
@ -1,7 +1,14 @@
@@ -1,7 +1,14 @@
|
||||
using Bit.Core.Entities; |
||||
#nullable enable |
||||
using Bit.Core.Entities; |
||||
|
||||
namespace Bit.Core.Repositories; |
||||
|
||||
public interface IAccessPolicyRepository : IRepository<AccessPolicy, Guid> |
||||
public interface IAccessPolicyRepository |
||||
{ |
||||
Task<List<BaseAccessPolicy>> CreateManyAsync(List<BaseAccessPolicy> baseAccessPolicies); |
||||
Task<bool> AccessPolicyExists(BaseAccessPolicy baseAccessPolicy); |
||||
Task<BaseAccessPolicy?> GetByIdAsync(Guid id); |
||||
Task<IEnumerable<BaseAccessPolicy>?> GetManyByProjectId(Guid id); |
||||
Task ReplaceAsync(BaseAccessPolicy baseAccessPolicy); |
||||
Task DeleteAsync(Guid id); |
||||
} |
||||
|
||||
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
using Bit.Core.Entities; |
||||
|
||||
namespace Bit.Core.SecretManagerFeatures.AccessPolicies.Interfaces; |
||||
|
||||
public interface ICreateAccessPoliciesCommand |
||||
{ |
||||
Task<List<BaseAccessPolicy>> CreateAsync(List<BaseAccessPolicy> accessPolicies); |
||||
} |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
namespace Bit.Core.SecretManagerFeatures.AccessPolicies.Interfaces; |
||||
|
||||
public interface IDeleteAccessPolicyCommand |
||||
{ |
||||
Task DeleteAsync(Guid id); |
||||
} |
||||
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
using Bit.Core.Entities; |
||||
|
||||
namespace Bit.Core.SecretManagerFeatures.AccessPolicies.Interfaces; |
||||
|
||||
public interface IUpdateAccessPolicyCommand |
||||
{ |
||||
public Task<BaseAccessPolicy> UpdateAsync(Guid id, bool read, bool write); |
||||
} |
||||
@ -1,27 +0,0 @@
@@ -1,27 +0,0 @@
|
||||
using AutoMapper; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Infrastructure.EntityFramework.Models; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using Microsoft.Extensions.DependencyInjection; |
||||
using CoreAccessPolicy = Bit.Core.Entities.AccessPolicy; |
||||
|
||||
namespace Bit.Infrastructure.EntityFramework.Repositories; |
||||
|
||||
public class AccessPolicyRepository : IAccessPolicyRepository |
||||
{ |
||||
public AccessPolicyRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) |
||||
{ |
||||
} |
||||
|
||||
protected Func<DatabaseContext, DbSet<AccessPolicy>> GetDbSet { get; private set; } |
||||
|
||||
public Task<CoreAccessPolicy> GetByIdAsync(Guid id) => throw new NotImplementedException(); |
||||
|
||||
public Task<CoreAccessPolicy> CreateAsync(CoreAccessPolicy obj) => throw new NotImplementedException(); |
||||
|
||||
public Task ReplaceAsync(CoreAccessPolicy obj) => throw new NotImplementedException(); |
||||
|
||||
public Task UpsertAsync(CoreAccessPolicy obj) => throw new NotImplementedException(); |
||||
|
||||
public Task DeleteAsync(CoreAccessPolicy obj) => throw new NotImplementedException(); |
||||
} |
||||
@ -0,0 +1,185 @@
@@ -0,0 +1,185 @@
|
||||
using System.Net.Http.Headers; |
||||
using Bit.Api.IntegrationTest.Factories; |
||||
using Bit.Api.IntegrationTest.Helpers; |
||||
using Bit.Api.SecretManagerFeatures.Models.Request; |
||||
using Bit.Api.SecretManagerFeatures.Models.Response; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Test.Common.Helpers; |
||||
using Xunit; |
||||
|
||||
namespace Bit.Api.IntegrationTest.Controllers; |
||||
|
||||
public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>, IAsyncLifetime |
||||
{ |
||||
private readonly IAccessPolicyRepository _accessPolicyRepository; |
||||
|
||||
private readonly HttpClient _client; |
||||
private readonly ApiApplicationFactory _factory; |
||||
|
||||
private const string _mockEncryptedString = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; |
||||
|
||||
private readonly IProjectRepository _projectRepository; |
||||
private readonly IServiceAccountRepository _serviceAccountRepository; |
||||
private Organization _organization = null!; |
||||
|
||||
public AccessPoliciesControllerTest(ApiApplicationFactory factory) |
||||
{ |
||||
_factory = factory; |
||||
_client = _factory.CreateClient(); |
||||
_accessPolicyRepository = _factory.GetService<IAccessPolicyRepository>(); |
||||
_serviceAccountRepository = _factory.GetService<IServiceAccountRepository>(); |
||||
_projectRepository = _factory.GetService<IProjectRepository>(); |
||||
} |
||||
|
||||
public async Task InitializeAsync() |
||||
{ |
||||
var ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com"; |
||||
var tokens = await _factory.LoginWithNewAccount(ownerEmail); |
||||
var (organization, _) = |
||||
await OrganizationTestHelpers.SignUpAsync(_factory, ownerEmail: ownerEmail, billingEmail: ownerEmail); |
||||
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token); |
||||
_organization = organization; |
||||
} |
||||
|
||||
public Task DisposeAsync() => Task.CompletedTask; |
||||
|
||||
[Fact] |
||||
public async Task CreateProjectAccessPolicies() |
||||
{ |
||||
var initialProject = await _projectRepository.CreateAsync(new Project |
||||
{ |
||||
OrganizationId = _organization.Id, |
||||
Name = _mockEncryptedString |
||||
}); |
||||
|
||||
var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount |
||||
{ |
||||
OrganizationId = _organization.Id, |
||||
Name = _mockEncryptedString |
||||
}); |
||||
|
||||
var request = new AccessPoliciesCreateRequest |
||||
{ |
||||
ServiceAccountAccessPolicyRequests = new List<AccessPolicyRequest> |
||||
{ |
||||
new() { GranteeId = initialServiceAccount.Id, Read = true, Write = true } |
||||
} |
||||
}; |
||||
|
||||
var response = await _client.PostAsJsonAsync($"/projects/{initialProject.Id}/access-policies", request); |
||||
response.EnsureSuccessStatusCode(); |
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ProjectAccessPoliciesResponseModel>(); |
||||
|
||||
Assert.NotNull(result); |
||||
Assert.Equal(initialServiceAccount.Id, result!.ServiceAccountAccessPolicies.First().ServiceAccountId); |
||||
Assert.True(result.ServiceAccountAccessPolicies.First().Read); |
||||
Assert.True(result.ServiceAccountAccessPolicies.First().Write); |
||||
AssertHelper.AssertRecent(result.ServiceAccountAccessPolicies.First().RevisionDate); |
||||
AssertHelper.AssertRecent(result.ServiceAccountAccessPolicies.First().CreationDate); |
||||
|
||||
var createdAccessPolicy = |
||||
await _accessPolicyRepository.GetByIdAsync(result.ServiceAccountAccessPolicies.First().Id); |
||||
Assert.NotNull(createdAccessPolicy); |
||||
Assert.Equal(result.ServiceAccountAccessPolicies.First().Read, createdAccessPolicy!.Read); |
||||
Assert.Equal(result.ServiceAccountAccessPolicies.First().Write, createdAccessPolicy.Write); |
||||
Assert.Equal(result.ServiceAccountAccessPolicies.First().Id, createdAccessPolicy.Id); |
||||
AssertHelper.AssertRecent(createdAccessPolicy.CreationDate); |
||||
AssertHelper.AssertRecent(createdAccessPolicy.RevisionDate); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task UpdateAccessPolicy() |
||||
{ |
||||
var initData = await SetupAccessPolicyRequest(); |
||||
|
||||
const bool expectedRead = true; |
||||
const bool expectedWrite = false; |
||||
var request = new AccessPolicyUpdateRequest { Read = expectedRead, Write = expectedWrite }; |
||||
|
||||
var response = await _client.PutAsJsonAsync($"/access-policies/{initData.InitialAccessPolicyId}", request); |
||||
response.EnsureSuccessStatusCode(); |
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ServiceAccountProjectAccessPolicyResponseModel>(); |
||||
|
||||
Assert.NotNull(result); |
||||
Assert.Equal(expectedRead, result!.Read); |
||||
Assert.Equal(expectedWrite, result.Write); |
||||
AssertHelper.AssertRecent(result.RevisionDate); |
||||
|
||||
var updatedAccessPolicy = await _accessPolicyRepository.GetByIdAsync(result.Id); |
||||
Assert.NotNull(updatedAccessPolicy); |
||||
Assert.Equal(expectedRead, updatedAccessPolicy!.Read); |
||||
Assert.Equal(expectedWrite, updatedAccessPolicy.Write); |
||||
AssertHelper.AssertRecent(updatedAccessPolicy.RevisionDate); |
||||
} |
||||
|
||||
|
||||
[Fact] |
||||
public async Task DeleteAccessPolicy() |
||||
{ |
||||
var initData = await SetupAccessPolicyRequest(); |
||||
|
||||
var response = await _client.DeleteAsync($"/access-policies/{initData.InitialAccessPolicyId}"); |
||||
response.EnsureSuccessStatusCode(); |
||||
|
||||
var test = await _accessPolicyRepository.GetByIdAsync(initData.InitialAccessPolicyId); |
||||
Assert.Null(test); |
||||
} |
||||
|
||||
[Fact] |
||||
public async Task GetProjectAccessPolicies() |
||||
{ |
||||
var initData = await SetupAccessPolicyRequest(); |
||||
|
||||
var response = await _client.GetAsync($"/projects/{initData.InitialProjectId}/access-policies"); |
||||
response.EnsureSuccessStatusCode(); |
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ProjectAccessPoliciesResponseModel>(); |
||||
|
||||
Assert.NotNull(result?.ServiceAccountAccessPolicies); |
||||
Assert.Single(result!.ServiceAccountAccessPolicies); |
||||
} |
||||
|
||||
private async Task<RequestSetupData> SetupAccessPolicyRequest() |
||||
{ |
||||
var initialProject = await _projectRepository.CreateAsync(new Project |
||||
{ |
||||
OrganizationId = _organization.Id, |
||||
Name = _mockEncryptedString, |
||||
}); |
||||
|
||||
var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount |
||||
{ |
||||
OrganizationId = _organization.Id, |
||||
Name = _mockEncryptedString, |
||||
}); |
||||
|
||||
var initialAccessPolicy = await _accessPolicyRepository.CreateManyAsync( |
||||
new List<BaseAccessPolicy> |
||||
{ |
||||
new ServiceAccountProjectAccessPolicy |
||||
{ |
||||
Read = true, |
||||
Write = true, |
||||
ServiceAccountId = initialServiceAccount.Id, |
||||
GrantedProjectId = initialProject.Id, |
||||
} |
||||
}); |
||||
|
||||
return new RequestSetupData |
||||
{ |
||||
InitialProjectId = initialProject.Id, |
||||
InitialServiceAccountId = initialServiceAccount.Id, |
||||
InitialAccessPolicyId = initialAccessPolicy.First().Id, |
||||
}; |
||||
} |
||||
|
||||
private class RequestSetupData |
||||
{ |
||||
public Guid InitialProjectId { get; set; } |
||||
public Guid InitialAccessPolicyId { get; set; } |
||||
public Guid InitialServiceAccountId { get; set; } |
||||
} |
||||
} |
||||
@ -0,0 +1,87 @@
@@ -0,0 +1,87 @@
|
||||
using Bit.Api.Controllers; |
||||
using Bit.Api.SecretManagerFeatures.Models.Request; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Core.SecretManagerFeatures.AccessPolicies.Interfaces; |
||||
using Bit.Test.Common.AutoFixture; |
||||
using Bit.Test.Common.AutoFixture.Attributes; |
||||
using Bit.Test.Common.Helpers; |
||||
using NSubstitute; |
||||
using NSubstitute.ReturnsExtensions; |
||||
using Xunit; |
||||
|
||||
namespace Bit.Api.Test.Controllers; |
||||
|
||||
[ControllerCustomize(typeof(AccessPoliciesController))] |
||||
[SutProviderCustomize] |
||||
[JsonDocumentCustomize] |
||||
public class AccessPoliciesControllerTests |
||||
{ |
||||
[Theory] |
||||
[BitAutoData] |
||||
public async void GetAccessPoliciesByProject_ReturnsEmptyList(SutProvider<AccessPoliciesController> sutProvider, |
||||
Guid id) |
||||
{ |
||||
var result = await sutProvider.Sut.GetProjectAccessPoliciesAsync(id); |
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1) |
||||
.GetManyByProjectId(Arg.Is(AssertHelper.AssertPropertyEqual(id))); |
||||
|
||||
Assert.Empty(result.GroupAccessPolicies); |
||||
Assert.Empty(result.UserAccessPolicies); |
||||
Assert.Empty(result.ServiceAccountAccessPolicies); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
public async void GetAccessPoliciesByProject_Success(SutProvider<AccessPoliciesController> sutProvider, Guid id, |
||||
UserProjectAccessPolicy resultAccessPolicy) |
||||
{ |
||||
sutProvider.GetDependency<IAccessPolicyRepository>().GetManyByProjectId(default) |
||||
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { resultAccessPolicy }); |
||||
|
||||
var result = await sutProvider.Sut.GetProjectAccessPoliciesAsync(id); |
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1) |
||||
.GetManyByProjectId(Arg.Is(AssertHelper.AssertPropertyEqual(id))); |
||||
|
||||
Assert.Empty(result.GroupAccessPolicies); |
||||
Assert.NotEmpty(result.UserAccessPolicies); |
||||
Assert.Empty(result.ServiceAccountAccessPolicies); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
public async void CreateAccessPolicies_Success(SutProvider<AccessPoliciesController> sutProvider, Guid id, |
||||
UserProjectAccessPolicy data, AccessPoliciesCreateRequest request) |
||||
{ |
||||
sutProvider.GetDependency<ICreateAccessPoliciesCommand>().CreateAsync(default) |
||||
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data }); |
||||
var result = await sutProvider.Sut.CreateProjectAccessPoliciesAsync(id, request); |
||||
await sutProvider.GetDependency<ICreateAccessPoliciesCommand>().Received(1) |
||||
.CreateAsync(Arg.Any<List<BaseAccessPolicy>>()); |
||||
} |
||||
|
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
public async void UpdateAccessPolicies_Success(SutProvider<AccessPoliciesController> sutProvider, Guid id, |
||||
UserProjectAccessPolicy data, AccessPolicyUpdateRequest request) |
||||
{ |
||||
sutProvider.GetDependency<IUpdateAccessPolicyCommand>().UpdateAsync(default, default, default) |
||||
.ReturnsForAnyArgs(data); |
||||
var result = await sutProvider.Sut.UpdateAccessPolicyAsync(id, request); |
||||
await sutProvider.GetDependency<IUpdateAccessPolicyCommand>().Received(1) |
||||
.UpdateAsync(Arg.Any<Guid>(), Arg.Is(request.Read), Arg.Is(request.Write)); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
public async void DeleteAccessPolicies_Success(SutProvider<AccessPoliciesController> sutProvider, Guid id) |
||||
{ |
||||
sutProvider.GetDependency<IDeleteAccessPolicyCommand>().DeleteAsync(default).ReturnsNull(); |
||||
await sutProvider.Sut.DeleteAccessPolicyAsync(id); |
||||
await sutProvider.GetDependency<IDeleteAccessPolicyCommand>().Received(1) |
||||
.DeleteAsync(Arg.Any<Guid>()); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue