mirror of
https://github.com/bitwarden/server.git
synced 2024-11-21 12:05:42 +01:00
[SM-704] Extract Authorization For ServiceAccounts (#2869)
* 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 * Extract authorization out of commands * Unit tests for authorization handler * Formatting * Swap to reflection for testing switch * Swap to check read & reflections in test * fix wording on exception * Refactor GetAccessClient into its own query * Use accessClientQuery in project handler
This commit is contained in:
parent
c08e2a7473
commit
d1155ee376
@ -2,23 +2,23 @@
|
||||
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.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Projects;
|
||||
|
||||
public class ProjectAuthorizationHandler : AuthorizationHandler<ProjectOperationRequirement, Project>
|
||||
{
|
||||
private readonly IAccessClientQuery _accessClientQuery;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public ProjectAuthorizationHandler(ICurrentContext currentContext, IUserService userService,
|
||||
public ProjectAuthorizationHandler(ICurrentContext currentContext, IAccessClientQuery accessClientQuery,
|
||||
IProjectRepository projectRepository)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_userService = userService;
|
||||
_accessClientQuery = accessClientQuery;
|
||||
_projectRepository = projectRepository;
|
||||
}
|
||||
|
||||
@ -40,14 +40,14 @@ public class ProjectAuthorizationHandler : AuthorizationHandler<ProjectOperation
|
||||
await CanUpdateProjectAsync(context, requirement, resource);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unsupported project operation requirement type provided.", nameof(requirement));
|
||||
throw new ArgumentException("Unsupported operation requirement type provided.", nameof(requirement));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CanCreateProjectAsync(AuthorizationHandlerContext context,
|
||||
ProjectOperationRequirement requirement, Project resource)
|
||||
{
|
||||
var accessClient = await GetAccessClientAsync(resource.OrganizationId);
|
||||
var (accessClient, _) = await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId);
|
||||
var hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
@ -65,13 +65,13 @@ public class ProjectAuthorizationHandler : AuthorizationHandler<ProjectOperation
|
||||
private async Task CanUpdateProjectAsync(AuthorizationHandlerContext context,
|
||||
ProjectOperationRequirement requirement, Project resource)
|
||||
{
|
||||
var accessClient = await GetAccessClientAsync(resource.OrganizationId);
|
||||
var (accessClient, userId) =
|
||||
await _accessClientQuery.GetAccessClientAsync(context.User, 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)
|
||||
@ -79,10 +79,4 @@ public class ProjectAuthorizationHandler : AuthorizationHandler<ProjectOperation
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<AccessClientType> GetAccessClientAsync(Guid organizationId)
|
||||
{
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
|
||||
return AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,100 @@
|
||||
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.ServiceAccounts;
|
||||
|
||||
public class
|
||||
ServiceAccountAuthorizationHandler : AuthorizationHandler<ServiceAccountOperationRequirement, ServiceAccount>
|
||||
{
|
||||
private readonly IAccessClientQuery _accessClientQuery;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
|
||||
public ServiceAccountAuthorizationHandler(ICurrentContext currentContext,
|
||||
IAccessClientQuery accessClientQuery,
|
||||
IServiceAccountRepository serviceAccountRepository)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_accessClientQuery = accessClientQuery;
|
||||
_serviceAccountRepository = serviceAccountRepository;
|
||||
}
|
||||
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
|
||||
ServiceAccountOperationRequirement requirement,
|
||||
ServiceAccount resource)
|
||||
{
|
||||
if (!_currentContext.AccessSecretsManager(resource.OrganizationId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requirement)
|
||||
{
|
||||
case not null when requirement == ServiceAccountOperations.Create:
|
||||
await CanCreateServiceAccountAsync(context, requirement, resource);
|
||||
break;
|
||||
case not null when requirement == ServiceAccountOperations.Read:
|
||||
await CanReadServiceAccountAsync(context, requirement, resource);
|
||||
break;
|
||||
case not null when requirement == ServiceAccountOperations.Update:
|
||||
await CanUpdateServiceAccountAsync(context, requirement, resource);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unsupported operation requirement type provided.",
|
||||
nameof(requirement));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CanCreateServiceAccountAsync(AuthorizationHandlerContext context,
|
||||
ServiceAccountOperationRequirement requirement, ServiceAccount resource)
|
||||
{
|
||||
var (accessClient, _) = await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId);
|
||||
var hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
AccessClientType.User => true,
|
||||
AccessClientType.ServiceAccount => false,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (hasAccess)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CanReadServiceAccountAsync(AuthorizationHandlerContext context,
|
||||
ServiceAccountOperationRequirement requirement, ServiceAccount resource)
|
||||
{
|
||||
var (accessClient, userId) =
|
||||
await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId);
|
||||
var access =
|
||||
await _serviceAccountRepository.AccessToServiceAccountAsync(resource.Id, userId,
|
||||
accessClient);
|
||||
|
||||
if (access.Read)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CanUpdateServiceAccountAsync(AuthorizationHandlerContext context,
|
||||
ServiceAccountOperationRequirement requirement, ServiceAccount resource)
|
||||
{
|
||||
var (accessClient, userId) =
|
||||
await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId);
|
||||
var access =
|
||||
await _serviceAccountRepository.AccessToServiceAccountAsync(resource.Id, userId,
|
||||
accessClient);
|
||||
|
||||
if (access.Write)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
@ -18,7 +17,7 @@ public class UpdateServiceAccountCommand : IUpdateServiceAccountCommand
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
public async Task<ServiceAccount> UpdateAsync(ServiceAccount updatedServiceAccount, Guid userId)
|
||||
public async Task<ServiceAccount> UpdateAsync(ServiceAccount updatedServiceAccount)
|
||||
{
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(updatedServiceAccount.Id);
|
||||
if (serviceAccount == null)
|
||||
@ -26,26 +25,6 @@ public class UpdateServiceAccountCommand : IUpdateServiceAccountCommand
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!_currentContext.AccessSecretsManager(serviceAccount.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
|
||||
var hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
AccessClientType.User => await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(updatedServiceAccount.Id, userId),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (!hasAccess)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
serviceAccount.Name = updatedServiceAccount.Name;
|
||||
serviceAccount.RevisionDate = DateTime.UtcNow;
|
||||
|
||||
|
@ -0,0 +1,28 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.Queries.Interfaces;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Commercial.Core.SecretsManager.Queries;
|
||||
|
||||
public class AccessClientQuery : IAccessClientQuery
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public AccessClientQuery(ICurrentContext currentContext, IUserService userService)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
public async Task<(AccessClientType AccessClientType, Guid UserId)> GetAccessClientAsync(
|
||||
ClaimsPrincipal claimsPrincipal, Guid organizationId)
|
||||
{
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
var userId = _userService.GetProperUserId(claimsPrincipal).Value;
|
||||
return (accessClient, userId);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Projects;
|
||||
using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.ServiceAccounts;
|
||||
using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
|
||||
using Bit.Commercial.Core.SecretsManager.Commands.AccessTokens;
|
||||
using Bit.Commercial.Core.SecretsManager.Commands.Porting;
|
||||
@ -6,6 +7,7 @@ using Bit.Commercial.Core.SecretsManager.Commands.Projects;
|
||||
using Bit.Commercial.Core.SecretsManager.Commands.Secrets;
|
||||
using Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts;
|
||||
using Bit.Commercial.Core.SecretsManager.Commands.Trash;
|
||||
using Bit.Commercial.Core.SecretsManager.Queries;
|
||||
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
|
||||
using Bit.Core.SecretsManager.Commands.AccessTokens.Interfaces;
|
||||
using Bit.Core.SecretsManager.Commands.Porting.Interfaces;
|
||||
@ -13,6 +15,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 Bit.Core.SecretsManager.Queries.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@ -23,6 +26,8 @@ public static class SecretsManagerCollectionExtensions
|
||||
public static void AddSecretsManagerServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IAuthorizationHandler, ProjectAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, ServiceAccountAuthorizationHandler>();
|
||||
services.AddScoped<IAccessClientQuery, AccessClientQuery>();
|
||||
services.AddScoped<ICreateSecretCommand, CreateSecretCommand>();
|
||||
services.AddScoped<IUpdateSecretCommand, UpdateSecretCommand>();
|
||||
services.AddScoped<IDeleteSecretCommand, DeleteSecretCommand>();
|
||||
|
@ -101,6 +101,35 @@ public class ServiceAccountRepository : Repository<Core.SecretsManager.Entities.
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<(bool Read, bool Write)> AccessToServiceAccountAsync(Guid id, Guid userId,
|
||||
AccessClientType accessType)
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
|
||||
var serviceAccount = dbContext.ServiceAccount.Where(sa => sa.Id == id);
|
||||
|
||||
var query = accessType switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => serviceAccount.Select(_ => new { Read = true, Write = true }),
|
||||
AccessClientType.User => serviceAccount.Select(sa => new
|
||||
{
|
||||
Read = sa.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Read) ||
|
||||
sa.GroupAccessPolicies.Any(ap =>
|
||||
ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Read)),
|
||||
Write = sa.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Write) ||
|
||||
sa.GroupAccessPolicies.Any(ap =>
|
||||
ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Write)),
|
||||
}),
|
||||
AccessClientType.ServiceAccount => serviceAccount.Select(_ => new { Read = false, Write = false }),
|
||||
_ => serviceAccount.Select(_ => new { Read = false, Write = false }),
|
||||
};
|
||||
|
||||
var policy = await query.FirstOrDefaultAsync();
|
||||
|
||||
return policy == null ? (false, false) : (policy.Read, policy.Write);
|
||||
}
|
||||
|
||||
private static Expression<Func<ServiceAccount, bool>> UserHasReadAccessToServiceAccount(Guid userId) => sa =>
|
||||
sa.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Read) ||
|
||||
sa.GroupAccessPolicies.Any(ap => ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Read));
|
||||
|
@ -4,11 +4,10 @@ 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.Queries.Interfaces;
|
||||
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;
|
||||
@ -23,21 +22,22 @@ namespace Bit.Commercial.Core.Test.SecretsManager.AuthorizationHandlers.Projects
|
||||
public class ProjectAuthorizationHandlerTests
|
||||
{
|
||||
private static void SetupPermission(SutProvider<ProjectAuthorizationHandler> sutProvider,
|
||||
PermissionType permissionType, Guid organizationId)
|
||||
PermissionType permissionType, Guid organizationId, Guid userId = new())
|
||||
{
|
||||
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);
|
||||
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, organizationId)
|
||||
.ReturnsForAnyArgs(
|
||||
(AccessClientType.NoAccessCheck, userId));
|
||||
break;
|
||||
case PermissionType.RunAsUserWithPermission:
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(false);
|
||||
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, organizationId)
|
||||
.ReturnsForAnyArgs(
|
||||
(AccessClientType.User, userId));
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
|
||||
@ -63,7 +63,6 @@ public class ProjectAuthorizationHandlerTests
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, project);
|
||||
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => sutProvider.Sut.HandleAsync(authzContext));
|
||||
}
|
||||
|
||||
@ -74,7 +73,6 @@ public class ProjectAuthorizationHandlerTests
|
||||
{
|
||||
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));
|
||||
@ -105,17 +103,16 @@ public class ProjectAuthorizationHandlerTests
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(ClientType.ServiceAccount)]
|
||||
[BitAutoData(ClientType.Organization)]
|
||||
public async Task CanCreateProject_NotSupportedClientTypes_DoesNotSucceed(ClientType clientType,
|
||||
[BitAutoData(AccessClientType.ServiceAccount)]
|
||||
[BitAutoData(AccessClientType.Organization)]
|
||||
public async Task CanCreateProject_NotSupportedClientTypes_DoesNotSucceed(AccessClientType 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);
|
||||
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, project.OrganizationId)
|
||||
.ReturnsForAnyArgs(
|
||||
(clientType, new Guid()));
|
||||
var requirement = ProjectOperations.Create;
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, project);
|
||||
@ -167,7 +164,6 @@ public class ProjectAuthorizationHandlerTests
|
||||
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));
|
||||
@ -188,8 +184,9 @@ public class ProjectAuthorizationHandlerTests
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(project.OrganizationId).Returns(false);
|
||||
sutProvider.GetDependency<ICurrentContext>().ClientType
|
||||
.Returns(ClientType.ServiceAccount);
|
||||
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, project.OrganizationId)
|
||||
.ReturnsForAnyArgs(
|
||||
(AccessClientType.ServiceAccount, new Guid()));
|
||||
var requirement = ProjectOperations.Update;
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, project);
|
||||
@ -206,8 +203,7 @@ public class ProjectAuthorizationHandlerTests
|
||||
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal,
|
||||
Guid userId)
|
||||
{
|
||||
SetupPermission(sutProvider, permissionType, project.OrganizationId);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||
SetupPermission(sutProvider, permissionType, project.OrganizationId, userId);
|
||||
sutProvider.GetDependency<IProjectRepository>()
|
||||
.AccessToProjectAsync(project.Id, userId, Arg.Any<AccessClientType>())
|
||||
.Returns((read, write));
|
||||
@ -229,8 +225,7 @@ public class ProjectAuthorizationHandlerTests
|
||||
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal,
|
||||
Guid userId)
|
||||
{
|
||||
SetupPermission(sutProvider, permissionType, project.OrganizationId);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||
SetupPermission(sutProvider, permissionType, project.OrganizationId, userId);
|
||||
sutProvider.GetDependency<IProjectRepository>()
|
||||
.AccessToProjectAsync(project.Id, userId, Arg.Any<AccessClientType>())
|
||||
.Returns((read, write));
|
||||
|
@ -0,0 +1,302 @@
|
||||
using System.Reflection;
|
||||
using System.Security.Claims;
|
||||
using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.ServiceAccounts;
|
||||
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.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.AuthorizationHandlers.ServiceAccounts;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class ServiceAccountAuthorizationHandlerTests
|
||||
{
|
||||
private static void SetupPermission(SutProvider<ServiceAccountAuthorizationHandler> sutProvider,
|
||||
PermissionType permissionType, Guid organizationId, Guid userId = new())
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId)
|
||||
.Returns(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(
|
||||
(AccessClientType.User, userId));
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServiceAccountOperations_OnlyPublicStatic()
|
||||
{
|
||||
var publicStaticFields = typeof(ServiceAccountOperations).GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||
var allFields = typeof(ServiceAccountOperations).GetFields();
|
||||
Assert.Equal(publicStaticFields.Length, allFields.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Handler_UnsupportedServiceAccountOperationRequirement_Throws(
|
||||
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(serviceAccount.OrganizationId)
|
||||
.Returns(true);
|
||||
var requirement = new ServiceAccountOperationRequirement();
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, serviceAccount);
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => sutProvider.Sut.HandleAsync(authzContext));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Handler_SupportedServiceAccountOperationRequirement_DoesNotThrow(
|
||||
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(serviceAccount.OrganizationId)
|
||||
.Returns(true);
|
||||
|
||||
var requirements = typeof(ServiceAccountOperations).GetFields(BindingFlags.Public | BindingFlags.Static)
|
||||
.Select(i => (ServiceAccountOperationRequirement)i.GetValue(null));
|
||||
|
||||
foreach (var req in requirements)
|
||||
{
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { req },
|
||||
claimsPrincipal, serviceAccount);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CanCreateServiceAccount_AccessToSecretsManagerFalse_DoesNotSucceed(
|
||||
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(serviceAccount.OrganizationId)
|
||||
.Returns(false);
|
||||
var requirement = ServiceAccountOperations.Create;
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, serviceAccount);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.False(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(AccessClientType.ServiceAccount)]
|
||||
[BitAutoData(AccessClientType.Organization)]
|
||||
public async Task CanCreateServiceAccount_NotSupportedClientTypes_DoesNotSucceed(AccessClientType clientType,
|
||||
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = ServiceAccountOperations.Create;
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(serviceAccount.OrganizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(serviceAccount.OrganizationId)
|
||||
.Returns(false);
|
||||
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, serviceAccount.OrganizationId)
|
||||
.ReturnsForAnyArgs(
|
||||
(clientType, new Guid()));
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, serviceAccount);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.False(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task CanCreateServiceAccount_Success(PermissionType permissionType,
|
||||
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = ServiceAccountOperations.Create;
|
||||
SetupPermission(sutProvider, permissionType, serviceAccount.OrganizationId);
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, serviceAccount);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.True(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CanUpdateServiceAccount_AccessToSecretsManagerFalse_DoesNotSucceed(
|
||||
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = ServiceAccountOperations.Update;
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(serviceAccount.OrganizationId)
|
||||
.Returns(false);
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, serviceAccount);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.False(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CanUpdateServiceAccount_NullResource_DoesNotSucceed(
|
||||
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
Guid userId)
|
||||
{
|
||||
var requirement = ServiceAccountOperations.Update;
|
||||
SetupPermission(sutProvider, PermissionType.RunAsAdmin, serviceAccount.OrganizationId, userId);
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, null);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.False(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false)]
|
||||
public async Task CanUpdateServiceAccount_ShouldNotSucceed(PermissionType permissionType, bool read, bool write,
|
||||
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
Guid userId)
|
||||
{
|
||||
var requirement = ServiceAccountOperations.Update;
|
||||
SetupPermission(sutProvider, permissionType, serviceAccount.OrganizationId, userId);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>()
|
||||
.AccessToServiceAccountAsync(serviceAccount.Id, userId, Arg.Any<AccessClientType>())
|
||||
.Returns((read, write));
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, serviceAccount);
|
||||
|
||||
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 CanUpdateServiceAccount_Success(PermissionType permissionType, bool read, bool write,
|
||||
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
Guid userId)
|
||||
{
|
||||
var requirement = ServiceAccountOperations.Update;
|
||||
SetupPermission(sutProvider, permissionType, serviceAccount.OrganizationId, userId);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>()
|
||||
.AccessToServiceAccountAsync(serviceAccount.Id, userId, Arg.Any<AccessClientType>())
|
||||
.Returns((read, write));
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, serviceAccount);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.True(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CanReadServiceAccount_AccessToSecretsManagerFalse_DoesNotSucceed(
|
||||
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = ServiceAccountOperations.Read;
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(serviceAccount.OrganizationId)
|
||||
.Returns(false);
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, serviceAccount);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.False(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CanReadServiceAccount_NullResource_DoesNotSucceed(
|
||||
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
Guid userId)
|
||||
{
|
||||
var requirement = ServiceAccountOperations.Read;
|
||||
SetupPermission(sutProvider, PermissionType.RunAsAdmin, serviceAccount.OrganizationId, userId);
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, null);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.False(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true)]
|
||||
public async Task CanReadServiceAccount_ShouldNotSucceed(PermissionType permissionType, bool read, bool write,
|
||||
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
Guid userId)
|
||||
{
|
||||
var requirement = ServiceAccountOperations.Read;
|
||||
SetupPermission(sutProvider, permissionType, serviceAccount.OrganizationId, userId);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>()
|
||||
.AccessToServiceAccountAsync(serviceAccount.Id, userId, Arg.Any<AccessClientType>())
|
||||
.Returns((read, write));
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, serviceAccount);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.False(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PermissionType.RunAsAdmin, true, true)]
|
||||
[BitAutoData(PermissionType.RunAsAdmin, true, false)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false)]
|
||||
public async Task CanReadServiceAccount_Success(PermissionType permissionType, bool read, bool write,
|
||||
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
Guid userId)
|
||||
{
|
||||
var requirement = ServiceAccountOperations.Read;
|
||||
SetupPermission(sutProvider, permissionType, serviceAccount.OrganizationId, userId);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>()
|
||||
.AccessToServiceAccountAsync(serviceAccount.Id, userId, Arg.Any<AccessClientType>())
|
||||
.Returns((read, write));
|
||||
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
|
||||
claimsPrincipal, serviceAccount);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.True(authzContext.HasSucceeded);
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
using Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
@ -16,49 +15,20 @@ public class UpdateServiceAccountCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_ServiceAccountDoesNotExist_ThrowsNotFound(ServiceAccount data, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider)
|
||||
public async Task UpdateAsync_ServiceAccountDoesNotExist_ThrowsNotFound(ServiceAccount data, SutProvider<UpdateServiceAccountCommand> sutProvider)
|
||||
{
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, userId));
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data));
|
||||
|
||||
await sutProvider.GetDependency<IServiceAccountRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_User_NoAccess(ServiceAccount data, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider)
|
||||
public async Task UpdateAsync_Success(ServiceAccount data, SutProvider<UpdateServiceAccountCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).Returns(data);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(data.Id, userId).Returns(false);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, userId));
|
||||
|
||||
await sutProvider.GetDependency<IServiceAccountRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_User_Success(ServiceAccount data, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(data.OrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).Returns(data);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(data.Id, userId).Returns(true);
|
||||
|
||||
await sutProvider.Sut.UpdateAsync(data, userId);
|
||||
|
||||
await sutProvider.GetDependency<IServiceAccountRepository>().Received(1)
|
||||
.ReplaceAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data)));
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_Admin_Success(ServiceAccount data, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).Returns(data);
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(data.OrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(true);
|
||||
|
||||
await sutProvider.Sut.UpdateAsync(data, userId);
|
||||
await sutProvider.Sut.UpdateAsync(data);
|
||||
|
||||
await sutProvider.GetDependency<IServiceAccountRepository>().Received(1)
|
||||
.ReplaceAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data)));
|
||||
@ -66,11 +36,9 @@ public class UpdateServiceAccountCommandTests
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_DoesNotModifyOrganizationId(ServiceAccount existingServiceAccount, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider)
|
||||
public async Task UpdateAsync_DoesNotModifyOrganizationId(ServiceAccount existingServiceAccount, SutProvider<UpdateServiceAccountCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingServiceAccount.OrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(existingServiceAccount.Id).Returns(existingServiceAccount);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(existingServiceAccount.Id, userId).Returns(true);
|
||||
|
||||
var updatedOrgId = Guid.NewGuid();
|
||||
var serviceAccountUpdate = new ServiceAccount()
|
||||
@ -80,7 +48,7 @@ public class UpdateServiceAccountCommandTests
|
||||
Name = existingServiceAccount.Name,
|
||||
};
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(serviceAccountUpdate, userId);
|
||||
var result = await sutProvider.Sut.UpdateAsync(serviceAccountUpdate);
|
||||
|
||||
Assert.Equal(existingServiceAccount.OrganizationId, result.OrganizationId);
|
||||
Assert.NotEqual(existingServiceAccount.OrganizationId, updatedOrgId);
|
||||
@ -88,11 +56,9 @@ public class UpdateServiceAccountCommandTests
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_DoesNotModifyCreationDate(ServiceAccount existingServiceAccount, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider)
|
||||
public async Task UpdateAsync_DoesNotModifyCreationDate(ServiceAccount existingServiceAccount, SutProvider<UpdateServiceAccountCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingServiceAccount.OrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(existingServiceAccount.Id).Returns(existingServiceAccount);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(existingServiceAccount.Id, userId).Returns(true);
|
||||
|
||||
var updatedCreationDate = DateTime.UtcNow;
|
||||
var serviceAccountUpdate = new ServiceAccount()
|
||||
@ -102,7 +68,7 @@ public class UpdateServiceAccountCommandTests
|
||||
Name = existingServiceAccount.Name,
|
||||
};
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(serviceAccountUpdate, userId);
|
||||
var result = await sutProvider.Sut.UpdateAsync(serviceAccountUpdate);
|
||||
|
||||
Assert.Equal(existingServiceAccount.CreationDate, result.CreationDate);
|
||||
Assert.NotEqual(existingServiceAccount.CreationDate, updatedCreationDate);
|
||||
@ -110,11 +76,9 @@ public class UpdateServiceAccountCommandTests
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_RevisionDateIsUpdatedToUtcNow(ServiceAccount existingServiceAccount, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider)
|
||||
public async Task UpdateAsync_RevisionDateIsUpdatedToUtcNow(ServiceAccount existingServiceAccount, SutProvider<UpdateServiceAccountCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingServiceAccount.OrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(existingServiceAccount.Id).Returns(existingServiceAccount);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(existingServiceAccount.Id, userId).Returns(true);
|
||||
|
||||
var updatedRevisionDate = DateTime.UtcNow.AddDays(10);
|
||||
var serviceAccountUpdate = new ServiceAccount()
|
||||
@ -124,7 +88,7 @@ public class UpdateServiceAccountCommandTests
|
||||
Name = existingServiceAccount.Name,
|
||||
};
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(serviceAccountUpdate, userId);
|
||||
var result = await sutProvider.Sut.UpdateAsync(serviceAccountUpdate);
|
||||
|
||||
Assert.NotEqual(serviceAccountUpdate.RevisionDate, result.RevisionDate);
|
||||
AssertHelper.AssertRecent(result.RevisionDate);
|
||||
|
@ -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.AccessTokens.Interfaces;
|
||||
using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
@ -21,6 +22,7 @@ public class ServiceAccountsController : Controller
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
private readonly IApiKeyRepository _apiKeyRepository;
|
||||
private readonly ICreateAccessTokenCommand _createAccessTokenCommand;
|
||||
@ -32,6 +34,7 @@ public class ServiceAccountsController : Controller
|
||||
public ServiceAccountsController(
|
||||
ICurrentContext currentContext,
|
||||
IUserService userService,
|
||||
IAuthorizationService authorizationService,
|
||||
IServiceAccountRepository serviceAccountRepository,
|
||||
IApiKeyRepository apiKeyRepository,
|
||||
ICreateAccessTokenCommand createAccessTokenCommand,
|
||||
@ -42,6 +45,7 @@ public class ServiceAccountsController : Controller
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_userService = userService;
|
||||
_authorizationService = authorizationService;
|
||||
_serviceAccountRepository = serviceAccountRepository;
|
||||
_apiKeyRepository = apiKeyRepository;
|
||||
_createServiceAccountCommand = createServiceAccountCommand;
|
||||
@ -73,32 +77,13 @@ public class ServiceAccountsController : Controller
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ServiceAccountResponseModel> GetByServiceAccountIdAsync(
|
||||
[FromRoute] Guid id)
|
||||
[FromRoute] Guid id)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
var authorizationResult =
|
||||
await _authorizationService.AuthorizeAsync(User, serviceAccount, ServiceAccountOperations.Read);
|
||||
|
||||
if (serviceAccount == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!_currentContext.AccessSecretsManager(serviceAccount.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId);
|
||||
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
|
||||
|
||||
var hasAccess = accessClient switch
|
||||
{
|
||||
AccessClientType.NoAccessCheck => true,
|
||||
AccessClientType.User => await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(id, userId),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (!hasAccess)
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -110,12 +95,18 @@ public class ServiceAccountsController : Controller
|
||||
public async Task<ServiceAccountResponseModel> CreateAsync([FromRoute] Guid organizationId,
|
||||
[FromBody] ServiceAccountCreateRequestModel createRequest)
|
||||
{
|
||||
if (!_currentContext.AccessSecretsManager(organizationId))
|
||||
var serviceAccount = createRequest.ToServiceAccount(organizationId);
|
||||
var authorizationResult =
|
||||
await _authorizationService.AuthorizeAsync(User, serviceAccount, ServiceAccountOperations.Create);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var result = await _createServiceAccountCommand.CreateAsync(createRequest.ToServiceAccount(organizationId), userId);
|
||||
var result =
|
||||
await _createServiceAccountCommand.CreateAsync(createRequest.ToServiceAccount(organizationId), userId);
|
||||
return new ServiceAccountResponseModel(result);
|
||||
}
|
||||
|
||||
@ -123,9 +114,16 @@ public class ServiceAccountsController : Controller
|
||||
public async Task<ServiceAccountResponseModel> UpdateAsync([FromRoute] Guid id,
|
||||
[FromBody] ServiceAccountUpdateRequestModel updateRequest)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
var authorizationResult =
|
||||
await _authorizationService.AuthorizeAsync(User, serviceAccount, ServiceAccountOperations.Update);
|
||||
|
||||
var result = await _updateServiceAccountCommand.UpdateAsync(updateRequest.ToServiceAccount(id), userId);
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var result = await _updateServiceAccountCommand.UpdateAsync(updateRequest.ToServiceAccount(id));
|
||||
return new ServiceAccountResponseModel(result);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,14 @@
|
||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||
|
||||
namespace Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||
|
||||
public class ServiceAccountOperationRequirement : OperationAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
|
||||
public static class ServiceAccountOperations
|
||||
{
|
||||
public static readonly ServiceAccountOperationRequirement Create = new() { Name = nameof(Create) };
|
||||
public static readonly ServiceAccountOperationRequirement Read = new() { Name = nameof(Read) };
|
||||
public static readonly ServiceAccountOperationRequirement Update = new() { Name = nameof(Update) };
|
||||
}
|
@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
|
||||
|
||||
public interface IUpdateServiceAccountCommand
|
||||
{
|
||||
Task<ServiceAccount> UpdateAsync(ServiceAccount serviceAccount, Guid userId);
|
||||
Task<ServiceAccount> UpdateAsync(ServiceAccount serviceAccount);
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Queries.Interfaces;
|
||||
|
||||
public interface IAccessClientQuery
|
||||
{
|
||||
Task<(AccessClientType AccessClientType, Guid UserId)> GetAccessClientAsync(ClaimsPrincipal claimsPrincipal, Guid organizationId);
|
||||
}
|
@ -14,4 +14,5 @@ public interface IServiceAccountRepository
|
||||
Task<bool> UserHasReadAccessToServiceAccount(Guid id, Guid userId);
|
||||
Task<bool> UserHasWriteAccessToServiceAccount(Guid id, Guid userId);
|
||||
Task<IEnumerable<ServiceAccount>> GetManyByOrganizationIdWriteAccessAsync(Guid organizationId, Guid userId, AccessClientType accessType);
|
||||
Task<(bool Read, bool Write)> AccessToServiceAccountAsync(Guid id, Guid userId, AccessClientType accessType);
|
||||
}
|
||||
|
@ -139,11 +139,21 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByServiceAccountId_ServiceAccountDoesNotExist_NotFound()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var response = await _client.GetAsync($"/service-accounts/{new Guid()}");
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByServiceAccountId_UserWithoutPermission_NotFound()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true);
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
|
||||
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
|
||||
@ -161,30 +171,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task GetByServiceAccountId_Success(PermissionType permissionType)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
|
||||
await _accessPolicyRepository.CreateManyAsync(new List<BaseAccessPolicy> {
|
||||
new UserServiceAccountAccessPolicy
|
||||
{
|
||||
GrantedServiceAccountId = serviceAccount.Id,
|
||||
OrganizationUserId = orgUser.Id,
|
||||
Write = true,
|
||||
Read = true,
|
||||
},
|
||||
});
|
||||
}
|
||||
var serviceAccount = await SetupServiceAccountWithAccessAsync(permissionType);
|
||||
|
||||
var response = await _client.GetAsync($"/service-accounts/{serviceAccount.Id}");
|
||||
response.EnsureSuccessStatusCode();
|
||||
@ -212,12 +199,25 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_Admin_Success()
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task Create_Success(PermissionType permissionType)
|
||||
{
|
||||
var (org, orgUser) = await _organizationHelper.Initialize(true, true);
|
||||
var (org, adminOrgUser) = await _organizationHelper.Initialize(true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var orgUserId = adminOrgUser.Id;
|
||||
var currentUserId = adminOrgUser.UserId!.Value;
|
||||
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
orgUserId = orgUser.Id;
|
||||
currentUserId = orgUser.UserId!.Value;
|
||||
}
|
||||
|
||||
var request = new ServiceAccountCreateRequestModel { Name = _mockEncryptedString };
|
||||
|
||||
var response = await _client.PostAsJsonAsync($"/organizations/{org.Id}/service-accounts", request);
|
||||
@ -236,9 +236,11 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
|
||||
AssertHelper.AssertRecent(createdServiceAccount.CreationDate);
|
||||
|
||||
// Check permissions have been bootstrapped.
|
||||
var accessPolicies = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(createdServiceAccount.Id, orgUser.UserId!.Value);
|
||||
var accessPolicies = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(createdServiceAccount.Id, currentUserId);
|
||||
Assert.NotNull(accessPolicies);
|
||||
var ap = accessPolicies!.First();
|
||||
var ap = (UserServiceAccountAccessPolicy)accessPolicies.First();
|
||||
Assert.Equal(createdServiceAccount.Id, ap.GrantedServiceAccountId);
|
||||
Assert.Equal(orgUserId, ap.OrganizationUserId);
|
||||
Assert.True(ap.Read);
|
||||
Assert.True(ap.Write);
|
||||
AssertHelper.AssertRecent(ap.CreationDate);
|
||||
@ -266,73 +268,6 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_Admin()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
|
||||
var request = new ServiceAccountUpdateRequestModel { Name = _mockNewName };
|
||||
|
||||
var response = await _client.PutAsJsonAsync($"/service-accounts/{initialServiceAccount.Id}", request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var result = await response.Content.ReadFromJsonAsync<ServiceAccountResponseModel>();
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(request.Name, result!.Name);
|
||||
Assert.NotEqual(initialServiceAccount.Name, result.Name);
|
||||
AssertHelper.AssertRecent(result.RevisionDate);
|
||||
Assert.NotEqual(initialServiceAccount.RevisionDate, result.RevisionDate);
|
||||
|
||||
var updatedServiceAccount = await _serviceAccountRepository.GetByIdAsync(initialServiceAccount.Id);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(request.Name, updatedServiceAccount.Name);
|
||||
AssertHelper.AssertRecent(updatedServiceAccount.RevisionDate);
|
||||
AssertHelper.AssertRecent(updatedServiceAccount.CreationDate);
|
||||
Assert.NotEqual(initialServiceAccount.Name, updatedServiceAccount.Name);
|
||||
Assert.NotEqual(initialServiceAccount.RevisionDate, updatedServiceAccount.RevisionDate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_User_WithPermission()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true);
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
|
||||
var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
|
||||
await CreateUserPolicyAsync(orgUser.Id, initialServiceAccount.Id, true, true);
|
||||
|
||||
var request = new ServiceAccountUpdateRequestModel { Name = _mockNewName };
|
||||
|
||||
var response = await _client.PutAsJsonAsync($"/service-accounts/{initialServiceAccount.Id}", request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var result = await response.Content.ReadFromJsonAsync<ServiceAccountResponseModel>();
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(request.Name, result!.Name);
|
||||
Assert.NotEqual(initialServiceAccount.Name, result.Name);
|
||||
AssertHelper.AssertRecent(result.RevisionDate);
|
||||
Assert.NotEqual(initialServiceAccount.RevisionDate, result.RevisionDate);
|
||||
|
||||
var updatedServiceAccount = await _serviceAccountRepository.GetByIdAsync(initialServiceAccount.Id);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(request.Name, updatedServiceAccount.Name);
|
||||
AssertHelper.AssertRecent(updatedServiceAccount.RevisionDate);
|
||||
AssertHelper.AssertRecent(updatedServiceAccount.CreationDate);
|
||||
Assert.NotEqual(initialServiceAccount.Name, updatedServiceAccount.Name);
|
||||
Assert.NotEqual(initialServiceAccount.RevisionDate, updatedServiceAccount.RevisionDate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_User_NoPermissions()
|
||||
{
|
||||
@ -352,6 +287,45 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_NonExistingServiceAccount_NotFound()
|
||||
{
|
||||
await _organizationHelper.Initialize(true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var request = new ServiceAccountUpdateRequestModel { Name = _mockNewName };
|
||||
|
||||
var response = await _client.PutAsJsonAsync("/service-accounts/c53de509-4581-402c-8cbd-f26d2c516fba", request);
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task Update_Success(PermissionType permissionType)
|
||||
{
|
||||
var initialServiceAccount = await SetupServiceAccountWithAccessAsync(permissionType);
|
||||
|
||||
var request = new ServiceAccountUpdateRequestModel { Name = _mockNewName };
|
||||
|
||||
var response = await _client.PutAsJsonAsync($"/service-accounts/{initialServiceAccount.Id}", request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var result = await response.Content.ReadFromJsonAsync<ServiceAccountResponseModel>();
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(request.Name, result!.Name);
|
||||
Assert.NotEqual(initialServiceAccount.Name, result.Name);
|
||||
AssertHelper.AssertRecent(result.RevisionDate);
|
||||
Assert.NotEqual(initialServiceAccount.RevisionDate, result.RevisionDate);
|
||||
|
||||
var updatedServiceAccount = await _serviceAccountRepository.GetByIdAsync(initialServiceAccount.Id);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(request.Name, updatedServiceAccount.Name);
|
||||
AssertHelper.AssertRecent(updatedServiceAccount.RevisionDate);
|
||||
AssertHelper.AssertRecent(updatedServiceAccount.CreationDate);
|
||||
Assert.NotEqual(initialServiceAccount.Name, updatedServiceAccount.Name);
|
||||
Assert.NotEqual(initialServiceAccount.RevisionDate, updatedServiceAccount.RevisionDate);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false)]
|
||||
[InlineData(true, false)]
|
||||
@ -837,4 +811,35 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
|
||||
|
||||
return serviceAccountIds;
|
||||
}
|
||||
|
||||
private async Task<ServiceAccount> SetupServiceAccountWithAccessAsync(PermissionType permissionType)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
|
||||
if (permissionType == PermissionType.RunAsAdmin)
|
||||
{
|
||||
return initialServiceAccount;
|
||||
}
|
||||
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new UserServiceAccountAccessPolicy
|
||||
{
|
||||
GrantedServiceAccountId = initialServiceAccount.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true,
|
||||
},
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
|
||||
return initialServiceAccount;
|
||||
}
|
||||
}
|
||||
|
@ -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.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
@ -11,6 +12,7 @@ using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Xunit;
|
||||
@ -62,13 +64,30 @@ public class ServiceAccountsControllerTests
|
||||
sutProvider.Sut.ListByOrganizationAsync(orgId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void CreateServiceAccount_NoAccess_Throws(SutProvider<ServiceAccountsController> sutProvider, ServiceAccountCreateRequestModel data, Guid organizationId)
|
||||
{
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data.ToServiceAccount(organizationId),
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Failed());
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
|
||||
var resultServiceAccount = data.ToServiceAccount(organizationId);
|
||||
sutProvider.GetDependency<ICreateServiceAccountCommand>().CreateAsync(default, default).ReturnsForAnyArgs(resultServiceAccount);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(organizationId, data));
|
||||
await sutProvider.GetDependency<ICreateServiceAccountCommand>().DidNotReceiveWithAnyArgs()
|
||||
.CreateAsync(Arg.Any<ServiceAccount>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void CreateServiceAccount_Success(SutProvider<ServiceAccountsController> sutProvider, ServiceAccountCreateRequestModel data, Guid organizationId)
|
||||
{
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data.ToServiceAccount(organizationId),
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(default).ReturnsForAnyArgs(true);
|
||||
var resultServiceAccount = data.ToServiceAccount(organizationId);
|
||||
sutProvider.GetDependency<ICreateServiceAccountCommand>().CreateAsync(default, default).ReturnsForAnyArgs(resultServiceAccount);
|
||||
|
||||
@ -79,30 +98,33 @@ public class ServiceAccountsControllerTests
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void CreateServiceAccount_NotOrgUser_Throws(SutProvider<ServiceAccountsController> sutProvider, ServiceAccountCreateRequestModel data, Guid organizationId)
|
||||
public async void UpdateServiceAccount_NoAccess_Throws(SutProvider<ServiceAccountsController> sutProvider, ServiceAccountUpdateRequestModel data, ServiceAccount existingServiceAccount)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(default).ReturnsForAnyArgs(false);
|
||||
var resultServiceAccount = data.ToServiceAccount(organizationId);
|
||||
sutProvider.GetDependency<ICreateServiceAccountCommand>().CreateAsync(default, default).ReturnsForAnyArgs(resultServiceAccount);
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data.ToServiceAccount(existingServiceAccount.Id),
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Failed());
|
||||
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(existingServiceAccount.Id).ReturnsForAnyArgs(existingServiceAccount);
|
||||
var resultServiceAccount = data.ToServiceAccount(existingServiceAccount.Id);
|
||||
sutProvider.GetDependency<IUpdateServiceAccountCommand>().UpdateAsync(default).ReturnsForAnyArgs(resultServiceAccount);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(organizationId, data));
|
||||
|
||||
await sutProvider.GetDependency<ICreateServiceAccountCommand>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.CreateAsync(Arg.Any<ServiceAccount>(), Arg.Any<Guid>());
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(existingServiceAccount.Id, data));
|
||||
await sutProvider.GetDependency<IUpdateServiceAccountCommand>().DidNotReceiveWithAnyArgs()
|
||||
.UpdateAsync(Arg.Any<ServiceAccount>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void UpdateServiceAccount_Success(SutProvider<ServiceAccountsController> sutProvider, ServiceAccountUpdateRequestModel data, Guid serviceAccountId)
|
||||
public async void UpdateServiceAccount_Success(SutProvider<ServiceAccountsController> sutProvider, ServiceAccountUpdateRequestModel data, ServiceAccount existingServiceAccount)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
|
||||
var resultServiceAccount = data.ToServiceAccount(serviceAccountId);
|
||||
sutProvider.GetDependency<IUpdateServiceAccountCommand>().UpdateAsync(default, default).ReturnsForAnyArgs(resultServiceAccount);
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data.ToServiceAccount(existingServiceAccount.Id),
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
|
||||
var resultServiceAccount = data.ToServiceAccount(existingServiceAccount.Id);
|
||||
sutProvider.GetDependency<IUpdateServiceAccountCommand>().UpdateAsync(default).ReturnsForAnyArgs(resultServiceAccount);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(serviceAccountId, data);
|
||||
var result = await sutProvider.Sut.UpdateAsync(existingServiceAccount.Id, data);
|
||||
await sutProvider.GetDependency<IUpdateServiceAccountCommand>().Received(1)
|
||||
.UpdateAsync(Arg.Any<ServiceAccount>(), Arg.Any<Guid>());
|
||||
.UpdateAsync(Arg.Any<ServiceAccount>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
Loading…
Reference in New Issue
Block a user