mirror of
https://github.com/bitwarden/server.git
synced 2025-02-22 02:51:33 +01: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(
|
||||
IAccessPolicyRepository accessPolicyRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IProjectRepository projectRepository
|
||||
)
|
||||
IProjectRepository projectRepository)
|
||||
{
|
||||
_accessPolicyRepository = accessPolicyRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
|
@ -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.Projects.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
@ -10,15 +8,13 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Projects;
|
||||
public class UpdateProjectCommand : IUpdateProjectCommand
|
||||
{
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public UpdateProjectCommand(IProjectRepository projectRepository, ICurrentContext currentContext)
|
||||
public UpdateProjectCommand(IProjectRepository 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);
|
||||
if (project == null)
|
||||
@ -26,20 +22,6 @@ public class UpdateProjectCommand : IUpdateProjectCommand
|
||||
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.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.Porting;
|
||||
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.ServiceAccounts.Interfaces;
|
||||
using Bit.Core.SecretsManager.Commands.Trash.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Bit.Commercial.Core.SecretsManager;
|
||||
@ -20,6 +22,7 @@ public static class SecretsManagerCollectionExtensions
|
||||
{
|
||||
public static void AddSecretsManagerServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IAuthorizationHandler, ProjectAuthorizationHandler>();
|
||||
services.AddScoped<ICreateSecretCommand, CreateSecretCommand>();
|
||||
services.AddScoped<IUpdateSecretCommand, UpdateSecretCommand>();
|
||||
services.AddScoped<IDeleteSecretCommand, DeleteSecretCommand>();
|
||||
|
@ -125,7 +125,7 @@ public class ProjectRepository : Repository<Core.SecretsManager.Entities.Project
|
||||
|
||||
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)
|
||||
|
@ -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 Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessPolicies;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[ProjectCustomize]
|
@ -13,7 +13,7 @@ using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessPolicies;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[ProjectCustomize]
|
@ -13,7 +13,7 @@ using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessPolicies;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[ProjectCustomize]
|
@ -9,7 +9,7 @@ using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.AccessTokens;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.AccessTokens;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class CreateServiceAccountCommandTests
|
@ -9,7 +9,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Projects;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Projects;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[ProjectCustomize]
|
@ -10,7 +10,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Projects;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Projects;
|
||||
|
||||
[SutProviderCustomize]
|
||||
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 Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Secrets;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Secrets;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[SecretCustomize]
|
@ -12,7 +12,7 @@ using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Secrets;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Secrets;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[ProjectCustomize]
|
@ -13,7 +13,7 @@ using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Secrets;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Secrets;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[SecretCustomize]
|
@ -9,7 +9,7 @@ using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.ServiceAccounts;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.ServiceAccounts;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class CreateServiceAccountCommandTests
|
@ -6,7 +6,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.ServiceAccounts;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.ServiceAccounts;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class RevokeAccessTokenCommandTests
|
@ -9,7 +9,7 @@ using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.ServiceAccounts;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.ServiceAccounts;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class UpdateServiceAccountCommandTests
|
@ -8,7 +8,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Trash;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Trash;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[ProjectCustomize]
|
@ -8,7 +8,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Trash;
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Trash;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[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.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||
using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@ -22,6 +23,7 @@ public class ProjectsController : Controller
|
||||
private readonly ICreateProjectCommand _createProjectCommand;
|
||||
private readonly IUpdateProjectCommand _updateProjectCommand;
|
||||
private readonly IDeleteProjectCommand _deleteProjectCommand;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
public ProjectsController(
|
||||
ICurrentContext currentContext,
|
||||
@ -29,7 +31,8 @@ public class ProjectsController : Controller
|
||||
IProjectRepository projectRepository,
|
||||
ICreateProjectCommand createProjectCommand,
|
||||
IUpdateProjectCommand updateProjectCommand,
|
||||
IDeleteProjectCommand deleteProjectCommand)
|
||||
IDeleteProjectCommand deleteProjectCommand,
|
||||
IAuthorizationService authorizationService)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_userService = userService;
|
||||
@ -37,6 +40,7 @@ public class ProjectsController : Controller
|
||||
_createProjectCommand = createProjectCommand;
|
||||
_updateProjectCommand = updateProjectCommand;
|
||||
_deleteProjectCommand = deleteProjectCommand;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
[HttpGet("organizations/{organizationId}/projects")]
|
||||
@ -58,26 +62,37 @@ public class ProjectsController : Controller
|
||||
}
|
||||
|
||||
[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();
|
||||
}
|
||||
|
||||
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.
|
||||
return new ProjectResponseModel(result, true, true);
|
||||
}
|
||||
|
||||
[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.
|
||||
return new ProjectResponseModel(result, true, true);
|
||||
|
@ -12,7 +12,7 @@ public class ProjectCreateRequestModel
|
||||
|
||||
public Project ToProject(Guid organizationId)
|
||||
{
|
||||
return new Project()
|
||||
return new Project
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
Name = Name,
|
||||
|
@ -12,11 +12,10 @@ public class ProjectUpdateRequestModel
|
||||
|
||||
public Project ToProject(Guid id)
|
||||
{
|
||||
return new Project()
|
||||
return new Project
|
||||
{
|
||||
Id = id,
|
||||
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
|
||||
{
|
||||
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.Test.SecretsManager.Enums;
|
||||
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.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
@ -102,10 +104,18 @@ public class ProjectsControllerTests
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void Create_SmNotEnabled_Throws(SutProvider<ProjectsController> sutProvider, Guid orgId,
|
||||
ProjectCreateRequestModel data)
|
||||
public async void Create_NoAccess_Throws(SutProvider<ProjectsController> sutProvider,
|
||||
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 sutProvider.GetDependency<ICreateProjectCommand>().DidNotReceiveWithAnyArgs()
|
||||
@ -113,22 +123,17 @@ public class ProjectsControllerTests
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async void Create_Success(PermissionType permissionType, SutProvider<ProjectsController> sutProvider,
|
||||
[BitAutoData]
|
||||
public async void Create_Success(SutProvider<ProjectsController> sutProvider,
|
||||
Guid orgId, ProjectCreateRequestModel data)
|
||||
{
|
||||
switch (permissionType)
|
||||
{
|
||||
case PermissionType.RunAsAdmin:
|
||||
SetupAdmin(sutProvider, orgId);
|
||||
break;
|
||||
case PermissionType.RunAsUserWithPermission:
|
||||
SetupUserWithPermission(sutProvider, orgId);
|
||||
break;
|
||||
}
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data.ToProject(orgId),
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
|
||||
|
||||
var resultProject = data.ToProject(orgId);
|
||||
|
||||
sutProvider.GetDependency<ICreateProjectCommand>().CreateAsync(default, default)
|
||||
.ReturnsForAnyArgs(resultProject);
|
||||
|
||||
@ -139,29 +144,44 @@ public class ProjectsControllerTests
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async void Update_Success(PermissionType permissionType, SutProvider<ProjectsController> sutProvider,
|
||||
Guid orgId, ProjectUpdateRequestModel data)
|
||||
[BitAutoData]
|
||||
public async void Update_NoAccess_Throws(SutProvider<ProjectsController> sutProvider,
|
||||
Guid userId, ProjectUpdateRequestModel data, Project existingProject)
|
||||
{
|
||||
switch (permissionType)
|
||||
{
|
||||
case PermissionType.RunAsAdmin:
|
||||
SetupAdmin(sutProvider, orgId);
|
||||
break;
|
||||
case PermissionType.RunAsUserWithPermission:
|
||||
SetupUserWithPermission(sutProvider, orgId);
|
||||
break;
|
||||
}
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data.ToProject(existingProject.Id),
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Failed());
|
||||
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(existingProject.Id).ReturnsForAnyArgs(existingProject);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||
|
||||
var resultProject = data.ToProject(orgId);
|
||||
sutProvider.GetDependency<IUpdateProjectCommand>().UpdateAsync(default, default)
|
||||
var resultProject = data.ToProject(existingProject.Id);
|
||||
sutProvider.GetDependency<IUpdateProjectCommand>().UpdateAsync(default)
|
||||
.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)
|
||||
.UpdateAsync(Arg.Any<Project>(), Arg.Any<Guid>());
|
||||
.UpdateAsync(Arg.Any<Project>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
Loading…
Reference in New Issue
Block a user