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:
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user