1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

[SM-909] Add service-account people access policy management endpoints (#3324)

* refactoring replace logic

* model for policies + authz handler + unit tests

* update AP repository

* add new endpoints to controller

* update unit tests and integration tests

---------

Co-authored-by: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com>
This commit is contained in:
Thomas Avery 2023-12-07 15:35:16 -06:00 committed by GitHub
parent a589af3588
commit f9232bcbb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1154 additions and 626 deletions

View File

@ -1,9 +1,8 @@
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.AuthorizationRequirements;
using Bit.Core.SecretsManager.Models.Data;
using Bit.Core.SecretsManager.Queries.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Queries.Interfaces;
using Bit.Core.SecretsManager.Repositories;
using Microsoft.AspNetCore.Authorization;
@ -11,25 +10,23 @@ using Microsoft.AspNetCore.Authorization;
namespace Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies;
public class
ProjectPeopleAccessPoliciesAuthorizationHandler : AuthorizationHandler<ProjectPeopleAccessPoliciesOperationRequirement,
ProjectPeopleAccessPoliciesAuthorizationHandler : AuthorizationHandler<
ProjectPeopleAccessPoliciesOperationRequirement,
ProjectPeopleAccessPolicies>
{
private readonly IAccessClientQuery _accessClientQuery;
private readonly ICurrentContext _currentContext;
private readonly IGroupRepository _groupRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProjectRepository _projectRepository;
private readonly ISameOrganizationQuery _sameOrganizationQuery;
public ProjectPeopleAccessPoliciesAuthorizationHandler(ICurrentContext currentContext,
IAccessClientQuery accessClientQuery,
IGroupRepository groupRepository,
IOrganizationUserRepository organizationUserRepository,
ISameOrganizationQuery sameOrganizationQuery,
IProjectRepository projectRepository)
{
_currentContext = currentContext;
_accessClientQuery = accessClientQuery;
_groupRepository = groupRepository;
_organizationUserRepository = organizationUserRepository;
_sameOrganizationQuery = sameOrganizationQuery;
_projectRepository = projectRepository;
}
@ -71,9 +68,7 @@ public class
if (resource.UserAccessPolicies != null && resource.UserAccessPolicies.Any())
{
var orgUserIds = resource.UserAccessPolicies.Select(ap => ap.OrganizationUserId!.Value).ToList();
var users = await _organizationUserRepository.GetManyAsync(orgUserIds);
if (users.Any(user => user.OrganizationId != resource.OrganizationId) ||
users.Count != orgUserIds.Count)
if (!await _sameOrganizationQuery.OrgUsersInTheSameOrgAsync(orgUserIds, resource.OrganizationId))
{
return;
}
@ -82,9 +77,7 @@ public class
if (resource.GroupAccessPolicies != null && resource.GroupAccessPolicies.Any())
{
var groupIds = resource.GroupAccessPolicies.Select(ap => ap.GroupId!.Value).ToList();
var groups = await _groupRepository.GetManyByManyIds(groupIds);
if (groups.Any(group => group.OrganizationId != resource.OrganizationId) ||
groups.Count != groupIds.Count)
if (!await _sameOrganizationQuery.GroupsInTheSameOrgAsync(groupIds, resource.OrganizationId))
{
return;
}

View File

@ -0,0 +1,89 @@
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.SecretsManager.AuthorizationRequirements;
using Bit.Core.SecretsManager.Models.Data;
using Bit.Core.SecretsManager.Queries.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Queries.Interfaces;
using Bit.Core.SecretsManager.Repositories;
using Microsoft.AspNetCore.Authorization;
namespace Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies;
public class
ServiceAccountPeopleAccessPoliciesAuthorizationHandler : AuthorizationHandler<
ServiceAccountPeopleAccessPoliciesOperationRequirement,
ServiceAccountPeopleAccessPolicies>
{
private readonly IAccessClientQuery _accessClientQuery;
private readonly ICurrentContext _currentContext;
private readonly ISameOrganizationQuery _sameOrganizationQuery;
private readonly IServiceAccountRepository _serviceAccountRepository;
public ServiceAccountPeopleAccessPoliciesAuthorizationHandler(ICurrentContext currentContext,
IAccessClientQuery accessClientQuery,
ISameOrganizationQuery sameOrganizationQuery,
IServiceAccountRepository serviceAccountRepository)
{
_currentContext = currentContext;
_accessClientQuery = accessClientQuery;
_sameOrganizationQuery = sameOrganizationQuery;
_serviceAccountRepository = serviceAccountRepository;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
ServiceAccountPeopleAccessPoliciesOperationRequirement requirement,
ServiceAccountPeopleAccessPolicies resource)
{
if (!_currentContext.AccessSecretsManager(resource.OrganizationId))
{
return;
}
// Only users and admins should be able to manipulate access policies
var (accessClient, userId) =
await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId);
if (accessClient != AccessClientType.User && accessClient != AccessClientType.NoAccessCheck)
{
return;
}
switch (requirement)
{
case not null when requirement == ServiceAccountPeopleAccessPoliciesOperations.Replace:
await CanReplaceServiceAccountPeopleAsync(context, requirement, resource, accessClient, userId);
break;
default:
throw new ArgumentException("Unsupported operation requirement type provided.",
nameof(requirement));
}
}
private async Task CanReplaceServiceAccountPeopleAsync(AuthorizationHandlerContext context,
ServiceAccountPeopleAccessPoliciesOperationRequirement requirement, ServiceAccountPeopleAccessPolicies resource,
AccessClientType accessClient, Guid userId)
{
var access = await _serviceAccountRepository.AccessToServiceAccountAsync(resource.Id, userId, accessClient);
if (access.Write)
{
if (resource.UserAccessPolicies != null && resource.UserAccessPolicies.Any())
{
var orgUserIds = resource.UserAccessPolicies.Select(ap => ap.OrganizationUserId!.Value).ToList();
if (!await _sameOrganizationQuery.OrgUsersInTheSameOrgAsync(orgUserIds, resource.OrganizationId))
{
return;
}
}
if (resource.GroupAccessPolicies != null && resource.GroupAccessPolicies.Any())
{
var groupIds = resource.GroupAccessPolicies.Select(ap => ap.GroupId!.Value).ToList();
if (!await _sameOrganizationQuery.GroupsInTheSameOrgAsync(groupIds, resource.OrganizationId))
{
return;
}
}
context.Succeed(requirement);
}
}
}

View File

@ -0,0 +1,32 @@
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Queries.AccessPolicies.Interfaces;
namespace Bit.Commercial.Core.SecretsManager.Queries.AccessPolicies;
public class SameOrganizationQuery : ISameOrganizationQuery
{
private readonly IGroupRepository _groupRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
public SameOrganizationQuery(IOrganizationUserRepository organizationUserRepository,
IGroupRepository groupRepository)
{
_organizationUserRepository = organizationUserRepository;
_groupRepository = groupRepository;
}
public async Task<bool> OrgUsersInTheSameOrgAsync(List<Guid> organizationUserIds, Guid organizationId)
{
var users = await _organizationUserRepository.GetManyAsync(organizationUserIds);
return users.All(user => user.OrganizationId == organizationId) &&
users.Count == organizationUserIds.Count;
}
public async Task<bool> GroupsInTheSameOrgAsync(List<Guid> groupIds, Guid organizationId)
{
var groups = await _groupRepository.GetManyByManyIds(groupIds);
return groups.All(group => group.OrganizationId == organizationId) &&
groups.Count == groupIds.Count;
}
}

View File

@ -10,6 +10,7 @@ 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.Commercial.Core.SecretsManager.Queries.AccessPolicies;
using Bit.Commercial.Core.SecretsManager.Queries.Projects;
using Bit.Commercial.Core.SecretsManager.Queries.ServiceAccounts;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
@ -19,6 +20,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.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Queries.Interfaces;
using Bit.Core.SecretsManager.Queries.Projects.Interfaces;
using Bit.Core.SecretsManager.Queries.ServiceAccounts.Interfaces;
@ -36,8 +38,10 @@ public static class SecretsManagerCollectionExtensions
services.AddScoped<IAuthorizationHandler, ServiceAccountAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, AccessPolicyAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, ProjectPeopleAccessPoliciesAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, ServiceAccountPeopleAccessPoliciesAuthorizationHandler>();
services.AddScoped<IAccessClientQuery, AccessClientQuery>();
services.AddScoped<IMaxProjectsQuery, MaxProjectsQuery>();
services.AddScoped<ISameOrganizationQuery, SameOrganizationQuery>();
services.AddScoped<IServiceAccountSecretsDetailsQuery, ServiceAccountSecretsDetailsQuery>();
services.AddScoped<ICreateSecretCommand, CreateSecretCommand>();
services.AddScoped<IUpdateSecretCommand, UpdateSecretCommand>();

View File

@ -183,28 +183,6 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
return entities.Select(e => MapToCore(e.ap, e.CurrentUserInGroup));
}
public async Task<IEnumerable<Core.SecretsManager.Entities.BaseAccessPolicy>> GetManyByGrantedServiceAccountIdAsync(Guid id, Guid userId)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var entities = await dbContext.AccessPolicies.Where(ap =>
((UserServiceAccountAccessPolicy)ap).GrantedServiceAccountId == id ||
((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccountId == id)
.Include(ap => ((UserServiceAccountAccessPolicy)ap).OrganizationUser.User)
.Include(ap => ((GroupServiceAccountAccessPolicy)ap).Group)
.Select(ap => new
{
ap,
CurrentUserInGroup = ap is GroupServiceAccountAccessPolicy &&
((GroupServiceAccountAccessPolicy)ap).Group.GroupUsers.Any(g =>
g.OrganizationUser.User.Id == userId),
})
.ToListAsync();
return entities.Select(e => MapToCore(e.ap, e.CurrentUserInGroup));
}
public async Task DeleteAsync(Guid id)
{
using var scope = ServiceScopeFactory.CreateScope();
@ -352,6 +330,81 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
return await GetPeoplePoliciesByGrantedProjectIdAsync(peopleAccessPolicies.Id, userId);
}
public async Task<IEnumerable<Core.SecretsManager.Entities.BaseAccessPolicy>>
GetPeoplePoliciesByGrantedServiceAccountIdAsync(Guid id, Guid userId)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var entities = await dbContext.AccessPolicies.Where(ap =>
((UserServiceAccountAccessPolicy)ap).GrantedServiceAccountId == id ||
((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccountId == id)
.Include(ap => ((UserServiceAccountAccessPolicy)ap).OrganizationUser.User)
.Include(ap => ((GroupServiceAccountAccessPolicy)ap).Group)
.Select(ap => new
{
ap,
CurrentUserInGroup = ap is GroupServiceAccountAccessPolicy &&
((GroupServiceAccountAccessPolicy)ap).Group.GroupUsers.Any(g =>
g.OrganizationUser.UserId == userId)
})
.ToListAsync();
return entities.Select(e => MapToCore(e.ap, e.CurrentUserInGroup));
}
public async Task<IEnumerable<Core.SecretsManager.Entities.BaseAccessPolicy>> ReplaceServiceAccountPeopleAsync(
ServiceAccountPeopleAccessPolicies peopleAccessPolicies, Guid userId)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var peoplePolicyEntities = await dbContext.AccessPolicies.Where(ap =>
((UserServiceAccountAccessPolicy)ap).GrantedServiceAccountId == peopleAccessPolicies.Id ||
((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccountId == peopleAccessPolicies.Id).ToListAsync();
var userPolicyEntities =
peoplePolicyEntities.Where(ap => ap.GetType() == typeof(UserServiceAccountAccessPolicy)).ToList();
var groupPolicyEntities =
peoplePolicyEntities.Where(ap => ap.GetType() == typeof(GroupServiceAccountAccessPolicy)).ToList();
if (peopleAccessPolicies.UserAccessPolicies == null || !peopleAccessPolicies.UserAccessPolicies.Any())
{
dbContext.RemoveRange(userPolicyEntities);
}
else
{
foreach (var userPolicyEntity in userPolicyEntities.Where(entity =>
peopleAccessPolicies.UserAccessPolicies.All(ap =>
((Core.SecretsManager.Entities.UserServiceAccountAccessPolicy)ap).OrganizationUserId !=
((UserServiceAccountAccessPolicy)entity).OrganizationUserId)))
{
dbContext.Remove(userPolicyEntity);
}
}
if (peopleAccessPolicies.GroupAccessPolicies == null || !peopleAccessPolicies.GroupAccessPolicies.Any())
{
dbContext.RemoveRange(groupPolicyEntities);
}
else
{
foreach (var groupPolicyEntity in groupPolicyEntities.Where(entity =>
peopleAccessPolicies.GroupAccessPolicies.All(ap =>
((Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy)ap).GroupId !=
((GroupServiceAccountAccessPolicy)entity).GroupId)))
{
dbContext.Remove(groupPolicyEntity);
}
}
await UpsertPeoplePoliciesAsync(dbContext,
peopleAccessPolicies.ToBaseAccessPolicies().Select(MapToEntity).ToList(), userPolicyEntities,
groupPolicyEntities);
await dbContext.SaveChangesAsync();
return await GetPeoplePoliciesByGrantedServiceAccountIdAsync(peopleAccessPolicies.Id, userId);
}
private static async Task UpsertPeoplePoliciesAsync(DatabaseContext dbContext,
List<BaseAccessPolicy> policies, IReadOnlyCollection<AccessPolicy> userPolicyEntities,
IReadOnlyCollection<AccessPolicy> groupPolicyEntities)

View File

@ -1,14 +1,11 @@
using System.Reflection;
using System.Security.Claims;
using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.AuthorizationRequirements;
using Bit.Core.SecretsManager.Models.Data;
using Bit.Core.SecretsManager.Queries.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Queries.Interfaces;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
@ -38,26 +35,16 @@ public class ProjectPeopleAccessPoliciesAuthorizationHandlerTests
}
private static void SetupOrganizationUsers(SutProvider<ProjectPeopleAccessPoliciesAuthorizationHandler> sutProvider,
ProjectPeopleAccessPolicies resource)
{
var orgUsers = resource.UserAccessPolicies.Select(userPolicy =>
new OrganizationUser
{
OrganizationId = resource.OrganizationId,
Id = userPolicy.OrganizationUserId!.Value
}).ToList();
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(default)
.ReturnsForAnyArgs(orgUsers);
}
ProjectPeopleAccessPolicies resource) =>
sutProvider.GetDependency<ISameOrganizationQuery>()
.OrgUsersInTheSameOrgAsync(Arg.Any<List<Guid>>(), resource.OrganizationId)
.Returns(true);
private static void SetupGroups(SutProvider<ProjectPeopleAccessPoliciesAuthorizationHandler> sutProvider,
ProjectPeopleAccessPolicies resource)
{
var groups = resource.GroupAccessPolicies.Select(groupPolicy =>
new Group { OrganizationId = resource.OrganizationId, Id = groupPolicy.GroupId!.Value }).ToList();
sutProvider.GetDependency<IGroupRepository>().GetManyByManyIds(default)
.ReturnsForAnyArgs(groups);
}
ProjectPeopleAccessPolicies resource) =>
sutProvider.GetDependency<ISameOrganizationQuery>()
.GroupsInTheSameOrgAsync(Arg.Any<List<Guid>>(), resource.OrganizationId)
.Returns(true);
[Fact]
public void PeopleAccessPoliciesOperations_OnlyPublicStatic()
@ -129,37 +116,10 @@ public class ProjectPeopleAccessPoliciesAuthorizationHandlerTests
{
var requirement = ProjectPeopleAccessPoliciesOperations.Replace;
SetupUserPermission(sutProvider, accessClient, resource, userId);
var orgUsers = resource.UserAccessPolicies.Select(userPolicy =>
new OrganizationUser { OrganizationId = Guid.NewGuid(), Id = userPolicy.OrganizationUserId!.Value })
.ToList();
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(default)
.ReturnsForAnyArgs(orgUsers);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, resource);
sutProvider.GetDependency<ISameOrganizationQuery>()
.OrgUsersInTheSameOrgAsync(Arg.Any<List<Guid>>(), resource.OrganizationId)
.Returns(false);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(AccessClientType.User)]
[BitAutoData(AccessClientType.NoAccessCheck)]
public async Task ReplaceProjectPeople_UserCountMismatch_DoesNotSucceed(AccessClientType accessClient,
SutProvider<ProjectPeopleAccessPoliciesAuthorizationHandler> sutProvider, ProjectPeopleAccessPolicies resource,
ClaimsPrincipal claimsPrincipal, Guid userId)
{
var requirement = ProjectPeopleAccessPoliciesOperations.Replace;
SetupUserPermission(sutProvider, accessClient, resource, userId);
var orgUsers = resource.UserAccessPolicies.Select(userPolicy =>
new OrganizationUser
{
OrganizationId = resource.OrganizationId,
Id = userPolicy.OrganizationUserId!.Value
}).ToList();
orgUsers.RemoveAt(0);
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(default)
.ReturnsForAnyArgs(orgUsers);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, resource);
@ -179,35 +139,8 @@ public class ProjectPeopleAccessPoliciesAuthorizationHandlerTests
SetupUserPermission(sutProvider, accessClient, resource, userId);
SetupOrganizationUsers(sutProvider, resource);
var groups = resource.GroupAccessPolicies.Select(groupPolicy =>
new Group { OrganizationId = Guid.NewGuid(), Id = groupPolicy.GroupId!.Value }).ToList();
sutProvider.GetDependency<IGroupRepository>().GetManyByManyIds(default)
.ReturnsForAnyArgs(groups);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, resource);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(AccessClientType.User)]
[BitAutoData(AccessClientType.NoAccessCheck)]
public async Task ReplaceProjectPeople_GroupCountMismatch_DoesNotSucceed(AccessClientType accessClient,
SutProvider<ProjectPeopleAccessPoliciesAuthorizationHandler> sutProvider, ProjectPeopleAccessPolicies resource,
ClaimsPrincipal claimsPrincipal, Guid userId)
{
var requirement = ProjectPeopleAccessPoliciesOperations.Replace;
SetupUserPermission(sutProvider, accessClient, resource, userId);
SetupOrganizationUsers(sutProvider, resource);
var groups = resource.GroupAccessPolicies.Select(groupPolicy =>
new Group { OrganizationId = resource.OrganizationId, Id = groupPolicy.GroupId!.Value }).ToList();
groups.RemoveAt(0);
sutProvider.GetDependency<IGroupRepository>().GetManyByManyIds(default)
.ReturnsForAnyArgs(groups);
sutProvider.GetDependency<ISameOrganizationQuery>()
.GroupsInTheSameOrgAsync(Arg.Any<List<Guid>>(), resource.OrganizationId).Returns(false);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, resource);

