mirror of
https://github.com/bitwarden/server.git
synced 2025-04-04 20:50:21 -05:00
[SM-910] Add service account granted policies management endpoints (#3736)
* Add the ability to get multi projects access * Add access policy helper + tests * Add new data/request models * Add access policy operations to repo * Add authz handler for new operations * Add new controller endpoints * add updating service account revision
This commit is contained in:
parent
a14646eaad
commit
ebd88393c8
@ -0,0 +1,88 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
using Bit.Core.SecretsManager.Queries.Interfaces;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies;
|
||||
|
||||
public class ServiceAccountGrantedPoliciesAuthorizationHandler : AuthorizationHandler<
|
||||
ServiceAccountGrantedPoliciesOperationRequirement,
|
||||
ServiceAccountGrantedPoliciesUpdates>
|
||||
{
|
||||
private readonly IAccessClientQuery _accessClientQuery;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
|
||||
public ServiceAccountGrantedPoliciesAuthorizationHandler(ICurrentContext currentContext,
|
||||
IAccessClientQuery accessClientQuery,
|
||||
IProjectRepository projectRepository,
|
||||
IServiceAccountRepository serviceAccountRepository)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_accessClientQuery = accessClientQuery;
|
||||
_serviceAccountRepository = serviceAccountRepository;
|
||||
_projectRepository = projectRepository;
|
||||
}
|
||||
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
|
||||
ServiceAccountGrantedPoliciesOperationRequirement requirement,
|
||||
ServiceAccountGrantedPoliciesUpdates 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 == ServiceAccountGrantedPoliciesOperations.Updates:
|
||||
await CanUpdateAsync(context, requirement, resource, accessClient,
|
||||
userId);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unsupported operation requirement type provided.",
|
||||
nameof(requirement));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CanUpdateAsync(AuthorizationHandlerContext context,
|
||||
ServiceAccountGrantedPoliciesOperationRequirement requirement, ServiceAccountGrantedPoliciesUpdates resource,
|
||||
AccessClientType accessClient, Guid userId)
|
||||
{
|
||||
var access =
|
||||
await _serviceAccountRepository.AccessToServiceAccountAsync(resource.ServiceAccountId, userId,
|
||||
accessClient);
|
||||
if (access.Write)
|
||||
{
|
||||
var projectIdsToCheck = resource.ProjectGrantedPolicyUpdates.Select(update =>
|
||||
update.AccessPolicy.GrantedProjectId!.Value).ToList();
|
||||
|
||||
var sameOrganization =
|
||||
await _projectRepository.ProjectsAreInOrganization(projectIdsToCheck, resource.OrganizationId);
|
||||
if (!sameOrganization)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var projectsAccess =
|
||||
await _projectRepository.AccessToProjectsAsync(projectIdsToCheck, userId, accessClient);
|
||||
if (projectsAccess.Count == projectIdsToCheck.Count && projectsAccess.All(a => a.Value.Write))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
#nullable enable
|
||||
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
|
||||
namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
|
||||
|
||||
public class UpdateServiceAccountGrantedPoliciesCommand : IUpdateServiceAccountGrantedPoliciesCommand
|
||||
{
|
||||
private readonly IAccessPolicyRepository _accessPolicyRepository;
|
||||
|
||||
public UpdateServiceAccountGrantedPoliciesCommand(IAccessPolicyRepository accessPolicyRepository)
|
||||
{
|
||||
_accessPolicyRepository = accessPolicyRepository;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(ServiceAccountGrantedPoliciesUpdates grantedPoliciesUpdates)
|
||||
{
|
||||
if (!grantedPoliciesUpdates.ProjectGrantedPolicyUpdates.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _accessPolicyRepository.UpdateServiceAccountGrantedPoliciesAsync(grantedPoliciesUpdates);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
#nullable enable
|
||||
using Bit.Core.SecretsManager.Enums.AccessPolicies;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
using Bit.Core.SecretsManager.Models.Data.AccessPolicyUpdates;
|
||||
using Bit.Core.SecretsManager.Queries.AccessPolicies.Interfaces;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
|
||||
namespace Bit.Commercial.Core.SecretsManager.Queries.AccessPolicies;
|
||||
|
||||
public class ServiceAccountGrantedPolicyUpdatesQuery : IServiceAccountGrantedPolicyUpdatesQuery
|
||||
{
|
||||
private readonly IAccessPolicyRepository _accessPolicyRepository;
|
||||
|
||||
public ServiceAccountGrantedPolicyUpdatesQuery(IAccessPolicyRepository accessPolicyRepository)
|
||||
{
|
||||
_accessPolicyRepository = accessPolicyRepository;
|
||||
}
|
||||
|
||||
public async Task<ServiceAccountGrantedPoliciesUpdates> GetAsync(
|
||||
ServiceAccountGrantedPolicies grantedPolicies)
|
||||
{
|
||||
var currentPolicies =
|
||||
await _accessPolicyRepository.GetServiceAccountGrantedPoliciesAsync(grantedPolicies.ServiceAccountId);
|
||||
if (currentPolicies == null)
|
||||
{
|
||||
return new ServiceAccountGrantedPoliciesUpdates
|
||||
{
|
||||
ServiceAccountId = grantedPolicies.ServiceAccountId,
|
||||
OrganizationId = grantedPolicies.OrganizationId,
|
||||
ProjectGrantedPolicyUpdates = grantedPolicies.ProjectGrantedPolicies.Select(p =>
|
||||
new ServiceAccountProjectAccessPolicyUpdate
|
||||
{
|
||||
Operation = AccessPolicyOperation.Create,
|
||||
AccessPolicy = p
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
return currentPolicies.GetPolicyUpdates(grantedPolicies);
|
||||
}
|
||||
}
|
@ -41,10 +41,12 @@ public static class SecretsManagerCollectionExtensions
|
||||
services.AddScoped<IAuthorizationHandler, AccessPolicyAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, ProjectPeopleAccessPoliciesAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, ServiceAccountPeopleAccessPoliciesAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, ServiceAccountGrantedPoliciesAuthorizationHandler>();
|
||||
services.AddScoped<IAccessClientQuery, AccessClientQuery>();
|
||||
services.AddScoped<IMaxProjectsQuery, MaxProjectsQuery>();
|
||||
services.AddScoped<ISameOrganizationQuery, SameOrganizationQuery>();
|
||||
services.AddScoped<IServiceAccountSecretsDetailsQuery, ServiceAccountSecretsDetailsQuery>();
|
||||
services.AddScoped<IServiceAccountGrantedPolicyUpdatesQuery, ServiceAccountGrantedPolicyUpdatesQuery>();
|
||||
services.AddScoped<ISecretsSyncQuery, SecretsSyncQuery>();
|
||||
services.AddScoped<ICreateSecretCommand, CreateSecretCommand>();
|
||||
services.AddScoped<IUpdateSecretCommand, UpdateSecretCommand>();
|
||||
@ -64,5 +66,6 @@ public static class SecretsManagerCollectionExtensions
|
||||
services.AddScoped<IImportCommand, ImportCommand>();
|
||||
services.AddScoped<IEmptyTrashCommand, EmptyTrashCommand>();
|
||||
services.AddScoped<IRestoreTrashCommand, RestoreTrashCommand>();
|
||||
services.AddScoped<IUpdateServiceAccountGrantedPoliciesCommand, UpdateServiceAccountGrantedPoliciesCommand>();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
using System.Linq.Expressions;
|
||||
using AutoMapper;
|
||||
using AutoMapper;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.Enums.AccessPolicies;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
using Bit.Core.SecretsManager.Models.Data.AccessPolicyUpdates;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.SecretsManager.Discriminators;
|
||||
@ -19,14 +20,7 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
|
||||
{
|
||||
}
|
||||
|
||||
private static Expression<Func<ServiceAccountProjectAccessPolicy, bool>> UserHasWriteAccessToProject(Guid userId) =>
|
||||
policy =>
|
||||
policy.GrantedProject.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Write) ||
|
||||
policy.GrantedProject.GroupAccessPolicies.Any(ap =>
|
||||
ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Write));
|
||||
|
||||
public async Task<List<Core.SecretsManager.Entities.BaseAccessPolicy>> CreateManyAsync(
|
||||
List<Core.SecretsManager.Entities.BaseAccessPolicy> baseAccessPolicies)
|
||||
public async Task<List<Core.SecretsManager.Entities.BaseAccessPolicy>> CreateManyAsync(List<Core.SecretsManager.Entities.BaseAccessPolicy> baseAccessPolicies)
|
||||
{
|
||||
await using var scope = ServiceScopeFactory.CreateAsyncScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
@ -219,29 +213,6 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Core.SecretsManager.Entities.BaseAccessPolicy>> GetManyByServiceAccountIdAsync(Guid id, Guid userId,
|
||||
AccessClientType accessType)
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var query = dbContext.ServiceAccountProjectAccessPolicy.Where(ap =>
|
||||
ap.ServiceAccountId == id);
|
||||
|
||||
query = accessType switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => query,
|
||||
AccessClientType.User => query.Where(UserHasWriteAccessToProject(userId)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null),
|
||||
};
|
||||
|
||||
var entities = await query
|
||||
.Include(ap => ap.ServiceAccount)
|
||||
.Include(ap => ap.GrantedProject)
|
||||
.ToListAsync();
|
||||
|
||||
return entities.Select(MapToCore);
|
||||
}
|
||||
|
||||
public async Task<PeopleGrantees> GetPeopleGranteesAsync(Guid organizationId, Guid currentUserId)
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
@ -429,6 +400,77 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
|
||||
return await GetPeoplePoliciesByGrantedServiceAccountIdAsync(peopleAccessPolicies.Id, userId);
|
||||
}
|
||||
|
||||
public async Task<ServiceAccountGrantedPolicies?> GetServiceAccountGrantedPoliciesAsync(Guid serviceAccountId)
|
||||
{
|
||||
await using var scope = ServiceScopeFactory.CreateAsyncScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var entities = await dbContext.ServiceAccountProjectAccessPolicy
|
||||
.Where(ap => ap.ServiceAccountId == serviceAccountId)
|
||||
.Include(ap => ap.ServiceAccount)
|
||||
.Include(ap => ap.GrantedProject)
|
||||
.ToListAsync();
|
||||
|
||||
if (entities.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new ServiceAccountGrantedPolicies(serviceAccountId, entities.Select(MapToCore).ToList());
|
||||
}
|
||||
|
||||
public async Task<ServiceAccountGrantedPoliciesPermissionDetails?>
|
||||
GetServiceAccountGrantedPoliciesPermissionDetailsAsync(Guid serviceAccountId, Guid userId,
|
||||
AccessClientType accessClientType)
|
||||
{
|
||||
await using var scope = ServiceScopeFactory.CreateAsyncScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var accessPolicyQuery = dbContext.ServiceAccountProjectAccessPolicy
|
||||
.Where(ap => ap.ServiceAccountId == serviceAccountId)
|
||||
.Include(ap => ap.ServiceAccount)
|
||||
.Include(ap => ap.GrantedProject);
|
||||
|
||||
var accessPoliciesPermissionDetails =
|
||||
await ToPermissionDetails(accessPolicyQuery, userId, accessClientType).ToListAsync();
|
||||
if (accessPoliciesPermissionDetails.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ServiceAccountGrantedPoliciesPermissionDetails
|
||||
{
|
||||
ServiceAccountId = serviceAccountId,
|
||||
OrganizationId = accessPoliciesPermissionDetails.First().AccessPolicy.GrantedProject!.OrganizationId,
|
||||
ProjectGrantedPolicies = accessPoliciesPermissionDetails
|
||||
};
|
||||
}
|
||||
|
||||
public async Task UpdateServiceAccountGrantedPoliciesAsync(ServiceAccountGrantedPoliciesUpdates updates)
|
||||
{
|
||||
await using var scope = ServiceScopeFactory.CreateAsyncScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var currentAccessPolicies = await dbContext.ServiceAccountProjectAccessPolicy
|
||||
.Where(ap => ap.ServiceAccountId == updates.ServiceAccountId)
|
||||
.ToListAsync();
|
||||
|
||||
if (currentAccessPolicies.Count != 0)
|
||||
{
|
||||
var projectIdsToDelete = updates.ProjectGrantedPolicyUpdates
|
||||
.Where(pu => pu.Operation == AccessPolicyOperation.Delete)
|
||||
.Select(pu => pu.AccessPolicy.GrantedProjectId!.Value)
|
||||
.ToList();
|
||||
|
||||
var policiesToDelete = currentAccessPolicies
|
||||
.Where(entity => projectIdsToDelete.Contains(entity.GrantedProjectId!.Value))
|
||||
.ToList();
|
||||
|
||||
dbContext.RemoveRange(policiesToDelete);
|
||||
}
|
||||
|
||||
await UpsertServiceAccountGrantedPoliciesAsync(dbContext, currentAccessPolicies,
|
||||
updates.ProjectGrantedPolicyUpdates.Where(pu => pu.Operation != AccessPolicyOperation.Delete).ToList());
|
||||
await UpdateServiceAccountRevisionAsync(dbContext, updates.ServiceAccountId);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task UpsertPeoplePoliciesAsync(DatabaseContext dbContext,
|
||||
List<BaseAccessPolicy> policies, IReadOnlyCollection<AccessPolicy> userPolicyEntities,
|
||||
IReadOnlyCollection<AccessPolicy> groupPolicyEntities)
|
||||
@ -464,6 +506,36 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpsertServiceAccountGrantedPoliciesAsync(DatabaseContext dbContext,
|
||||
IReadOnlyCollection<ServiceAccountProjectAccessPolicy> currentPolices,
|
||||
List<ServiceAccountProjectAccessPolicyUpdate> policyUpdates)
|
||||
{
|
||||
var currentDate = DateTime.UtcNow;
|
||||
foreach (var policyUpdate in policyUpdates)
|
||||
{
|
||||
var updatedEntity = MapToEntity(policyUpdate.AccessPolicy);
|
||||
var currentEntity = currentPolices.FirstOrDefault(e =>
|
||||
e.GrantedProjectId == policyUpdate.AccessPolicy.GrantedProjectId!.Value);
|
||||
|
||||
switch (policyUpdate.Operation)
|
||||
{
|
||||
case AccessPolicyOperation.Create when currentEntity == null:
|
||||
updatedEntity.SetNewId();
|
||||
await dbContext.AddAsync(updatedEntity);
|
||||
break;
|
||||
|
||||
case AccessPolicyOperation.Update when currentEntity != null:
|
||||
dbContext.AccessPolicies.Attach(currentEntity);
|
||||
currentEntity.Read = updatedEntity.Read;
|
||||
currentEntity.Write = updatedEntity.Write;
|
||||
currentEntity.RevisionDate = currentDate;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Policy updates failed due to unexpected state.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore(
|
||||
BaseAccessPolicy baseAccessPolicyEntity) =>
|
||||
baseAccessPolicyEntity switch
|
||||
@ -518,4 +590,42 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
|
||||
return MapToCore(baseAccessPolicyEntity);
|
||||
}
|
||||
}
|
||||
|
||||
private IQueryable<ServiceAccountProjectAccessPolicyPermissionDetails> ToPermissionDetails(
|
||||
IQueryable<ServiceAccountProjectAccessPolicy>
|
||||
query, Guid userId, AccessClientType accessClientType)
|
||||
{
|
||||
var permissionDetails = accessClientType switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => query.Select(ap => new ServiceAccountProjectAccessPolicyPermissionDetails
|
||||
{
|
||||
AccessPolicy =
|
||||
Mapper.Map<Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy>(ap),
|
||||
HasPermission = true
|
||||
}),
|
||||
AccessClientType.User => query.Select(ap => new ServiceAccountProjectAccessPolicyPermissionDetails
|
||||
{
|
||||
AccessPolicy =
|
||||
Mapper.Map<Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy>(ap),
|
||||
HasPermission =
|
||||
(ap.GrantedProject.UserAccessPolicies.Any(p => p.OrganizationUser.UserId == userId && p.Write) ||
|
||||
ap.GrantedProject.GroupAccessPolicies.Any(p =>
|
||||
p.Group.GroupUsers.Any(gu => gu.OrganizationUser.UserId == userId && p.Write))) &&
|
||||
(ap.ServiceAccount.UserAccessPolicies.Any(p => p.OrganizationUser.UserId == userId && p.Write) ||
|
||||
ap.ServiceAccount.GroupAccessPolicies.Any(p =>
|
||||
p.Group.GroupUsers.Any(gu => gu.OrganizationUser.UserId == userId && p.Write)))
|
||||
}),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(accessClientType), accessClientType, null)
|
||||
};
|
||||
return permissionDetails;
|
||||
}
|
||||
|
||||
private static async Task UpdateServiceAccountRevisionAsync(DatabaseContext dbContext, Guid serviceAccountId)
|
||||
{
|
||||
var entity = await dbContext.ServiceAccount.FindAsync(serviceAccountId);
|
||||
if (entity != null)
|
||||
{
|
||||
entity.RevisionDate = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,27 +140,8 @@ public class ProjectRepository : Repository<Core.SecretsManager.Entities.Project
|
||||
var projectQuery = dbContext.Project
|
||||
.Where(s => s.Id == id);
|
||||
|
||||
var query = accessType switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => projectQuery.Select(_ => new { Read = true, Write = true }),
|
||||
AccessClientType.User => projectQuery.Select(p => new
|
||||
{
|
||||
Read = p.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Read)
|
||||
|| p.GroupAccessPolicies.Any(ap =>
|
||||
ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Read)),
|
||||
Write = p.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Write) ||
|
||||
p.GroupAccessPolicies.Any(ap =>
|
||||
ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Write)),
|
||||
}),
|
||||
AccessClientType.ServiceAccount => projectQuery.Select(p => new
|
||||
{
|
||||
Read = p.ServiceAccountAccessPolicies.Any(ap => ap.ServiceAccountId == userId && ap.Read),
|
||||
Write = p.ServiceAccountAccessPolicies.Any(ap => ap.ServiceAccountId == userId && ap.Write),
|
||||
}),
|
||||
_ => projectQuery.Select(_ => new { Read = false, Write = false }),
|
||||
};
|
||||
|
||||
var policy = await query.FirstOrDefaultAsync();
|
||||
var accessQuery = BuildProjectAccessQuery(projectQuery, userId, accessType);
|
||||
var policy = await accessQuery.FirstOrDefaultAsync();
|
||||
|
||||
return policy == null ? (false, false) : (policy.Read, policy.Write);
|
||||
}
|
||||
@ -174,6 +155,46 @@ public class ProjectRepository : Repository<Core.SecretsManager.Entities.Project
|
||||
return projectIds.Count == results.Count;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<Guid, (bool Read, bool Write)>> AccessToProjectsAsync(
|
||||
IEnumerable<Guid> projectIds,
|
||||
Guid userId,
|
||||
AccessClientType accessType)
|
||||
{
|
||||
await using var scope = ServiceScopeFactory.CreateAsyncScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
|
||||
var projectsQuery = dbContext.Project.Where(p => projectIds.Contains(p.Id));
|
||||
var accessQuery = BuildProjectAccessQuery(projectsQuery, userId, accessType);
|
||||
|
||||
return await accessQuery.ToDictionaryAsync(pa => pa.Id, pa => (pa.Read, pa.Write));
|
||||
}
|
||||
|
||||
private record ProjectAccess(Guid Id, bool Read, bool Write);
|
||||
|
||||
private static IQueryable<ProjectAccess> BuildProjectAccessQuery(IQueryable<Project> projectQuery, Guid userId,
|
||||
AccessClientType accessType) =>
|
||||
accessType switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => projectQuery.Select(p => new ProjectAccess(p.Id, true, true)),
|
||||
AccessClientType.User => projectQuery.Select(p => new ProjectAccess
|
||||
(
|
||||
p.Id,
|
||||
p.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Read) ||
|
||||
p.GroupAccessPolicies.Any(ap =>
|
||||
ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Read)),
|
||||
p.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Write) ||
|
||||
p.GroupAccessPolicies.Any(ap =>
|
||||
ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Write))
|
||||
)),
|
||||
AccessClientType.ServiceAccount => projectQuery.Select(p => new ProjectAccess
|
||||
(
|
||||
p.Id,
|
||||
p.ServiceAccountAccessPolicies.Any(ap => ap.ServiceAccountId == userId && ap.Read),
|
||||
p.ServiceAccountAccessPolicies.Any(ap => ap.ServiceAccountId == userId && ap.Write)
|
||||
)),
|
||||
_ => projectQuery.Select(p => new ProjectAccess(p.Id, false, false))
|
||||
};
|
||||
|
||||
private IQueryable<ProjectPermissionDetails> ProjectToPermissionDetails(IQueryable<Project> query, Guid userId, AccessClientType accessType)
|
||||
{
|
||||
var projects = accessType switch
|
||||
|
@ -0,0 +1,273 @@
|
||||
#nullable enable
|
||||
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.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 ServiceAccountGrantedPoliciesAuthorizationHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public void ServiceAccountGrantedPoliciesOperations_OnlyPublicStatic()
|
||||
{
|
||||
var publicStaticFields =
|
||||
typeof(ServiceAccountGrantedPoliciesOperations).GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||
var allFields = typeof(ServiceAccountGrantedPoliciesOperations).GetFields();
|
||||
Assert.Equal(publicStaticFields.Length, allFields.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Handler_AccessSecretsManagerFalse_DoesNotSucceed(
|
||||
SutProvider<ServiceAccountGrantedPoliciesAuthorizationHandler> sutProvider,
|
||||
ServiceAccountGrantedPoliciesUpdates resource,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = ServiceAccountGrantedPoliciesOperations.Updates;
|
||||
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.ServiceAccount)]
|
||||
[BitAutoData(AccessClientType.Organization)]
|
||||
public async Task Handler_UnsupportedClientTypes_DoesNotSucceed(
|
||||
AccessClientType accessClientType,
|
||||
SutProvider<ServiceAccountGrantedPoliciesAuthorizationHandler> sutProvider,
|
||||
ServiceAccountGrantedPoliciesUpdates resource,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = ServiceAccountGrantedPoliciesOperations.Updates;
|
||||
SetupUserSubstitutes(sutProvider, accessClientType, resource);
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, resource);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.False(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Handler_UnsupportedServiceAccountGrantedPoliciesOperationRequirement_Throws(
|
||||
SutProvider<ServiceAccountGrantedPoliciesAuthorizationHandler> sutProvider,
|
||||
ServiceAccountGrantedPoliciesUpdates resource,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = new ServiceAccountGrantedPoliciesOperationRequirement();
|
||||
SetupUserSubstitutes(sutProvider, AccessClientType.NoAccessCheck, resource);
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, resource);
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => sutProvider.Sut.HandleAsync(authzContext));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(AccessClientType.NoAccessCheck, false, false)]
|
||||
[BitAutoData(AccessClientType.NoAccessCheck, true, false)]
|
||||
[BitAutoData(AccessClientType.User, false, false)]
|
||||
[BitAutoData(AccessClientType.User, true, false)]
|
||||
public async Task Handler_UserHasNoWriteAccessToServiceAccount_DoesNotSucceed(
|
||||
AccessClientType accessClientType,
|
||||
bool saReadAccess,
|
||||
bool saWriteAccess,
|
||||
SutProvider<ServiceAccountGrantedPoliciesAuthorizationHandler> sutProvider,
|
||||
ServiceAccountGrantedPoliciesUpdates resource,
|
||||
Guid userId,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = ServiceAccountGrantedPoliciesOperations.Updates;
|
||||
SetupUserSubstitutes(sutProvider, accessClientType, resource, userId);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>()
|
||||
.AccessToServiceAccountAsync(resource.ServiceAccountId, userId, accessClientType)
|
||||
.Returns((saReadAccess, saWriteAccess));
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, resource);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.False(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Handler_GrantedProjectsInDifferentOrganization_DoesNotSucceed(
|
||||
SutProvider<ServiceAccountGrantedPoliciesAuthorizationHandler> sutProvider,
|
||||
ServiceAccountGrantedPoliciesUpdates resource,
|
||||
Guid userId,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = ServiceAccountGrantedPoliciesOperations.Updates;
|
||||
SetupUserSubstitutes(sutProvider, AccessClientType.NoAccessCheck, resource, userId);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>()
|
||||
.AccessToServiceAccountAsync(resource.ServiceAccountId, userId, AccessClientType.NoAccessCheck)
|
||||
.Returns((true, true));
|
||||
sutProvider.GetDependency<IProjectRepository>()
|
||||
.ProjectsAreInOrganization(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.NoAccessCheck)]
|
||||
[BitAutoData(AccessClientType.User)]
|
||||
public async Task Handler_UserHasNoAccessToGrantedProjects_DoesNotSucceed(
|
||||
AccessClientType accessClientType,
|
||||
SutProvider<ServiceAccountGrantedPoliciesAuthorizationHandler> sutProvider,
|
||||
ServiceAccountGrantedPoliciesUpdates resource,
|
||||
Guid userId,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = ServiceAccountGrantedPoliciesOperations.Updates;
|
||||
var projectIds = SetupProjectAccessTest(sutProvider, accessClientType, resource, userId);
|
||||
|
||||
sutProvider.GetDependency<IProjectRepository>()
|
||||
.AccessToProjectsAsync(Arg.Any<List<Guid>>(), userId, accessClientType)
|
||||
.Returns(projectIds.ToDictionary(projectId => projectId, _ => (false, false)));
|
||||
|
||||
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, resource);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.False(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(AccessClientType.NoAccessCheck)]
|
||||
[BitAutoData(AccessClientType.User)]
|
||||
public async Task Handler_UserHasAccessToSomeGrantedProjects_DoesNotSucceed(
|
||||
AccessClientType accessClientType,
|
||||
SutProvider<ServiceAccountGrantedPoliciesAuthorizationHandler> sutProvider,
|
||||
ServiceAccountGrantedPoliciesUpdates resource,
|
||||
Guid userId,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = ServiceAccountGrantedPoliciesOperations.Updates;
|
||||
var projectIds = SetupProjectAccessTest(sutProvider, accessClientType, resource, userId);
|
||||
|
||||
var accessResult = projectIds.ToDictionary(projectId => projectId, _ => (false, false));
|
||||
accessResult[projectIds.First()] = (true, true);
|
||||
sutProvider.GetDependency<IProjectRepository>()
|
||||
.AccessToProjectsAsync(Arg.Any<List<Guid>>(), userId, accessClientType)
|
||||
.Returns(accessResult);
|
||||
|
||||
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, resource);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.False(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(AccessClientType.NoAccessCheck)]
|
||||
[BitAutoData(AccessClientType.User)]
|
||||
public async Task Handler_AccessResultsPartial_DoesNotSucceed(
|
||||
AccessClientType accessClientType,
|
||||
SutProvider<ServiceAccountGrantedPoliciesAuthorizationHandler> sutProvider,
|
||||
ServiceAccountGrantedPoliciesUpdates resource,
|
||||
Guid userId,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = ServiceAccountGrantedPoliciesOperations.Updates;
|
||||
var projectIds = SetupProjectAccessTest(sutProvider, accessClientType, resource, userId);
|
||||
|
||||
var accessResult = projectIds.ToDictionary(projectId => projectId, _ => (false, false));
|
||||
accessResult.Remove(projectIds.First());
|
||||
sutProvider.GetDependency<IProjectRepository>()
|
||||
.AccessToProjectsAsync(Arg.Any<List<Guid>>(), userId, accessClientType)
|
||||
.Returns(accessResult);
|
||||
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, resource);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.False(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(AccessClientType.NoAccessCheck)]
|
||||
[BitAutoData(AccessClientType.User)]
|
||||
public async Task Handler_UserHasAccessToAllGrantedProjects_Success(
|
||||
AccessClientType accessClientType,
|
||||
SutProvider<ServiceAccountGrantedPoliciesAuthorizationHandler> sutProvider,
|
||||
ServiceAccountGrantedPoliciesUpdates resource,
|
||||
Guid userId,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = ServiceAccountGrantedPoliciesOperations.Updates;
|
||||
var projectIds = SetupProjectAccessTest(sutProvider, accessClientType, resource, userId);
|
||||
|
||||
sutProvider.GetDependency<IProjectRepository>()
|
||||
.AccessToProjectsAsync(Arg.Any<List<Guid>>(), userId, accessClientType)
|
||||
.Returns(projectIds.ToDictionary(projectId => projectId, _ => (true, true)));
|
||||
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, resource);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.True(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
private static void SetupUserSubstitutes(
|
||||
SutProvider<ServiceAccountGrantedPoliciesAuthorizationHandler> sutProvider,
|
||||
AccessClientType accessClientType,
|
||||
ServiceAccountGrantedPoliciesUpdates resource,
|
||||
Guid userId = new())
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(resource.OrganizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, resource.OrganizationId)
|
||||
.ReturnsForAnyArgs((accessClientType, userId));
|
||||
}
|
||||
|
||||
private static List<Guid> SetupProjectAccessTest(
|
||||
SutProvider<ServiceAccountGrantedPoliciesAuthorizationHandler> sutProvider,
|
||||
AccessClientType accessClientType,
|
||||
ServiceAccountGrantedPoliciesUpdates resource,
|
||||
Guid userId = new())
|
||||
{
|
||||
SetupUserSubstitutes(sutProvider, accessClientType, resource, userId);
|
||||
|
||||
sutProvider.GetDependency<IServiceAccountRepository>()
|
||||
.AccessToServiceAccountAsync(resource.ServiceAccountId, userId, accessClientType)
|
||||
.Returns((true, true));
|
||||
sutProvider.GetDependency<IProjectRepository>()
|
||||
.ProjectsAreInOrganization(Arg.Any<List<Guid>>(), resource.OrganizationId)
|
||||
.Returns(true);
|
||||
|
||||
return resource.ProjectGrantedPolicyUpdates
|
||||
.Select(pu => pu.AccessPolicy.GrantedProjectId!.Value)
|
||||
.ToList();
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
#nullable enable
|
||||
using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessPolicies;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[ProjectCustomize]
|
||||
public class UpdateServiceAccountGrantedPoliciesCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_NoUpdates_DoesNotCallRepository(
|
||||
SutProvider<UpdateServiceAccountGrantedPoliciesCommand> sutProvider,
|
||||
ServiceAccountGrantedPoliciesUpdates data)
|
||||
{
|
||||
data.ProjectGrantedPolicyUpdates = [];
|
||||
await sutProvider.Sut.UpdateAsync(data);
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpdateServiceAccountGrantedPoliciesAsync(Arg.Any<ServiceAccountGrantedPoliciesUpdates>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_HasUpdates_CallsRepository(
|
||||
SutProvider<UpdateServiceAccountGrantedPoliciesCommand> sutProvider,
|
||||
ServiceAccountGrantedPoliciesUpdates data)
|
||||
{
|
||||
await sutProvider.Sut.UpdateAsync(data);
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>()
|
||||
.Received(1)
|
||||
.UpdateServiceAccountGrantedPoliciesAsync(Arg.Any<ServiceAccountGrantedPoliciesUpdates>());
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
#nullable enable
|
||||
using Bit.Commercial.Core.SecretsManager.Queries.AccessPolicies;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Enums.AccessPolicies;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Queries.AccessPolicies;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[ProjectCustomize]
|
||||
public class ServiceAccountGrantedPolicyUpdatesQueryTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetAsync_NoCurrentGrantedPolicies_ReturnsAllCreates(
|
||||
SutProvider<ServiceAccountGrantedPolicyUpdatesQuery> sutProvider,
|
||||
ServiceAccountGrantedPolicies data)
|
||||
{
|
||||
sutProvider.GetDependency<IAccessPolicyRepository>()
|
||||
.GetServiceAccountGrantedPoliciesAsync(data.ServiceAccountId)
|
||||
.ReturnsNullForAnyArgs();
|
||||
|
||||
var result = await sutProvider.Sut.GetAsync(data);
|
||||
|
||||
Assert.Equal(data.ServiceAccountId, result.ServiceAccountId);
|
||||
Assert.Equal(data.OrganizationId, result.OrganizationId);
|
||||
Assert.Equal(data.ProjectGrantedPolicies.Count(), result.ProjectGrantedPolicyUpdates.Count());
|
||||
Assert.All(result.ProjectGrantedPolicyUpdates, p =>
|
||||
{
|
||||
Assert.Equal(AccessPolicyOperation.Create, p.Operation);
|
||||
Assert.Contains(data.ProjectGrantedPolicies, x => x == p.AccessPolicy);
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetAsync_CurrentGrantedPolicies_ReturnsChanges(
|
||||
SutProvider<ServiceAccountGrantedPolicyUpdatesQuery> sutProvider,
|
||||
ServiceAccountGrantedPolicies data, ServiceAccountProjectAccessPolicy currentPolicyToDelete)
|
||||
{
|
||||
foreach (var grantedPolicy in data.ProjectGrantedPolicies)
|
||||
{
|
||||
grantedPolicy.ServiceAccountId = data.ServiceAccountId;
|
||||
}
|
||||
|
||||
currentPolicyToDelete.ServiceAccountId = data.ServiceAccountId;
|
||||
|
||||
var updatePolicy = new ServiceAccountProjectAccessPolicy
|
||||
{
|
||||
ServiceAccountId = data.ServiceAccountId,
|
||||
GrantedProjectId = data.ProjectGrantedPolicies.First().GrantedProjectId,
|
||||
Read = !data.ProjectGrantedPolicies.First().Read,
|
||||
Write = !data.ProjectGrantedPolicies.First().Write
|
||||
};
|
||||
|
||||
var currentPolicies = new ServiceAccountGrantedPolicies
|
||||
{
|
||||
ServiceAccountId = data.ServiceAccountId,
|
||||
OrganizationId = data.OrganizationId,
|
||||
ProjectGrantedPolicies = [updatePolicy, currentPolicyToDelete]
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IAccessPolicyRepository>()
|
||||
.GetServiceAccountGrantedPoliciesAsync(data.ServiceAccountId)
|
||||
.ReturnsForAnyArgs(currentPolicies);
|
||||
|
||||
var result = await sutProvider.Sut.GetAsync(data);
|
||||
|
||||
Assert.Equal(data.ServiceAccountId, result.ServiceAccountId);
|
||||
Assert.Equal(data.OrganizationId, result.OrganizationId);
|
||||
Assert.Single(result.ProjectGrantedPolicyUpdates.Where(x =>
|
||||
x.Operation == AccessPolicyOperation.Delete && x.AccessPolicy == currentPolicyToDelete));
|
||||
Assert.Single(result.ProjectGrantedPolicyUpdates.Where(x =>
|
||||
x.Operation == AccessPolicyOperation.Update &&
|
||||
x.AccessPolicy.GrantedProjectId == updatePolicy.GrantedProjectId));
|
||||
Assert.Equal(result.ProjectGrantedPolicyUpdates.Count() - 2,
|
||||
result.ProjectGrantedPolicyUpdates.Count(x => x.Operation == AccessPolicyOperation.Create));
|
||||
}
|
||||
}
|
@ -7,6 +7,8 @@ using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Queries.AccessPolicies.Interfaces;
|
||||
using Bit.Core.SecretsManager.Queries.Interfaces;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -26,6 +28,9 @@ public class AccessPoliciesController : Controller
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
private readonly IUpdateAccessPolicyCommand _updateAccessPolicyCommand;
|
||||
private readonly IUpdateServiceAccountGrantedPoliciesCommand _updateServiceAccountGrantedPoliciesCommand;
|
||||
private readonly IAccessClientQuery _accessClientQuery;
|
||||
private readonly IServiceAccountGrantedPolicyUpdatesQuery _serviceAccountGrantedPolicyUpdatesQuery;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
@ -36,6 +41,9 @@ public class AccessPoliciesController : Controller
|
||||
IAccessPolicyRepository accessPolicyRepository,
|
||||
IServiceAccountRepository serviceAccountRepository,
|
||||
IProjectRepository projectRepository,
|
||||
IAccessClientQuery accessClientQuery,
|
||||
IServiceAccountGrantedPolicyUpdatesQuery serviceAccountGrantedPolicyUpdatesQuery,
|
||||
IUpdateServiceAccountGrantedPoliciesCommand updateServiceAccountGrantedPoliciesCommand,
|
||||
ICreateAccessPoliciesCommand createAccessPoliciesCommand,
|
||||
IDeleteAccessPolicyCommand deleteAccessPolicyCommand,
|
||||
IUpdateAccessPolicyCommand updateAccessPolicyCommand)
|
||||
@ -49,6 +57,9 @@ public class AccessPoliciesController : Controller
|
||||
_createAccessPoliciesCommand = createAccessPoliciesCommand;
|
||||
_deleteAccessPolicyCommand = deleteAccessPolicyCommand;
|
||||
_updateAccessPolicyCommand = updateAccessPolicyCommand;
|
||||
_updateServiceAccountGrantedPoliciesCommand = updateServiceAccountGrantedPoliciesCommand;
|
||||
_accessClientQuery = accessClientQuery;
|
||||
_serviceAccountGrantedPolicyUpdatesQuery = serviceAccountGrantedPolicyUpdatesQuery;
|
||||
}
|
||||
|
||||
[HttpPost("/projects/{id}/access-policies")]
|
||||
@ -89,61 +100,6 @@ public class AccessPoliciesController : Controller
|
||||
return new ProjectAccessPoliciesResponseModel(results);
|
||||
}
|
||||
|
||||
[HttpPost("/service-accounts/{id}/granted-policies")]
|
||||
public async Task<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>
|
||||
CreateServiceAccountGrantedPoliciesAsync([FromRoute] Guid id,
|
||||
[FromBody] List<GrantedAccessPolicyRequest> requests)
|
||||
{
|
||||
if (requests.Count > _maxBulkCreation)
|
||||
{
|
||||
throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once.");
|
||||
}
|
||||
|
||||
if (requests.Count != requests.DistinctBy(request => request.GrantedId).Count())
|
||||
{
|
||||
throw new BadRequestException("Resources must be unique");
|
||||
}
|
||||
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
if (serviceAccount == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var policies = requests.Select(request => request.ToServiceAccountProjectAccessPolicy(id, serviceAccount.OrganizationId)).ToList();
|
||||
foreach (var policy in policies)
|
||||
{
|
||||
var authorizationResult = await _authorizationService.AuthorizeAsync(User, policy, AccessPolicyOperations.Create);
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
var results =
|
||||
await _createAccessPoliciesCommand.CreateManyAsync(new List<BaseAccessPolicy>(policies));
|
||||
var responses = results.Select(ap =>
|
||||
new ServiceAccountProjectAccessPolicyResponseModel((ServiceAccountProjectAccessPolicy)ap));
|
||||
return new ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>(responses);
|
||||
}
|
||||
|
||||
[HttpGet("/service-accounts/{id}/granted-policies")]
|
||||
public async Task<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>
|
||||
GetServiceAccountGrantedPoliciesAsync([FromRoute] Guid id)
|
||||
{
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
if (serviceAccount == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId);
|
||||
var results = await _accessPolicyRepository.GetManyByServiceAccountIdAsync(id, userId, accessClient);
|
||||
var responses = results.Select(ap =>
|
||||
new ServiceAccountProjectAccessPolicyResponseModel((ServiceAccountProjectAccessPolicy)ap));
|
||||
return new ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>(responses);
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<BaseAccessPolicyResponseModel> UpdateAccessPolicyAsync([FromRoute] Guid id,
|
||||
[FromBody] AccessPolicyUpdateRequest request)
|
||||
@ -303,6 +259,43 @@ public class AccessPoliciesController : Controller
|
||||
return new ServiceAccountPeopleAccessPoliciesResponseModel(results, userId);
|
||||
}
|
||||
|
||||
[HttpGet("/service-accounts/{id}/granted-policies")]
|
||||
public async Task<ServiceAccountGrantedPoliciesPermissionDetailsResponseModel>
|
||||
GetServiceAccountGrantedPoliciesAsync([FromRoute] Guid id)
|
||||
{
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
var authorizationResult =
|
||||
await _authorizationService.AuthorizeAsync(User, serviceAccount, ServiceAccountOperations.Update);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return await GetServiceAccountGrantedPoliciesAsync(serviceAccount);
|
||||
}
|
||||
|
||||
|
||||
[HttpPut("/service-accounts/{id}/granted-policies")]
|
||||
public async Task<ServiceAccountGrantedPoliciesPermissionDetailsResponseModel>
|
||||
PutServiceAccountGrantedPoliciesAsync([FromRoute] Guid id,
|
||||
[FromBody] ServiceAccountGrantedPoliciesRequestModel request)
|
||||
{
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id) ?? throw new NotFoundException();
|
||||
var grantedPoliciesUpdates =
|
||||
await _serviceAccountGrantedPolicyUpdatesQuery.GetAsync(request.ToGrantedPolicies(serviceAccount));
|
||||
|
||||
var authorizationResult = await _authorizationService.AuthorizeAsync(User, grantedPoliciesUpdates,
|
||||
ServiceAccountGrantedPoliciesOperations.Updates);
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await _updateServiceAccountGrantedPoliciesCommand.UpdateAsync(grantedPoliciesUpdates);
|
||||
return await GetServiceAccountGrantedPoliciesAsync(serviceAccount);
|
||||
}
|
||||
|
||||
private async Task<(AccessClientType AccessClientType, Guid UserId)> CheckUserHasWriteAccessToProjectAsync(Project project)
|
||||
{
|
||||
if (project == null)
|
||||
@ -355,4 +348,11 @@ public class AccessPoliciesController : Controller
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
return (accessClient, userId);
|
||||
}
|
||||
|
||||
private async Task<ServiceAccountGrantedPoliciesPermissionDetailsResponseModel> GetServiceAccountGrantedPoliciesAsync(ServiceAccount serviceAccount)
|
||||
{
|
||||
var (accessClient, userId) = await _accessClientQuery.GetAccessClientAsync(User, serviceAccount.OrganizationId);
|
||||
var results = await _accessPolicyRepository.GetServiceAccountGrantedPoliciesPermissionDetailsAsync(serviceAccount.Id, userId, accessClient);
|
||||
return new ServiceAccountGrantedPoliciesPermissionDetailsResponseModel(results);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
#nullable enable
|
||||
using Bit.Api.SecretsManager.Utilities;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Request;
|
||||
|
||||
public class ServiceAccountGrantedPoliciesRequestModel
|
||||
{
|
||||
public required IEnumerable<GrantedAccessPolicyRequest> ProjectGrantedPolicyRequests { get; set; }
|
||||
|
||||
public ServiceAccountGrantedPolicies ToGrantedPolicies(ServiceAccount serviceAccount)
|
||||
{
|
||||
var projectGrantedPolicies = ProjectGrantedPolicyRequests
|
||||
.Select(x => x.ToServiceAccountProjectAccessPolicy(serviceAccount.Id, serviceAccount.OrganizationId))
|
||||
.ToList();
|
||||
|
||||
AccessPolicyHelpers.CheckForDistinctAccessPolicies(projectGrantedPolicies);
|
||||
AccessPolicyHelpers.CheckAccessPoliciesHaveReadPermission(projectGrantedPolicies);
|
||||
|
||||
return new ServiceAccountGrantedPolicies
|
||||
{
|
||||
ServiceAccountId = serviceAccount.Id,
|
||||
OrganizationId = serviceAccount.OrganizationId,
|
||||
ProjectGrantedPolicies = projectGrantedPolicies
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class ServiceAccountGrantedPoliciesPermissionDetailsResponseModel : ResponseModel
|
||||
{
|
||||
private const string _objectName = "ServiceAccountGrantedPoliciesPermissionDetails";
|
||||
|
||||
public ServiceAccountGrantedPoliciesPermissionDetailsResponseModel(
|
||||
ServiceAccountGrantedPoliciesPermissionDetails? grantedPoliciesPermissionDetails)
|
||||
: base(_objectName)
|
||||
{
|
||||
if (grantedPoliciesPermissionDetails == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GrantedProjectPolicies = grantedPoliciesPermissionDetails.ProjectGrantedPolicies
|
||||
.Select(x => new ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel(x)).ToList();
|
||||
}
|
||||
|
||||
public ServiceAccountGrantedPoliciesPermissionDetailsResponseModel() : base(_objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public List<ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel> GrantedProjectPolicies { get; set; } =
|
||||
[];
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel : ResponseModel
|
||||
{
|
||||
private const string _objectName = "serviceAccountProjectAccessPolicyPermissionDetails";
|
||||
|
||||
public ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel(
|
||||
ServiceAccountProjectAccessPolicyPermissionDetails apPermissionDetails, string obj = _objectName) : base(obj)
|
||||
{
|
||||
AccessPolicy = new ServiceAccountProjectAccessPolicyResponseModel(apPermissionDetails.AccessPolicy);
|
||||
HasPermission = apPermissionDetails.HasPermission;
|
||||
}
|
||||
|
||||
public ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel()
|
||||
: base(_objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public ServiceAccountProjectAccessPolicyResponseModel AccessPolicy { get; set; } = new();
|
||||
public bool HasPermission { get; set; }
|
||||
}
|
40
src/Api/SecretsManager/Utilities/AccessPolicyHelpers.cs
Normal file
40
src/Api/SecretsManager/Utilities/AccessPolicyHelpers.cs
Normal file
@ -0,0 +1,40 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Utilities;
|
||||
|
||||
public static 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 CheckAccessPoliciesHaveReadPermission(IEnumerable<BaseAccessPolicy> accessPolicies)
|
||||
{
|
||||
var accessPoliciesPermission = accessPolicies.All(policy => policy.Read);
|
||||
if (!accessPoliciesPermission)
|
||||
{
|
||||
throw new BadRequestException("Resources must be Read = true");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
#nullable enable
|
||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||
|
||||
namespace Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||
|
||||
public class ServiceAccountGrantedPoliciesOperationRequirement : OperationAuthorizationRequirement
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static class ServiceAccountGrantedPoliciesOperations
|
||||
{
|
||||
public static readonly ServiceAccountGrantedPoliciesOperationRequirement Updates = new() { Name = nameof(Updates) };
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
#nullable enable
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
|
||||
|
||||
public interface IUpdateServiceAccountGrantedPoliciesCommand
|
||||
{
|
||||
Task UpdateAsync(ServiceAccountGrantedPoliciesUpdates grantedPoliciesUpdates);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
namespace Bit.Core.SecretsManager.Enums.AccessPolicies;
|
||||
|
||||
public enum AccessPolicyOperation
|
||||
{
|
||||
Create,
|
||||
Update,
|
||||
Delete
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
#nullable enable
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Enums.AccessPolicies;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Models.Data.AccessPolicyUpdates;
|
||||
|
||||
public class ServiceAccountProjectAccessPolicyUpdate
|
||||
{
|
||||
public AccessPolicyOperation Operation { get; set; }
|
||||
public required ServiceAccountProjectAccessPolicy AccessPolicy { get; set; }
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
#nullable enable
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Enums.AccessPolicies;
|
||||
using Bit.Core.SecretsManager.Models.Data.AccessPolicyUpdates;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
public class ServiceAccountGrantedPolicies
|
||||
{
|
||||
public ServiceAccountGrantedPolicies(Guid serviceAccountId, IEnumerable<BaseAccessPolicy> policies)
|
||||
{
|
||||
ServiceAccountId = serviceAccountId;
|
||||
ProjectGrantedPolicies = policies.Where(x => x is ServiceAccountProjectAccessPolicy)
|
||||
.Cast<ServiceAccountProjectAccessPolicy>().ToList();
|
||||
|
||||
var serviceAccount = ProjectGrantedPolicies.FirstOrDefault()?.ServiceAccount;
|
||||
if (serviceAccount != null)
|
||||
{
|
||||
OrganizationId = serviceAccount.OrganizationId;
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceAccountGrantedPolicies()
|
||||
{
|
||||
}
|
||||
|
||||
public Guid ServiceAccountId { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
|
||||
public IEnumerable<ServiceAccountProjectAccessPolicy> ProjectGrantedPolicies { get; set; } =
|
||||
new List<ServiceAccountProjectAccessPolicy>();
|
||||
|
||||
public ServiceAccountGrantedPoliciesUpdates GetPolicyUpdates(ServiceAccountGrantedPolicies requested)
|
||||
{
|
||||
var currentProjectIds = ProjectGrantedPolicies.Select(p => p.GrantedProjectId!.Value).ToList();
|
||||
var requestedProjectIds = requested.ProjectGrantedPolicies.Select(p => p.GrantedProjectId!.Value).ToList();
|
||||
|
||||
var projectIdsToBeDeleted = currentProjectIds.Except(requestedProjectIds).ToList();
|
||||
var projectIdsToBeCreated = requestedProjectIds.Except(currentProjectIds).ToList();
|
||||
var projectIdsToBeUpdated = GetProjectIdsToBeUpdated(requested);
|
||||
|
||||
var policiesToBeDeleted =
|
||||
CreatePolicyUpdates(ProjectGrantedPolicies, projectIdsToBeDeleted, AccessPolicyOperation.Delete);
|
||||
var policiesToBeCreated = CreatePolicyUpdates(requested.ProjectGrantedPolicies, projectIdsToBeCreated,
|
||||
AccessPolicyOperation.Create);
|
||||
var policiesToBeUpdated = CreatePolicyUpdates(requested.ProjectGrantedPolicies, projectIdsToBeUpdated,
|
||||
AccessPolicyOperation.Update);
|
||||
|
||||
return new ServiceAccountGrantedPoliciesUpdates
|
||||
{
|
||||
OrganizationId = OrganizationId,
|
||||
ServiceAccountId = ServiceAccountId,
|
||||
ProjectGrantedPolicyUpdates =
|
||||
policiesToBeDeleted.Concat(policiesToBeCreated).Concat(policiesToBeUpdated)
|
||||
};
|
||||
}
|
||||
|
||||
private static List<ServiceAccountProjectAccessPolicyUpdate> CreatePolicyUpdates(
|
||||
IEnumerable<ServiceAccountProjectAccessPolicy> policies, List<Guid> projectIds,
|
||||
AccessPolicyOperation operation) =>
|
||||
policies
|
||||
.Where(ap => projectIds.Contains(ap.GrantedProjectId!.Value))
|
||||
.Select(ap => new ServiceAccountProjectAccessPolicyUpdate { Operation = operation, AccessPolicy = ap })
|
||||
.ToList();
|
||||
|
||||
private List<Guid> GetProjectIdsToBeUpdated(ServiceAccountGrantedPolicies requested) =>
|
||||
ProjectGrantedPolicies
|
||||
.Where(currentAp => requested.ProjectGrantedPolicies.Any(requestedAp =>
|
||||
requestedAp.GrantedProjectId == currentAp.GrantedProjectId &&
|
||||
requestedAp.ServiceAccountId == currentAp.ServiceAccountId &&
|
||||
(requestedAp.Write != currentAp.Write || requestedAp.Read != currentAp.Read)))
|
||||
.Select(ap => ap.GrantedProjectId!.Value)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public class ServiceAccountGrantedPoliciesUpdates
|
||||
{
|
||||
public Guid ServiceAccountId { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
|
||||
public IEnumerable<ServiceAccountProjectAccessPolicyUpdate> ProjectGrantedPolicyUpdates { get; set; } =
|
||||
new List<ServiceAccountProjectAccessPolicyUpdate>();
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
#nullable enable
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
public class ServiceAccountGrantedPoliciesPermissionDetails
|
||||
{
|
||||
public Guid ServiceAccountId { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public required IEnumerable<ServiceAccountProjectAccessPolicyPermissionDetails> ProjectGrantedPolicies { get; set; }
|
||||
}
|
||||
|
||||
public class ServiceAccountProjectAccessPolicyPermissionDetails
|
||||
{
|
||||
public required ServiceAccountProjectAccessPolicy AccessPolicy { get; set; }
|
||||
public bool HasPermission { get; set; }
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
#nullable enable
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Queries.AccessPolicies.Interfaces;
|
||||
|
||||
public interface IServiceAccountGrantedPolicyUpdatesQuery
|
||||
{
|
||||
Task<ServiceAccountGrantedPoliciesUpdates> GetAsync(ServiceAccountGrantedPolicies grantedPolicies);
|
||||
}
|
@ -11,8 +11,6 @@ public interface IAccessPolicyRepository
|
||||
Task<bool> AccessPolicyExists(BaseAccessPolicy baseAccessPolicy);
|
||||
Task<BaseAccessPolicy?> GetByIdAsync(Guid id);
|
||||
Task<IEnumerable<BaseAccessPolicy>> GetManyByGrantedProjectIdAsync(Guid id, Guid userId);
|
||||
Task<IEnumerable<BaseAccessPolicy>> GetManyByServiceAccountIdAsync(Guid id, Guid userId,
|
||||
AccessClientType accessType);
|
||||
Task ReplaceAsync(BaseAccessPolicy baseAccessPolicy);
|
||||
Task DeleteAsync(Guid id);
|
||||
Task<IEnumerable<BaseAccessPolicy>> GetPeoplePoliciesByGrantedProjectIdAsync(Guid id, Guid userId);
|
||||
@ -20,4 +18,8 @@ public interface IAccessPolicyRepository
|
||||
Task<PeopleGrantees> GetPeopleGranteesAsync(Guid organizationId, Guid currentUserId);
|
||||
Task<IEnumerable<BaseAccessPolicy>> GetPeoplePoliciesByGrantedServiceAccountIdAsync(Guid id, Guid userId);
|
||||
Task<IEnumerable<BaseAccessPolicy>> ReplaceServiceAccountPeopleAsync(ServiceAccountPeopleAccessPolicies peopleAccessPolicies, Guid userId);
|
||||
Task<ServiceAccountGrantedPolicies?> GetServiceAccountGrantedPoliciesAsync(Guid serviceAccountId);
|
||||
Task<ServiceAccountGrantedPoliciesPermissionDetails?> GetServiceAccountGrantedPoliciesPermissionDetailsAsync(
|
||||
Guid serviceAccountId, Guid userId, AccessClientType accessClientType);
|
||||
Task UpdateServiceAccountGrantedPoliciesAsync(ServiceAccountGrantedPoliciesUpdates policyUpdates);
|
||||
}
|
||||
|
@ -17,4 +17,6 @@ public interface IProjectRepository
|
||||
Task<(bool Read, bool Write)> AccessToProjectAsync(Guid id, Guid userId, AccessClientType accessType);
|
||||
Task<bool> ProjectsAreInOrganization(List<Guid> projectIds, Guid organizationId);
|
||||
Task<int> GetProjectCountByOrganizationIdAsync(Guid organizationId);
|
||||
Task<Dictionary<Guid, (bool Read, bool Write)>> AccessToProjectsAsync(IEnumerable<Guid> projectIds, Guid userId,
|
||||
AccessClientType accessType);
|
||||
}
|
||||
|
@ -62,4 +62,10 @@ public class NoopProjectRepository : IProjectRepository
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<Dictionary<Guid, (bool Read, bool Write)>> AccessToProjectsAsync(IEnumerable<Guid> projectIds,
|
||||
Guid userId, AccessClientType accessType)
|
||||
{
|
||||
return Task.FromResult(null as Dictionary<Guid, (bool Read, bool Write)>);
|
||||
}
|
||||
}
|
||||
|
@ -623,210 +623,6 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
Assert.Equal(project.Id, result.Data.First(x => x.Id == project.Id).Id);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false, false)]
|
||||
[InlineData(false, false, true)]
|
||||
[InlineData(false, true, false)]
|
||||
[InlineData(false, true, true)]
|
||||
[InlineData(true, false, false)]
|
||||
[InlineData(true, false, true)]
|
||||
[InlineData(true, true, false)]
|
||||
public async Task CreateServiceAccountGrantedPolicies_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
|
||||
var request = new List<GrantedAccessPolicyRequest> { new() { GrantedId = new Guid() } };
|
||||
|
||||
var response =
|
||||
await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/granted-policies", request);
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateServiceAccountGrantedPolicies_NoPermission()
|
||||
{
|
||||
// Create a new account as a user
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
|
||||
var project = await _projectRepository.CreateAsync(new Project
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
|
||||
var request =
|
||||
new List<GrantedAccessPolicyRequest> { new() { GrantedId = project.Id, Read = true, Write = true } };
|
||||
|
||||
var response =
|
||||
await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/granted-policies", request);
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task CreateServiceAccountGrantedPolicies_MismatchedOrgId_NotFound(PermissionType permissionType)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id, true);
|
||||
await SetupProjectAndServiceAccountPermissionAsync(permissionType, projectId, serviceAccountId);
|
||||
|
||||
var request =
|
||||
new List<GrantedAccessPolicyRequest> { new() { GrantedId = projectId, Read = true, Write = true } };
|
||||
|
||||
var response =
|
||||
await _client.PostAsJsonAsync($"/service-accounts/{serviceAccountId}/granted-policies", request);
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task CreateServiceAccountGrantedPolicies_Success(PermissionType permissionType)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id);
|
||||
await SetupProjectAndServiceAccountPermissionAsync(permissionType, projectId, serviceAccountId);
|
||||
|
||||
var request =
|
||||
new List<GrantedAccessPolicyRequest> { new() { GrantedId = projectId, Read = true, Write = true } };
|
||||
|
||||
var response =
|
||||
await _client.PostAsJsonAsync($"/service-accounts/{serviceAccountId}/granted-policies", request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content
|
||||
.ReadFromJsonAsync<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEmpty(result.Data);
|
||||
Assert.Equal(projectId, result.Data.First().GrantedProjectId);
|
||||
|
||||
var createdAccessPolicy =
|
||||
await _accessPolicyRepository.GetByIdAsync(result.Data.First().Id);
|
||||
Assert.NotNull(createdAccessPolicy);
|
||||
Assert.Equal(result.Data.First().Read, createdAccessPolicy.Read);
|
||||
Assert.Equal(result.Data.First().Write, createdAccessPolicy.Write);
|
||||
Assert.Equal(result.Data.First().Id, createdAccessPolicy.Id);
|
||||
AssertHelper.AssertRecent(createdAccessPolicy.CreationDate);
|
||||
AssertHelper.AssertRecent(createdAccessPolicy.RevisionDate);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false, false)]
|
||||
[InlineData(false, false, true)]
|
||||
[InlineData(false, true, false)]
|
||||
[InlineData(false, true, true)]
|
||||
[InlineData(true, false, false)]
|
||||
[InlineData(true, false, true)]
|
||||
[InlineData(true, true, false)]
|
||||
public async Task GetServiceAccountGrantedPolicies_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
var initData = await SetupAccessPolicyRequest(org.Id);
|
||||
|
||||
var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/granted-policies");
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetServiceAccountGrantedPolicies_ReturnsEmpty()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
|
||||
var response = await _client.GetAsync($"/service-accounts/{serviceAccount.Id}/granted-policies");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content
|
||||
.ReadFromJsonAsync<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result.Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetServiceAccountGrantedPolicies_NoPermission_ReturnsEmpty()
|
||||
{
|
||||
// Create a new account as a user
|
||||
await _organizationHelper.Initialize(true, true, true);
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var initData = await SetupAccessPolicyRequest(orgUser.OrganizationId);
|
||||
|
||||
var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/granted-policies");
|
||||
|
||||
var result = await response.Content
|
||||
.ReadFromJsonAsync<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result.Data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task GetServiceAccountGrantedPolicies(PermissionType permissionType)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
var initData = await SetupAccessPolicyRequest(org.Id);
|
||||
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await _loginHelper.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($"/service-accounts/{initData.ServiceAccountId}/granted-policies");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content
|
||||
.ReadFromJsonAsync<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>();
|
||||
|
||||
Assert.NotNull(result?.Data);
|
||||
Assert.NotEmpty(result.Data);
|
||||
Assert.Equal(initData.ServiceAccountId, result.Data.First().ServiceAccountId);
|
||||
Assert.NotNull(result.Data.First().ServiceAccountName);
|
||||
Assert.NotNull(result.Data.First().GrantedProjectName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false, false)]
|
||||
[InlineData(false, false, true)]
|
||||
@ -1090,12 +886,16 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false)]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(false, true)]
|
||||
public async Task PutServiceAccountPeopleAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
|
||||
[InlineData(false, false, false)]
|
||||
[InlineData(false, false, true)]
|
||||
[InlineData(false, true, false)]
|
||||
[InlineData(false, true, true)]
|
||||
[InlineData(true, false, false)]
|
||||
[InlineData(true, false, true)]
|
||||
[InlineData(true, true, false)]
|
||||
public async Task PutServiceAccountPeopleAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
|
||||
{
|
||||
var (_, organizationUser) = await _organizationHelper.Initialize(useSecrets, accessSecrets, true);
|
||||
var (_, organizationUser) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var (serviceAccount, request) = await SetupServiceAccountPeopleRequestAsync(PermissionType.RunAsAdmin, organizationUser);
|
||||
@ -1185,6 +985,190 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
Assert.Equal(result.UserAccessPolicies.First().Id, createdAccessPolicy.Id);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false, false)]
|
||||
[InlineData(false, false, true)]
|
||||
[InlineData(false, true, false)]
|
||||
[InlineData(false, true, true)]
|
||||
[InlineData(true, false, false)]
|
||||
[InlineData(true, false, true)]
|
||||
[InlineData(true, true, false)]
|
||||
public async Task GetServiceAccountGrantedPoliciesAsync_SmAccessDenied_ReturnsNotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
var initData = await SetupAccessPolicyRequest(org.Id);
|
||||
|
||||
var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/granted-policies");
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetServiceAccountGrantedPoliciesAsync_NoAccessPolicies_ReturnsEmpty()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
|
||||
var response = await _client.GetAsync($"/service-accounts/{serviceAccount.Id}/granted-policies");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ServiceAccountGrantedPoliciesPermissionDetailsResponseModel>();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result.GrantedProjectPolicies);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetServiceAccountGrantedPoliciesAsync_UserDoesntHavePermission_ReturnsNotFound()
|
||||
{
|
||||
// Create a new account as a user
|
||||
await _organizationHelper.Initialize(true, true, true);
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var initData = await SetupAccessPolicyRequest(orgUser.OrganizationId);
|
||||
|
||||
var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/granted-policies");
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task GetServiceAccountGrantedPoliciesAsync_Success(PermissionType permissionType)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
var initData = await SetupAccessPolicyRequest(org.Id);
|
||||
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await _loginHelper.LoginAsync(email);
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new UserServiceAccountAccessPolicy
|
||||
{
|
||||
GrantedServiceAccountId = initData.ServiceAccountId, OrganizationUserId = orgUser.Id, Read = true, Write = true,
|
||||
}
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
}
|
||||
|
||||
var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/granted-policies");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content
|
||||
.ReadFromJsonAsync<ServiceAccountGrantedPoliciesPermissionDetailsResponseModel>();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEmpty(result.GrantedProjectPolicies);
|
||||
Assert.Equal(initData.ServiceAccountId, result.GrantedProjectPolicies.First().AccessPolicy.ServiceAccountId);
|
||||
Assert.NotNull(result.GrantedProjectPolicies.First().AccessPolicy.ServiceAccountName);
|
||||
Assert.NotNull(result.GrantedProjectPolicies.First().AccessPolicy.GrantedProjectName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false, false)]
|
||||
[InlineData(false, false, true)]
|
||||
[InlineData(false, true, false)]
|
||||
[InlineData(false, true, true)]
|
||||
[InlineData(true, false, false)]
|
||||
[InlineData(true, false, true)]
|
||||
[InlineData(true, true, false)]
|
||||
public async Task PutServiceAccountGrantedPoliciesAsync_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
|
||||
{
|
||||
var (_, organizationUser) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var (serviceAccount, request) = await SetupServiceAccountGrantedPoliciesRequestAsync(PermissionType.RunAsAdmin, organizationUser, false);
|
||||
|
||||
var response = await _client.PutAsJsonAsync($"/service-accounts/{serviceAccount.Id}/granted-policies", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PutServiceAccountGrantedPoliciesAsync_UserHasNoPermission_ReturnsNotFound()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id);
|
||||
|
||||
var request = new ServiceAccountGrantedPoliciesRequestModel
|
||||
{
|
||||
ProjectGrantedPolicyRequests = new List<GrantedAccessPolicyRequest>
|
||||
{
|
||||
new() { GrantedId = projectId, Read = true, Write = true }
|
||||
}
|
||||
};
|
||||
|
||||
var response = await _client.PutAsJsonAsync($"/service-accounts/{serviceAccountId}/granted-policies", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task PutServiceAccountGrantedPoliciesAsync_MismatchedOrgIds_ReturnsNotFound(PermissionType permissionType)
|
||||
{
|
||||
var (_, organizationUser) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var (serviceAccount, request) = await SetupServiceAccountGrantedPoliciesRequestAsync(permissionType, organizationUser, false);
|
||||
var newOrg = await _organizationHelper.CreateSmOrganizationAsync();
|
||||
|
||||
var project = await _projectRepository.CreateAsync(new Project
|
||||
{
|
||||
Name = _mockEncryptedString,
|
||||
OrganizationId = newOrg.Id
|
||||
});
|
||||
request.ProjectGrantedPolicyRequests = new List<GrantedAccessPolicyRequest>
|
||||
{
|
||||
new() { GrantedId = project.Id, Read = true, Write = true }
|
||||
};
|
||||
|
||||
var response = await _client.PutAsJsonAsync($"/service-accounts/{serviceAccount.Id}/granted-policies", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin, false)]
|
||||
[InlineData(PermissionType.RunAsAdmin, true)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission, false)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission, true)]
|
||||
public async Task PutServiceAccountGrantedPoliciesAsync_Success(PermissionType permissionType, bool createPreviousAccessPolicy)
|
||||
{
|
||||
var (_, organizationUser) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var (serviceAccount, request) = await SetupServiceAccountGrantedPoliciesRequestAsync(permissionType, organizationUser, createPreviousAccessPolicy);
|
||||
|
||||
var response = await _client.PutAsJsonAsync($"/service-accounts/{serviceAccount.Id}/granted-policies", request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ServiceAccountGrantedPoliciesPermissionDetailsResponseModel>();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(request.ProjectGrantedPolicyRequests.First().GrantedId,
|
||||
result.GrantedProjectPolicies.First().AccessPolicy.GrantedProjectId);
|
||||
Assert.True(result.GrantedProjectPolicies.First().AccessPolicy.Read);
|
||||
Assert.True(result.GrantedProjectPolicies.First().AccessPolicy.Write);
|
||||
Assert.True(result.GrantedProjectPolicies.First().HasPermission);
|
||||
Assert.Single(result.GrantedProjectPolicies);
|
||||
}
|
||||
|
||||
private async Task<RequestSetupData> SetupAccessPolicyRequest(Guid organizationId)
|
||||
{
|
||||
var project = await _projectRepository.CreateAsync(new Project
|
||||
@ -1275,6 +1259,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
Write = true
|
||||
}
|
||||
};
|
||||
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
|
||||
return (serviceAccount, organizationUser);
|
||||
@ -1357,6 +1342,59 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(ServiceAccount serviceAccount, ServiceAccountGrantedPoliciesRequestModel request)> SetupServiceAccountGrantedPoliciesRequestAsync(
|
||||
PermissionType permissionType, OrganizationUser organizationUser, bool createPreviousAccessPolicy)
|
||||
{
|
||||
var (serviceAccount, currentUser) = await SetupServiceAccountPeoplePermissionAsync(permissionType, organizationUser);
|
||||
var project = await _projectRepository.CreateAsync(new Project
|
||||
{
|
||||
Name = _mockEncryptedString,
|
||||
OrganizationId = organizationUser.OrganizationId
|
||||
});
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new UserProjectAccessPolicy
|
||||
{
|
||||
GrantedProjectId = project.Id, OrganizationUserId = currentUser.Id, Read = true, Write = true,
|
||||
},
|
||||
};
|
||||
|
||||
if (createPreviousAccessPolicy)
|
||||
{
|
||||
var anotherProject = await _projectRepository.CreateAsync(new Project
|
||||
{
|
||||
Name = _mockEncryptedString,
|
||||
OrganizationId = organizationUser.OrganizationId
|
||||
});
|
||||
|
||||
accessPolicies.Add(new UserProjectAccessPolicy
|
||||
{
|
||||
GrantedProjectId = anotherProject.Id,
|
||||
OrganizationUserId = currentUser.Id,
|
||||
Read = true,
|
||||
Write = true,
|
||||
});
|
||||
accessPolicies.Add(new ServiceAccountProjectAccessPolicy
|
||||
{
|
||||
GrantedProjectId = anotherProject.Id,
|
||||
ServiceAccountId = serviceAccount.Id,
|
||||
Read = true,
|
||||
Write = true,
|
||||
});
|
||||
}
|
||||
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
|
||||
var request = new ServiceAccountGrantedPoliciesRequestModel
|
||||
{
|
||||
ProjectGrantedPolicyRequests = new List<GrantedAccessPolicyRequest>
|
||||
{
|
||||
new() { GrantedId = project.Id, Read = true, Write = true }
|
||||
}
|
||||
};
|
||||
return (serviceAccount, request);
|
||||
}
|
||||
|
||||
private class RequestSetupData
|
||||
{
|
||||
public Guid ProjectId { get; set; }
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Security.Claims;
|
||||
#nullable enable
|
||||
using System.Security.Claims;
|
||||
using Bit.Api.SecretsManager.Controllers;
|
||||
using Bit.Api.SecretsManager.Models.Request;
|
||||
using Bit.Api.Test.SecretsManager.Enums;
|
||||
@ -8,6 +9,7 @@ using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
using Bit.Core.SecretsManager.Queries.Interfaces;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
|
||||
@ -29,83 +31,6 @@ public class AccessPoliciesControllerTests
|
||||
{
|
||||
private const int _overMax = 16;
|
||||
|
||||
private static AccessPoliciesCreateRequest AddRequestsOverMax(AccessPoliciesCreateRequest request)
|
||||
{
|
||||
var newRequests = new List<AccessPolicyRequest>();
|
||||
for (var i = 0; i < _overMax; i++)
|
||||
{
|
||||
newRequests.Add(new AccessPolicyRequest { GranteeId = new Guid(), Read = true, Write = true });
|
||||
}
|
||||
|
||||
request.UserAccessPolicyRequests = newRequests;
|
||||
return request;
|
||||
}
|
||||
|
||||
private static List<GrantedAccessPolicyRequest> AddRequestsOverMax(List<GrantedAccessPolicyRequest> request)
|
||||
{
|
||||
for (var i = 0; i < _overMax; i++)
|
||||
{
|
||||
request.Add(new GrantedAccessPolicyRequest { GrantedId = new Guid() });
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private static PeopleAccessPoliciesRequestModel SetRequestToCanReadWrite(PeopleAccessPoliciesRequestModel request)
|
||||
{
|
||||
foreach (var ap in request.UserAccessPolicyRequests)
|
||||
{
|
||||
ap.Read = true;
|
||||
ap.Write = true;
|
||||
}
|
||||
|
||||
foreach (var ap in request.GroupAccessPolicyRequests)
|
||||
{
|
||||
ap.Read = true;
|
||||
ap.Write = true;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private static void SetupAdmin(SutProvider<AccessPoliciesController> sutProvider, Guid organizationId)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
|
||||
}
|
||||
|
||||
private static void SetupUserWithPermission(SutProvider<AccessPoliciesController> sutProvider, Guid organizationId)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(false);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(default).ReturnsForAnyArgs(true);
|
||||
}
|
||||
|
||||
private static void SetupUserWithoutPermission(SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid organizationId)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(false);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(default).ReturnsForAnyArgs(true);
|
||||
}
|
||||
|
||||
private static void SetupPermission(SutProvider<AccessPoliciesController> sutProvider,
|
||||
PermissionType permissionType, Guid orgId)
|
||||
{
|
||||
switch (permissionType)
|
||||
{
|
||||
case PermissionType.RunAsAdmin:
|
||||
SetupAdmin(sutProvider, orgId);
|
||||
break;
|
||||
case PermissionType.RunAsUserWithPermission:
|
||||
SetupUserWithPermission(sutProvider, orgId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
@ -222,71 +147,6 @@ public class AccessPoliciesControllerTests
|
||||
.GetManyByGrantedProjectIdAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task GetServiceAccountGrantedPolicies_ReturnsEmptyList(
|
||||
PermissionType permissionType,
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid id, ServiceAccount data)
|
||||
{
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).ReturnsForAnyArgs(data);
|
||||
|
||||
switch (permissionType)
|
||||
{
|
||||
case PermissionType.RunAsAdmin:
|
||||
SetupAdmin(sutProvider, data.OrganizationId);
|
||||
break;
|
||||
case PermissionType.RunAsUserWithPermission:
|
||||
SetupUserWithPermission(sutProvider, data.OrganizationId);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>()
|
||||
.UserHasWriteAccessToServiceAccount(default, default)
|
||||
.ReturnsForAnyArgs(true);
|
||||
break;
|
||||
}
|
||||
|
||||
var result = await sutProvider.Sut.GetServiceAccountGrantedPoliciesAsync(id);
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
|
||||
.GetManyByServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), Arg.Any<Guid>(),
|
||||
Arg.Any<AccessClientType>());
|
||||
|
||||
Assert.Empty(result.Data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task GetServiceAccountGrantedPolicies_Success(
|
||||
PermissionType permissionType,
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid id,
|
||||
ServiceAccount data,
|
||||
ServiceAccountProjectAccessPolicy resultAccessPolicy)
|
||||
{
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(data);
|
||||
switch (permissionType)
|
||||
{
|
||||
case PermissionType.RunAsAdmin:
|
||||
SetupAdmin(sutProvider, data.OrganizationId);
|
||||
break;
|
||||
case PermissionType.RunAsUserWithPermission:
|
||||
SetupUserWithPermission(sutProvider, data.OrganizationId);
|
||||
break;
|
||||
}
|
||||
|
||||
sutProvider.GetDependency<IAccessPolicyRepository>().GetManyByServiceAccountIdAsync(default, default, default)
|
||||
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { resultAccessPolicy });
|
||||
|
||||
var result = await sutProvider.Sut.GetServiceAccountGrantedPoliciesAsync(id);
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
|
||||
.GetManyByServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), Arg.Any<Guid>(),
|
||||
Arg.Any<AccessClientType>());
|
||||
|
||||
Assert.NotEmpty(result.Data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateProjectAccessPolicies_RequestMoreThanMax_Throws(
|
||||
@ -403,121 +263,6 @@ public class AccessPoliciesControllerTests
|
||||
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateServiceAccountGrantedPolicies_RequestMoreThanMax_Throws(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid id,
|
||||
ServiceAccount serviceAccount,
|
||||
ServiceAccountProjectAccessPolicy data,
|
||||
List<GrantedAccessPolicyRequest> request)
|
||||
{
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount);
|
||||
sutProvider.GetDependency<ICreateAccessPoliciesCommand>()
|
||||
.CreateManyAsync(default)
|
||||
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data });
|
||||
|
||||
request = AddRequestsOverMax(request);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.CreateServiceAccountGrantedPoliciesAsync(id, request));
|
||||
|
||||
await sutProvider.GetDependency<ICreateAccessPoliciesCommand>().DidNotReceiveWithAnyArgs()
|
||||
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateServiceAccountGrantedPolicies_ServiceAccountDoesNotExist_Throws(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid id,
|
||||
List<GrantedAccessPolicyRequest> request)
|
||||
{
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.CreateServiceAccountGrantedPoliciesAsync(id, request));
|
||||
|
||||
await sutProvider.GetDependency<ICreateAccessPoliciesCommand>().DidNotReceiveWithAnyArgs()
|
||||
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateServiceAccountGrantedPolicies_DuplicatePolicy_Throws(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid id,
|
||||
ServiceAccount serviceAccount,
|
||||
ServiceAccountProjectAccessPolicy data,
|
||||
List<GrantedAccessPolicyRequest> request)
|
||||
{
|
||||
var dup = new GrantedAccessPolicyRequest { GrantedId = Guid.NewGuid(), Read = true, Write = true };
|
||||
request.Add(dup);
|
||||
request.Add(dup);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount);
|
||||
|
||||
sutProvider.GetDependency<ICreateAccessPoliciesCommand>()
|
||||
.CreateManyAsync(default)
|
||||
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data });
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.CreateServiceAccountGrantedPoliciesAsync(id, request));
|
||||
|
||||
await sutProvider.GetDependency<ICreateAccessPoliciesCommand>().DidNotReceiveWithAnyArgs()
|
||||
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateServiceAccountGrantedPolicies_NoAccess_Throws(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid id,
|
||||
ServiceAccount serviceAccount,
|
||||
ServiceAccountProjectAccessPolicy data,
|
||||
List<GrantedAccessPolicyRequest> request)
|
||||
{
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount);
|
||||
sutProvider.GetDependency<ICreateAccessPoliciesCommand>()
|
||||
.CreateManyAsync(default)
|
||||
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data });
|
||||
foreach (var policy in request)
|
||||
{
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), policy,
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Failed());
|
||||
}
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.CreateServiceAccountGrantedPoliciesAsync(id, request));
|
||||
|
||||
await sutProvider.GetDependency<ICreateAccessPoliciesCommand>().DidNotReceiveWithAnyArgs()
|
||||
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateServiceAccountGrantedPolicies_Success(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid id,
|
||||
ServiceAccount serviceAccount,
|
||||
ServiceAccountProjectAccessPolicy data,
|
||||
List<GrantedAccessPolicyRequest> request)
|
||||
{
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount);
|
||||
sutProvider.GetDependency<ICreateAccessPoliciesCommand>()
|
||||
.CreateManyAsync(default)
|
||||
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data });
|
||||
foreach (var policy in request)
|
||||
{
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), policy,
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
|
||||
}
|
||||
|
||||
await sutProvider.Sut.CreateServiceAccountGrantedPoliciesAsync(id, request);
|
||||
|
||||
await sutProvider.GetDependency<ICreateAccessPoliciesCommand>().Received(1)
|
||||
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAccessPolicies_NoAccess_Throws(
|
||||
@ -1165,4 +910,262 @@ public class AccessPoliciesControllerTests
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
|
||||
.ReplaceServiceAccountPeopleAsync(Arg.Any<ServiceAccountPeopleAccessPolicies>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetServiceAccountGrantedPoliciesAsync_NoAccess_ThrowsNotFound(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
ServiceAccount data)
|
||||
{
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).Returns(data);
|
||||
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data,
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Failed());
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.GetServiceAccountGrantedPoliciesAsync(data.Id));
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(0)
|
||||
.GetServiceAccountGrantedPoliciesPermissionDetailsAsync(Arg.Any<Guid>(), Arg.Any<Guid>(),
|
||||
Arg.Any<AccessClientType>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(AccessClientType.NoAccessCheck)]
|
||||
[BitAutoData(AccessClientType.User)]
|
||||
public async Task GetServiceAccountGrantedPoliciesAsync_HasAccessNoPolicies_ReturnsEmptyList(
|
||||
AccessClientType accessClientType,
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid userId,
|
||||
ServiceAccount data)
|
||||
{
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).Returns(data);
|
||||
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data,
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
|
||||
|
||||
sutProvider.GetDependency<IAccessClientQuery>()
|
||||
.GetAccessClientAsync(Arg.Any<ClaimsPrincipal>(), data.OrganizationId).Returns((accessClientType, userId));
|
||||
|
||||
sutProvider.GetDependency<IAccessPolicyRepository>()
|
||||
.GetServiceAccountGrantedPoliciesPermissionDetailsAsync(Arg.Any<Guid>(), Arg.Any<Guid>(),
|
||||
Arg.Any<AccessClientType>())
|
||||
.ReturnsNull();
|
||||
|
||||
var result = await sutProvider.Sut.GetServiceAccountGrantedPoliciesAsync(data.Id);
|
||||
|
||||
Assert.Empty(result.GrantedProjectPolicies);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(AccessClientType.NoAccessCheck)]
|
||||
[BitAutoData(AccessClientType.User)]
|
||||
public async Task GetServiceAccountGrantedPoliciesAsync_HasAccess_Success(
|
||||
AccessClientType accessClientType,
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid userId,
|
||||
ServiceAccountGrantedPoliciesPermissionDetails policies,
|
||||
ServiceAccount data)
|
||||
{
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).Returns(data);
|
||||
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data,
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
|
||||
|
||||
sutProvider.GetDependency<IAccessClientQuery>()
|
||||
.GetAccessClientAsync(Arg.Any<ClaimsPrincipal>(), data.OrganizationId).Returns((accessClientType, userId));
|
||||
|
||||
sutProvider.GetDependency<IAccessPolicyRepository>()
|
||||
.GetServiceAccountGrantedPoliciesPermissionDetailsAsync(Arg.Any<Guid>(), Arg.Any<Guid>(),
|
||||
Arg.Any<AccessClientType>())
|
||||
.Returns(policies);
|
||||
|
||||
var result = await sutProvider.Sut.GetServiceAccountGrantedPoliciesAsync(data.Id);
|
||||
|
||||
Assert.NotEmpty(result.GrantedProjectPolicies);
|
||||
Assert.Equal(policies.ProjectGrantedPolicies.Count(), result.GrantedProjectPolicies.Count);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PutServiceAccountGrantedPoliciesAsync_ServiceAccountDoesNotExist_Throws(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
ServiceAccount data,
|
||||
ServiceAccountGrantedPoliciesRequestModel request)
|
||||
{
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.PutServiceAccountGrantedPoliciesAsync(data.Id, request));
|
||||
|
||||
await sutProvider.GetDependency<IUpdateServiceAccountGrantedPoliciesCommand>().DidNotReceiveWithAnyArgs()
|
||||
.UpdateAsync(Arg.Any<ServiceAccountGrantedPoliciesUpdates>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PutServiceAccountGrantedPoliciesAsync_DuplicatePolicyRequest_ThrowsBadRequestException(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
ServiceAccount data,
|
||||
ServiceAccountGrantedPoliciesRequestModel request)
|
||||
{
|
||||
var dup = new GrantedAccessPolicyRequest { GrantedId = Guid.NewGuid(), Read = true, Write = true };
|
||||
request.ProjectGrantedPolicyRequests = new[] { dup, dup };
|
||||
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).ReturnsForAnyArgs(data);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.PutServiceAccountGrantedPoliciesAsync(data.Id, request));
|
||||
|
||||
await sutProvider.GetDependency<IUpdateServiceAccountGrantedPoliciesCommand>().DidNotReceiveWithAnyArgs()
|
||||
.UpdateAsync(Arg.Any<ServiceAccountGrantedPoliciesUpdates>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PutServiceAccountGrantedPoliciesAsync_InvalidPolicyRequest_ThrowsBadRequestException(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
ServiceAccount data,
|
||||
ServiceAccountGrantedPoliciesRequestModel request)
|
||||
{
|
||||
var policyRequest = new GrantedAccessPolicyRequest { GrantedId = Guid.NewGuid(), Read = false, Write = true };
|
||||
request.ProjectGrantedPolicyRequests = new[] { policyRequest };
|
||||
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).ReturnsForAnyArgs(data);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.PutServiceAccountGrantedPoliciesAsync(data.Id, request));
|
||||
|
||||
await sutProvider.GetDependency<IUpdateServiceAccountGrantedPoliciesCommand>().DidNotReceiveWithAnyArgs()
|
||||
.UpdateAsync(Arg.Any<ServiceAccountGrantedPoliciesUpdates>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PutServiceAccountGrantedPoliciesAsync_UserHasNoAccess_ThrowsNotFoundException(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
ServiceAccount data,
|
||||
ServiceAccountGrantedPoliciesRequestModel request)
|
||||
{
|
||||
request = SetupValidRequest(request);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).ReturnsForAnyArgs(data);
|
||||
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), Arg.Any<ServiceAccountGrantedPoliciesUpdates>(),
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).Returns(AuthorizationResult.Failed());
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.PutServiceAccountGrantedPoliciesAsync(data.Id, request));
|
||||
|
||||
await sutProvider.GetDependency<IUpdateServiceAccountGrantedPoliciesCommand>().DidNotReceiveWithAnyArgs()
|
||||
.UpdateAsync(Arg.Any<ServiceAccountGrantedPoliciesUpdates>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PutServiceAccountGrantedPoliciesAsync_Success(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
ServiceAccount data,
|
||||
ServiceAccountGrantedPoliciesRequestModel request)
|
||||
{
|
||||
request = SetupValidRequest(request);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).ReturnsForAnyArgs(data);
|
||||
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), Arg.Any<ServiceAccountGrantedPoliciesUpdates>(),
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).Returns(AuthorizationResult.Success());
|
||||
|
||||
await sutProvider.Sut.PutServiceAccountGrantedPoliciesAsync(data.Id, request);
|
||||
|
||||
await sutProvider.GetDependency<IUpdateServiceAccountGrantedPoliciesCommand>().Received(1)
|
||||
.UpdateAsync(Arg.Any<ServiceAccountGrantedPoliciesUpdates>());
|
||||
}
|
||||
|
||||
private static AccessPoliciesCreateRequest AddRequestsOverMax(AccessPoliciesCreateRequest request)
|
||||
{
|
||||
var newRequests = new List<AccessPolicyRequest>();
|
||||
for (var i = 0; i < _overMax; i++)
|
||||
{
|
||||
newRequests.Add(new AccessPolicyRequest { GranteeId = new Guid(), Read = true, Write = true });
|
||||
}
|
||||
|
||||
request.UserAccessPolicyRequests = newRequests;
|
||||
return request;
|
||||
}
|
||||
|
||||
private static List<GrantedAccessPolicyRequest> AddRequestsOverMax(List<GrantedAccessPolicyRequest> request)
|
||||
{
|
||||
for (var i = 0; i < _overMax; i++)
|
||||
{
|
||||
request.Add(new GrantedAccessPolicyRequest { GrantedId = new Guid() });
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private static PeopleAccessPoliciesRequestModel SetRequestToCanReadWrite(PeopleAccessPoliciesRequestModel request)
|
||||
{
|
||||
foreach (var ap in request.UserAccessPolicyRequests)
|
||||
{
|
||||
ap.Read = true;
|
||||
ap.Write = true;
|
||||
}
|
||||
|
||||
foreach (var ap in request.GroupAccessPolicyRequests)
|
||||
{
|
||||
ap.Read = true;
|
||||
ap.Write = true;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private static void SetupAdmin(SutProvider<AccessPoliciesController> sutProvider, Guid organizationId)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
|
||||
}
|
||||
|
||||
private static void SetupUserWithPermission(SutProvider<AccessPoliciesController> sutProvider, Guid organizationId)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(false);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(default).ReturnsForAnyArgs(true);
|
||||
}
|
||||
|
||||
private static void SetupUserWithoutPermission(SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid organizationId)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(false);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(default).ReturnsForAnyArgs(true);
|
||||
}
|
||||
|
||||
private static void SetupPermission(SutProvider<AccessPoliciesController> sutProvider,
|
||||
PermissionType permissionType, Guid orgId)
|
||||
{
|
||||
switch (permissionType)
|
||||
{
|
||||
case PermissionType.RunAsAdmin:
|
||||
SetupAdmin(sutProvider, orgId);
|
||||
break;
|
||||
case PermissionType.RunAsUserWithPermission:
|
||||
SetupUserWithPermission(sutProvider, orgId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static ServiceAccountGrantedPoliciesRequestModel SetupValidRequest(ServiceAccountGrantedPoliciesRequestModel request)
|
||||
{
|
||||
foreach (var policyRequest in request.ProjectGrantedPolicyRequests)
|
||||
{
|
||||
policyRequest.Read = true;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,101 @@
|
||||
#nullable enable
|
||||
using Bit.Api.SecretsManager.Utilities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
|
||||
using Bit.Core.Test.SecretsManager.AutoFixture.SecretsFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.SecretsManager.Utilities;
|
||||
|
||||
[ProjectCustomize]
|
||||
[SecretCustomize]
|
||||
public class AccessPolicyHelpersTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void CheckForDistinctAccessPolicies_DuplicateAccessPolicies_ThrowsBadRequestException(
|
||||
UserProjectAccessPolicy userProjectAccessPolicy, UserServiceAccountAccessPolicy userServiceAccountAccessPolicy,
|
||||
GroupProjectAccessPolicy groupProjectAccessPolicy,
|
||||
GroupServiceAccountAccessPolicy groupServiceAccountAccessPolicy,
|
||||
ServiceAccountProjectAccessPolicy serviceAccountProjectAccessPolicy)
|
||||
{
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
userProjectAccessPolicy,
|
||||
userProjectAccessPolicy,
|
||||
userServiceAccountAccessPolicy,
|
||||
userServiceAccountAccessPolicy,
|
||||
groupProjectAccessPolicy,
|
||||
groupProjectAccessPolicy,
|
||||
groupServiceAccountAccessPolicy,
|
||||
groupServiceAccountAccessPolicy,
|
||||
serviceAccountProjectAccessPolicy,
|
||||
serviceAccountProjectAccessPolicy
|
||||
};
|
||||
|
||||
Assert.Throws<BadRequestException>(() =>
|
||||
{
|
||||
AccessPolicyHelpers.CheckForDistinctAccessPolicies(accessPolicies);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckForDistinctAccessPolicies_UnsupportedAccessPolicy_ThrowsArgumentException()
|
||||
{
|
||||
var accessPolicies = new List<BaseAccessPolicy> { new UnsupportedAccessPolicy() };
|
||||
|
||||
Assert.Throws<ArgumentException>(() => { AccessPolicyHelpers.CheckForDistinctAccessPolicies(accessPolicies); });
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void CheckForDistinctAccessPolicies_DistinctPolicies_Success(UserProjectAccessPolicy userProjectAccessPolicy,
|
||||
UserServiceAccountAccessPolicy userServiceAccountAccessPolicy,
|
||||
GroupProjectAccessPolicy groupProjectAccessPolicy,
|
||||
GroupServiceAccountAccessPolicy groupServiceAccountAccessPolicy,
|
||||
ServiceAccountProjectAccessPolicy serviceAccountProjectAccessPolicy)
|
||||
{
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
userProjectAccessPolicy,
|
||||
userServiceAccountAccessPolicy,
|
||||
groupProjectAccessPolicy,
|
||||
groupServiceAccountAccessPolicy,
|
||||
serviceAccountProjectAccessPolicy
|
||||
};
|
||||
|
||||
AccessPolicyHelpers.CheckForDistinctAccessPolicies(accessPolicies);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckAccessPoliciesHaveReadPermission_ReadPermissionFalse_ThrowsBadRequestException()
|
||||
{
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new UserProjectAccessPolicy { Read = false, Write = true },
|
||||
new GroupProjectAccessPolicy { Read = true, Write = false }
|
||||
};
|
||||
|
||||
Assert.Throws<BadRequestException>(() =>
|
||||
{
|
||||
AccessPolicyHelpers.CheckAccessPoliciesHaveReadPermission(accessPolicies);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckAccessPoliciesHaveReadPermission_AllReadIsTrue_Success()
|
||||
{
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new UserProjectAccessPolicy { Read = true, Write = true },
|
||||
new GroupProjectAccessPolicy { Read = true, Write = false }
|
||||
};
|
||||
|
||||
AccessPolicyHelpers.CheckAccessPoliciesHaveReadPermission(accessPolicies);
|
||||
}
|
||||
|
||||
private class UnsupportedAccessPolicy : BaseAccessPolicy;
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
#nullable enable
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Enums.AccessPolicies;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.SecretsManager.Models;
|
||||
|
||||
public class ServiceAccountGrantedPoliciesTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetPolicyUpdates_NoChanges_ReturnsEmptyLists()
|
||||
{
|
||||
var projectId1 = Guid.NewGuid();
|
||||
var projectId2 = Guid.NewGuid();
|
||||
|
||||
var existing = new ServiceAccountGrantedPolicies
|
||||
{
|
||||
ProjectGrantedPolicies = new List<ServiceAccountProjectAccessPolicy>
|
||||
{
|
||||
new() { GrantedProjectId = projectId1, Read = true, Write = true },
|
||||
new() { GrantedProjectId = projectId2, Read = false, Write = true }
|
||||
}
|
||||
};
|
||||
|
||||
var result = existing.GetPolicyUpdates(existing);
|
||||
|
||||
Assert.Empty(result.ProjectGrantedPolicyUpdates);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPolicyUpdates_ReturnsCorrectPolicyChanges()
|
||||
{
|
||||
var projectId1 = Guid.NewGuid();
|
||||
var projectId2 = Guid.NewGuid();
|
||||
var projectId3 = Guid.NewGuid();
|
||||
var projectId4 = Guid.NewGuid();
|
||||
|
||||
var existing = new ServiceAccountGrantedPolicies
|
||||
{
|
||||
ProjectGrantedPolicies = new List<ServiceAccountProjectAccessPolicy>
|
||||
{
|
||||
new() { GrantedProjectId = projectId1, Read = true, Write = true },
|
||||
new() { GrantedProjectId = projectId3, Read = true, Write = true },
|
||||
new() { GrantedProjectId = projectId4, Read = true, Write = true }
|
||||
}
|
||||
};
|
||||
|
||||
var requested = new ServiceAccountGrantedPolicies
|
||||
{
|
||||
ProjectGrantedPolicies = new List<ServiceAccountProjectAccessPolicy>
|
||||
{
|
||||
new() { GrantedProjectId = projectId1, Read = true, Write = false },
|
||||
new() { GrantedProjectId = projectId2, Read = false, Write = true },
|
||||
new() { GrantedProjectId = projectId3, Read = true, Write = true }
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var result = existing.GetPolicyUpdates(requested);
|
||||
|
||||
Assert.Contains(projectId2, result.ProjectGrantedPolicyUpdates
|
||||
.Where(pu => pu.Operation == AccessPolicyOperation.Create)
|
||||
.Select(pu => pu.AccessPolicy.GrantedProjectId!.Value));
|
||||
|
||||
Assert.Contains(projectId4, result.ProjectGrantedPolicyUpdates
|
||||
.Where(pu => pu.Operation == AccessPolicyOperation.Delete)
|
||||
.Select(pu => pu.AccessPolicy.GrantedProjectId!.Value));
|
||||
|
||||
Assert.Contains(projectId1, result.ProjectGrantedPolicyUpdates
|
||||
.Where(pu => pu.Operation == AccessPolicyOperation.Update)
|
||||
.Select(pu => pu.AccessPolicy.GrantedProjectId!.Value));
|
||||
|
||||
Assert.DoesNotContain(projectId3, result.ProjectGrantedPolicyUpdates
|
||||
.Select(pu => pu.AccessPolicy.GrantedProjectId!.Value));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user