1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-03 00:52:49 -05:00

[SM-429] Add permission checks to access policy endpoints (#2628)

* Add permission checks to access policy endpoints

* Fix unit tests

* Add service account grant permission checks

* Add service account grant tests

* Add new endpoint unit tests

* Cleanup unit tests add integration tests

* User permission enum in create tests

* Swap to NotFoundException for access checks

* Add filter for potential grantees

* Add in AccessSecretsManager check and test it

* Add code review updates

* Code review updates

* Refactor potential grantees endpoint

* Code review updates
This commit is contained in:
Thomas Avery
2023-02-06 11:26:06 -06:00
committed by GitHub
parent 9110efa44e
commit cf669286ed
20 changed files with 1710 additions and 146 deletions

View File

@ -1,4 +1,6 @@
using Bit.Core.Exceptions;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
@ -8,22 +10,52 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand
{
private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly ICurrentContext _currentContext;
private readonly IProjectRepository _projectRepository;
public CreateAccessPoliciesCommand(IAccessPolicyRepository accessPolicyRepository)
public CreateAccessPoliciesCommand(
IAccessPolicyRepository accessPolicyRepository,
ICurrentContext currentContext,
IProjectRepository projectRepository)
{
_projectRepository = projectRepository;
_accessPolicyRepository = accessPolicyRepository;
_currentContext = currentContext;
}
public async Task<List<BaseAccessPolicy>> CreateAsync(List<BaseAccessPolicy> accessPolicies)
public async Task<IEnumerable<BaseAccessPolicy>> CreateForProjectAsync(Guid projectId,
List<BaseAccessPolicy> accessPolicies, Guid userId)
{
var project = await _projectRepository.GetByIdAsync(projectId);
if (project == null || !_currentContext.AccessSecretsManager(project.OrganizationId))
{
throw new NotFoundException();
}
var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var hasAccess = accessClient switch
{
AccessClientType.NoAccessCheck => true,
AccessClientType.User => await _projectRepository.UserHasWriteAccessToProject(project.Id, userId),
_ => false,
};
if (!hasAccess)
{
throw new NotFoundException();
}
var distinctAccessPolicies = accessPolicies.DistinctBy(baseAccessPolicy =>
{
return baseAccessPolicy switch
{
UserProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId, ap.GrantedProjectId),
GroupProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedProjectId),
ServiceAccountProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.ServiceAccountId, ap.GrantedProjectId),
_ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy))
ServiceAccountProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.ServiceAccountId,
ap.GrantedProjectId),
_ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)),
};
}).ToList();
@ -39,7 +71,7 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand
throw new BadRequestException("Resource already exists");
}
}
return await _accessPolicyRepository.CreateManyAsync(accessPolicies);
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
return await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(projectId);
}
}

View File

@ -1,5 +1,8 @@
using Bit.Core.Exceptions;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
@ -7,14 +10,23 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
public class DeleteAccessPolicyCommand : IDeleteAccessPolicyCommand
{
private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly ICurrentContext _currentContext;
private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;
public DeleteAccessPolicyCommand(IAccessPolicyRepository accessPolicyRepository)
public DeleteAccessPolicyCommand(
IAccessPolicyRepository accessPolicyRepository,
ICurrentContext currentContext,
IProjectRepository projectRepository,
IServiceAccountRepository serviceAccountRepository)
{
_projectRepository = projectRepository;
_serviceAccountRepository = serviceAccountRepository;
_accessPolicyRepository = accessPolicyRepository;
_currentContext = currentContext;
}
public async Task DeleteAsync(Guid id)
public async Task DeleteAsync(Guid id, Guid userId)
{
var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id);
if (accessPolicy == null)
@ -22,6 +34,74 @@ public class DeleteAccessPolicyCommand : IDeleteAccessPolicyCommand
throw new NotFoundException();
}
if (!await IsAllowedToDeleteAsync(accessPolicy, userId))
{
throw new NotFoundException();
}
await _accessPolicyRepository.DeleteAsync(id);
}
private async Task<bool> IsAllowedToDeleteAsync(BaseAccessPolicy baseAccessPolicy, Guid userId) =>
baseAccessPolicy switch
{
UserProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId, userId,
ap.GrantedProjectId),
GroupProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId, userId,
ap.GrantedProjectId),
ServiceAccountProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId,
userId, ap.GrantedProjectId),
UserServiceAccountAccessPolicy ap => await HasPermissionAsync(
ap.GrantedServiceAccount!.OrganizationId,
userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId),
GroupServiceAccountAccessPolicy ap => await HasPermissionAsync(
ap.GrantedServiceAccount!.OrganizationId,
userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId),
_ => throw new ArgumentException("Unsupported access policy type provided."),
};
private async Task<bool> HasPermissionAsync(
Guid organizationId,
Guid userId,
Guid? projectIdToCheck = null,
Guid? serviceAccountIdToCheck = null)
{
if (!_currentContext.AccessSecretsManager(organizationId))
{
return false;
}
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.HasValue)
{
hasAccess = await _projectRepository.UserHasWriteAccessToProject(projectIdToCheck.Value, userId);
}
else if (serviceAccountIdToCheck.HasValue)
{
hasAccess =
await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(
serviceAccountIdToCheck.Value, userId);
}
else
{
throw new ArgumentException("No ID to check provided.");
}
break;
default:
hasAccess = false;
break;
}
return hasAccess;
}
}