View File

@ -0,0 +1,186 @@
using System.Reflection;
using System.Security.Claims;
using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.SecretsManager.AuthorizationRequirements;
using Bit.Core.SecretsManager.Models.Data;
using Bit.Core.SecretsManager.Queries.AccessPolicies.Interfaces;
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.AccessPolicies;
[SutProviderCustomize]
public class ServiceAccountPeopleAccessPoliciesAuthorizationHandlerTests
{
private static void SetupUserPermission(
SutProvider<ServiceAccountPeopleAccessPoliciesAuthorizationHandler> sutProvider,
AccessClientType accessClientType, ServiceAccountPeopleAccessPolicies resource, Guid userId = new(),
bool read = true,
bool write = true)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(resource.OrganizationId)
.Returns(true);
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, resource.OrganizationId)
.ReturnsForAnyArgs(
(accessClientType, userId));
sutProvider.GetDependency<IServiceAccountRepository>()
.AccessToServiceAccountAsync(resource.Id, userId, accessClientType)
.Returns((read, write));
}
private static void SetupOrganizationUsers(
SutProvider<ServiceAccountPeopleAccessPoliciesAuthorizationHandler> sutProvider,
ServiceAccountPeopleAccessPolicies resource) =>
sutProvider.GetDependency<ISameOrganizationQuery>()
.OrgUsersInTheSameOrgAsync(Arg.Any<List<Guid>>(), resource.OrganizationId)
.Returns(true);
private static void SetupGroups(SutProvider<ServiceAccountPeopleAccessPoliciesAuthorizationHandler> sutProvider,
ServiceAccountPeopleAccessPolicies resource) =>
sutProvider.GetDependency<ISameOrganizationQuery>()
.GroupsInTheSameOrgAsync(Arg.Any<List<Guid>>(), resource.OrganizationId)
.Returns(true);
[Fact]
public void ServiceAccountPeopleAccessPoliciesOperations_OnlyPublicStatic()
{
var publicStaticFields =
typeof(ServiceAccountPeopleAccessPoliciesOperations).GetFields(BindingFlags.Public | BindingFlags.Static);
var allFields = typeof(ServiceAccountPeopleAccessPoliciesOperations).GetFields();
Assert.Equal(publicStaticFields.Length, allFields.Length);
}
[Theory]
[BitAutoData]
public async Task Handler_UnsupportedServiceAccountPeopleAccessPoliciesOperationRequirement_Throws(
SutProvider<ServiceAccountPeopleAccessPoliciesAuthorizationHandler> sutProvider,
ServiceAccountPeopleAccessPolicies resource,
ClaimsPrincipal claimsPrincipal)
{
var requirement = new ServiceAccountPeopleAccessPoliciesOperationRequirement();
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(resource.OrganizationId)
.Returns(true);
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, resource.OrganizationId)
.ReturnsForAnyArgs(
(AccessClientType.NoAccessCheck, new Guid()));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, resource);
await Assert.ThrowsAsync<ArgumentException>(() => sutProvider.Sut.HandleAsync(authzContext));
}
[Theory]
[BitAutoData]
public async Task Handler_AccessSecretsManagerFalse_DoesNotSucceed(
SutProvider<ServiceAccountPeopleAccessPoliciesAuthorizationHandler> sutProvider,
ServiceAccountPeopleAccessPolicies resource,
ClaimsPrincipal claimsPrincipal)
{
var requirement = new ServiceAccountPeopleAccessPoliciesOperationRequirement();
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(resource.OrganizationId)
.Returns(false);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, resource);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(AccessClientType.ServiceAccount)]
[BitAutoData(AccessClientType.Organization)]
public async Task Handler_UnsupportedClientTypes_DoesNotSucceed(AccessClientType clientType,
SutProvider<ServiceAccountPeopleAccessPoliciesAuthorizationHandler> sutProvider,
ServiceAccountPeopleAccessPolicies resource,
ClaimsPrincipal claimsPrincipal)
{
var requirement = new ServiceAccountPeopleAccessPoliciesOperationRequirement();
SetupUserPermission(sutProvider, clientType, resource);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, resource);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(AccessClientType.User)]
[BitAutoData(AccessClientType.NoAccessCheck)]
public async Task ReplaceServiceAccountPeople_UserNotInOrg_DoesNotSucceed(AccessClientType accessClient,
SutProvider<ServiceAccountPeopleAccessPoliciesAuthorizationHandler> sutProvider,
ServiceAccountPeopleAccessPolicies resource,
ClaimsPrincipal claimsPrincipal, Guid userId)
{
var requirement = ServiceAccountPeopleAccessPoliciesOperations.Replace;
SetupUserPermission(sutProvider, accessClient, resource, userId);
sutProvider.GetDependency<ISameOrganizationQuery>()
.OrgUsersInTheSameOrgAsync(Arg.Any<List<Guid>>(), resource.OrganizationId)
.Returns(false);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, resource);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(AccessClientType.User)]
[BitAutoData(AccessClientType.NoAccessCheck)]
public async Task ReplaceServiceAccountPeople_GroupNotInOrg_DoesNotSucceed(AccessClientType accessClient,
SutProvider<ServiceAccountPeopleAccessPoliciesAuthorizationHandler> sutProvider,
ServiceAccountPeopleAccessPolicies resource,
ClaimsPrincipal claimsPrincipal, Guid userId)
{
var requirement = ServiceAccountPeopleAccessPoliciesOperations.Replace;
SetupUserPermission(sutProvider, accessClient, resource, userId);
SetupOrganizationUsers(sutProvider, resource);
sutProvider.GetDependency<ISameOrganizationQuery>()
.GroupsInTheSameOrgAsync(Arg.Any<List<Guid>>(), resource.OrganizationId).Returns(false);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, resource);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(AccessClientType.User, false, false, false)]
[BitAutoData(AccessClientType.User, false, true, true)]
[BitAutoData(AccessClientType.User, true, false, false)]
[BitAutoData(AccessClientType.User, true, true, true)]
[BitAutoData(AccessClientType.NoAccessCheck, false, false, false)]
[BitAutoData(AccessClientType.NoAccessCheck, false, true, true)]
[BitAutoData(AccessClientType.NoAccessCheck, true, false, false)]
[BitAutoData(AccessClientType.NoAccessCheck, true, true, true)]
public async Task ReplaceServiceAccountPeople_AccessCheck(AccessClientType accessClient, bool read, bool write,
bool expected,
SutProvider<ServiceAccountPeopleAccessPoliciesAuthorizationHandler> sutProvider,
ServiceAccountPeopleAccessPolicies resource,
ClaimsPrincipal claimsPrincipal, Guid userId)
{
var requirement = ServiceAccountPeopleAccessPoliciesOperations.Replace;
SetupUserPermission(sutProvider, accessClient, resource, userId, read, write);
SetupOrganizationUsers(sutProvider, resource);
SetupGroups(sutProvider, resource);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, resource);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.Equal(expected, authzContext.HasSucceeded);
}
}

