1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 08:32: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:
Thomas Avery
2023-06-08 16:40:35 -05:00
committed by GitHub
parent 6a9e7a1d0a
commit 05f11a8ee1
15 changed files with 674 additions and 231 deletions

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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>();

View File

@ -0,0 +1,377 @@
using System.Reflection;
using System.Security.Claims;
using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Secrets;
using Bit.Commercial.Core.Test.SecretsManager.Enums;
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 Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Authorization;
using NSubstitute;
using Xunit;
namespace Bit.Commercial.Core.Test.SecretsManager.AuthorizationHandlers.Secrets;
[SutProviderCustomize]
[ProjectCustomize]
public class SecretAuthorizationHandlerTests
{
private static void SetupPermission(SutProvider<SecretAuthorizationHandler> sutProvider,
PermissionType permissionType, Guid organizationId, Guid userId = new(),
AccessClientType clientType = AccessClientType.User)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId)
.Returns(true);
sutProvider.GetDependency<IProjectRepository>().ProjectsAreInOrganization(default, default)
.ReturnsForAnyArgs(true);
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, organizationId).ReturnsForAnyArgs(
(AccessClientType.NoAccessCheck, userId));
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, organizationId).ReturnsForAnyArgs(
(clientType, userId));
break;
default:
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
}
}
[Fact]
public void SecretOperations_OnlyPublicStatic()
{
var publicStaticFields = typeof(SecretOperations).GetFields(BindingFlags.Public | BindingFlags.Static);
var allFields = typeof(SecretOperations).GetFields();
Assert.Equal(publicStaticFields.Length, allFields.Length);
}
[Theory]
[BitAutoData]
public async Task Handler_UnsupportedSecretOperationRequirement_Throws(
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, ClaimsPrincipal claimsPrincipal)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(secret.OrganizationId)
.Returns(true);
var requirement = new SecretOperationRequirement();
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await Assert.ThrowsAsync<ArgumentException>(() => sutProvider.Sut.HandleAsync(authzContext));
}
[Theory]
[BitAutoData]
public async Task Handler_SupportedSecretOperationRequirement_Throws(
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, ClaimsPrincipal claimsPrincipal)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(secret.OrganizationId)
.Returns(true);
var requirements = typeof(SecretOperations).GetFields(BindingFlags.Public | BindingFlags.Static)
.Select(i => (SecretOperationRequirement)i.GetValue(null));
foreach (var req in requirements)
{
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { req },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
}
}
[Theory]
[BitAutoData]
public async Task CanCreateSecret_AccessToSecretsManagerFalse_DoesNotSucceed(
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
ClaimsPrincipal claimsPrincipal)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(secret.OrganizationId)
.Returns(false);
var requirement = SecretOperations.Create;
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(AccessClientType.ServiceAccount)]
[BitAutoData(AccessClientType.Organization)]
public async Task CanCreateSecret_NotSupportedClientTypes_DoesNotSucceed(AccessClientType clientType,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, Guid userId,
ClaimsPrincipal claimsPrincipal)
{
var requirement = SecretOperations.Create;
SetupPermission(sutProvider, PermissionType.RunAsUserWithPermission, secret.OrganizationId, userId, clientType);
sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, default).Returns(
(true, true));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task CanCreateSecret_ProjectsNotInOrg_DoesNotSucceed(
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
Guid userId,
ClaimsPrincipal claimsPrincipal)
{
var requirement = SecretOperations.Create;
SetupPermission(sutProvider, PermissionType.RunAsUserWithPermission, secret.OrganizationId, userId);
sutProvider.GetDependency<IProjectRepository>().ProjectsAreInOrganization(default, default)
.ReturnsForAnyArgs(false);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task CanCreateSecret_WithoutProjectUser_DoesNotSucceed(
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
Guid userId,
ClaimsPrincipal claimsPrincipal)
{
secret.Projects = null;
var requirement = SecretOperations.Create;
SetupPermission(sutProvider, PermissionType.RunAsUserWithPermission, secret.OrganizationId, userId);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task CanCreateSecret_WithoutProjectAdmin_Success(SutProvider<SecretAuthorizationHandler> sutProvider,
Secret secret,
Guid userId,
ClaimsPrincipal claimsPrincipal)
{
secret.Projects = null;
var requirement = SecretOperations.Create;
SetupPermission(sutProvider, PermissionType.RunAsAdmin, secret.OrganizationId, userId);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.True(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false)]
public async Task CanCreateSecret_DoesNotSucceed(PermissionType permissionType, bool read, bool write,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
Guid userId,
ClaimsPrincipal claimsPrincipal)
{
var requirement = SecretOperations.Create;
SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId);
sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, default).Returns(
(read, write));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin, true, true)]
[BitAutoData(PermissionType.RunAsAdmin, false, true)]
[BitAutoData(PermissionType.RunAsAdmin, true, false)]
[BitAutoData(PermissionType.RunAsAdmin, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true)]
public async Task CanCreateSecret_Success(PermissionType permissionType, bool read, bool write,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
Guid userId,
ClaimsPrincipal claimsPrincipal)
{
var requirement = SecretOperations.Create;
SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId);
sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, default).Returns(
(read, write));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.True(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task CanUpdateSecret_AccessToSecretsManagerFalse_DoesNotSucceed(
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
ClaimsPrincipal claimsPrincipal)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(secret.OrganizationId)
.Returns(false);
var requirement = SecretOperations.Update;
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(AccessClientType.ServiceAccount)]
[BitAutoData(AccessClientType.Organization)]
public async Task CanUpdateSecret_NotSupportedClientTypes_DoesNotSucceed(AccessClientType clientType,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, Guid userId,
ClaimsPrincipal claimsPrincipal)
{
var requirement = SecretOperations.Update;
SetupPermission(sutProvider, PermissionType.RunAsUserWithPermission, secret.OrganizationId, userId, clientType);
sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, default).Returns(
(true, true));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task CanUpdateSecret_ProjectsNotInOrg_DoesNotSucceed(
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
Guid userId,
ClaimsPrincipal claimsPrincipal)
{
var requirement = SecretOperations.Update;
SetupPermission(sutProvider, PermissionType.RunAsUserWithPermission, secret.OrganizationId, userId);
sutProvider.GetDependency<IProjectRepository>().ProjectsAreInOrganization(default, default)
.ReturnsForAnyArgs(false);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task CanUpdateSecret_WithoutProjectUser_DoesNotSucceed(
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
Guid userId,
ClaimsPrincipal claimsPrincipal)
{
secret.Projects = null;
var requirement = SecretOperations.Update;
SetupPermission(sutProvider, PermissionType.RunAsUserWithPermission, secret.OrganizationId, userId);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task CanUpdateSecret_WithoutProjectAdmin_Success(SutProvider<SecretAuthorizationHandler> sutProvider,
Secret secret,
Guid userId,
ClaimsPrincipal claimsPrincipal)
{
secret.Projects = null;
var requirement = SecretOperations.Update;
SetupPermission(sutProvider, PermissionType.RunAsAdmin, secret.OrganizationId, userId);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.True(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true, true, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true, true, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false, false, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false, false)]
public async Task CanUpdateSecret_DoesNotSucceed(PermissionType permissionType, bool read, bool write,
bool projectRead, bool projectWrite,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
Guid userId,
ClaimsPrincipal claimsPrincipal)
{
var requirement = SecretOperations.Update;
SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId);
sutProvider.GetDependency<ISecretRepository>().AccessToSecretAsync(secret.Id, userId, default).Returns(
(read, write));
sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, default).Returns(
(projectRead, projectWrite));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin, true, true)]
[BitAutoData(PermissionType.RunAsAdmin, false, true)]
[BitAutoData(PermissionType.RunAsAdmin, true, false)]
[BitAutoData(PermissionType.RunAsAdmin, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true)]
public async Task CanUpdateSecret_Success(PermissionType permissionType, bool read, bool write,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
Guid userId,
ClaimsPrincipal claimsPrincipal)
{
var requirement = SecretOperations.Update;
SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId);
sutProvider.GetDependency<ISecretRepository>().AccessToSecretAsync(secret.Id, userId, default).Returns(
(read, write));
sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, default).Returns(
(read, write));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.True(authzContext.HasSucceeded);
}
}

