Skip to content

Commit

Permalink
[SM-495] Access Policies - Individual Service Account - Project Tab (#…
Browse files Browse the repository at this point in the history
…2697)

* New endpoints to support sa projects tab

* Refactor create; Add tests

* Add creation request limit
  • Loading branch information
Thomas-Avery authored Feb 16, 2023
1 parent 770a341 commit f288787
Show file tree
Hide file tree
Showing 14 changed files with 941 additions and 327 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities;
Expand All @@ -10,54 +9,33 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand
{
private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly ICurrentContext _currentContext;
private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;

public CreateAccessPoliciesCommand(
IAccessPolicyRepository accessPolicyRepository,
ICurrentContext currentContext,
IProjectRepository projectRepository,
IServiceAccountRepository serviceAccountRepository)
{
_accessPolicyRepository = accessPolicyRepository;
_currentContext = currentContext;
_projectRepository = projectRepository;
_serviceAccountRepository = serviceAccountRepository;
}

public async Task<IEnumerable<BaseAccessPolicy>> CreateForProjectAsync(Guid projectId,
List<BaseAccessPolicy> accessPolicies, Guid userId)
private static IEnumerable<Guid?> GetDistinctGrantedProjectIds(List<BaseAccessPolicy> accessPolicies)
{
var project = await _projectRepository.GetByIdAsync(projectId);
if (project == null || !_currentContext.AccessSecretsManager(project.OrganizationId))
{
throw new NotFoundException();
}

await CheckPermissionAsync(project.OrganizationId, userId, projectId);
CheckForDistinctAccessPolicies(accessPolicies);
await CheckAccessPoliciesDoNotExistAsync(accessPolicies);

await _accessPolicyRepository.CreateManyAsync(accessPolicies);
return await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(projectId);
var userGrantedIds = accessPolicies.OfType<UserProjectAccessPolicy>().Select(ap => ap.GrantedProjectId);
var groupGrantedIds = accessPolicies.OfType<GroupProjectAccessPolicy>().Select(ap => ap.GrantedProjectId);
var saGrantedIds = accessPolicies.OfType<ServiceAccountProjectAccessPolicy>().Select(ap => ap.GrantedProjectId);
return userGrantedIds.Concat(groupGrantedIds).Concat(saGrantedIds).Distinct();
}

public async Task<IEnumerable<BaseAccessPolicy>> CreateForServiceAccountAsync(Guid serviceAccountId,
List<BaseAccessPolicy> accessPolicies, Guid userId)
private static IEnumerable<Guid?> GetDistinctGrantedServiceAccountIds(List<BaseAccessPolicy> accessPolicies)
{
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(serviceAccountId);
if (serviceAccount == null || !_currentContext.AccessSecretsManager(serviceAccount.OrganizationId))
{
throw new NotFoundException();
}

await CheckPermissionAsync(serviceAccount.OrganizationId, userId, serviceAccountIdToCheck: serviceAccountId);
CheckForDistinctAccessPolicies(accessPolicies);
await CheckAccessPoliciesDoNotExistAsync(accessPolicies);

await _accessPolicyRepository.CreateManyAsync(accessPolicies);
return await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(serviceAccountId);
var userGrantedIds = accessPolicies.OfType<UserServiceAccountAccessPolicy>().Select(ap => ap.GrantedServiceAccountId);
var groupGrantedIds = accessPolicies.OfType<GroupServiceAccountAccessPolicy>()
.Select(ap => ap.GrantedServiceAccountId);
return userGrantedIds.Concat(groupGrantedIds).Distinct();
}

private static void CheckForDistinctAccessPolicies(IReadOnlyCollection<BaseAccessPolicy> accessPolicies)
Expand All @@ -83,6 +61,40 @@ private static void CheckForDistinctAccessPolicies(IReadOnlyCollection<BaseAcces
}
}

public async Task<IEnumerable<BaseAccessPolicy>> CreateManyAsync(List<BaseAccessPolicy> accessPolicies, Guid userId, AccessClientType accessType)
{
CheckForDistinctAccessPolicies(accessPolicies);
await CheckAccessPoliciesDoNotExistAsync(accessPolicies);
await CheckCanCreateAsync(accessPolicies, userId, accessType);
return await _accessPolicyRepository.CreateManyAsync(accessPolicies);
}

private async Task CheckCanCreateAsync(List<BaseAccessPolicy> accessPolicies, Guid userId, AccessClientType accessType)
{
var projectIds = GetDistinctGrantedProjectIds(accessPolicies).ToList();
var serviceAccountIds = GetDistinctGrantedServiceAccountIds(accessPolicies).ToList();

if (projectIds.Any())
{
foreach (var projectId in projectIds)
{
await CheckPermissionAsync(accessType, userId, projectId);
}
}
if (serviceAccountIds.Any())
{
foreach (var serviceAccountId in serviceAccountIds)
{
await CheckPermissionAsync(accessType, userId, serviceAccountIdToCheck: serviceAccountId);
}
}

if (!projectIds.Any() && !serviceAccountIds.Any())
{
throw new BadRequestException("No granted IDs specified");
}
}