View File

@ -0,0 +1,151 @@
using Bit.Commercial.Core.SecretsManager.Queries.AccessPolicies;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Commercial.Core.Test.SecretsManager.Queries.AccessPolicies;
[SutProviderCustomize]
public class SameOrganizationQueryTests
{
[Theory]
[BitAutoData]
public async Task OrgUsersInTheSameOrg_NoOrgUsers_ReturnsFalse(SutProvider<SameOrganizationQuery> sutProvider,
List<OrganizationUser> orgUsers, Guid organizationId)
{
var orgUserIds = orgUsers.Select(ou => ou.Id).ToList();
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(orgUserIds)
.ReturnsForAnyArgs(new List<OrganizationUser>());
var result = await sutProvider.Sut.OrgUsersInTheSameOrgAsync(orgUserIds, organizationId);
Assert.False(result);
}
[Theory]
[BitAutoData]
public async Task OrgUsersInTheSameOrg_OrgMismatch_ReturnsFalse(SutProvider<SameOrganizationQuery> sutProvider,
List<OrganizationUser> orgUsers, Guid organizationId)
{
var orgUserIds = orgUsers.Select(ou => ou.Id).ToList();
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(orgUserIds)
.ReturnsForAnyArgs(orgUsers);
var result = await sutProvider.Sut.OrgUsersInTheSameOrgAsync(orgUserIds, organizationId);
Assert.False(result);
}
[Theory]
[BitAutoData]
public async Task OrgUsersInTheSameOrg_CountMismatch_ReturnsFalse(SutProvider<SameOrganizationQuery> sutProvider,
List<OrganizationUser> orgUsers, Guid organizationId)
{
var orgUserIds = orgUsers.Select(ou => ou.Id).ToList();
foreach (var organizationUser in orgUsers)
{
organizationUser.OrganizationId = organizationId;
}
orgUsers.RemoveAt(0);
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(orgUserIds)
.ReturnsForAnyArgs(orgUsers);
var result = await sutProvider.Sut.OrgUsersInTheSameOrgAsync(orgUserIds, organizationId);
Assert.False(result);
}
[Theory]
[BitAutoData]
public async Task OrgUsersInTheSameOrg_Success_ReturnsTrue(SutProvider<SameOrganizationQuery> sutProvider,
List<OrganizationUser> orgUsers, Guid organizationId)
{
var orgUserIds = orgUsers.Select(ou => ou.Id).ToList();
foreach (var organizationUser in orgUsers)
{
organizationUser.OrganizationId = organizationId;
}
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(orgUserIds)
.ReturnsForAnyArgs(orgUsers);
var result = await sutProvider.Sut.OrgUsersInTheSameOrgAsync(orgUserIds, organizationId);
Assert.True(result);
}
[Theory]
[BitAutoData]
public async Task GroupsInTheSameOrg_NoGroups_ReturnsFalse(SutProvider<SameOrganizationQuery> sutProvider,
List<Group> groups, Guid organizationId)
{
var groupIds = groups.Select(ou => ou.Id).ToList();
sutProvider.GetDependency<IGroupRepository>().GetManyByManyIds(groupIds)
.ReturnsForAnyArgs(new List<Group>());
var result = await sutProvider.Sut.GroupsInTheSameOrgAsync(groupIds, organizationId);
Assert.False(result);
}
[Theory]
[BitAutoData]
public async Task GroupsInTheSameOrg_OrgMismatch_ReturnsFalse(SutProvider<SameOrganizationQuery> sutProvider,
List<Group> groups, Guid organizationId)
{
var groupIds = groups.Select(ou => ou.Id).ToList();
sutProvider.GetDependency<IGroupRepository>().GetManyByManyIds(groupIds)
.ReturnsForAnyArgs(groups);
var result = await sutProvider.Sut.GroupsInTheSameOrgAsync(groupIds, organizationId);
Assert.False(result);
}
[Theory]
[BitAutoData]
public async Task GroupsInTheSameOrg_CountMismatch_ReturnsFalse(SutProvider<SameOrganizationQuery> sutProvider,
List<Group> groups, Guid organizationId)
{
var groupIds = groups.Select(ou => ou.Id).ToList();
foreach (var group in groups)
{
group.OrganizationId = organizationId;
}
groups.RemoveAt(0);
sutProvider.GetDependency<IGroupRepository>().GetManyByManyIds(groupIds)
.ReturnsForAnyArgs(groups);
var result = await sutProvider.Sut.GroupsInTheSameOrgAsync(groupIds, organizationId);
Assert.False(result);
}
[Theory]
[BitAutoData]
public async Task GroupsInTheSameOrg_Success_ReturnsTrue(SutProvider<SameOrganizationQuery> sutProvider,
List<Group> groups, Guid organizationId)
{
var groupIds = groups.Select(ou => ou.Id).ToList();
foreach (var group in groups)
{
group.OrganizationId = organizationId;
}
sutProvider.GetDependency<IGroupRepository>().GetManyByManyIds(groupIds)
.ReturnsForAnyArgs(groups);
var result = await sutProvider.Sut.GroupsInTheSameOrgAsync(groupIds, organizationId);
Assert.True(result);
}
}

