mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 16:42:50 -05:00
[SM-706] Extract Authorization From Create/Update Secret Commands (#2896)
* Extract authorization from commands * Swap to request model validation. * Swap to pattern detection
This commit is contained in:
@ -0,0 +1,123 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Queries.Interfaces;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Secrets;
|
||||
|
||||
public class SecretAuthorizationHandler : AuthorizationHandler<SecretOperationRequirement, Secret>
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IAccessClientQuery _accessClientQuery;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
|
||||
public SecretAuthorizationHandler(ICurrentContext currentContext, IAccessClientQuery accessClientQuery,
|
||||
IProjectRepository projectRepository, ISecretRepository secretRepository)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_accessClientQuery = accessClientQuery;
|
||||
_projectRepository = projectRepository;
|
||||
_secretRepository = secretRepository;
|
||||
}
|
||||
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
|
||||
SecretOperationRequirement requirement,
|
||||
Secret resource)
|
||||
{
|
||||
if (!_currentContext.AccessSecretsManager(resource.OrganizationId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requirement)
|
||||
{
|
||||
case not null when requirement == SecretOperations.Create:
|
||||
await CanCreateSecretAsync(context, requirement, resource);
|
||||
break;
|
||||
case not null when requirement == SecretOperations.Update:
|
||||
await CanUpdateSecretAsync(context, requirement, resource);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unsupported operation requirement type provided.", nameof(requirement));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CanCreateSecretAsync(AuthorizationHandlerContext context,
|
||||
SecretOperationRequirement requirement, Secret resource)
|
||||
{
|
||||
var (accessClient, userId) = await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId);
|
||||
var project = resource.Projects?.FirstOrDefault();
|
||||
|
||||
if (project == null && accessClient != AccessClientType.NoAccessCheck)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// All projects should be apart of the same organization
|
||||
if (resource.Projects != null
|
||||
&& resource.Projects.Any()
|
||||
&& !await _projectRepository.ProjectsAreInOrganization(resource.Projects.Select(p => p.Id).ToList(),
|
||||
resource.OrganizationId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
AccessClientType.User => (await _projectRepository.AccessToProjectAsync(project!.Id, userId, accessClient))
|
||||
.Write,
|
||||
AccessClientType.ServiceAccount => false,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (hasAccess)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CanUpdateSecretAsync(AuthorizationHandlerContext context,
|
||||
SecretOperationRequirement requirement, Secret resource)
|
||||
{
|
||||
var (accessClient, userId) = await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId);
|
||||
|
||||
// All projects should be apart of the same organization
|
||||
if (resource.Projects != null
|
||||
&& resource.Projects.Any()
|
||||
&& !await _projectRepository.ProjectsAreInOrganization(resource.Projects.Select(p => p.Id).ToList(),
|
||||
resource.OrganizationId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasAccess;
|
||||
|
||||
switch (accessClient)
|
||||
{
|
||||
case AccessClientType.NoAccessCheck:
|
||||
hasAccess = true;
|
||||
break;
|
||||
case AccessClientType.User:
|
||||
var newProject = resource.Projects?.FirstOrDefault();
|
||||
var access = (await _secretRepository.AccessToSecretAsync(resource.Id, userId, accessClient)).Write;
|
||||
var accessToNew = newProject != null &&
|
||||
(await _projectRepository.AccessToProjectAsync(newProject.Id, userId, accessClient))
|
||||
.Write;
|
||||
hasAccess = access && accessToNew;
|
||||
break;
|
||||
default:
|
||||
hasAccess = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasAccess)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,4 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
|
||||
@ -10,44 +7,14 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Secrets;
|
||||
public class CreateSecretCommand : ICreateSecretCommand
|
||||
{
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public CreateSecretCommand(ISecretRepository secretRepository, IProjectRepository projectRepository, ICurrentContext currentContext)
|
||||
public CreateSecretCommand(ISecretRepository secretRepository)
|
||||
{
|
||||
_secretRepository = secretRepository;
|
||||
_projectRepository = projectRepository;
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
public async Task<Secret> CreateAsync(Secret secret, Guid userId)
|
||||
public async Task<Secret> CreateAsync(Secret secret)
|
||||
{
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(secret.OrganizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
var project = secret.Projects?.FirstOrDefault();
|
||||
|
||||
if (project == null && !orgAdmin)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (secret.Projects != null && secret.Projects.Any() && !(await _projectRepository.ProjectsAreInOrganization(secret.Projects.Select(p => p.Id).ToList(), secret.OrganizationId)))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
AccessClientType.User => (await _projectRepository.AccessToProjectAsync(project.Id, userId, accessClient)).Write,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (!hasAccess)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return await _secretRepository.CreateAsync(secret);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
@ -10,33 +8,16 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Secrets;
|
||||
public class UpdateSecretCommand : IUpdateSecretCommand
|
||||
{
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public UpdateSecretCommand(ISecretRepository secretRepository, IProjectRepository projectRepository, ICurrentContext currentContext)
|
||||
public UpdateSecretCommand(ISecretRepository secretRepository)
|
||||
{
|
||||
_secretRepository = secretRepository;
|
||||
_projectRepository = projectRepository;
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
public async Task<Secret> UpdateAsync(Secret updatedSecret, Guid userId)
|
||||
public async Task<Secret> UpdateAsync(Secret updatedSecret)
|
||||
{
|
||||
var secret = await _secretRepository.GetByIdAsync(updatedSecret.Id);
|
||||
if (secret == null || !_currentContext.AccessSecretsManager(secret.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (updatedSecret.Projects != null && updatedSecret.Projects.Any() && !(await _projectRepository.ProjectsAreInOrganization(updatedSecret.Projects.Select(p => p.Id).ToList(), secret.OrganizationId)))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(secret.OrganizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
|
||||
if (!await HasAccessToOriginalAndUpdatedProject(accessClient, secret, updatedSecret, userId))
|
||||
if (secret == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -50,21 +31,4 @@ public class UpdateSecretCommand : IUpdateSecretCommand
|
||||
await _secretRepository.UpdateAsync(secret);
|
||||
return secret;
|
||||
}
|
||||
|
||||
private async Task<bool> HasAccessToOriginalAndUpdatedProject(AccessClientType accessClient, Secret secret, Secret updatedSecret, Guid userId)
|
||||
{
|
||||
switch (accessClient)
|
||||
{
|
||||
case AccessClientType.NoAccessCheck:
|
||||
return true;
|
||||
case AccessClientType.User:
|
||||
var oldProject = secret.Projects?.FirstOrDefault();
|
||||
var newProject = updatedSecret.Projects?.FirstOrDefault();
|
||||
var accessToOld = oldProject != null && (await _projectRepository.AccessToProjectAsync(oldProject.Id, userId, accessClient)).Write;
|
||||
var accessToNew = newProject != null && (await _projectRepository.AccessToProjectAsync(newProject.Id, userId, accessClient)).Write;
|
||||
return accessToOld && accessToNew;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Projects;
|
||||
using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Secrets;
|
||||
using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.ServiceAccounts;
|
||||
using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
|
||||
using Bit.Commercial.Core.SecretsManager.Commands.AccessTokens;
|
||||
@ -26,6 +27,7 @@ public static class SecretsManagerCollectionExtensions
|
||||
public static void AddSecretsManagerServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IAuthorizationHandler, ProjectAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, SecretAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, ServiceAccountAuthorizationHandler>();
|
||||
services.AddScoped<IAccessClientQuery, AccessClientQuery>();
|
||||
services.AddScoped<ICreateSecretCommand, CreateSecretCommand>();
|
||||
|
Reference in New Issue
Block a user