private async Task CheckAccessPoliciesDoNotExistAsync(List<BaseAccessPolicy> accessPolicies)
{
foreach (var accessPolicy in accessPolicies)
Expand All @@ -94,35 +106,31 @@ private async Task CheckAccessPoliciesDoNotExistAsync(List<BaseAccessPolicy> acc
}
}

private async Task CheckPermissionAsync(Guid organizationId,
Guid userId,
Guid? projectIdToCheck = null,
private async Task CheckPermissionAsync(AccessClientType accessClient, Guid userId, Guid? projectIdToCheck = null,
Guid? serviceAccountIdToCheck = null)
{
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);

bool hasAccess;
switch (accessClient)
{
case AccessClientType.NoAccessCheck:
hasAccess = true;
break;
case AccessClientType.User:
if (projectIdToCheck != null)
if (projectIdToCheck.HasValue)
{
hasAccess = await _projectRepository.UserHasWriteAccessToProject(projectIdToCheck.Value, userId);
}
else if (serviceAccountIdToCheck != null)
else if (serviceAccountIdToCheck.HasValue)
{
hasAccess = await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(
serviceAccountIdToCheck.Value,
userId);
hasAccess =
await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(
serviceAccountIdToCheck.Value, userId);
}
else
{
hasAccess = false;
throw new ArgumentException("No ID to check provided.");
}

break;
default:
hasAccess = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using AutoMapper;
using System.Linq.Expressions;
using AutoMapper;
using Bit.Core.Enums;
using Bit.Core.SecretsManager.Repositories;
using Bit.Infrastructure.EntityFramework.Repositories;
using Bit.Infrastructure.EntityFramework.SecretsManager.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;


namespace Bit.Commercial.Infrastructure.EntityFramework.SecretsManager.Repositories;

public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPolicyRepository
Expand All @@ -14,6 +17,12 @@ public AccessPolicyRepository(IServiceScopeFactory serviceScopeFactory, IMapper
{
}

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)
{
using var scope = ServiceScopeFactory.CreateScope();
Expand Down Expand Up @@ -191,16 +200,41 @@ public async Task DeleteAsync(Guid id)
}
}

private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore(BaseAccessPolicy baseAccessPolicyEntity)
public async Task<IEnumerable<Core.SecretsManager.Entities.BaseAccessPolicy>> GetManyByServiceAccountIdAsync(Guid id, Guid userId,
AccessClientType accessType)
{
return baseAccessPolicyEntity switch
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);
}

private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore(
BaseAccessPolicy baseAccessPolicyEntity) =>
baseAccessPolicyEntity switch
{
UserProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserProjectAccessPolicy>(ap),
GroupProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupProjectAccessPolicy>(ap),
ServiceAccountProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy>(ap),
UserServiceAccountAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserServiceAccountAccessPolicy>(ap),
GroupServiceAccountAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap),
_ => throw new ArgumentException("Unsupported access policy type")
ServiceAccountProjectAccessPolicy ap => Mapper
.Map<Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy>(ap),
UserServiceAccountAccessPolicy ap =>
Mapper.Map<Core.SecretsManager.Entities.UserServiceAccountAccessPolicy>(ap),
GroupServiceAccountAccessPolicy ap => Mapper
.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap),
_ => throw new ArgumentException("Unsupported access policy type"),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ public ProjectRepository(IServiceScopeFactory serviceScopeFactory, IMapper mappe
return Mapper.Map<List<Core.SecretsManager.Entities.Project>>(projects);
}

public async Task<IEnumerable<Core.SecretsManager.Entities.Project>> GetManyByOrganizationIdWriteAccessAsync(
Guid organizationId, Guid userId, AccessClientType accessType)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var query = dbContext.Project.Where(p => p.OrganizationId == organizationId && p.DeletedDate == null);

query = accessType switch
{
AccessClientType.NoAccessCheck => query,
AccessClientType.User => query.Where(UserHasWriteAccessToProject(userId)),
_ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null),
};

var projects = await query.OrderBy(p => p.RevisionDate).ToListAsync();
return Mapper.Map<List<Core.SecretsManager.Entities.Project>>(projects);
}

private static Expression<Func<Project, bool>> UserHasReadAccessToProject(Guid userId) => p =>
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));
Expand Down
Loading

0 comments on commit f288787

Please sign in to comment.