View File

@ -89,46 +89,6 @@ public class AccessPoliciesController : Controller
return new ProjectAccessPoliciesResponseModel(results);
}
[HttpPost("/service-accounts/{id}/access-policies")]
public async Task<ServiceAccountAccessPoliciesResponseModel> CreateServiceAccountAccessPoliciesAsync(
[FromRoute] Guid id,
[FromBody] AccessPoliciesCreateRequest request)
{
if (request.Count() > _maxBulkCreation)
{
throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once.");
}
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
if (serviceAccount == null)
{
throw new NotFoundException();
}
var policies = request.ToBaseAccessPoliciesForServiceAccount(id, serviceAccount.OrganizationId);
foreach (var policy in policies)
{
var authorizationResult = await _authorizationService.AuthorizeAsync(User, policy, AccessPolicyOperations.Create);
if (!authorizationResult.Succeeded)
{
throw new NotFoundException();
}
}
var results = await _createAccessPoliciesCommand.CreateManyAsync(policies);
return new ServiceAccountAccessPoliciesResponseModel(results);
}
[HttpGet("/service-accounts/{id}/access-policies")]
public async Task<ServiceAccountAccessPoliciesResponseModel> GetServiceAccountAccessPoliciesAsync(
[FromRoute] Guid id)
{
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
var (_, userId) = await CheckUserHasWriteAccessToServiceAccountAsync(serviceAccount);
var results = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(id, userId);
return new ServiceAccountAccessPoliciesResponseModel(results);
}
[HttpPost("/service-accounts/{id}/granted-policies")]
public async Task<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>
CreateServiceAccountGrantedPoliciesAsync([FromRoute] Guid id,
@ -308,6 +268,40 @@ public class AccessPoliciesController : Controller
return new ProjectPeopleAccessPoliciesResponseModel(results, userId);
}
[HttpGet("/service-accounts/{id}/access-policies/people")]
public async Task<ServiceAccountPeopleAccessPoliciesResponseModel> GetServiceAccountPeopleAccessPoliciesAsync(
[FromRoute] Guid id)
{
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
var (_, userId) = await CheckUserHasWriteAccessToServiceAccountAsync(serviceAccount);
var results = await _accessPolicyRepository.GetPeoplePoliciesByGrantedServiceAccountIdAsync(id, userId);
return new ServiceAccountPeopleAccessPoliciesResponseModel(results, userId);
}
[HttpPut("/service-accounts/{id}/access-policies/people")]
public async Task<ServiceAccountPeopleAccessPoliciesResponseModel> PutServiceAccountPeopleAccessPoliciesAsync(
[FromRoute] Guid id,
[FromBody] PeopleAccessPoliciesRequestModel request)
{
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
if (serviceAccount == null)
{
throw new NotFoundException();
}
var peopleAccessPolicies = request.ToServiceAccountPeopleAccessPolicies(id, serviceAccount.OrganizationId);
var authorizationResult = await _authorizationService.AuthorizeAsync(User, peopleAccessPolicies,
ServiceAccountPeopleAccessPoliciesOperations.Replace);
if (!authorizationResult.Succeeded)
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User)!.Value;
var results = await _accessPolicyRepository.ReplaceServiceAccountPeopleAsync(peopleAccessPolicies, userId);
return new ServiceAccountPeopleAccessPoliciesResponseModel(results, userId);
}
private async Task<(AccessClientType AccessClientType, Guid UserId)> CheckUserHasWriteAccessToProjectAsync(Project project)
{
@ -345,6 +339,7 @@ public class AccessPoliciesController : Controller
{
throw new NotFoundException();
}
return (accessClient, userId);
}

View File

