1
0
mirror of https://github.com/bitwarden/server synced 2025-12-16 00:03:54 +00: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
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);
}
}