View File

@ -1,4 +1,6 @@
using Bit.Core.Exceptions;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
@ -8,13 +10,23 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
public class UpdateAccessPolicyCommand : IUpdateAccessPolicyCommand
{
private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly ICurrentContext _currentContext;
private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;
public UpdateAccessPolicyCommand(IAccessPolicyRepository accessPolicyRepository)
public UpdateAccessPolicyCommand(
IAccessPolicyRepository accessPolicyRepository,
ICurrentContext currentContext,
IProjectRepository projectRepository,
IServiceAccountRepository serviceAccountRepository)
{
_accessPolicyRepository = accessPolicyRepository;
_currentContext = currentContext;
_projectRepository = projectRepository;
_serviceAccountRepository = serviceAccountRepository;
}
public async Task<BaseAccessPolicy> UpdateAsync(Guid id, bool read, bool write)
public async Task<BaseAccessPolicy> UpdateAsync(Guid id, bool read, bool write, Guid userId)
{
var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id);
if (accessPolicy == null)
@ -22,11 +34,78 @@ public class UpdateAccessPolicyCommand : IUpdateAccessPolicyCommand
throw new NotFoundException();
}
if (!await IsAllowedToUpdateAsync(accessPolicy, userId))
{
throw new NotFoundException();
}
accessPolicy.Read = read;
accessPolicy.Write = write;
accessPolicy.RevisionDate = DateTime.UtcNow;
await _accessPolicyRepository.ReplaceAsync(accessPolicy);
return accessPolicy;
}
private async Task<bool> IsAllowedToUpdateAsync(BaseAccessPolicy baseAccessPolicy, Guid userId) =>
baseAccessPolicy switch
{
UserProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId, userId,
ap.GrantedProjectId),
GroupProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId, userId,
ap.GrantedProjectId),
ServiceAccountProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId,
userId, ap.GrantedProjectId),
UserServiceAccountAccessPolicy ap => await HasPermissionsAsync(
ap.GrantedServiceAccount!.OrganizationId,
userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId),
GroupServiceAccountAccessPolicy ap => await HasPermissionsAsync(
ap.GrantedServiceAccount!.OrganizationId,
userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId),
_ => throw new ArgumentException("Unsupported access policy type provided."),
};
private async Task<bool> HasPermissionsAsync(
Guid organizationId,
Guid userId,
Guid? projectIdToCheck = null,
Guid? serviceAccountIdToCheck = null)
{
if (!_currentContext.AccessSecretsManager(organizationId))
{
return false;
}
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.HasValue)
{
hasAccess = await _projectRepository.UserHasWriteAccessToProject(projectIdToCheck.Value, userId);
}
else if (serviceAccountIdToCheck.HasValue)
{
hasAccess =
await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(
serviceAccountIdToCheck.Value, userId);
}
else
{
throw new ArgumentException("No ID to check provided.");
}
break;
default:
hasAccess = false;
break;
}
return hasAccess;
}
}