@ -61,4 +61,39 @@ public class PeopleAccessPoliciesRequestModel
GroupAccessPolicies = groupAccessPolicies
};
}
public ServiceAccountPeopleAccessPolicies ToServiceAccountPeopleAccessPolicies(Guid grantedServiceAccountId, Guid organizationId)
{
var userAccessPolicies = UserAccessPolicyRequests?
.Select(x => x.ToUserServiceAccountAccessPolicy(grantedServiceAccountId, organizationId)).ToList();
var groupAccessPolicies = GroupAccessPolicyRequests?
.Select(x => x.ToGroupServiceAccountAccessPolicy(grantedServiceAccountId, organizationId)).ToList();
var policies = new List<BaseAccessPolicy>();
if (userAccessPolicies != null)
{
policies.AddRange(userAccessPolicies);
}
if (groupAccessPolicies != null)
{
policies.AddRange(groupAccessPolicies);
}
CheckForDistinctAccessPolicies(policies);
if (!policies.All(ap => ap.Read && ap.Write))
{
throw new BadRequestException("Service account access must be Can read, write");
}
return new ServiceAccountPeopleAccessPolicies
{
Id = grantedServiceAccountId,
OrganizationId = organizationId,
UserAccessPolicies = userAccessPolicies,
GroupAccessPolicies = groupAccessPolicies
};
}
}

View File

@ -69,10 +69,14 @@ public class UserServiceAccountAccessPolicyResponseModel : BaseAccessPolicyRespo
public UserServiceAccountAccessPolicyResponseModel(UserServiceAccountAccessPolicy accessPolicy)
: base(accessPolicy, _objectName)
{
OrganizationUserId = accessPolicy.OrganizationUserId;
GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId;
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
UserId = accessPolicy.User?.Id;
SetProperties(accessPolicy);
}
public UserServiceAccountAccessPolicyResponseModel(UserServiceAccountAccessPolicy accessPolicy, Guid userId)
: base(accessPolicy, _objectName)
{
SetProperties(accessPolicy);
CurrentUser = accessPolicy.User?.Id == userId;
}
public UserServiceAccountAccessPolicyResponseModel() : base(new UserServiceAccountAccessPolicy(), _objectName)
@ -83,6 +87,15 @@ public class UserServiceAccountAccessPolicyResponseModel : BaseAccessPolicyRespo
public string? OrganizationUserName { get; set; }
public Guid? UserId { get; set; }
public Guid? GrantedServiceAccountId { get; set; }
public bool CurrentUser { get; set; }
private void SetProperties(UserServiceAccountAccessPolicy accessPolicy)
{
OrganizationUserId = accessPolicy.OrganizationUserId;
GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId;
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
UserId = accessPolicy.User?.Id;
}
}
public class GroupProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel

View File

@ -3,11 +3,11 @@ using Bit.Core.SecretsManager.Entities;
namespace Bit.Api.SecretsManager.Models.Response;
public class ServiceAccountAccessPoliciesResponseModel : ResponseModel
public class ServiceAccountPeopleAccessPoliciesResponseModel : ResponseModel
{
private const string _objectName = "serviceAccountAccessPolicies";
public ServiceAccountAccessPoliciesResponseModel(IEnumerable<BaseAccessPolicy> baseAccessPolicies)
public ServiceAccountPeopleAccessPoliciesResponseModel(IEnumerable<BaseAccessPolicy> baseAccessPolicies, Guid userId)
: base(_objectName)
{
if (baseAccessPolicies == null)
@ -20,7 +20,7 @@ public class ServiceAccountAccessPoliciesResponseModel : ResponseModel
switch (baseAccessPolicy)
{
case UserServiceAccountAccessPolicy accessPolicy:
UserAccessPolicies.Add(new UserServiceAccountAccessPolicyResponseModel(accessPolicy));
UserAccessPolicies.Add(new UserServiceAccountAccessPolicyResponseModel(accessPolicy, userId));
break;
case GroupServiceAccountAccessPolicy accessPolicy:
GroupAccessPolicies.Add(new GroupServiceAccountAccessPolicyResponseModel(accessPolicy));
@ -29,7 +29,7 @@ public class ServiceAccountAccessPoliciesResponseModel : ResponseModel
}
}
public ServiceAccountAccessPoliciesResponseModel() : base(_objectName)
public ServiceAccountPeopleAccessPoliciesResponseModel() : base(_objectName)
{
}

View File

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Authorization.Infrastructure;
namespace Bit.Core.SecretsManager.AuthorizationRequirements;
public class ServiceAccountPeopleAccessPoliciesOperationRequirement : OperationAuthorizationRequirement
{
}
public static class ServiceAccountPeopleAccessPoliciesOperations
{
public static readonly ServiceAccountPeopleAccessPoliciesOperationRequirement Replace = new() { Name = nameof(Replace) };
}

View File

@ -0,0 +1,27 @@
using Bit.Core.SecretsManager.Entities;
namespace Bit.Core.SecretsManager.Models.Data;
public class ServiceAccountPeopleAccessPolicies
{
public Guid Id { get; set; }
public Guid OrganizationId { get; set; }
public IEnumerable<UserServiceAccountAccessPolicy> UserAccessPolicies { get; set; }
public IEnumerable<GroupServiceAccountAccessPolicy> GroupAccessPolicies { get; set; }
public IEnumerable<BaseAccessPolicy> ToBaseAccessPolicies()
{
var policies = new List<BaseAccessPolicy>();
if (UserAccessPolicies != null && UserAccessPolicies.Any())
{
policies.AddRange(UserAccessPolicies);
}
if (GroupAccessPolicies != null && GroupAccessPolicies.Any())
{
policies.AddRange(GroupAccessPolicies);
}
return policies;
}
}

View File

@ -0,0 +1,7 @@
namespace Bit.Core.SecretsManager.Queries.AccessPolicies.Interfaces;
public interface ISameOrganizationQuery
{
Task<bool> OrgUsersInTheSameOrgAsync(List<Guid> organizationUserIds, Guid organizationId);
Task<bool> GroupsInTheSameOrgAsync(List<Guid> groupIds, Guid organizationId);
}

View File

@ -11,7 +11,6 @@ public interface IAccessPolicyRepository
Task<bool> AccessPolicyExists(BaseAccessPolicy baseAccessPolicy);
Task<BaseAccessPolicy?> GetByIdAsync(Guid id);
Task<IEnumerable<BaseAccessPolicy>> GetManyByGrantedProjectIdAsync(Guid id, Guid userId);
Task<IEnumerable<BaseAccessPolicy>> GetManyByGrantedServiceAccountIdAsync(Guid id, Guid userId);
Task<IEnumerable<BaseAccessPolicy>> GetManyByServiceAccountIdAsync(Guid id, Guid userId,
AccessClientType accessType);
Task ReplaceAsync(BaseAccessPolicy baseAccessPolicy);
@ -19,4 +18,6 @@ public interface IAccessPolicyRepository
Task<IEnumerable<BaseAccessPolicy>> GetPeoplePoliciesByGrantedProjectIdAsync(Guid id, Guid userId);
Task<IEnumerable<BaseAccessPolicy>> ReplaceProjectPeopleAsync(ProjectPeopleAccessPolicies peopleAccessPolicies, Guid userId);
Task<PeopleGrantees> GetPeopleGranteesAsync(Guid organizationId, Guid currentUserId);
Task<IEnumerable<BaseAccessPolicy>> GetPeoplePoliciesByGrantedServiceAccountIdAsync(Guid id, Guid userId);
Task<IEnumerable<BaseAccessPolicy>> ReplaceServiceAccountPeopleAsync(ServiceAccountPeopleAccessPolicies peopleAccessPolicies, Guid userId);
}

View File

@ -627,228 +627,6 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
Assert.Equal(project.Id, result.Data.First(x => x.Id == project.Id).Id);
}
[Theory]
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task CreateServiceAccountAccessPolicies_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, orgUser) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var request = new AccessPoliciesCreateRequest
{
UserAccessPolicyRequests = new List<AccessPolicyRequest>
{
new() { GranteeId = orgUser.Id, Read = true, Write = true },
},
};
var response =
await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateServiceAccountAccessPolicies_MismatchOrgId_NotFound(PermissionType permissionType)
{
var (_, orgUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var anotherOrg = await _organizationHelper.CreateSmOrganizationAsync();
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = anotherOrg.Id,
Name = _mockEncryptedString,
});
var request =
await SetupUserServiceAccountAccessPolicyRequestAsync(permissionType, orgUser.Id, serviceAccount.Id);
var response =
await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateServiceAccountAccessPolicies_Success(PermissionType permissionType)
{
var (org, orgUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var ownerOrgUserId = orgUser.Id;
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var request =
await SetupUserServiceAccountAccessPolicyRequestAsync(permissionType, orgUser.Id, serviceAccount.Id);
var response =
await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ServiceAccountAccessPoliciesResponseModel>();
Assert.NotNull(result);
Assert.Equal(ownerOrgUserId,
result!.UserAccessPolicies.First(ap => ap.OrganizationUserId == ownerOrgUserId).OrganizationUserId);
Assert.True(result.UserAccessPolicies.First().Read);
Assert.True(result.UserAccessPolicies.First().Write);
var createdAccessPolicy =
await _accessPolicyRepository.GetByIdAsync(result.UserAccessPolicies.First().Id);
Assert.NotNull(createdAccessPolicy);
Assert.Equal(result.UserAccessPolicies.First().Read, createdAccessPolicy!.Read);
Assert.Equal(result.UserAccessPolicies.First().Write, createdAccessPolicy.Write);
Assert.Equal(result.UserAccessPolicies.First().Id, createdAccessPolicy.Id);
}
[Fact]
public async Task CreateServiceAccountAccessPolicies_NoPermission()
{
// Create a new account as a user
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var request = new AccessPoliciesCreateRequest
{
UserAccessPolicyRequests = new List<AccessPolicyRequest>
{
new() { GranteeId = orgUser.Id, Read = true, Write = true },
},
};
var response =
await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task GetServiceAccountAccessPolicies_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/access-policies");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetServiceAccountAccessPolicies_ReturnsEmpty()
{
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var response = await _client.GetAsync($"/service-accounts/{serviceAccount.Id}/access-policies");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ServiceAccountAccessPoliciesResponseModel>();
Assert.NotNull(result);
Assert.Empty(result!.UserAccessPolicies);
Assert.Empty(result.GroupAccessPolicies);
}
[Fact]
public async Task GetServiceAccountAccessPolicies_NoPermission()
{
// Create a new account as a user
await _organizationHelper.Initialize(true, true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var initData = await SetupAccessPolicyRequest(orgUser.OrganizationId);
var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/access-policies");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetServiceAccountAccessPolicies(PermissionType permissionType)
{
var (org, ownerOrgUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
if (permissionType == PermissionType.RunAsUserWithPermission)
{
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var accessPolicies = new List<BaseAccessPolicy>
{
new UserServiceAccountAccessPolicy
{
GrantedServiceAccountId = initData.ServiceAccountId,
OrganizationUserId = orgUser.Id,
Read = true,
Write = true,
},
};
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
}
var policies = new List<BaseAccessPolicy>
{
new UserServiceAccountAccessPolicy
{
GrantedServiceAccountId = initData.ServiceAccountId,
OrganizationUserId = ownerOrgUser.Id,
Read = true,
Write = true,
},
};
await _accessPolicyRepository.CreateManyAsync(policies);
var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/access-policies");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ServiceAccountAccessPoliciesResponseModel>();
Assert.NotNull(result?.UserAccessPolicies);
Assert.NotEmpty(result!.UserAccessPolicies);
Assert.Equal(ownerOrgUser.Id,
result.UserAccessPolicies.First(x => x.OrganizationUserId == ownerOrgUser.Id).OrganizationUserId);
}
[Theory]
[InlineData(false, false, false)]
[InlineData(false, false, true)]
@ -1066,9 +844,13 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
var project = await _projectRepository.CreateAsync(new Project
{
OrganizationId = org.Id,
Name = _mockEncryptedString
});
var response = await _client.GetAsync($"/projects/{initData.ProjectId}/access-policies/people");
var response = await _client.GetAsync($"/projects/{project.Id}/access-policies/people");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@ -1230,6 +1012,183 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
Assert.Equal(result.UserAccessPolicies.First().Id, createdAccessPolicy.Id);
}
[Theory]
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task GetServiceAccountPeopleAccessPolicies_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var response = await _client.GetAsync($"/service-accounts/{serviceAccount.Id}/access-policies/people");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetServiceAccountPeopleAccessPolicies_ReturnsEmpty()
{
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var response = await _client.GetAsync($"/service-accounts/{serviceAccount.Id}/access-policies/people");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ServiceAccountPeopleAccessPoliciesResponseModel>();
Assert.NotNull(result);
Assert.Empty(result!.UserAccessPolicies);
Assert.Empty(result.GroupAccessPolicies);
}
[Fact]
public async Task GetServiceAccountPeopleAccessPolicies_NoPermission()
{
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var response = await _client.GetAsync($"/service-accounts/{serviceAccount.Id}/access-policies/people");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetServiceAccountPeopleAccessPolicies_Success(PermissionType permissionType)
{
var (_, organizationUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var (serviceAccount, _) = await SetupServiceAccountPeoplePermissionAsync(permissionType, organizationUser);
var response = await _client.GetAsync($"/service-accounts/{serviceAccount.Id}/access-policies/people");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ServiceAccountPeopleAccessPoliciesResponseModel>();
Assert.NotNull(result?.UserAccessPolicies);
Assert.Single(result!.UserAccessPolicies);
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task PutServiceAccountPeopleAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
{
var (_, organizationUser) = await _organizationHelper.Initialize(useSecrets, accessSecrets, true);
await LoginAsync(_email);
var (serviceAccount, request) = await SetupServiceAccountPeopleRequestAsync(PermissionType.RunAsAdmin, organizationUser);
var response = await _client.PutAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies/people", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task PutServiceAccountPeopleAccessPolicies_NoPermission()
{
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, organizationUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var request = new PeopleAccessPoliciesRequestModel
{
UserAccessPolicyRequests = new List<AccessPolicyRequest>
{
new() { GranteeId = organizationUser.Id, Read = true, Write = true }
}
};
var response = await _client.PutAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies/people", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task PutServiceAccountPeopleAccessPolicies_MismatchedOrgIds_NotFound(PermissionType permissionType)
{
var (_, organizationUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var (serviceAccount, request) = await SetupServiceAccountPeopleRequestAsync(permissionType, organizationUser);
var newOrg = await _organizationHelper.CreateSmOrganizationAsync();
var group = await _groupRepository.CreateAsync(new Group
{
OrganizationId = newOrg.Id,
Name = _mockEncryptedString
});
request.GroupAccessPolicyRequests = new List<AccessPolicyRequest>
{
new() { GranteeId = group.Id, Read = true, Write = true }
};
var response = await _client.PutAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies/people", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task PutServiceAccountPeopleAccessPolicies_Success(PermissionType permissionType)
{
var (_, organizationUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var (serviceAccount, request) = await SetupServiceAccountPeopleRequestAsync(permissionType, organizationUser);
var response = await _client.PutAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies/people", request);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ServiceAccountPeopleAccessPoliciesResponseModel>();
Assert.NotNull(result);
Assert.Equal(request.UserAccessPolicyRequests.First().GranteeId,
result!.UserAccessPolicies.First().OrganizationUserId);
Assert.True(result.UserAccessPolicies.First().Read);
Assert.True(result.UserAccessPolicies.First().Write);
var createdAccessPolicy =
await _accessPolicyRepository.GetByIdAsync(result.UserAccessPolicies.First().Id);
Assert.NotNull(createdAccessPolicy);
Assert.Equal(result.UserAccessPolicies.First().Read, createdAccessPolicy!.Read);
Assert.Equal(result.UserAccessPolicies.First().Write, createdAccessPolicy.Write);
Assert.Equal(result.UserAccessPolicies.First().Id, createdAccessPolicy.Id);
}
private async Task<RequestSetupData> SetupAccessPolicyRequest(Guid organizationId)
{
var project = await _projectRepository.CreateAsync(new Project
@ -1293,6 +1252,38 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
return (project, organizationUser);
}
private async Task<(ServiceAccount serviceAccount, OrganizationUser currentUser)> SetupServiceAccountPeoplePermissionAsync(
PermissionType permissionType,
OrganizationUser organizationUser)
{
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = organizationUser.OrganizationId,
Name = _mockEncryptedString,
});
if (permissionType == PermissionType.RunAsUserWithPermission)
{
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
organizationUser = orgUser;
}
var accessPolicies = new List<BaseAccessPolicy>
{
new UserServiceAccountAccessPolicy
{
GrantedServiceAccountId = serviceAccount.Id,
OrganizationUserId = organizationUser.Id,
Read = true,
Write = true
}
};
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
return (serviceAccount, organizationUser);
}
private async Task<(Project project, PeopleAccessPoliciesRequestModel request)> SetupProjectPeopleRequestAsync(
PermissionType permissionType, OrganizationUser organizationUser)
{
@ -1307,6 +1298,20 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
return (project, request);
}
private async Task<(ServiceAccount serviceAccount, PeopleAccessPoliciesRequestModel request)> SetupServiceAccountPeopleRequestAsync(
PermissionType permissionType, OrganizationUser organizationUser)
{
var (serviceAccount, currentUser) = await SetupServiceAccountPeoplePermissionAsync(permissionType, organizationUser);
var request = new PeopleAccessPoliciesRequestModel
{
UserAccessPolicyRequests = new List<AccessPolicyRequest>
{
new() { GranteeId = currentUser.Id, Read = true, Write = true }
}
};
return (serviceAccount, request);
}
private async Task<(Guid ProjectId, Guid ServiceAccountId)> CreateProjectAndServiceAccountAsync(Guid organizationId,
bool misMatchOrganization = false)
{

View File

@ -248,7 +248,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
AssertHelper.AssertRecent(createdServiceAccount.CreationDate);
// Check permissions have been bootstrapped.
var accessPolicies = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(createdServiceAccount.Id, currentUserId);
var accessPolicies = await _accessPolicyRepository.GetPeoplePoliciesByGrantedServiceAccountIdAsync(createdServiceAccount.Id, currentUserId);
Assert.NotNull(accessPolicies);
var ap = (UserServiceAccountAccessPolicy)accessPolicies.First();
Assert.Equal(createdServiceAccount.Id, ap.GrantedServiceAccountId);

View File

@ -51,6 +51,23 @@ public class AccessPoliciesControllerTests
return request;
}
private static PeopleAccessPoliciesRequestModel SetRequestToCanReadWrite(PeopleAccessPoliciesRequestModel request)
{
foreach (var ap in request.UserAccessPolicyRequests)
{
ap.Read = true;
ap.Write = true;
}
foreach (var ap in request.GroupAccessPolicyRequests)
{
ap.Read = true;
ap.Write = true;
}
return request;
}
private static void SetupAdmin(SutProvider<AccessPoliciesController> sutProvider, Guid organizationId)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
@ -103,12 +120,14 @@ public class AccessPoliciesControllerTests
{
case PermissionType.RunAsAdmin:
SetupAdmin(sutProvider, data.OrganizationId);
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync(Arg.Any<Guid>(), Arg.Any<Guid>(), AccessClientType.NoAccessCheck)
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync(Arg.Any<Guid>(), Arg.Any<Guid>(),
AccessClientType.NoAccessCheck)
.Returns((true, true));
break;
case PermissionType.RunAsUserWithPermission:
SetupUserWithPermission(sutProvider, data.OrganizationId);
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync(Arg.Any<Guid>(), Arg.Any<Guid>(), AccessClientType.User)
sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(Arg.Any<Guid>(), Arg.Any<Guid>(), AccessClientType.User)
.Returns((true, true));
break;
}
@ -156,12 +175,14 @@ public class AccessPoliciesControllerTests
{
case PermissionType.RunAsAdmin:
SetupAdmin(sutProvider, data.OrganizationId);
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync(Arg.Any<Guid>(), Arg.Any<Guid>(), AccessClientType.NoAccessCheck)
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync(Arg.Any<Guid>(), Arg.Any<Guid>(),
AccessClientType.NoAccessCheck)
.Returns((true, true));
break;
case PermissionType.RunAsUserWithPermission:
SetupUserWithPermission(sutProvider, data.OrganizationId);
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync(Arg.Any<Guid>(), Arg.Any<Guid>(), AccessClientType.User)
sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(Arg.Any<Guid>(), Arg.Any<Guid>(), AccessClientType.User)
.Returns((true, true));
break;
}
@ -201,114 +222,6 @@ public class AccessPoliciesControllerTests
.GetManyByGrantedProjectIdAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async void GetServiceAccountAccessPolicies_ReturnsEmptyList(
PermissionType permissionType,
SutProvider<AccessPoliciesController> sutProvider,
Guid id, ServiceAccount data)
{
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).ReturnsForAnyArgs(data);
switch (permissionType)
{
case PermissionType.RunAsAdmin:
SetupAdmin(sutProvider, data.OrganizationId);
break;
case PermissionType.RunAsUserWithPermission:
SetupUserWithPermission(sutProvider, data.OrganizationId);
sutProvider.GetDependency<IServiceAccountRepository>()
.UserHasWriteAccessToServiceAccount(default, default)
.ReturnsForAnyArgs(true);
break;
}
var result = await sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
.GetManyByGrantedServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), Arg.Any<Guid>());
Assert.Empty(result.UserAccessPolicies);
Assert.Empty(result.GroupAccessPolicies);
}
[Theory]
[BitAutoData]
public async void GetServiceAccountAccessPolicies_UserWithoutPermission_Throws(
SutProvider<AccessPoliciesController> sutProvider,
Guid id,
ServiceAccount data)
{
SetupUserWithoutPermission(sutProvider, data.OrganizationId);
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(data);
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(default, default)
.ReturnsForAnyArgs(false);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
.GetManyByGrantedServiceAccountIdAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async void GetServiceAccountAccessPolicies_Success(
PermissionType permissionType,
SutProvider<AccessPoliciesController> sutProvider,
Guid id,
ServiceAccount data,
UserServiceAccountAccessPolicy resultAccessPolicy)
{
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(data);
switch (permissionType)
{
case PermissionType.RunAsAdmin:
SetupAdmin(sutProvider, data.OrganizationId);
break;
case PermissionType.RunAsUserWithPermission:
SetupUserWithPermission(sutProvider, data.OrganizationId);
sutProvider.GetDependency<IServiceAccountRepository>()
.UserHasWriteAccessToServiceAccount(default, default)
.ReturnsForAnyArgs(true);
break;
}
sutProvider.GetDependency<IAccessPolicyRepository>().GetManyByGrantedServiceAccountIdAsync(default, default)
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { resultAccessPolicy });
var result = await sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
.GetManyByGrantedServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), Arg.Any<Guid>());
Assert.Empty(result.GroupAccessPolicies);
Assert.NotEmpty(result.UserAccessPolicies);
}
[Theory]
[BitAutoData]
public async void GetServiceAccountAccessPolicies_ServiceAccountExists_UserWithoutPermission_Throws(
SutProvider<AccessPoliciesController> sutProvider,
Guid id,
ServiceAccount data,
UserServiceAccountAccessPolicy resultAccessPolicy)
{
SetupUserWithoutPermission(sutProvider, data.OrganizationId);
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(data);
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(default, default)
.ReturnsForAnyArgs(false);
sutProvider.GetDependency<IAccessPolicyRepository>().GetManyByGrantedServiceAccountIdAsync(default, default)
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { resultAccessPolicy });
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
.GetManyByGrantedServiceAccountIdAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
@ -419,12 +332,12 @@ public class AccessPoliciesControllerTests
UserProjectAccessPolicy data,
AccessPoliciesCreateRequest request)
{
var dup = new AccessPolicyRequest() { GranteeId = Guid.NewGuid(), Read = true, Write = true };
var dup = new AccessPolicyRequest { GranteeId = Guid.NewGuid(), Read = true, Write = true };
request.UserAccessPolicyRequests = new[] { dup, dup };
mockProject.Id = id;
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(default).ReturnsForAnyArgs(mockProject);
sutProvider.GetDependency<ICreateAccessPoliciesCommand>().CreateManyAsync(default)
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data });
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data });
await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateProjectAccessPoliciesAsync(id, request));
@ -451,6 +364,7 @@ public class AccessPoliciesControllerTests
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), policy,
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Failed());
}
sutProvider.GetDependency<ICreateAccessPoliciesCommand>().CreateManyAsync(default)
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data });
@ -479,6 +393,7 @@ public class AccessPoliciesControllerTests
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), policy,
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
}
sutProvider.GetDependency<ICreateAccessPoliciesCommand>().CreateManyAsync(default)
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data });
@ -488,124 +403,6 @@ public class AccessPoliciesControllerTests
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
}
[Theory]
[BitAutoData]
public async void CreateServiceAccountAccessPolicies_RequestMoreThanMax_Throws(
SutProvider<AccessPoliciesController> sutProvider,
Guid id,
ServiceAccount serviceAccount,
UserServiceAccountAccessPolicy data,
AccessPoliciesCreateRequest request)
{
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount);
sutProvider.GetDependency<ICreateAccessPoliciesCommand>()
.CreateManyAsync(default)
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data });
request = AddRequestsOverMax(request);
await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request));
await sutProvider.GetDependency<ICreateAccessPoliciesCommand>().DidNotReceiveWithAnyArgs()
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
}
[Theory]
[BitAutoData]
public async void CreateServiceAccountAccessPolicies_ServiceAccountDoesNotExist_Throws(
SutProvider<AccessPoliciesController> sutProvider,
Guid id,
AccessPoliciesCreateRequest request)
{
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request));
await sutProvider.GetDependency<ICreateAccessPoliciesCommand>().DidNotReceiveWithAnyArgs()
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
}
[Theory]
[BitAutoData]
public async void CreateServiceAccountAccessPolicies_DuplicatePolicy_Throws(
SutProvider<AccessPoliciesController> sutProvider,
Guid id,
ServiceAccount serviceAccount,
UserServiceAccountAccessPolicy data,
AccessPoliciesCreateRequest request)
{
var dup = new AccessPolicyRequest() { GranteeId = Guid.NewGuid(), Read = true, Write = true };
request.UserAccessPolicyRequests = new[] { dup, dup };
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount);
sutProvider.GetDependency<ICreateAccessPoliciesCommand>()
.CreateManyAsync(default)
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data });
await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request));
await sutProvider.GetDependency<ICreateAccessPoliciesCommand>().DidNotReceiveWithAnyArgs()
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
}
[Theory]
[BitAutoData]
public async void CreateServiceAccountAccessPolicies_NoAccess_Throws(
SutProvider<AccessPoliciesController> sutProvider,
Guid id,
ServiceAccount serviceAccount,
UserServiceAccountAccessPolicy data,
AccessPoliciesCreateRequest request)
{
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount);
var policies = request.ToBaseAccessPoliciesForServiceAccount(id, serviceAccount.OrganizationId);
foreach (var policy in policies)
{
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), policy,
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Failed());
}
sutProvider.GetDependency<ICreateAccessPoliciesCommand>()
.CreateManyAsync(default)
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data });
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request));
await sutProvider.GetDependency<ICreateAccessPoliciesCommand>().DidNotReceiveWithAnyArgs()
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
}
[Theory]
[BitAutoData]
public async void CreateServiceAccountAccessPolicies_Success(
SutProvider<AccessPoliciesController> sutProvider,
Guid id,
ServiceAccount serviceAccount,
UserServiceAccountAccessPolicy data,
AccessPoliciesCreateRequest request)
{
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount);
var policies = request.ToBaseAccessPoliciesForServiceAccount(id, serviceAccount.OrganizationId);
foreach (var policy in policies)
{
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), policy,
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
}
sutProvider.GetDependency<ICreateAccessPoliciesCommand>()
.CreateManyAsync(default)
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { data });
await sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request);
await sutProvider.GetDependency<ICreateAccessPoliciesCommand>().Received(1)
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
}
[Theory]
[BitAutoData]
public async void CreateServiceAccountGrantedPolicies_RequestMoreThanMax_Throws(
@ -652,7 +449,7 @@ public class AccessPoliciesControllerTests
ServiceAccountProjectAccessPolicy data,
List<GrantedAccessPolicyRequest> request)
{
var dup = new GrantedAccessPolicyRequest() { GrantedId = Guid.NewGuid(), Read = true, Write = true };
var dup = new GrantedAccessPolicyRequest { GrantedId = Guid.NewGuid(), Read = true, Write = true };
request.Add(dup);
request.Add(dup);
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount);
@ -1173,4 +970,199 @@ public class AccessPoliciesControllerTests
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
.ReplaceProjectPeopleAsync(Arg.Any<ProjectPeopleAccessPolicies>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData]
public async void GetServiceAccountPeopleAccessPoliciesAsync_ServiceAccountDoesntExist_ThrowsNotFound(
SutProvider<AccessPoliciesController> sutProvider,
ServiceAccount data)
{
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).ReturnsNull();
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.GetServiceAccountPeopleAccessPoliciesAsync(data.Id));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
.GetPeoplePoliciesByGrantedServiceAccountIdAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async void GetServiceAccountPeopleAccessPoliciesAsync_ReturnsEmptyList(
PermissionType permissionType,
SutProvider<AccessPoliciesController> sutProvider,
ServiceAccount data)
{
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).ReturnsForAnyArgs(data);
switch (permissionType)
{
case PermissionType.RunAsAdmin:
SetupAdmin(sutProvider, data.OrganizationId);
break;
case PermissionType.RunAsUserWithPermission:
SetupUserWithPermission(sutProvider, data.OrganizationId);
sutProvider.GetDependency<IServiceAccountRepository>()
.UserHasWriteAccessToServiceAccount(default, default)
.ReturnsForAnyArgs(true);
break;
}
var result = await sutProvider.Sut.GetServiceAccountPeopleAccessPoliciesAsync(data.Id);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
.GetPeoplePoliciesByGrantedServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data.Id)), Arg.Any<Guid>());
Assert.Empty(result.UserAccessPolicies);
Assert.Empty(result.GroupAccessPolicies);
}
[Theory]
[BitAutoData]
public async void GetServiceAccountPeopleAccessPoliciesAsync_UserWithoutPermission_Throws(
SutProvider<AccessPoliciesController> sutProvider,
ServiceAccount data)
{
SetupUserWithoutPermission(sutProvider, data.OrganizationId);
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(data);
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(default, default)
.ReturnsForAnyArgs(false);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.GetServiceAccountPeopleAccessPoliciesAsync(data.Id));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
.GetPeoplePoliciesByGrantedServiceAccountIdAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async void GetServiceAccountPeopleAccessPoliciesAsync_Success(
PermissionType permissionType,
SutProvider<AccessPoliciesController> sutProvider,
ServiceAccount data,
UserServiceAccountAccessPolicy resultAccessPolicy)
{
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(default).ReturnsForAnyArgs(data);
switch (permissionType)
{
case PermissionType.RunAsAdmin:
SetupAdmin(sutProvider, data.OrganizationId);
break;
case PermissionType.RunAsUserWithPermission:
SetupUserWithPermission(sutProvider, data.OrganizationId);
sutProvider.GetDependency<IServiceAccountRepository>()
.UserHasWriteAccessToServiceAccount(default, default)
.ReturnsForAnyArgs(true);
break;
}
sutProvider.GetDependency<IAccessPolicyRepository>().GetPeoplePoliciesByGrantedServiceAccountIdAsync(default, default)
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { resultAccessPolicy });
var result = await sutProvider.Sut.GetServiceAccountPeopleAccessPoliciesAsync(data.Id);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
.GetPeoplePoliciesByGrantedServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data.Id)), Arg.Any<Guid>());
Assert.Empty(result.GroupAccessPolicies);
Assert.NotEmpty(result.UserAccessPolicies);
}
[Theory]
[BitAutoData]
public async void PutServiceAccountPeopleAccessPolicies_ServiceAccountDoesNotExist_Throws(
SutProvider<AccessPoliciesController> sutProvider,
ServiceAccount data,
PeopleAccessPoliciesRequestModel request)
{
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.PutServiceAccountPeopleAccessPoliciesAsync(data.Id, request));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
.ReplaceServiceAccountPeopleAsync(Arg.Any<ServiceAccountPeopleAccessPolicies>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData]
public async void PutServiceAccountPeopleAccessPolicies_DuplicatePolicy_Throws(
SutProvider<AccessPoliciesController> sutProvider,
ServiceAccount data,
PeopleAccessPoliciesRequestModel request)
{
var dup = new AccessPolicyRequest { GranteeId = Guid.NewGuid(), Read = true, Write = true };
request.UserAccessPolicyRequests = new[] { dup, dup };
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).ReturnsForAnyArgs(data);
await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.PutServiceAccountPeopleAccessPoliciesAsync(data.Id, request));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
.ReplaceServiceAccountPeopleAsync(Arg.Any<ServiceAccountPeopleAccessPolicies>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData]
public async void PutServiceAccountPeopleAccessPolicies_NotCanReadWrite_Throws(
SutProvider<AccessPoliciesController> sutProvider,
ServiceAccount data,
PeopleAccessPoliciesRequestModel request)
{
request.UserAccessPolicyRequests.First().Read = false;
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).ReturnsForAnyArgs(data);
await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.PutServiceAccountPeopleAccessPoliciesAsync(data.Id, request));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
.ReplaceServiceAccountPeopleAsync(Arg.Any<ServiceAccountPeopleAccessPolicies>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData]
public async void PutServiceAccountPeopleAccessPolicies_NoAccess_Throws(
SutProvider<AccessPoliciesController> sutProvider,
ServiceAccount data,
PeopleAccessPoliciesRequestModel request)
{
request = SetRequestToCanReadWrite(request);
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).ReturnsForAnyArgs(data);
var peoplePolicies = request.ToServiceAccountPeopleAccessPolicies(data.Id, data.OrganizationId);
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), peoplePolicies,
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Failed());
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.PutServiceAccountPeopleAccessPoliciesAsync(data.Id, request));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
.ReplaceServiceAccountPeopleAsync(Arg.Any<ServiceAccountPeopleAccessPolicies>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData]
public async void PutServiceAccountPeopleAccessPolicies_Success(
SutProvider<AccessPoliciesController> sutProvider,
ServiceAccount data,
Guid userId,
PeopleAccessPoliciesRequestModel request)
{
request = SetRequestToCanReadWrite(request);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).ReturnsForAnyArgs(data);
var peoplePolicies = request.ToServiceAccountPeopleAccessPolicies(data.Id, data.OrganizationId);
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), peoplePolicies,
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
sutProvider.GetDependency<IAccessPolicyRepository>().ReplaceServiceAccountPeopleAsync(peoplePolicies, Arg.Any<Guid>())
.Returns(peoplePolicies.ToBaseAccessPolicies());
await sutProvider.Sut.PutServiceAccountPeopleAccessPoliciesAsync(data.Id, request);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
.ReplaceServiceAccountPeopleAsync(Arg.Any<ServiceAccountPeopleAccessPolicies>(), Arg.Any<Guid>());
}
}