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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user