mirror of
https://github.com/bitwarden/server.git
synced 2025-04-05 13:08:17 -05:00
[SM-702] Extract access policy checks from create/update project commands (#2842)
* Move to access query for project commands * Swap to hasAccess method per action * Swap to authorization handler pattern * Move ProjectOperationRequirement to Core * Add default throw + tests * Swap to reflection for testing switch
This commit is contained in:
parent
7be19b53f4
commit
5474d3da18
@ -0,0 +1,88 @@
|
|||||||
|
using Bit.Core.Context;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||||
|
using Bit.Core.SecretsManager.Entities;
|
||||||
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
|
namespace Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Projects;
|
||||||
|
|
||||||
|
public class ProjectAuthorizationHandler : AuthorizationHandler<ProjectOperationRequirement, Project>
|
||||||
|
{
|
||||||
|
private readonly ICurrentContext _currentContext;
|
||||||
|
private readonly IProjectRepository _projectRepository;
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
|
||||||
|
public ProjectAuthorizationHandler(ICurrentContext currentContext, IUserService userService,
|
||||||
|
IProjectRepository projectRepository)
|
||||||
|
{
|
||||||
|
_currentContext = currentContext;
|
||||||
|
_userService = userService;
|
||||||
|
_projectRepository = projectRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
|
||||||
|
ProjectOperationRequirement requirement,
|
||||||
|
Project resource)
|
||||||
|
{
|
||||||
|
if (!_currentContext.AccessSecretsManager(resource.OrganizationId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (requirement)
|
||||||
|
{
|
||||||
|
case not null when requirement == ProjectOperations.Create:
|
||||||
|
await CanCreateProjectAsync(context, requirement, resource);
|
||||||
|
break;
|
||||||
|
case not null when requirement == ProjectOperations.Update:
|
||||||
|
await CanUpdateProjectAsync(context, requirement, resource);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException("Unsupported project operation requirement type provided.", nameof(requirement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CanCreateProjectAsync(AuthorizationHandlerContext context,
|
||||||
|
ProjectOperationRequirement requirement, Project resource)
|
||||||
|
{
|
||||||
|
var accessClient = await GetAccessClientAsync(resource.OrganizationId);
|
||||||
|
var hasAccess = accessClient switch
|
||||||
|
{
|
||||||
|
AccessClientType.NoAccessCheck => true,
|
||||||
|
AccessClientType.User => true,
|
||||||
|
AccessClientType.ServiceAccount => false,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hasAccess)
|
||||||
|
{
|
||||||
|
context.Succeed(requirement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CanUpdateProjectAsync(AuthorizationHandlerContext context,
|
||||||
|
ProjectOperationRequirement requirement, Project resource)
|
||||||
|
{
|
||||||
|
var accessClient = await GetAccessClientAsync(resource.OrganizationId);
|
||||||
|
if (accessClient == AccessClientType.ServiceAccount)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userId = _userService.GetProperUserId(context.User).Value;
|
||||||
|
var access = await _projectRepository.AccessToProjectAsync(resource.Id, userId, accessClient);
|
||||||
|
|
||||||
|
if (access.Write)
|
||||||
|
{
|
||||||
|
context.Succeed(requirement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<AccessClientType> GetAccessClientAsync(Guid organizationId)
|
||||||
|
{
|
||||||
|
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
|
||||||
|
return AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||||
|
}
|
||||||
|
}
|
@ -14,8 +14,7 @@ public class CreateProjectCommand : ICreateProjectCommand
|
|||||||
public CreateProjectCommand(
|
public CreateProjectCommand(
|
||||||
IAccessPolicyRepository accessPolicyRepository,
|
IAccessPolicyRepository accessPolicyRepository,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
IProjectRepository projectRepository
|
IProjectRepository projectRepository)
|
||||||
)
|
|
||||||
{
|
{
|
||||||
_accessPolicyRepository = accessPolicyRepository;
|
_accessPolicyRepository = accessPolicyRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
using Bit.Core.Context;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Exceptions;
|
|
||||||
using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
|
using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
|
||||||
using Bit.Core.SecretsManager.Entities;
|
using Bit.Core.SecretsManager.Entities;
|
||||||
using Bit.Core.SecretsManager.Repositories;
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
@ -10,15 +8,13 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Projects;
|
|||||||
public class UpdateProjectCommand : IUpdateProjectCommand
|
public class UpdateProjectCommand : IUpdateProjectCommand
|
||||||
{
|
{
|
||||||
private readonly IProjectRepository _projectRepository;
|
private readonly IProjectRepository _projectRepository;
|
||||||
private readonly ICurrentContext _currentContext;
|
|
||||||
|
|
||||||
public UpdateProjectCommand(IProjectRepository projectRepository, ICurrentContext currentContext)
|
public UpdateProjectCommand(IProjectRepository projectRepository)
|
||||||
{
|
{
|
||||||
_projectRepository = projectRepository;
|
_projectRepository = projectRepository;
|
||||||
_currentContext = currentContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Project> UpdateAsync(Project updatedProject, Guid userId)
|
public async Task<Project> UpdateAsync(Project updatedProject)
|
||||||
{
|
{
|
||||||
var project = await _projectRepository.GetByIdAsync(updatedProject.Id);
|
var project = await _projectRepository.GetByIdAsync(updatedProject.Id);
|
||||||
if (project == null)
|
if (project == null)
|
||||||
@ -26,20 +22,6 @@ public class UpdateProjectCommand : IUpdateProjectCommand
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_currentContext.AccessSecretsManager(project.OrganizationId))
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId);
|
|
||||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
|
||||||
|
|
||||||
var access = await _projectRepository.AccessToProjectAsync(updatedProject.Id, userId, accessClient);
|
|
||||||
if (!access.Write || accessClient == AccessClientType.ServiceAccount)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
project.Name = updatedProject.Name;
|
project.Name = updatedProject.Name;
|
||||||
project.RevisionDate = DateTime.UtcNow;
|
project.RevisionDate = DateTime.UtcNow;
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
|
using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Projects;
|
||||||
|
using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
|
||||||
using Bit.Commercial.Core.SecretsManager.Commands.AccessTokens;
|
using Bit.Commercial.Core.SecretsManager.Commands.AccessTokens;
|
||||||
using Bit.Commercial.Core.SecretsManager.Commands.Porting;
|
using Bit.Commercial.Core.SecretsManager.Commands.Porting;
|
||||||
using Bit.Commercial.Core.SecretsManager.Commands.Projects;
|
using Bit.Commercial.Core.SecretsManager.Commands.Projects;
|
||||||
@ -12,6 +13,7 @@ using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
|
|||||||
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||||
using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
|
using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
|
||||||
using Bit.Core.SecretsManager.Commands.Trash.Interfaces;
|
using Bit.Core.SecretsManager.Commands.Trash.Interfaces;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.SecretsManager;
|
namespace Bit.Commercial.Core.SecretsManager;
|
||||||
@ -20,6 +22,7 @@ public static class SecretsManagerCollectionExtensions
|
|||||||
{
|
{
|
||||||
public static void AddSecretsManagerServices(this IServiceCollection services)
|
public static void AddSecretsManagerServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
services.AddScoped<IAuthorizationHandler, ProjectAuthorizationHandler>();
|
||||||
services.AddScoped<ICreateSecretCommand, CreateSecretCommand>();
|
services.AddScoped<ICreateSecretCommand, CreateSecretCommand>();
|
||||||
services.AddScoped<IUpdateSecretCommand, UpdateSecretCommand>();
|
services.AddScoped<IUpdateSecretCommand, UpdateSecretCommand>();
|
||||||
services.AddScoped<IDeleteSecretCommand, DeleteSecretCommand>();
|
services.AddScoped<IDeleteSecretCommand, DeleteSecretCommand>();
|
||||||
|
@ -125,7 +125,7 @@ public class ProjectRepository : Repository<Core.SecretsManager.Entities.Project
|
|||||||
|
|
||||||
var policy = await query.FirstOrDefaultAsync();
|
var policy = await query.FirstOrDefaultAsync();
|
||||||
|
|
||||||
return (policy.Read, policy.Write);
|
return policy == null ? (false, false) : (policy.Read, policy.Write);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> ProjectsAreInOrganization(List<Guid> projectIds, Guid organizationId)
|
public async Task<bool> ProjectsAreInOrganization(List<Guid> projectIds, Guid organizationId)
|
||||||
|
@ -0,0 +1,245 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Projects;
|
||||||
|
using Bit.Commercial.Core.Test.SecretsManager.Enums;
|
||||||
|
using Bit.Core.Context;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Identity;
|
||||||
|
using Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||||
|
using Bit.Core.SecretsManager.Entities;
|
||||||
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
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.Projects;
|
||||||
|
|
||||||
|
[SutProviderCustomize]
|
||||||
|
[ProjectCustomize]
|
||||||
|
public class ProjectAuthorizationHandlerTests
|
||||||
|
{
|
||||||
|
private static void SetupPermission(SutProvider<ProjectAuthorizationHandler> sutProvider,
|
||||||
|
PermissionType permissionType, Guid organizationId)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().ClientType
|
||||||
|
.Returns(ClientType.User);
|
||||||
|
|
||||||
|
switch (permissionType)
|
||||||
|
{
|
||||||
|
case PermissionType.RunAsAdmin:
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
|
||||||
|
break;
|
||||||
|
case PermissionType.RunAsUserWithPermission:
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProjectOperations_OnlyPublicStatic()
|
||||||
|
{
|
||||||
|
var publicStaticFields = typeof(ProjectOperations).GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||||
|
var allFields = typeof(ProjectOperations).GetFields();
|
||||||
|
Assert.Equal(publicStaticFields.Length, allFields.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task Handler_UnsupportedProjectOperationRequirement_Throws(
|
||||||
|
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId)
|
||||||
|
.Returns(true);
|
||||||
|
var requirement = new ProjectOperationRequirement();
|
||||||
|
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||||
|
claimsPrincipal, project);
|
||||||
|
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<ArgumentException>(() => sutProvider.Sut.HandleAsync(authzContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task Handler_SupportedProjectOperationRequirement_DoesNotThrow(
|
||||||
|
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(new Guid());
|
||||||
|
|
||||||
|
var requirements = typeof(ProjectOperations).GetFields(BindingFlags.Public | BindingFlags.Static)
|
||||||
|
.Select(i => (ProjectOperationRequirement)i.GetValue(null));
|
||||||
|
|
||||||
|
foreach (var req in requirements)
|
||||||
|
{
|
||||||
|
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { req },
|
||||||
|
claimsPrincipal, project);
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleAsync(authzContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task CanCreateProject_AccessToSecretsManagerFalse_DoesNotSucceed(
|
||||||
|
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId)
|
||||||
|
.Returns(false);
|
||||||
|
var requirement = ProjectOperations.Create;
|
||||||
|
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||||
|
claimsPrincipal, project);
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleAsync(authzContext);
|
||||||
|
|
||||||
|
Assert.False(authzContext.HasSucceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData(ClientType.ServiceAccount)]
|
||||||
|
[BitAutoData(ClientType.Organization)]
|
||||||
|
public async Task CanCreateProject_NotSupportedClientTypes_DoesNotSucceed(ClientType clientType,
|
||||||
|
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(project.OrganizationId)
|
||||||
|
.Returns(false);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().ClientType
|
||||||
|
.Returns(clientType);
|
||||||
|
var requirement = ProjectOperations.Create;
|
||||||
|
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||||
|
claimsPrincipal, project);
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleAsync(authzContext);
|
||||||
|
|
||||||
|
Assert.False(authzContext.HasSucceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||||
|
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||||
|
public async Task CanCreateProject_Success(PermissionType permissionType,
|
||||||
|
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal)
|
||||||
|
{
|
||||||
|
SetupPermission(sutProvider, permissionType, project.OrganizationId);
|
||||||
|
var requirement = ProjectOperations.Create;
|
||||||
|
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||||
|
claimsPrincipal, project);
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleAsync(authzContext);
|
||||||
|
|
||||||
|
Assert.True(authzContext.HasSucceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task CanUpdateProject_AccessToSecretsManagerFalse_DoesNotSucceed(
|
||||||
|
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId)
|
||||||
|
.Returns(false);
|
||||||
|
var requirement = ProjectOperations.Update;
|
||||||
|
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||||
|
claimsPrincipal, project);
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleAsync(authzContext);
|
||||||
|
|
||||||
|
Assert.False(authzContext.HasSucceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task CanUpdateProject_NullResource_DoesNotSucceed(
|
||||||
|
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal,
|
||||||
|
Guid userId)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId)
|
||||||
|
.Returns(true);
|
||||||
|
SetupPermission(sutProvider, PermissionType.RunAsAdmin, project.OrganizationId);
|
||||||
|
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||||
|
sutProvider.GetDependency<IProjectRepository>()
|
||||||
|
.AccessToProjectAsync(project.Id, userId, Arg.Any<AccessClientType>())
|
||||||
|
.Returns((true, true));
|
||||||
|
var requirement = ProjectOperations.Update;
|
||||||
|
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||||
|
claimsPrincipal, null);
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleAsync(authzContext);
|
||||||
|
|
||||||
|
Assert.False(authzContext.HasSucceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task CanUpdateProject_NotSupportedClientType_DoesNotSucceed(
|
||||||
|
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(project.OrganizationId).Returns(false);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().ClientType
|
||||||
|
.Returns(ClientType.ServiceAccount);
|
||||||
|
var requirement = ProjectOperations.Update;
|
||||||
|
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||||
|
claimsPrincipal, project);
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleAsync(authzContext);
|
||||||
|
|
||||||
|
Assert.False(authzContext.HasSucceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false)]
|
||||||
|
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false)]
|
||||||
|
public async Task CanUpdateProject_ShouldNotSucceed(PermissionType permissionType, bool read, bool write,
|
||||||
|
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal,
|
||||||
|
Guid userId)
|
||||||
|
{
|
||||||
|
SetupPermission(sutProvider, permissionType, project.OrganizationId);
|
||||||
|
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||||
|
sutProvider.GetDependency<IProjectRepository>()
|
||||||
|
.AccessToProjectAsync(project.Id, userId, Arg.Any<AccessClientType>())
|
||||||
|
.Returns((read, write));
|
||||||
|
var requirement = ProjectOperations.Update;
|
||||||
|
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||||
|
claimsPrincipal, project);
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleAsync(authzContext);
|
||||||
|
|
||||||
|
Assert.False(authzContext.HasSucceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData(PermissionType.RunAsAdmin, true, true)]
|
||||||
|
[BitAutoData(PermissionType.RunAsAdmin, false, true)]
|
||||||
|
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true)]
|
||||||
|
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true)]
|
||||||
|
public async Task CanUpdateProject_Success(PermissionType permissionType, bool read, bool write,
|
||||||
|
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal,
|
||||||
|
Guid userId)
|
||||||
|
{
|
||||||
|
SetupPermission(sutProvider, permissionType, project.OrganizationId);
|
||||||
|
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||||
|
sutProvider.GetDependency<IProjectRepository>()
|
||||||
|
.AccessToProjectAsync(project.Id, userId, Arg.Any<AccessClientType>())
|
||||||
|
.Returns((read, write));
|
||||||
|
var requirement = ProjectOperations.Update;
|
||||||
|
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||||
|
claimsPrincipal, project);
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleAsync(authzContext);
|
||||||
|
|
||||||
|
Assert.True(authzContext.HasSucceeded);
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ using Bit.Test.Common.Helpers;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessPolicies;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
[ProjectCustomize]
|
[ProjectCustomize]
|
@ -13,7 +13,7 @@ using NSubstitute;
|
|||||||
using NSubstitute.ReturnsExtensions;
|
using NSubstitute.ReturnsExtensions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessPolicies;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
[ProjectCustomize]
|
[ProjectCustomize]
|
@ -13,7 +13,7 @@ using Bit.Test.Common.Helpers;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessPolicies;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
[ProjectCustomize]
|
[ProjectCustomize]
|
@ -9,7 +9,7 @@ using Bit.Test.Common.Helpers;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.AccessTokens;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessTokens;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
public class CreateServiceAccountCommandTests
|
public class CreateServiceAccountCommandTests
|
@ -9,7 +9,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.Projects;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Projects;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
[ProjectCustomize]
|
[ProjectCustomize]
|
@ -10,7 +10,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.Projects;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Projects;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
public class DeleteProjectCommandTests
|
public class DeleteProjectCommandTests
|
@ -0,0 +1,43 @@
|
|||||||
|
using Bit.Commercial.Core.SecretsManager.Commands.Projects;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.SecretsManager.Entities;
|
||||||
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
|
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
|
||||||
|
using Bit.Test.Common.AutoFixture;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using NSubstitute;
|
||||||
|
using NSubstitute.ReturnsExtensions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Projects;
|
||||||
|
|
||||||
|
[SutProviderCustomize]
|
||||||
|
[ProjectCustomize]
|
||||||
|
public class UpdateProjectCommandTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task UpdateAsync_Throws_NotFoundException(Project project, SutProvider<UpdateProjectCommand> sutProvider)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).ReturnsNull();
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(project));
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IProjectRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task UpdateAsync_Success(Project project, SutProvider<UpdateProjectCommand> sutProvider)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
|
||||||
|
|
||||||
|
var updatedProject = new Project { Id = project.Id, Name = "newName" };
|
||||||
|
var result = await sutProvider.Sut.UpdateAsync(updatedProject);
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("newName", result.Name);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IProjectRepository>().ReceivedWithAnyArgs(1).ReplaceAsync(default);
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.Secrets;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Secrets;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
[SecretCustomize]
|
[SecretCustomize]
|
@ -12,7 +12,7 @@ using Bit.Test.Common.Helpers;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.Secrets;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Secrets;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
[ProjectCustomize]
|
[ProjectCustomize]
|
@ -13,7 +13,7 @@ using Bit.Test.Common.Helpers;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.Secrets;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Secrets;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
[SecretCustomize]
|
[SecretCustomize]
|
@ -9,7 +9,7 @@ using Bit.Test.Common.Helpers;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.ServiceAccounts;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.ServiceAccounts;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
public class CreateServiceAccountCommandTests
|
public class CreateServiceAccountCommandTests
|
@ -6,7 +6,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.ServiceAccounts;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.ServiceAccounts;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
public class RevokeAccessTokenCommandTests
|
public class RevokeAccessTokenCommandTests
|
@ -9,7 +9,7 @@ using Bit.Test.Common.Helpers;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.ServiceAccounts;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.ServiceAccounts;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
public class UpdateServiceAccountCommandTests
|
public class UpdateServiceAccountCommandTests
|
@ -8,7 +8,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.Trash;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Trash;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
[ProjectCustomize]
|
[ProjectCustomize]
|
@ -8,7 +8,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.Trash;
|
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Trash;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
[ProjectCustomize]
|
[ProjectCustomize]
|
@ -1,83 +0,0 @@
|
|||||||
using Bit.Commercial.Core.SecretsManager.Commands.Projects;
|
|
||||||
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.ProjectsFixture;
|
|
||||||
using Bit.Test.Common.AutoFixture;
|
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
|
||||||
using Bit.Test.Common.Helpers;
|
|
||||||
using NSubstitute;
|
|
||||||
using NSubstitute.ReturnsExtensions;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.SecretsManager.Projects;
|
|
||||||
|
|
||||||
[SutProviderCustomize]
|
|
||||||
[ProjectCustomize]
|
|
||||||
public class UpdateProjectCommandTests
|
|
||||||
{
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData]
|
|
||||||
public async Task UpdateAsync_Throws_NotFoundException(Project project, Guid userId, SutProvider<UpdateProjectCommand> sutProvider)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).ReturnsNull();
|
|
||||||
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(project, userId));
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IProjectRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData]
|
|
||||||
public async Task UpdateAsync_Admin_Succeeds(Project project, Guid userId, SutProvider<UpdateProjectCommand> sutProvider)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId).Returns(true);
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(project.OrganizationId).Returns(true);
|
|
||||||
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync(project.Id, userId, AccessClientType.NoAccessCheck)
|
|
||||||
.Returns((true, true));
|
|
||||||
|
|
||||||
var project2 = new Project { Id = project.Id, Name = "newName" };
|
|
||||||
var result = await sutProvider.Sut.UpdateAsync(project2, userId);
|
|
||||||
|
|
||||||
Assert.NotNull(result);
|
|
||||||
Assert.Equal("newName", result.Name);
|
|
||||||
AssertHelper.AssertRecent(result.RevisionDate);
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IProjectRepository>().ReceivedWithAnyArgs(1).ReplaceAsync(default);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData]
|
|
||||||
public async Task UpdateAsync_User_NoAccess(Project project, Guid userId, SutProvider<UpdateProjectCommand> sutProvider)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
|
|
||||||
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync(project.Id, userId, AccessClientType.User)
|
|
||||||
.Returns((false, false));
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId).Returns(true);
|
|
||||||
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(project, userId));
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IProjectRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData]
|
|
||||||
public async Task UpdateAsync_User_Success(Project project, Guid userId, SutProvider<UpdateProjectCommand> sutProvider)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
|
|
||||||
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync(project.Id, userId, AccessClientType.User)
|
|
||||||
.Returns((true, true));
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId).Returns(true);
|
|
||||||
|
|
||||||
var project2 = new Project { Id = project.Id, Name = "newName" };
|
|
||||||
var result = await sutProvider.Sut.UpdateAsync(project2, userId);
|
|
||||||
|
|
||||||
Assert.NotNull(result);
|
|
||||||
Assert.Equal("newName", result.Name);
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IProjectRepository>().ReceivedWithAnyArgs(1).ReplaceAsync(default);
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,6 +4,7 @@ using Bit.Api.SecretsManager.Models.Response;
|
|||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||||
using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
|
using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
|
||||||
using Bit.Core.SecretsManager.Repositories;
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -22,6 +23,7 @@ public class ProjectsController : Controller
|
|||||||
private readonly ICreateProjectCommand _createProjectCommand;
|
private readonly ICreateProjectCommand _createProjectCommand;
|
||||||
private readonly IUpdateProjectCommand _updateProjectCommand;
|
private readonly IUpdateProjectCommand _updateProjectCommand;
|
||||||
private readonly IDeleteProjectCommand _deleteProjectCommand;
|
private readonly IDeleteProjectCommand _deleteProjectCommand;
|
||||||
|
private readonly IAuthorizationService _authorizationService;
|
||||||
|
|
||||||
public ProjectsController(
|
public ProjectsController(
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
@ -29,7 +31,8 @@ public class ProjectsController : Controller
|
|||||||
IProjectRepository projectRepository,
|
IProjectRepository projectRepository,
|
||||||
ICreateProjectCommand createProjectCommand,
|
ICreateProjectCommand createProjectCommand,
|
||||||
IUpdateProjectCommand updateProjectCommand,
|
IUpdateProjectCommand updateProjectCommand,
|
||||||
IDeleteProjectCommand deleteProjectCommand)
|
IDeleteProjectCommand deleteProjectCommand,
|
||||||
|
IAuthorizationService authorizationService)
|
||||||
{
|
{
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
@ -37,6 +40,7 @@ public class ProjectsController : Controller
|
|||||||
_createProjectCommand = createProjectCommand;
|
_createProjectCommand = createProjectCommand;
|
||||||
_updateProjectCommand = updateProjectCommand;
|
_updateProjectCommand = updateProjectCommand;
|
||||||
_deleteProjectCommand = deleteProjectCommand;
|
_deleteProjectCommand = deleteProjectCommand;
|
||||||
|
_authorizationService = authorizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("organizations/{organizationId}/projects")]
|
[HttpGet("organizations/{organizationId}/projects")]
|
||||||
@ -58,26 +62,37 @@ public class ProjectsController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("organizations/{organizationId}/projects")]
|
[HttpPost("organizations/{organizationId}/projects")]
|
||||||
public async Task<ProjectResponseModel> CreateAsync([FromRoute] Guid organizationId, [FromBody] ProjectCreateRequestModel createRequest)
|
public async Task<ProjectResponseModel> CreateAsync([FromRoute] Guid organizationId,
|
||||||
|
[FromBody] ProjectCreateRequestModel createRequest)
|
||||||
{
|
{
|
||||||
if (!_currentContext.AccessSecretsManager(organizationId))
|
var project = createRequest.ToProject(organizationId);
|
||||||
|
var authorizationResult =
|
||||||
|
await _authorizationService.AuthorizeAsync(User, project, ProjectOperations.Create);
|
||||||
|
if (!authorizationResult.Succeeded)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var result = await _createProjectCommand.CreateAsync(createRequest.ToProject(organizationId), userId);
|
var result = await _createProjectCommand.CreateAsync(project, userId);
|
||||||
|
|
||||||
// Creating a project means you have read & write permission.
|
// Creating a project means you have read & write permission.
|
||||||
return new ProjectResponseModel(result, true, true);
|
return new ProjectResponseModel(result, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("projects/{id}")]
|
[HttpPut("projects/{id}")]
|
||||||
public async Task<ProjectResponseModel> UpdateAsync([FromRoute] Guid id, [FromBody] ProjectUpdateRequestModel updateRequest)
|
public async Task<ProjectResponseModel> UpdateAsync([FromRoute] Guid id,
|
||||||
|
[FromBody] ProjectUpdateRequestModel updateRequest)
|
||||||
{
|
{
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var project = await _projectRepository.GetByIdAsync(id);
|
||||||
|
var authorizationResult =
|
||||||
|
await _authorizationService.AuthorizeAsync(User, project, ProjectOperations.Update);
|
||||||
|
if (!authorizationResult.Succeeded)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
var result = await _updateProjectCommand.UpdateAsync(updateRequest.ToProject(id), userId);
|
var result = await _updateProjectCommand.UpdateAsync(updateRequest.ToProject(id));
|
||||||
|
|
||||||
// Updating a project means you have read & write permission.
|
// Updating a project means you have read & write permission.
|
||||||
return new ProjectResponseModel(result, true, true);
|
return new ProjectResponseModel(result, true, true);
|
||||||
|
@ -12,7 +12,7 @@ public class ProjectCreateRequestModel
|
|||||||
|
|
||||||
public Project ToProject(Guid organizationId)
|
public Project ToProject(Guid organizationId)
|
||||||
{
|
{
|
||||||
return new Project()
|
return new Project
|
||||||
{
|
{
|
||||||
OrganizationId = organizationId,
|
OrganizationId = organizationId,
|
||||||
Name = Name,
|
Name = Name,
|
||||||
|
@ -12,11 +12,10 @@ public class ProjectUpdateRequestModel
|
|||||||
|
|
||||||
public Project ToProject(Guid id)
|
public Project ToProject(Guid id)
|
||||||
{
|
{
|
||||||
return new Project()
|
return new Project
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
Name = Name,
|
Name = Name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||||
|
|
||||||
|
namespace Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||||
|
|
||||||
|
public class ProjectOperationRequirement : OperationAuthorizationRequirement
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ProjectOperations
|
||||||
|
{
|
||||||
|
public static readonly ProjectOperationRequirement Create = new() { Name = nameof(Create) };
|
||||||
|
public static readonly ProjectOperationRequirement Update = new() { Name = nameof(Update) };
|
||||||
|
}
|
@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.Projects.Interfaces;
|
|||||||
|
|
||||||
public interface IUpdateProjectCommand
|
public interface IUpdateProjectCommand
|
||||||
{
|
{
|
||||||
Task<Project> UpdateAsync(Project updatedProject, Guid userId);
|
Task<Project> UpdateAsync(Project updatedProject);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Api.SecretsManager.Controllers;
|
using System.Security.Claims;
|
||||||
|
using Bit.Api.SecretsManager.Controllers;
|
||||||
using Bit.Api.SecretsManager.Models.Request;
|
using Bit.Api.SecretsManager.Models.Request;
|
||||||
using Bit.Api.Test.SecretsManager.Enums;
|
using Bit.Api.Test.SecretsManager.Enums;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
@ -13,6 +14,7 @@ using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
|
|||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
using Bit.Test.Common.Helpers;
|
using Bit.Test.Common.Helpers;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@ -102,10 +104,18 @@ public class ProjectsControllerTests
|
|||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData]
|
[BitAutoData]
|
||||||
public async void Create_SmNotEnabled_Throws(SutProvider<ProjectsController> sutProvider, Guid orgId,
|
public async void Create_NoAccess_Throws(SutProvider<ProjectsController> sutProvider,
|
||||||
ProjectCreateRequestModel data)
|
Guid orgId, ProjectCreateRequestModel data)
|
||||||
{
|
{
|
||||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(orgId).Returns(false);
|
sutProvider.GetDependency<IAuthorizationService>()
|
||||||
|
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data.ToProject(orgId),
|
||||||
|
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Failed());
|
||||||
|
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
|
||||||
|
|
||||||
|
var resultProject = data.ToProject(orgId);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ICreateProjectCommand>().CreateAsync(default, default)
|
||||||
|
.ReturnsForAnyArgs(resultProject);
|
||||||
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(orgId, data));
|
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(orgId, data));
|
||||||
await sutProvider.GetDependency<ICreateProjectCommand>().DidNotReceiveWithAnyArgs()
|
await sutProvider.GetDependency<ICreateProjectCommand>().DidNotReceiveWithAnyArgs()
|
||||||
@ -113,22 +123,17 @@ public class ProjectsControllerTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
[BitAutoData]
|
||||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
public async void Create_Success(SutProvider<ProjectsController> sutProvider,
|
||||||
public async void Create_Success(PermissionType permissionType, SutProvider<ProjectsController> sutProvider,
|
|
||||||
Guid orgId, ProjectCreateRequestModel data)
|
Guid orgId, ProjectCreateRequestModel data)
|
||||||
{
|
{
|
||||||
switch (permissionType)
|
sutProvider.GetDependency<IAuthorizationService>()
|
||||||
{
|
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data.ToProject(orgId),
|
||||||
case PermissionType.RunAsAdmin:
|
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
|
||||||
SetupAdmin(sutProvider, orgId);
|
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
|
||||||
break;
|
|
||||||
case PermissionType.RunAsUserWithPermission:
|
|
||||||
SetupUserWithPermission(sutProvider, orgId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var resultProject = data.ToProject(orgId);
|
var resultProject = data.ToProject(orgId);
|
||||||
|
|
||||||
sutProvider.GetDependency<ICreateProjectCommand>().CreateAsync(default, default)
|
sutProvider.GetDependency<ICreateProjectCommand>().CreateAsync(default, default)
|
||||||
.ReturnsForAnyArgs(resultProject);
|
.ReturnsForAnyArgs(resultProject);
|
||||||
|
|
||||||
@ -139,29 +144,44 @@ public class ProjectsControllerTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
[BitAutoData]
|
||||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
public async void Update_NoAccess_Throws(SutProvider<ProjectsController> sutProvider,
|
||||||
public async void Update_Success(PermissionType permissionType, SutProvider<ProjectsController> sutProvider,
|
Guid userId, ProjectUpdateRequestModel data, Project existingProject)
|
||||||
Guid orgId, ProjectUpdateRequestModel data)
|
|
||||||
{
|
{
|
||||||
switch (permissionType)
|
sutProvider.GetDependency<IAuthorizationService>()
|
||||||
{
|
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data.ToProject(existingProject.Id),
|
||||||
case PermissionType.RunAsAdmin:
|
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Failed());
|
||||||
SetupAdmin(sutProvider, orgId);
|
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(existingProject.Id).ReturnsForAnyArgs(existingProject);
|
||||||
break;
|
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||||
case PermissionType.RunAsUserWithPermission:
|
|
||||||
SetupUserWithPermission(sutProvider, orgId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var resultProject = data.ToProject(orgId);
|
var resultProject = data.ToProject(existingProject.Id);
|
||||||
sutProvider.GetDependency<IUpdateProjectCommand>().UpdateAsync(default, default)
|
sutProvider.GetDependency<IUpdateProjectCommand>().UpdateAsync(default)
|
||||||
.ReturnsForAnyArgs(resultProject);
|
.ReturnsForAnyArgs(resultProject);
|
||||||
|
|
||||||
await sutProvider.Sut.UpdateAsync(orgId, data);
|
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(existingProject.Id, data));
|
||||||
|
await sutProvider.GetDependency<IUpdateProjectCommand>().DidNotReceiveWithAnyArgs()
|
||||||
|
.UpdateAsync(Arg.Any<Project>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async void Update_Success(SutProvider<ProjectsController> sutProvider,
|
||||||
|
Guid userId, ProjectUpdateRequestModel data, Project existingProject)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IAuthorizationService>()
|
||||||
|
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data.ToProject(existingProject.Id),
|
||||||
|
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
|
||||||
|
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(existingProject.Id).ReturnsForAnyArgs(existingProject);
|
||||||
|
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||||
|
|
||||||
|
var resultProject = data.ToProject(existingProject.Id);
|
||||||
|
sutProvider.GetDependency<IUpdateProjectCommand>().UpdateAsync(default)
|
||||||
|
.ReturnsForAnyArgs(resultProject);
|
||||||
|
|
||||||
|
await sutProvider.Sut.UpdateAsync(existingProject.Id, data);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IUpdateProjectCommand>().Received(1)
|
await sutProvider.GetDependency<IUpdateProjectCommand>().Received(1)
|
||||||
.UpdateAsync(Arg.Any<Project>(), Arg.Any<Guid>());
|
.UpdateAsync(Arg.Any<Project>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user