View File

@ -1,8 +1,4 @@
using Bit.Commercial.Core.SecretsManager.Commands.Secrets;
using Bit.Commercial.Core.Test.SecretsManager.Enums;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.SecretsFixture;
@ -18,61 +14,15 @@ namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Secrets;
public class CreateSecretCommandTests
{
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async Task CreateAsync_Success(PermissionType permissionType, Secret data,
SutProvider<CreateSecretCommand> sutProvider, Guid userId, Project mockProject)
[BitAutoData]
public async Task CreateAsync_Success(Secret data,
SutProvider<CreateSecretCommand> sutProvider, Project mockProject)
{
sutProvider.GetDependency<IProjectRepository>().ProjectsAreInOrganization(default, default).ReturnsForAnyArgs(true);
mockProject.OrganizationId = data.OrganizationId;
data.Projects = new List<Project>() { mockProject };
if (permissionType == PermissionType.RunAsAdmin)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(true);
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync((Guid)data.Projects?.First().Id, userId, AccessClientType.NoAccessCheck)
.Returns((true, true));
}
else
{
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync((Guid)data.Projects?.First().Id, userId, AccessClientType.User)
.Returns((true, true));
}
await sutProvider.Sut.CreateAsync(data, userId);
await sutProvider.Sut.CreateAsync(data);
await sutProvider.GetDependency<ISecretRepository>().Received(1)
.CreateAsync(data);
}
[Theory]
[BitAutoData]
public async Task CreateAsync_UserWithoutPermission_ThrowsNotFound(Secret data,
SutProvider<CreateSecretCommand> sutProvider, Guid userId, Project mockProject)
{
data.Projects = new List<Project>() { mockProject };
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync((Guid)data.Projects?.First().Id, userId, AccessClientType.User)
.Returns((false, false));
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.CreateAsync(data, userId));
}
[Theory]
[BitAutoData]
public async Task CreateAsync_NoProjects_User_ThrowsNotFound(Secret data,
SutProvider<CreateSecretCommand> sutProvider, Guid userId)
{
data.Projects = null;
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.CreateAsync(data, userId));
}
}

