21 changed files with 1165 additions and 46 deletions
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
using Bit.Core.Context; |
||||
using Bit.Core.Enums; |
||||
using Bit.Core.SecretsManager.AuthorizationRequirements; |
||||
using Bit.Core.SecretsManager.Models.Data; |
||||
using Bit.Core.SecretsManager.Queries.AccessPolicies.Interfaces; |
||||
using Bit.Core.SecretsManager.Queries.Interfaces; |
||||
using Bit.Core.SecretsManager.Repositories; |
||||
using Microsoft.AspNetCore.Authorization; |
||||
|
||||
namespace Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies; |
||||
|
||||
public class
|
||||
ProjectServiceAccountsAccessPoliciesAuthorizationHandler : AuthorizationHandler< |
||||
ProjectServiceAccountsAccessPoliciesOperationRequirement, |
||||
ProjectServiceAccountsAccessPolicies> |
||||
{ |
||||
private readonly IAccessClientQuery _accessClientQuery; |
||||
private readonly ICurrentContext _currentContext; |
||||
private readonly IProjectRepository _projectRepository; |
||||
private readonly ISameOrganizationQuery _sameOrganizationQuery; |
||||
private readonly IServiceAccountRepository _serviceAccountRepository; |
||||
|
||||
public ProjectServiceAccountsAccessPoliciesAuthorizationHandler(ICurrentContext currentContext, |
||||
IAccessClientQuery accessClientQuery, |
||||
ISameOrganizationQuery sameOrganizationQuery, |
||||
IProjectRepository projectRepository, |
||||
IServiceAccountRepository serviceAccountRepository) |
||||
{ |
||||
_currentContext = currentContext; |
||||
_accessClientQuery = accessClientQuery; |
||||
_sameOrganizationQuery = sameOrganizationQuery; |
||||
_projectRepository = projectRepository; |
||||
_serviceAccountRepository = serviceAccountRepository; |
||||
} |
||||
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, |
||||
ProjectServiceAccountsAccessPoliciesOperationRequirement requirement, |
||||
ProjectServiceAccountsAccessPolicies resource) |
||||
{ |
||||
if (!_currentContext.AccessSecretsManager(resource.OrganizationId)) |
||||
{ |
||||
return; |
||||
} |
||||
|
||||
// Only users and admins should be able to manipulate access policies |
||||
var (accessClient, userId) = |
||||
await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId); |
||||
if (accessClient != AccessClientType.User && accessClient != AccessClientType.NoAccessCheck) |
||||
{ |
||||
return; |
||||
} |
||||
|
||||
switch (requirement) |
||||
{ |
||||
case not null when requirement == ProjectServiceAccountsAccessPoliciesOperations.Replace: |
||||
await CanReplaceProjectServiceAccountsAsync(context, requirement, resource, accessClient, userId); |
||||
break; |
||||
default: |
||||
throw new ArgumentException("Unsupported operation requirement type provided.", |
||||
nameof(requirement)); |
||||
} |
||||
} |
||||
|
||||
private async Task CanReplaceProjectServiceAccountsAsync(AuthorizationHandlerContext context, |
||||
ProjectServiceAccountsAccessPoliciesOperationRequirement requirement, ProjectServiceAccountsAccessPolicies resource, |
||||
AccessClientType accessClient, Guid userId) |
||||
{ |
||||
var projectAccess = await _projectRepository.AccessToProjectAsync(resource.Id, userId, accessClient); |
||||
if (projectAccess.Write) |
||||
{ |
||||
if (resource.ServiceAccountProjectsAccessPolicies != null && resource.ServiceAccountProjectsAccessPolicies.Any()) |
||||
{ |
||||
var serviceAccountIds = resource.ServiceAccountProjectsAccessPolicies.Select(ap => ap.ServiceAccountId!.Value).ToList(); |
||||
if (!await _sameOrganizationQuery.ServiceAccountsInTheSameOrgAsync(serviceAccountIds, resource.OrganizationId)) |
||||
{ |
||||
return; |
||||
} |
||||
|
||||
var serviceAccountAccess = await _serviceAccountRepository.AccessToServiceAccountsAsync(serviceAccountIds, userId, accessClient); |
||||
if (!serviceAccountAccess.Write) |
||||
{ |
||||
return; |
||||
} |
||||
} |
||||
|
||||
context.Succeed(requirement); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,222 @@
@@ -0,0 +1,222 @@
|
||||
using System.Reflection; |
||||
using System.Security.Claims; |
||||
using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies; |
||||
using Bit.Core.Context; |
||||
using Bit.Core.Enums; |
||||
using Bit.Core.SecretsManager.AuthorizationRequirements; |
||||
using Bit.Core.SecretsManager.Models.Data; |
||||
using Bit.Core.SecretsManager.Queries.AccessPolicies.Interfaces; |
||||
using Bit.Core.SecretsManager.Queries.Interfaces; |
||||
using Bit.Core.SecretsManager.Repositories; |
||||
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; |
||||
using Bit.Test.Common.AutoFixture; |
||||
using Bit.Test.Common.AutoFixture.Attributes; |
||||
using Microsoft.AspNetCore.Authorization; |
||||
using NSubstitute; |
||||
using Xunit; |
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.AuthorizationHandlers.AccessPolicies; |
||||
|
||||
[SutProviderCustomize] |
||||
[ProjectCustomize] |
||||
public class ProjectServiceAccountAccessPoliciesAuthorizationHandlerTests |
||||
{ |
||||
private static void SetupUserPermission(SutProvider<ProjectServiceAccountsAccessPoliciesAuthorizationHandler> sutProvider, |
||||
AccessClientType accessClientType, ProjectServiceAccountsAccessPolicies resource, Guid userId = new(), bool read = true, |
||||
bool write = true) |
||||
{ |
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(resource.OrganizationId) |
||||
.Returns(true); |
||||
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, resource.OrganizationId) |
||||
.ReturnsForAnyArgs( |
||||
(accessClientType, userId)); |
||||
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync(resource.Id, userId, accessClientType) |
||||
.Returns((read, write)); |
||||
sutProvider.GetDependency<IServiceAccountRepository>().AccessToServiceAccountsAsync(Arg.Any<List<Guid>>(), userId, accessClientType) |
||||
.Returns((read, write)); |
||||
} |
||||
|
||||
private static void SetupOrganizationServiceAccounts(SutProvider<ProjectServiceAccountsAccessPoliciesAuthorizationHandler> sutProvider, |
||||
ProjectServiceAccountsAccessPolicies resource) => |
||||
sutProvider.GetDependency<ISameOrganizationQuery>() |
||||
.ServiceAccountsInTheSameOrgAsync(Arg.Any<List<Guid>>(), resource.OrganizationId) |
||||
.Returns(true); |
||||
|
||||
[Fact] |
||||
public void ServiceAccountAccessPoliciesOperations_OnlyPublicStatic() |
||||
{ |
||||
var publicStaticFields = |
||||
typeof(ProjectServiceAccountsAccessPoliciesOperations).GetFields(BindingFlags.Public | BindingFlags.Static); |
||||
var allFields = typeof(ProjectServiceAccountsAccessPoliciesOperations).GetFields(); |
||||
Assert.Equal(publicStaticFields.Length, allFields.Length); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
public async Task Handler_UnsupportedProjectServiceAccountsAccessPoliciesOperationRequirement_Throws( |
||||
SutProvider<ProjectServiceAccountsAccessPoliciesAuthorizationHandler> sutProvider, ProjectServiceAccountsAccessPolicies resource, |
||||
ClaimsPrincipal claimsPrincipal) |
||||
{ |
||||
var requirement = new ProjectServiceAccountsAccessPoliciesOperationRequirement(); |
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(resource.OrganizationId) |
||||
.Returns(true); |
||||
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, resource.OrganizationId) |
||||
.ReturnsForAnyArgs( |
||||
(AccessClientType.NoAccessCheck, new Guid())); |
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, |
||||
claimsPrincipal, resource); |
||||
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => sutProvider.Sut.HandleAsync(authzContext)); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
public async Task Handler_AccessSecretsManagerFalse_DoesNotSucceed( |
||||
SutProvider<ProjectServiceAccountsAccessPoliciesAuthorizationHandler> sutProvider, ProjectServiceAccountsAccessPolicies resource, |
||||
ClaimsPrincipal claimsPrincipal) |
||||
{ |
||||
var requirement = new ProjectServiceAccountsAccessPoliciesOperationRequirement(); |
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(resource.OrganizationId) |
||||
.Returns(false); |
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, |
||||
claimsPrincipal, resource); |
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext); |
||||
|
||||
Assert.False(authzContext.HasSucceeded); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData(AccessClientType.User)] |
||||
[BitAutoData(AccessClientType.NoAccessCheck)] |
||||
public async Task ReplaceProjectServiceAccount_ServiceAccountNotInOrg_DoesNotSucceed(AccessClientType accessClient, |
||||
SutProvider<ProjectServiceAccountsAccessPoliciesAuthorizationHandler> sutProvider, ProjectServiceAccountsAccessPolicies resource, |
||||
ClaimsPrincipal claimsPrincipal, Guid userId) |
||||
{ |
||||
var requirement = ProjectServiceAccountsAccessPoliciesOperations.Replace; |
||||
SetupUserPermission(sutProvider, accessClient, resource, userId); |
||||
sutProvider.GetDependency<ISameOrganizationQuery>() |
||||
.ServiceAccountsInTheSameOrgAsync(Arg.Any<List<Guid>>(), resource.OrganizationId) |
||||
.Returns(false); |
||||
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, |
||||
claimsPrincipal, resource); |
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext); |
||||
|
||||
Assert.False(authzContext.HasSucceeded); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData(AccessClientType.User, false, false, false)] |
||||
[BitAutoData(AccessClientType.User, false, true, true)] |
||||
[BitAutoData(AccessClientType.User, true, false, false)] |
||||
[BitAutoData(AccessClientType.User, true, true, true)] |
||||
[BitAutoData(AccessClientType.NoAccessCheck, false, false, false)] |
||||
[BitAutoData(AccessClientType.NoAccessCheck, false, true, true)] |
||||
[BitAutoData(AccessClientType.NoAccessCheck, true, false, false)] |
||||
[BitAutoData(AccessClientType.NoAccessCheck, true, true, true)] |
||||
public async Task ReplaceProjectServiceAccount_ProjectAccessCheck(AccessClientType accessClient, bool read, bool write, |
||||
bool expected, |
||||
SutProvider<ProjectServiceAccountsAccessPoliciesAuthorizationHandler> sutProvider, ProjectServiceAccountsAccessPolicies resource, |
||||
ClaimsPrincipal claimsPrincipal, Guid userId) |
||||
{ |
||||
var requirement = ProjectServiceAccountsAccessPoliciesOperations.Replace; |
||||
SetupUserPermission(sutProvider, accessClient, resource, userId, read, write); |
||||
SetupOrganizationServiceAccounts(sutProvider, resource); |
||||
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, |
||||
claimsPrincipal, resource); |
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext); |
||||
|
||||
Assert.Equal(expected, authzContext.HasSucceeded); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData(AccessClientType.User, false, false, false)] |
||||
[BitAutoData(AccessClientType.User, false, true, true)] |
||||
[BitAutoData(AccessClientType.User, true, false, false)] |
||||
[BitAutoData(AccessClientType.User, true, true, true)] |
||||
[BitAutoData(AccessClientType.NoAccessCheck, false, false, false)] |
||||
[BitAutoData(AccessClientType.NoAccessCheck, false, true, true)] |
||||
[BitAutoData(AccessClientType.NoAccessCheck, true, false, false)] |
||||
[BitAutoData(AccessClientType.NoAccessCheck, true, true, true)] |
||||
public async Task ReplaceProjectServiceAccount_ServiceAccountsAccessCheck(AccessClientType accessClient, bool read, bool write, |
||||
bool expected, |
||||
SutProvider<ProjectServiceAccountsAccessPoliciesAuthorizationHandler> sutProvider, ProjectServiceAccountsAccessPolicies resource, |
||||
ClaimsPrincipal claimsPrincipal, Guid userId) |
||||
{ |
||||
var requirement = ProjectServiceAccountsAccessPoliciesOperations.Replace; |
||||
SetupUserPermission(sutProvider, accessClient, resource, userId, true, true); |
||||
SetupOrganizationServiceAccounts(sutProvider, resource); |
||||
|
||||
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().AccessToServiceAccountsAsync(Arg.Any<List<Guid>>(), userId, accessClient).Returns((read, write)); |
||||
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, |
||||
claimsPrincipal, resource); |
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext); |
||||
|
||||
Assert.Equal(expected, authzContext.HasSucceeded); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData(AccessClientType.User, false, false, false)] |
||||
[BitAutoData(AccessClientType.User, false, true, false)] |
||||
[BitAutoData(AccessClientType.User, true, false, false)] |
||||
[BitAutoData(AccessClientType.User, true, true, false)] |
||||
[BitAutoData(AccessClientType.NoAccessCheck, false, false, false)] |
||||
[BitAutoData(AccessClientType.NoAccessCheck, false, true, false)] |
||||
[BitAutoData(AccessClientType.NoAccessCheck, true, false, false)] |
||||
[BitAutoData(AccessClientType.NoAccessCheck, true, true, false)] |
||||
public async Task ReplaceProjectServiceAccount_ProjectAccessFalseCheck(AccessClientType accessClient, bool read, bool write, |
||||
bool expected, |
||||
SutProvider<ProjectServiceAccountsAccessPoliciesAuthorizationHandler> sutProvider, ProjectServiceAccountsAccessPolicies resource, |
||||
ClaimsPrincipal claimsPrincipal, Guid userId) |
||||
{ |
||||
var requirement = ProjectServiceAccountsAccessPoliciesOperations.Replace; |
||||
SetupUserPermission(sutProvider, accessClient, resource, userId, false, false); |
||||
SetupOrganizationServiceAccounts(sutProvider, resource); |
||||
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().AccessToServiceAccountsAsync(resource.ServiceAccountProjectsAccessPolicies.Select(ap => ap.ServiceAccountId!.Value).ToList(), userId, accessClient) |
||||
.Returns((read, write)); |
||||
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, |
||||
claimsPrincipal, resource); |
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext); |
||||
|
||||
Assert.Equal(expected, authzContext.HasSucceeded); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData(AccessClientType.ServiceAccount, true, true, false)] |
||||
[BitAutoData(AccessClientType.ServiceAccount, true, false, false)] |
||||
[BitAutoData(AccessClientType.ServiceAccount, false, true, false)] |
||||
[BitAutoData(AccessClientType.ServiceAccount, false, false, false)] |
||||
[BitAutoData(AccessClientType.Organization, true, true, false)] |
||||
[BitAutoData(AccessClientType.Organization, true, false, false)] |
||||
[BitAutoData(AccessClientType.Organization, false, true, false)] |
||||
[BitAutoData(AccessClientType.Organization, false, false, false)] |
||||
public async Task ReplaceProjectServiceAccount_UnsupportedAccessType(AccessClientType accessClient, bool read, bool write, |
||||
bool expected, |
||||
SutProvider<ProjectServiceAccountsAccessPoliciesAuthorizationHandler> sutProvider, ProjectServiceAccountsAccessPolicies resource, |
||||
ClaimsPrincipal claimsPrincipal, Guid userId) |
||||
{ |
||||
var requirement = ProjectServiceAccountsAccessPoliciesOperations.Replace; |
||||
SetupUserPermission(sutProvider, accessClient, resource, userId, false, false); |
||||
SetupOrganizationServiceAccounts(sutProvider, resource); |
||||
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().AccessToServiceAccountsAsync(resource.ServiceAccountProjectsAccessPolicies.Select(ap => ap.ServiceAccountId!.Value).ToList(), userId, accessClient) |
||||
.Returns((read, write)); |
||||
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, |
||||
claimsPrincipal, resource); |
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext); |
||||
|
||||
Assert.Equal(expected, authzContext.HasSucceeded); |
||||
} |
||||
} |
||||
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
using Bit.Api.SecretsManager.Utilities; |
||||
using Bit.Core.SecretsManager.Entities; |
||||
using Bit.Core.SecretsManager.Models.Data; |
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Request; |
||||
|
||||
public class ServiceAccountsAccessPoliciesRequestModel |
||||
{ |
||||
public IEnumerable<AccessPolicyRequest> ProjectServiceAccountsAccessPolicyRequests { get; set; } |
||||
|
||||
public ProjectServiceAccountsAccessPolicies ToProjectServiceAccountsAccessPolicies(Guid grantedProjectId, Guid organizationId) |
||||
{ |
||||
var projectServiceAccountsAccessPolicies = ProjectServiceAccountsAccessPolicyRequests? |
||||
.Select(x => x.ToServiceAccountProjectAccessPolicy(grantedProjectId, organizationId)).ToList(); |
||||
var policies = new List<BaseAccessPolicy>(); |
||||
|
||||
if (projectServiceAccountsAccessPolicies != null) |
||||
{ |
||||
policies.AddRange(projectServiceAccountsAccessPolicies); |
||||
} |
||||
|
||||
AccessPolicyHelpers.CheckForDistinctAccessPolicies(policies); |
||||
AccessPolicyHelpers.CheckAccessPoliciesHasReadPermission(policies); |
||||
|
||||
return new ProjectServiceAccountsAccessPolicies |
||||
{ |
||||
Id = grantedProjectId, |
||||
OrganizationId = organizationId, |
||||
ServiceAccountProjectsAccessPolicies = projectServiceAccountsAccessPolicies, |
||||
}; |
||||
} |
||||
} |
||||
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
using Bit.Core.Models.Api; |
||||
using Bit.Core.SecretsManager.Entities; |
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response; |
||||
|
||||
public class ProjectServiceAccountsAccessPoliciesResponseModel : ResponseModel |
||||
{ |
||||
private const string _objectName = "projectServiceAccountsAccessPolicies"; |
||||
|
||||
public ProjectServiceAccountsAccessPoliciesResponseModel(IEnumerable<BaseAccessPolicy> baseAccessPolicies) |
||||
: base(_objectName) |
||||
{ |
||||
if (baseAccessPolicies == null) |
||||
{ |
||||
return; |
||||
} |
||||
|
||||
foreach (var baseAccessPolicy in baseAccessPolicies) |
||||
{ |
||||
switch (baseAccessPolicy) |
||||
{ |
||||
case ServiceAccountProjectAccessPolicy accessPolicy: |
||||
ServiceAccountsAccessPolicies.Add(new ServiceAccountProjectAccessPolicyResponseModel(accessPolicy)); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public ProjectServiceAccountsAccessPoliciesResponseModel() : base(_objectName) |
||||
{ |
||||
} |
||||
|
||||
public List<ServiceAccountProjectAccessPolicyResponseModel> ServiceAccountsAccessPolicies { get; set; } = new(); |
||||
|
||||
} |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.SecretsManager.Entities; |
||||
|
||||
namespace Bit.Api.SecretsManager.Utilities; |
||||
|
||||
public class AccessPolicyHelpers |
||||
{ |
||||
public static void CheckForDistinctAccessPolicies(IReadOnlyCollection<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), |
||||
UserServiceAccountAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId, |
||||
ap.GrantedServiceAccountId), |
||||
GroupServiceAccountAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedServiceAccountId), |
||||
_ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)), |
||||
}; |
||||
}).ToList(); |
||||
|
||||
if (accessPolicies.Count != distinctAccessPolicies.Count) |
||||
{ |
||||
throw new BadRequestException("Resources must be unique"); |
||||
} |
||||
} |
||||
|
||||
public static void CheckAccessPoliciesHasReadPermission(IReadOnlyCollection<BaseAccessPolicy> accessPolicies) |
||||
{ |
||||
var accessPoliciesPermission = accessPolicies.All(Policy => Policy.Read); //Has to be read, write can be true or false. |
||||
if (!accessPoliciesPermission) |
||||
{ |
||||
throw new BadRequestException("Resources must be Read = true"); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Authorization.Infrastructure; |
||||
|
||||
namespace Bit.Core.SecretsManager.AuthorizationRequirements; |
||||
|
||||
public class ProjectServiceAccountsAccessPoliciesOperationRequirement : OperationAuthorizationRequirement |
||||
{ |
||||
} |
||||
|
||||
public static class ProjectServiceAccountsAccessPoliciesOperations |
||||
{ |
||||
public static readonly ProjectServiceAccountsAccessPoliciesOperationRequirement Replace = new() { Name = nameof(Replace) }; |
||||
} |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
using Bit.Core.SecretsManager.Entities; |
||||
|
||||
namespace Bit.Core.SecretsManager.Models.Data; |
||||
|
||||
public class ProjectServiceAccountsAccessPolicies |
||||
{ |
||||
public Guid Id { get; set; } |
||||
public Guid OrganizationId { get; set; } |
||||
public IEnumerable<ServiceAccountProjectAccessPolicy> ServiceAccountProjectsAccessPolicies { get; set; } |
||||
|
||||
public IEnumerable<BaseAccessPolicy> ToBaseAccessPolicies() |
||||
{ |
||||
var policies = new List<BaseAccessPolicy>(); |
||||
|
||||
if (ServiceAccountProjectsAccessPolicies != null && ServiceAccountProjectsAccessPolicies.Any()) |
||||
{ |
||||
policies.AddRange(ServiceAccountProjectsAccessPolicies); |
||||
} |
||||
|
||||
return policies; |
||||
} |
||||
} |
||||
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
using Bit.Api.SecretsManager.Utilities; |
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.SecretsManager.Entities; |
||||
using Xunit; |
||||
|
||||
namespace Bit.Api.Test.SecretsManager.Utilities; |
||||
|
||||
public class AccessPolicyHelpersTests |
||||
{ |
||||
[Fact] |
||||
public void CheckForDistinctAccessPolicies_ThrowsExceptionWhenDuplicateExists() |
||||
{ |
||||
var duplicatePolicy = new UserProjectAccessPolicy |
||||
{ |
||||
OrganizationUserId = Guid.NewGuid(), |
||||
GrantedProjectId = Guid.NewGuid() |
||||
}; |
||||
|
||||
var accessPolicies = new List<BaseAccessPolicy> |
||||
{ |
||||
duplicatePolicy, |
||||
duplicatePolicy // Duplicate policy |
||||
}; |
||||
|
||||
Assert.Throws<BadRequestException>(() => |
||||
{ |
||||
AccessPolicyHelpers.CheckForDistinctAccessPolicies(accessPolicies); |
||||
}); |
||||
} |
||||
|
||||
[Fact] |
||||
public void CheckForDistinctAccessPolicies_Success() |
||||
{ |
||||
var accessPolicies = new List<BaseAccessPolicy> |
||||
{ |
||||
new UserProjectAccessPolicy |
||||
{ |
||||
OrganizationUserId = Guid.NewGuid(), |
||||
GrantedProjectId = Guid.NewGuid() |
||||
}, |
||||
new GroupProjectAccessPolicy |
||||
{ |
||||
GroupId = Guid.NewGuid(), |
||||
GrantedProjectId = Guid.NewGuid() |
||||
} |
||||
}; |
||||
|
||||
var exception = Record.Exception(() => AccessPolicyHelpers.CheckForDistinctAccessPolicies(accessPolicies)); |
||||
Assert.Null(exception); |
||||
} |
||||
|
||||
[Fact] |
||||
public void CheckAccessPoliciesHasReadPermission_ThrowsExceptionWhenReadPermissionIsFalse() |
||||
{ |
||||
var accessPolicies = new List<BaseAccessPolicy> |
||||
{ |
||||
new UserProjectAccessPolicy { Read = false, Write = true }, |
||||
new GroupProjectAccessPolicy { Read = true, Write = false } |
||||
}; |
||||
|
||||
Assert.Throws<BadRequestException>(() => |
||||
{ |
||||
AccessPolicyHelpers.CheckAccessPoliciesHasReadPermission(accessPolicies); |
||||
}); |
||||
} |
||||
|
||||
[Fact] |
||||
public void CheckAccessPoliciesHasReadPermission_Success() |
||||
{ |
||||
var accessPolicies = new List<BaseAccessPolicy> |
||||
{ |
||||
new UserProjectAccessPolicy { Read = true, Write = true }, |
||||
new GroupProjectAccessPolicy { Read = true, Write = false } |
||||
}; |
||||
|
||||
var exception = Record.Exception(() => AccessPolicyHelpers.CheckAccessPoliciesHasReadPermission(accessPolicies)); |
||||
Assert.Null(exception); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue