Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SM-495] Access Policies - Individual Service Account - Project Tab #2697

Merged
merged 8 commits into from
Feb 16, 2023
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);
Thomas-Avery marked this conversation as resolved.
Show resolved Hide resolved
}

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