View File

@ -1,7 +1,4 @@
using Bit.Commercial.Core.SecretsManager.Commands.Secrets;
using Bit.Commercial.Core.Test.SecretsManager.Enums;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
@ -24,36 +21,20 @@ public class UpdateSecretCommandTests
[BitAutoData]
public async Task UpdateAsync_SecretDoesNotExist_ThrowsNotFound(Secret data, SutProvider<UpdateSecretCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, default));
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data));
await sutProvider.GetDependency<ISecretRepository>().DidNotReceiveWithAnyArgs().UpdateAsync(default);
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async Task UpdateAsync_Success(PermissionType permissionType, Secret data, SutProvider<UpdateSecretCommand> sutProvider, Guid userId, Project mockProject)
[BitAutoData]
public async Task UpdateAsync_Success(Secret existingSecret, Secret data, SutProvider<UpdateSecretCommand> sutProvider, Project mockProject)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(data.OrganizationId).Returns(true);
sutProvider.GetDependency<IProjectRepository>().ProjectsAreInOrganization(default, default).ReturnsForAnyArgs(true);
mockProject.OrganizationId = data.OrganizationId;
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
data.Projects = new List<Project>() { mockProject };
if (permissionType == PermissionType.RunAsAdmin)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(true);
}
else
{
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync((Guid)data.Projects?.First().Id, userId, AccessClientType.User)
.Returns((true, true));
}
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(data.Id).Returns(data);
await sutProvider.Sut.UpdateAsync(data, userId);
await sutProvider.Sut.UpdateAsync(data);
await sutProvider.GetDependency<ISecretRepository>().Received(1)
.UpdateAsync(data);
@ -61,13 +42,10 @@ public class UpdateSecretCommandTests
[Theory]
[BitAutoData]
public async Task UpdateAsync_DoesNotModifyOrganizationId(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider, Guid userId)
public async Task UpdateAsync_DoesNotModifyOrganizationId(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider)
{
var updatedOrgId = Guid.NewGuid();
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingSecret.OrganizationId).Returns(true);
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(updatedOrgId).Returns(true);
var secretUpdate = new Secret()
{
@ -76,7 +54,7 @@ public class UpdateSecretCommandTests
Key = existingSecret.Key,
};
var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId);
var result = await sutProvider.Sut.UpdateAsync(secretUpdate);
Assert.Equal(existingSecret.OrganizationId, result.OrganizationId);
Assert.NotEqual(existingSecret.OrganizationId, updatedOrgId);
@ -84,11 +62,9 @@ public class UpdateSecretCommandTests
[Theory]
[BitAutoData]
public async Task UpdateAsync_DoesNotModifyCreationDate(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider, Guid userId)
public async Task UpdateAsync_DoesNotModifyCreationDate(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingSecret.OrganizationId).Returns(true);
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
var updatedCreationDate = DateTime.UtcNow;
var secretUpdate = new Secret()
@ -99,7 +75,7 @@ public class UpdateSecretCommandTests
OrganizationId = existingSecret.OrganizationId
};
var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId);
var result = await sutProvider.Sut.UpdateAsync(secretUpdate);
Assert.Equal(existingSecret.CreationDate, result.CreationDate);
Assert.NotEqual(existingSecret.CreationDate, updatedCreationDate);
@ -107,11 +83,9 @@ public class UpdateSecretCommandTests
[Theory]
[BitAutoData]
public async Task UpdateAsync_DoesNotModifyDeletionDate(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider, Guid userId)
public async Task UpdateAsync_DoesNotModifyDeletionDate(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingSecret.OrganizationId).Returns(true);
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
var updatedDeletionDate = DateTime.UtcNow;
var secretUpdate = new Secret()
@ -122,7 +96,7 @@ public class UpdateSecretCommandTests
OrganizationId = existingSecret.OrganizationId
};
var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId);
var result = await sutProvider.Sut.UpdateAsync(secretUpdate);
Assert.Equal(existingSecret.DeletedDate, result.DeletedDate);
Assert.NotEqual(existingSecret.DeletedDate, updatedDeletionDate);
@ -131,12 +105,9 @@ public class UpdateSecretCommandTests
[Theory]
[BitAutoData]
public async Task UpdateAsync_RevisionDateIsUpdatedToUtcNow(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider, Guid userId)
public async Task UpdateAsync_RevisionDateIsUpdatedToUtcNow(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingSecret.OrganizationId).Returns(true);
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
var updatedRevisionDate = DateTime.UtcNow.AddDays(10);
var secretUpdate = new Secret()
@ -147,7 +118,7 @@ public class UpdateSecretCommandTests
OrganizationId = existingSecret.OrganizationId
};
var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId);
var result = await sutProvider.Sut.UpdateAsync(secretUpdate);
Assert.NotEqual(secretUpdate.RevisionDate, result.RevisionDate);
AssertHelper.AssertRecent(result.RevisionDate);