mirror of
https://github.com/bitwarden/server.git
synced 2024-11-24 12:35:25 +01:00
[SM-919] Add project people access policy management endpoints (#3285)
* Expose access policy discriminators * Add people policy model and auth handler * Add unit tests for authz handler * Add people policies support in repo * Add new endpoints and request/response models * Update tests
This commit is contained in:
parent
35500b197d
commit
0ca65e3f9d
@ -0,0 +1,96 @@
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
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.Interfaces;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies;
|
||||
|
||||
public class
|
||||
ProjectPeopleAccessPoliciesAuthorizationHandler : AuthorizationHandler<ProjectPeopleAccessPoliciesOperationRequirement,
|
||||
ProjectPeopleAccessPolicies>
|
||||
{
|
||||
private readonly IAccessClientQuery _accessClientQuery;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
|
||||
public ProjectPeopleAccessPoliciesAuthorizationHandler(ICurrentContext currentContext,
|
||||
IAccessClientQuery accessClientQuery,
|
||||
IGroupRepository groupRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IProjectRepository projectRepository)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_accessClientQuery = accessClientQuery;
|
||||
_groupRepository = groupRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_projectRepository = projectRepository;
|
||||
}
|
||||
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
|
||||
ProjectPeopleAccessPoliciesOperationRequirement requirement,
|
||||
ProjectPeopleAccessPolicies 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 == ProjectPeopleAccessPoliciesOperations.Replace:
|
||||
await CanReplaceProjectPeopleAsync(context, requirement, resource, accessClient, userId);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unsupported operation requirement type provided.",
|
||||
nameof(requirement));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CanReplaceProjectPeopleAsync(AuthorizationHandlerContext context,
|
||||
ProjectPeopleAccessPoliciesOperationRequirement requirement, ProjectPeopleAccessPolicies resource,
|
||||
AccessClientType accessClient, Guid userId)
|
||||
{
|
||||
var access = await _projectRepository.AccessToProjectAsync(resource.Id, userId, accessClient);
|
||||
if (access.Write)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ public static class SecretsManagerCollectionExtensions
|
||||
services.AddScoped<IAuthorizationHandler, SecretAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, ServiceAccountAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, AccessPolicyAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, ProjectPeopleAccessPoliciesAuthorizationHandler>();
|
||||
services.AddScoped<IAccessClientQuery, AccessClientQuery>();
|
||||
services.AddScoped<IMaxProjectsQuery, MaxProjectsQuery>();
|
||||
services.AddScoped<IServiceAccountSecretsDetailsQuery, ServiceAccountSecretsDetailsQuery>();
|
||||
|
@ -1,8 +1,10 @@
|
||||
using System.Linq.Expressions;
|
||||
using AutoMapper;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.SecretsManager.Discriminators;
|
||||
using Bit.Infrastructure.EntityFramework.SecretsManager.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@ -238,6 +240,153 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
|
||||
return entities.Select(MapToCore);
|
||||
}
|
||||
|
||||
public async Task<PeopleGrantees> GetPeopleGranteesAsync(Guid organizationId, Guid currentUserId)
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
|
||||
var userGrantees = await dbContext.OrganizationUsers
|
||||
.Where(ou =>
|
||||
ou.OrganizationId == organizationId &&
|
||||
ou.AccessSecretsManager &&
|
||||
ou.Status == OrganizationUserStatusType.Confirmed)
|
||||
.Include(ou => ou.User)
|
||||
.Select(ou => new
|
||||
UserGrantee
|
||||
{
|
||||
OrganizationUserId = ou.Id,
|
||||
Name = ou.User.Name,
|
||||
Email = ou.User.Email,
|
||||
CurrentUser = ou.UserId == currentUserId
|
||||
}).ToListAsync();
|
||||
|
||||
var groupGrantees = await dbContext.Groups
|
||||
.Where(g => g.OrganizationId == organizationId)
|
||||
.Include(g => g.GroupUsers)
|
||||
.Select(g => new GroupGrantee
|
||||
{
|
||||
GroupId = g.Id,
|
||||
Name = g.Name,
|
||||
CurrentUserInGroup = g.GroupUsers.Any(gu =>
|
||||
gu.OrganizationUser.User.Id == currentUserId)
|
||||
}).ToListAsync();
|
||||
|
||||
return new PeopleGrantees { UserGrantees = userGrantees, GroupGrantees = groupGrantees };
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Core.SecretsManager.Entities.BaseAccessPolicy>>
|
||||
GetPeoplePoliciesByGrantedProjectIdAsync(Guid id, Guid userId)
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
|
||||
var entities = await dbContext.AccessPolicies.Where(ap =>
|
||||
ap.Discriminator != AccessPolicyDiscriminator.ServiceAccountProject &&
|
||||
(((UserProjectAccessPolicy)ap).GrantedProjectId == id ||
|
||||
((GroupProjectAccessPolicy)ap).GrantedProjectId == id))
|
||||
.Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User)
|
||||
.Include(ap => ((GroupProjectAccessPolicy)ap).Group)
|
||||
.Select(ap => new
|
||||
{
|
||||
ap,
|
||||
CurrentUserInGroup = ap is GroupProjectAccessPolicy &&
|
||||
((GroupProjectAccessPolicy)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>> ReplaceProjectPeopleAsync(
|
||||
ProjectPeopleAccessPolicies peopleAccessPolicies, Guid userId)
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var peoplePolicyEntities = await dbContext.AccessPolicies.Where(ap =>
|
||||
ap.Discriminator != AccessPolicyDiscriminator.ServiceAccountProject &&
|
||||
(((UserProjectAccessPolicy)ap).GrantedProjectId == peopleAccessPolicies.Id ||
|
||||
((GroupProjectAccessPolicy)ap).GrantedProjectId == peopleAccessPolicies.Id)).ToListAsync();
|
||||
|
||||
var userPolicyEntities =
|
||||
peoplePolicyEntities.Where(ap => ap.GetType() == typeof(UserProjectAccessPolicy)).ToList();
|
||||
var groupPolicyEntities =
|
||||
peoplePolicyEntities.Where(ap => ap.GetType() == typeof(GroupProjectAccessPolicy)).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.UserProjectAccessPolicy)ap).OrganizationUserId !=
|
||||
((UserProjectAccessPolicy)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.GroupProjectAccessPolicy)ap).GroupId !=
|
||||
((GroupProjectAccessPolicy)entity).GroupId)))
|
||||
{
|
||||
dbContext.Remove(groupPolicyEntity);
|
||||
}
|
||||
}
|
||||
|
||||
await UpsertPeoplePoliciesAsync(dbContext,
|
||||
peopleAccessPolicies.ToBaseAccessPolicies().Select(MapToEntity).ToList(), userPolicyEntities,
|
||||
groupPolicyEntities);
|
||||
|
||||
await dbContext.SaveChangesAsync();
|
||||
return await GetPeoplePoliciesByGrantedProjectIdAsync(peopleAccessPolicies.Id, userId);
|
||||
}
|
||||
|
||||
private static async Task UpsertPeoplePoliciesAsync(DatabaseContext dbContext,
|
||||
List<BaseAccessPolicy> policies, IReadOnlyCollection<AccessPolicy> userPolicyEntities,
|
||||
IReadOnlyCollection<AccessPolicy> groupPolicyEntities)
|
||||
{
|
||||
var currentDate = DateTime.UtcNow;
|
||||
foreach (var updatedEntity in policies)
|
||||
{
|
||||
var currentEntity = updatedEntity switch
|
||||
{
|
||||
UserProjectAccessPolicy ap => userPolicyEntities.FirstOrDefault(e =>
|
||||
((UserProjectAccessPolicy)e).OrganizationUserId == ap.OrganizationUserId),
|
||||
GroupProjectAccessPolicy ap => groupPolicyEntities.FirstOrDefault(e =>
|
||||
((GroupProjectAccessPolicy)e).GroupId == ap.GroupId),
|
||||
UserServiceAccountAccessPolicy ap => userPolicyEntities.FirstOrDefault(e =>
|
||||
((UserServiceAccountAccessPolicy)e).OrganizationUserId == ap.OrganizationUserId),
|
||||
GroupServiceAccountAccessPolicy ap => groupPolicyEntities.FirstOrDefault(e =>
|
||||
((GroupServiceAccountAccessPolicy)e).GroupId == ap.GroupId),
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (currentEntity != null)
|
||||
{
|
||||
dbContext.AccessPolicies.Attach(currentEntity);
|
||||
currentEntity.Read = updatedEntity.Read;
|
||||
currentEntity.Write = updatedEntity.Write;
|
||||
currentEntity.RevisionDate = currentDate;
|
||||
}
|
||||
else
|
||||
{
|
||||
updatedEntity.SetNewId();
|
||||
await dbContext.AddAsync(updatedEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore(
|
||||
BaseAccessPolicy baseAccessPolicyEntity) =>
|
||||
baseAccessPolicyEntity switch
|
||||
@ -250,9 +399,27 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
|
||||
Mapper.Map<Core.SecretsManager.Entities.UserServiceAccountAccessPolicy>(ap),
|
||||
GroupServiceAccountAccessPolicy ap => Mapper
|
||||
.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap),
|
||||
_ => throw new ArgumentException("Unsupported access policy type"),
|
||||
_ => throw new ArgumentException("Unsupported access policy type")
|
||||
};
|
||||
|
||||
private BaseAccessPolicy MapToEntity(Core.SecretsManager.Entities.BaseAccessPolicy baseAccessPolicy)
|
||||
{
|
||||
return baseAccessPolicy switch
|
||||
{
|
||||
Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy => Mapper.Map<UserProjectAccessPolicy>(
|
||||
accessPolicy),
|
||||
Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy => Mapper
|
||||
.Map<UserServiceAccountAccessPolicy>(accessPolicy),
|
||||
Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy => Mapper.Map<GroupProjectAccessPolicy>(
|
||||
accessPolicy),
|
||||
Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy => Mapper
|
||||
.Map<GroupServiceAccountAccessPolicy>(accessPolicy),
|
||||
Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy => Mapper
|
||||
.Map<ServiceAccountProjectAccessPolicy>(accessPolicy),
|
||||
_ => throw new ArgumentException("Unsupported access policy type")
|
||||
};
|
||||
}
|
||||
|
||||
private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore(
|
||||
BaseAccessPolicy baseAccessPolicyEntity, bool currentUserInGroup)
|
||||
{
|
||||
|
@ -0,0 +1,246 @@
|
||||
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.Interfaces;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.AuthorizationHandlers.AccessPolicies;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[ProjectCustomize]
|
||||
public class ProjectPeopleAccessPoliciesAuthorizationHandlerTests
|
||||
{
|
||||
private static void SetupUserPermission(SutProvider<ProjectPeopleAccessPoliciesAuthorizationHandler> sutProvider,
|
||||
AccessClientType accessClientType, ProjectPeopleAccessPolicies 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<IProjectRepository>().AccessToProjectAsync(resource.Id, userId, accessClientType)
|
||||
.Returns((read, write));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PeopleAccessPoliciesOperations_OnlyPublicStatic()
|
||||
{
|
||||
var publicStaticFields =
|
||||
typeof(ProjectPeopleAccessPoliciesOperations).GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||
var allFields = typeof(ProjectPeopleAccessPoliciesOperations).GetFields();
|
||||
Assert.Equal(publicStaticFields.Length, allFields.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Handler_UnsupportedProjectPeopleAccessPoliciesOperationRequirement_Throws(
|
||||
SutProvider<ProjectPeopleAccessPoliciesAuthorizationHandler> sutProvider, ProjectPeopleAccessPolicies resource,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = new ProjectPeopleAccessPoliciesOperationRequirement();
|
||||
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<ProjectPeopleAccessPoliciesAuthorizationHandler> sutProvider, ProjectPeopleAccessPolicies resource,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = new ProjectPeopleAccessPoliciesOperationRequirement();
|
||||
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<ProjectPeopleAccessPoliciesAuthorizationHandler> sutProvider, ProjectPeopleAccessPolicies resource,
|
||||
ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var requirement = new ProjectPeopleAccessPoliciesOperationRequirement();
|
||||
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 ReplaceProjectPeople_UserNotInOrg_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 = Guid.NewGuid(), Id = userPolicy.OrganizationUserId!.Value })
|
||||
.ToList();
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(default)
|
||||
.ReturnsForAnyArgs(orgUsers);
|
||||
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_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);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(authzContext);
|
||||
|
||||
Assert.False(authzContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(AccessClientType.User)]
|
||||
[BitAutoData(AccessClientType.NoAccessCheck)]
|
||||
public async Task ReplaceProjectPeople_GroupNotInOrg_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 = 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);
|
||||
|
||||
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 ReplaceProjectPeople_AccessCheck(AccessClientType accessClient, bool read, bool write,
|
||||
bool expected,
|
||||
SutProvider<ProjectPeopleAccessPoliciesAuthorizationHandler> sutProvider, ProjectPeopleAccessPolicies resource,
|
||||
ClaimsPrincipal claimsPrincipal, Guid userId)
|
||||
{
|
||||
var requirement = ProjectPeopleAccessPoliciesOperations.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);
|
||||
}
|
||||
}
|
@ -1,11 +1,9 @@
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.SecretsManager.Models.Request;
|
||||
using Bit.Api.SecretsManager.Models.Response;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
@ -25,8 +23,6 @@ public class AccessPoliciesController : Controller
|
||||
private readonly ICreateAccessPoliciesCommand _createAccessPoliciesCommand;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IDeleteAccessPolicyCommand _deleteAccessPolicyCommand;
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
private readonly IUpdateAccessPolicyCommand _updateAccessPolicyCommand;
|
||||
@ -39,9 +35,7 @@ public class AccessPoliciesController : Controller
|
||||
ICurrentContext currentContext,
|
||||
IAccessPolicyRepository accessPolicyRepository,
|
||||
IServiceAccountRepository serviceAccountRepository,
|
||||
IGroupRepository groupRepository,
|
||||
IProjectRepository projectRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
ICreateAccessPoliciesCommand createAccessPoliciesCommand,
|
||||
IDeleteAccessPolicyCommand deleteAccessPolicyCommand,
|
||||
IUpdateAccessPolicyCommand updateAccessPolicyCommand)
|
||||
@ -51,8 +45,6 @@ public class AccessPoliciesController : Controller
|
||||
_currentContext = currentContext;
|
||||
_serviceAccountRepository = serviceAccountRepository;
|
||||
_projectRepository = projectRepository;
|
||||
_groupRepository = groupRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_accessPolicyRepository = accessPolicyRepository;
|
||||
_createAccessPoliciesCommand = createAccessPoliciesCommand;
|
||||
_deleteAccessPolicyCommand = deleteAccessPolicyCommand;
|
||||
@ -243,15 +235,11 @@ public class AccessPoliciesController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var groups = await _groupRepository.GetManyByOrganizationIdAsync(id);
|
||||
var groupResponses = groups.Select(g => new PotentialGranteeResponseModel(g));
|
||||
|
||||
var organizationUsers =
|
||||
await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id);
|
||||
var userResponses = organizationUsers
|
||||
.Where(user => user.AccessSecretsManager && user.Status == OrganizationUserStatusType.Confirmed)
|
||||
.Select(userDetails => new PotentialGranteeResponseModel(userDetails));
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var peopleGrantees = await _accessPolicyRepository.GetPeopleGranteesAsync(id, userId);
|
||||
|
||||
var userResponses = peopleGrantees.UserGrantees.Select(ug => new PotentialGranteeResponseModel(ug));
|
||||
var groupResponses = peopleGrantees.GroupGrantees.Select(g => new PotentialGranteeResponseModel(g));
|
||||
return new ListResponseModel<PotentialGranteeResponseModel>(userResponses.Concat(groupResponses));
|
||||
}
|
||||
|
||||
@ -287,6 +275,40 @@ public class AccessPoliciesController : Controller
|
||||
return new ListResponseModel<PotentialGranteeResponseModel>(projectResponses);
|
||||
}
|
||||
|
||||
[HttpGet("/projects/{id}/access-policies/people")]
|
||||
public async Task<ProjectPeopleAccessPoliciesResponseModel> GetProjectPeopleAccessPoliciesAsync([FromRoute] Guid id)
|
||||
{
|
||||
var project = await _projectRepository.GetByIdAsync(id);
|
||||
var (_, userId) = await CheckUserHasWriteAccessToProjectAsync(project);
|
||||
var results = await _accessPolicyRepository.GetPeoplePoliciesByGrantedProjectIdAsync(id, userId);
|
||||
return new ProjectPeopleAccessPoliciesResponseModel(results, userId);
|
||||
}
|
||||
|
||||
[HttpPut("/projects/{id}/access-policies/people")]
|
||||
public async Task<ProjectPeopleAccessPoliciesResponseModel> PutProjectPeopleAccessPoliciesAsync([FromRoute] Guid id,
|
||||
[FromBody] PeopleAccessPoliciesRequestModel request)
|
||||
{
|
||||
var project = await _projectRepository.GetByIdAsync(id);
|
||||
if (project == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var peopleAccessPolicies = request.ToProjectPeopleAccessPolicies(id, project.OrganizationId);
|
||||
|
||||
var authorizationResult = await _authorizationService.AuthorizeAsync(User, peopleAccessPolicies,
|
||||
ProjectPeopleAccessPoliciesOperations.Replace);
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var results = await _accessPolicyRepository.ReplaceProjectPeopleAsync(peopleAccessPolicies, userId);
|
||||
return new ProjectPeopleAccessPoliciesResponseModel(results, userId);
|
||||
}
|
||||
|
||||
|
||||
private async Task<(AccessClientType AccessClientType, Guid UserId)> CheckUserHasWriteAccessToProjectAsync(Project project)
|
||||
{
|
||||
if (project == null)
|
||||
|
@ -0,0 +1,64 @@
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Request;
|
||||
|
||||
public class PeopleAccessPoliciesRequestModel
|
||||
{
|
||||
public IEnumerable<AccessPolicyRequest> UserAccessPolicyRequests { get; set; }
|
||||
|
||||
public IEnumerable<AccessPolicyRequest> GroupAccessPolicyRequests { get; set; }
|
||||
|
||||
private static void CheckForDistinctAccessPolicies(IReadOnlyCollection<BaseAccessPolicy> accessPolicies)
|
||||
{
|
||||
var distinctAccessPolicies = accessPolicies.DistinctBy(baseAccessPolicy =>
|
||||
{
|
||||
return baseAccessPolicy switch
|
||||
{
|
||||
UserProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId, ap.GrantedProjectId),
|
||||
GroupProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedProjectId),
|
||||
ServiceAccountProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.ServiceAccountId,
|
||||
ap.GrantedProjectId),
|
||||
UserServiceAccountAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId,
|
||||
ap.GrantedServiceAccountId),
|
||||
GroupServiceAccountAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedServiceAccountId),
|
||||
_ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy))
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
if (accessPolicies.Count != distinctAccessPolicies.Count)
|
||||
{
|
||||
throw new BadRequestException("Resources must be unique");
|
||||
}
|
||||
}
|
||||
|
||||
public ProjectPeopleAccessPolicies ToProjectPeopleAccessPolicies(Guid grantedProjectId, Guid organizationId)
|
||||
{
|
||||
var userAccessPolicies = UserAccessPolicyRequests?
|
||||
.Select(x => x.ToUserProjectAccessPolicy(grantedProjectId, organizationId)).ToList();
|
||||
|
||||
var groupAccessPolicies = GroupAccessPolicyRequests?
|
||||
.Select(x => x.ToGroupProjectAccessPolicy(grantedProjectId, organizationId)).ToList();
|
||||
var policies = new List<BaseAccessPolicy>();
|
||||
if (userAccessPolicies != null)
|
||||
{
|
||||
policies.AddRange(userAccessPolicies);
|
||||
}
|
||||
|
||||
if (groupAccessPolicies != null)
|
||||
{
|
||||
policies.AddRange(groupAccessPolicies);
|
||||
}
|
||||
|
||||
CheckForDistinctAccessPolicies(policies);
|
||||
|
||||
return new ProjectPeopleAccessPolicies
|
||||
{
|
||||
Id = grantedProjectId,
|
||||
OrganizationId = organizationId,
|
||||
UserAccessPolicies = userAccessPolicies,
|
||||
GroupAccessPolicies = groupAccessPolicies
|
||||
};
|
||||
}
|
||||
}
|
@ -34,10 +34,13 @@ public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseMode
|
||||
|
||||
public UserProjectAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy) : base(accessPolicy, _objectName)
|
||||
{
|
||||
OrganizationUserId = accessPolicy.OrganizationUserId;
|
||||
GrantedProjectId = accessPolicy.GrantedProjectId;
|
||||
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
|
||||
UserId = accessPolicy.User?.Id;
|
||||
SetProperties(accessPolicy);
|
||||
}
|
||||
|
||||
public UserProjectAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy, Guid currentUserId) : base(accessPolicy, _objectName)
|
||||
{
|
||||
CurrentUser = currentUserId == accessPolicy.User?.Id;
|
||||
SetProperties(accessPolicy);
|
||||
}
|
||||
|
||||
public UserProjectAccessPolicyResponseModel() : base(new UserProjectAccessPolicy(), _objectName)
|
||||
@ -48,6 +51,15 @@ public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseMode
|
||||
public string? OrganizationUserName { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
public Guid? GrantedProjectId { get; set; }
|
||||
public bool? CurrentUser { get; set; }
|
||||
|
||||
private void SetProperties(UserProjectAccessPolicy accessPolicy)
|
||||
{
|
||||
OrganizationUserId = accessPolicy.OrganizationUserId;
|
||||
GrantedProjectId = accessPolicy.GrantedProjectId;
|
||||
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
|
||||
UserId = accessPolicy.User?.Id;
|
||||
}
|
||||
}
|
||||
|
||||
public class UserServiceAccountAccessPolicyResponseModel : BaseAccessPolicyResponseModel
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
@ -9,31 +8,33 @@ public class PotentialGranteeResponseModel : ResponseModel
|
||||
{
|
||||
private const string _objectName = "potentialGrantee";
|
||||
|
||||
public PotentialGranteeResponseModel(Group group)
|
||||
public PotentialGranteeResponseModel(GroupGrantee grantee)
|
||||
: base(_objectName)
|
||||
{
|
||||
if (group == null)
|
||||
if (grantee == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(group));
|
||||
throw new ArgumentNullException(nameof(grantee));
|
||||
}
|
||||
|
||||
Id = group.Id;
|
||||
Name = group.Name;
|
||||
Type = "group";
|
||||
Id = grantee.GroupId;
|
||||
Name = grantee.Name;
|
||||
CurrentUserInGroup = grantee.CurrentUserInGroup;
|
||||
}
|
||||
|
||||
public PotentialGranteeResponseModel(OrganizationUserUserDetails user)
|
||||
public PotentialGranteeResponseModel(UserGrantee grantee)
|
||||
: base(_objectName)
|
||||
{
|
||||
if (user == null)
|
||||
if (grantee == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
throw new ArgumentNullException(nameof(grantee));
|
||||
}
|
||||
|
||||
Id = user.Id;
|
||||
Name = user.Name;
|
||||
Email = user.Email;
|
||||
Type = "user";
|
||||
Id = grantee.OrganizationUserId;
|
||||
Name = grantee.Name;
|
||||
Email = grantee.Email;
|
||||
CurrentUser = grantee.CurrentUser;
|
||||
}
|
||||
|
||||
public PotentialGranteeResponseModel(ServiceAccount serviceAccount)
|
||||
@ -67,9 +68,9 @@ public class PotentialGranteeResponseModel : ResponseModel
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
public string Email { get; set; }
|
||||
public bool CurrentUserInGroup { get; set; }
|
||||
public bool CurrentUser { get; set; }
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class ProjectPeopleAccessPoliciesResponseModel : ResponseModel
|
||||
{
|
||||
private const string _objectName = "projectPeopleAccessPolicies";
|
||||
|
||||
public ProjectPeopleAccessPoliciesResponseModel(IEnumerable<BaseAccessPolicy> baseAccessPolicies, Guid userId)
|
||||
: base(_objectName)
|
||||
{
|
||||
foreach (var baseAccessPolicy in baseAccessPolicies)
|
||||
{
|
||||
switch (baseAccessPolicy)
|
||||
{
|
||||
case UserProjectAccessPolicy accessPolicy:
|
||||
UserAccessPolicies.Add(new UserProjectAccessPolicyResponseModel(accessPolicy, userId));
|
||||
break;
|
||||
case GroupProjectAccessPolicy accessPolicy:
|
||||
GroupAccessPolicies.Add(new GroupProjectAccessPolicyResponseModel(accessPolicy));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ProjectPeopleAccessPoliciesResponseModel() : base(_objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public List<UserProjectAccessPolicyResponseModel> UserAccessPolicies { get; set; } = new();
|
||||
|
||||
public List<GroupProjectAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = new();
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||
|
||||
namespace Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||
|
||||
public class ProjectPeopleAccessPoliciesOperationRequirement : OperationAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
|
||||
public static class ProjectPeopleAccessPoliciesOperations
|
||||
{
|
||||
public static readonly ProjectPeopleAccessPoliciesOperationRequirement Replace = new() { Name = nameof(Replace) };
|
||||
}
|
22
src/Core/SecretsManager/Models/Data/PeopleGrantees.cs
Normal file
22
src/Core/SecretsManager/Models/Data/PeopleGrantees.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
public class PeopleGrantees
|
||||
{
|
||||
public IEnumerable<UserGrantee> UserGrantees { get; set; }
|
||||
public IEnumerable<GroupGrantee> GroupGrantees { get; set; }
|
||||
}
|
||||
|
||||
public class UserGrantee
|
||||
{
|
||||
public Guid OrganizationUserId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
public bool CurrentUser { get; set; }
|
||||
}
|
||||
|
||||
public class GroupGrantee
|
||||
{
|
||||
public Guid GroupId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool CurrentUserInGroup { get; set; }
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
public class ProjectPeopleAccessPolicies
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public IEnumerable<UserProjectAccessPolicy> UserAccessPolicies { get; set; }
|
||||
public IEnumerable<GroupProjectAccessPolicy> 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;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Repositories;
|
||||
|
||||
@ -15,4 +16,7 @@ public interface IAccessPolicyRepository
|
||||
AccessClientType accessType);
|
||||
Task ReplaceAsync(BaseAccessPolicy baseAccessPolicy);
|
||||
Task DeleteAsync(Guid id);
|
||||
Task<IEnumerable<BaseAccessPolicy>> GetPeoplePoliciesByGrantedProjectIdAsync(Guid id, Guid userId);
|
||||
Task<IEnumerable<BaseAccessPolicy>> ReplaceProjectPeopleAsync(ProjectPeopleAccessPolicies peopleAccessPolicies, Guid userId);
|
||||
Task<PeopleGrantees> GetPeopleGranteesAsync(Guid organizationId, Guid currentUserId);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Infrastructure.EntityFramework.SecretsManager.Models;
|
||||
using Bit.Infrastructure.EntityFramework.SecretsManager.Discriminators;
|
||||
using Bit.Infrastructure.EntityFramework.SecretsManager.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
@ -10,11 +11,11 @@ public class AccessPolicyEntityTypeConfiguration : IEntityTypeConfiguration<Acce
|
||||
{
|
||||
builder
|
||||
.HasDiscriminator<string>("Discriminator")
|
||||
.HasValue<UserProjectAccessPolicy>("user_project")
|
||||
.HasValue<UserServiceAccountAccessPolicy>("user_service_account")
|
||||
.HasValue<GroupProjectAccessPolicy>("group_project")
|
||||
.HasValue<GroupServiceAccountAccessPolicy>("group_service_account")
|
||||
.HasValue<ServiceAccountProjectAccessPolicy>("service_account_project");
|
||||
.HasValue<UserProjectAccessPolicy>(AccessPolicyDiscriminator.UserProject)
|
||||
.HasValue<UserServiceAccountAccessPolicy>(AccessPolicyDiscriminator.UserServiceAccount)
|
||||
.HasValue<GroupProjectAccessPolicy>(AccessPolicyDiscriminator.GroupProject)
|
||||
.HasValue<GroupServiceAccountAccessPolicy>(AccessPolicyDiscriminator.GroupServiceAccount)
|
||||
.HasValue<ServiceAccountProjectAccessPolicy>(AccessPolicyDiscriminator.ServiceAccountProject);
|
||||
|
||||
builder
|
||||
.Property(s => s.Id)
|
||||
|
@ -0,0 +1,11 @@
|
||||
namespace Bit.Infrastructure.EntityFramework.SecretsManager.Discriminators;
|
||||
|
||||
public static class AccessPolicyDiscriminator
|
||||
{
|
||||
public const string UserProject = "user_project";
|
||||
public const string UserServiceAccount = "user_service_account";
|
||||
public const string GroupProject = "group_project";
|
||||
public const string GroupServiceAccount = "group_service_account";
|
||||
public const string ServiceAccountProject = "service_account_project";
|
||||
|
||||
}
|
@ -5,6 +5,8 @@ using Bit.Api.IntegrationTest.SecretsManager.Enums;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.SecretsManager.Models.Request;
|
||||
using Bit.Api.SecretsManager.Models.Response;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
@ -25,6 +27,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
private readonly ApiApplicationFactory _factory;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private string _email = null!;
|
||||
private SecretsManagerOrganizationHelper _organizationHelper = null!;
|
||||
|
||||
@ -35,6 +38,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
_accessPolicyRepository = _factory.GetService<IAccessPolicyRepository>();
|
||||
_serviceAccountRepository = _factory.GetService<IServiceAccountRepository>();
|
||||
_projectRepository = _factory.GetService<IProjectRepository>();
|
||||
_groupRepository = _factory.GetService<IGroupRepository>();
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
@ -660,7 +664,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task CreateServiceAccountAccessPolicies_MismatchOrgId_NotFound(PermissionType permissionType)
|
||||
{
|
||||
var (org, orgUser) = await _organizationHelper.Initialize(true, true, true);
|
||||
var (_, orgUser) = await _organizationHelper.Initialize(true, true, true);
|
||||
await LoginAsync(_email);
|
||||
var anotherOrg = await _organizationHelper.CreateSmOrganizationAsync();
|
||||
|
||||
@ -904,9 +908,8 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task CreateServiceAccountGrantedPolicies_MismatchedOrgId_NotFound(PermissionType permissionType)
|
||||
{
|
||||
var (org, orgUser) = await _organizationHelper.Initialize(true, true, true);
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await LoginAsync(_email);
|
||||
var ownerOrgUserId = orgUser.Id;
|
||||
|
||||
var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id, true);
|
||||
await SetupProjectAndServiceAccountPermissionAsync(permissionType, projectId, serviceAccountId);
|
||||
@ -925,9 +928,8 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task CreateServiceAccountGrantedPolicies_Success(PermissionType permissionType)
|
||||
{
|
||||
var (org, orgUser) = await _organizationHelper.Initialize(true, true, true);
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await LoginAsync(_email);
|
||||
var ownerOrgUserId = orgUser.Id;
|
||||
|
||||
var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id);
|
||||
await SetupProjectAndServiceAccountPermissionAsync(permissionType, projectId, serviceAccountId);
|
||||
@ -1051,6 +1053,183 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
Assert.NotNull(result.Data.First().GrantedProjectName);
|
||||
}
|
||||
|
||||
[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 GetProjectPeopleAccessPolicies_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($"/projects/{initData.ProjectId}/access-policies/people");
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetProjectPeopleAccessPolicies_ReturnsEmpty()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var project = await _projectRepository.CreateAsync(new Project
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString
|
||||
});
|
||||
|
||||
var response = await _client.GetAsync($"/projects/{project.Id}/access-policies/people");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ProjectPeopleAccessPoliciesResponseModel>();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result!.UserAccessPolicies);
|
||||
Assert.Empty(result.GroupAccessPolicies);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetProjectPeopleAccessPolicies_NoPermission_NotFound()
|
||||
{
|
||||
await _organizationHelper.Initialize(true, true, true);
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
|
||||
var project = await _projectRepository.CreateAsync(new Project
|
||||
{
|
||||
OrganizationId = orgUser.OrganizationId,
|
||||
Name = _mockEncryptedString
|
||||
});
|
||||
|
||||
var response = await _client.GetAsync($"/projects/{project.Id}/access-policies/people");
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task GetProjectPeopleAccessPolicies_Success(PermissionType permissionType)
|
||||
{
|
||||
var (_, organizationUser) = await _organizationHelper.Initialize(true, true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var (project, _) = await SetupProjectPeoplePermissionAsync(permissionType, organizationUser);
|
||||
|
||||
var response = await _client.GetAsync($"/projects/{project.Id}/access-policies/people");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ProjectPeopleAccessPoliciesResponseModel>();
|
||||
|
||||
Assert.NotNull(result?.UserAccessPolicies);
|
||||
Assert.Single(result!.UserAccessPolicies);
|
||||
}
|
||||
|
||||
[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 PutProjectPeopleAccessPolicies_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
|
||||
{
|
||||
var (_, organizationUser) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var (project, request) = await SetupProjectPeopleRequestAsync(PermissionType.RunAsAdmin, organizationUser);
|
||||
|
||||
var response = await _client.PutAsJsonAsync($"/projects/{project.Id}/access-policies/people", request);
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PutProjectPeopleAccessPolicies_NoPermission()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
var (email, organizationUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
|
||||
var project = await _projectRepository.CreateAsync(new Project
|
||||
{
|
||||
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($"/projects/{project.Id}/access-policies/people", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task PutProjectPeopleAccessPolicies_MismatchedOrgIds_NotFound(PermissionType permissionType)
|
||||
{
|
||||
var (_, organizationUser) = await _organizationHelper.Initialize(true, true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var (project, request) = await SetupProjectPeopleRequestAsync(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($"/projects/{project.Id}/access-policies/people", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task PutProjectPeopleAccessPolicies_Success(PermissionType permissionType)
|
||||
{
|
||||
var (_, organizationUser) = await _organizationHelper.Initialize(true, true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var (project, request) = await SetupProjectPeopleRequestAsync(permissionType, organizationUser);
|
||||
|
||||
var response = await _client.PutAsJsonAsync($"/projects/{project.Id}/access-policies/people", request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ProjectPeopleAccessPoliciesResponseModel>();
|
||||
|
||||
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
|
||||
@ -1082,6 +1261,52 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<(Project project, OrganizationUser currentUser)> SetupProjectPeoplePermissionAsync(
|
||||
PermissionType permissionType,
|
||||
OrganizationUser organizationUser)
|
||||
{
|
||||
var project = await _projectRepository.CreateAsync(new Project
|
||||
{
|
||||
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 UserProjectAccessPolicy
|
||||
{
|
||||
GrantedProjectId = project.Id,
|
||||
OrganizationUserId = organizationUser.Id,
|
||||
Read = true,
|
||||
Write = true
|
||||
}
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
|
||||
return (project, organizationUser);
|
||||
}
|
||||
|
||||
private async Task<(Project project, PeopleAccessPoliciesRequestModel request)> SetupProjectPeopleRequestAsync(
|
||||
PermissionType permissionType, OrganizationUser organizationUser)
|
||||
{
|
||||
var (project, currentUser) = await SetupProjectPeoplePermissionAsync(permissionType, organizationUser);
|
||||
var request = new PeopleAccessPoliciesRequestModel
|
||||
{
|
||||
UserAccessPolicyRequests = new List<AccessPolicyRequest>
|
||||
{
|
||||
new() { GranteeId = currentUser.Id, Read = true, Write = true }
|
||||
}
|
||||
};
|
||||
return (project, request);
|
||||
}
|
||||
|
||||
private async Task<(Guid ProjectId, Guid ServiceAccountId)> CreateProjectAndServiceAccountAsync(Guid organizationId,
|
||||
bool misMatchOrganization = false)
|
||||
{
|
||||
|
@ -2,14 +2,12 @@
|
||||
using Bit.Api.SecretsManager.Controllers;
|
||||
using Bit.Api.SecretsManager.Models.Request;
|
||||
using Bit.Api.Test.SecretsManager.Enums;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
|
||||
@ -806,14 +804,17 @@ public class AccessPoliciesControllerTests
|
||||
Guid id)
|
||||
{
|
||||
SetupPermission(sutProvider, permissionType, id);
|
||||
sutProvider.GetDependency<IAccessPolicyRepository>().GetPeopleGranteesAsync(default, default)
|
||||
.ReturnsForAnyArgs(new PeopleGrantees
|
||||
{
|
||||
UserGrantees = new List<UserGrantee>(),
|
||||
GroupGrantees = new List<GroupGrantee>()
|
||||
});
|
||||
|
||||
var result = await sutProvider.Sut.GetPeoplePotentialGranteesAsync(id);
|
||||
|
||||
await sutProvider.GetDependency<IGroupRepository>().Received(1)
|
||||
.GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
|
||||
.GetManyDetailsByOrganizationAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)));
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
|
||||
.GetPeopleGranteesAsync(id, Arg.Any<Guid>());
|
||||
Assert.Empty(result.Data);
|
||||
}
|
||||
|
||||
@ -826,17 +827,17 @@ public class AccessPoliciesControllerTests
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(id).Returns(false);
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(false);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid());
|
||||
sutProvider.GetDependency<IAccessPolicyRepository>().GetPeopleGranteesAsync(default, default)
|
||||
.ReturnsForAnyArgs(new PeopleGrantees
|
||||
{
|
||||
UserGrantees = new List<UserGrantee>(),
|
||||
GroupGrantees = new List<GroupGrantee>()
|
||||
});
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetPeoplePotentialGranteesAsync(id));
|
||||
|
||||
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs()
|
||||
.GetManyByOrganizationIdAsync(Arg.Any<Guid>());
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
|
||||
.GetManyDetailsByOrganizationAsync(Arg.Any<Guid>());
|
||||
|
||||
await sutProvider.GetDependency<IServiceAccountRepository>().DidNotReceiveWithAnyArgs()
|
||||
.GetManyByOrganizationIdWriteAccessAsync(Arg.Any<Guid>(), Arg.Any<Guid>(), Arg.Any<AccessClientType>());
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
|
||||
.GetPeopleGranteesAsync(id, Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -846,19 +847,20 @@ public class AccessPoliciesControllerTests
|
||||
PermissionType permissionType,
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid id,
|
||||
Group mockGroup)
|
||||
GroupGrantee groupGrantee)
|
||||
{
|
||||
SetupPermission(sutProvider, permissionType, id);
|
||||
sutProvider.GetDependency<IGroupRepository>().GetManyByOrganizationIdAsync(default)
|
||||
.ReturnsForAnyArgs(new List<Group> { mockGroup });
|
||||
sutProvider.GetDependency<IAccessPolicyRepository>().GetPeopleGranteesAsync(default, default)
|
||||
.ReturnsForAnyArgs(new PeopleGrantees
|
||||
{
|
||||
UserGrantees = new List<UserGrantee>(),
|
||||
GroupGrantees = new List<GroupGrantee> { groupGrantee }
|
||||
});
|
||||
|
||||
var result = await sutProvider.Sut.GetPeoplePotentialGranteesAsync(id);
|
||||
|
||||
await sutProvider.GetDependency<IGroupRepository>().Received(1)
|
||||
.GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
|
||||
.GetManyDetailsByOrganizationAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)));
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
|
||||
.GetPeopleGranteesAsync(id, Arg.Any<Guid>());
|
||||
|
||||
Assert.NotEmpty(result.Data);
|
||||
}
|
||||
@ -980,4 +982,195 @@ public class AccessPoliciesControllerTests
|
||||
|
||||
Assert.NotEmpty(result.Data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async void GetProjectPeopleAccessPolicies_ReturnsEmptyList(
|
||||
PermissionType permissionType,
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid id, Project data)
|
||||
{
|
||||
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(default).ReturnsForAnyArgs(data);
|
||||
|
||||
switch (permissionType)
|
||||
{
|
||||
case PermissionType.RunAsAdmin:
|
||||
SetupAdmin(sutProvider, data.OrganizationId);
|
||||
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)
|
||||
.Returns((true, true));
|
||||
break;
|
||||
}
|
||||
|
||||
var result = await sutProvider.Sut.GetProjectPeopleAccessPoliciesAsync(id);
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
|
||||
.GetPeoplePoliciesByGrantedProjectIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), Arg.Any<Guid>());
|
||||
|
||||
Assert.Empty(result.GroupAccessPolicies);
|
||||
Assert.Empty(result.UserAccessPolicies);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void GetProjectPeopleAccessPolicies_UserWithoutPermission_Throws(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid id,
|
||||
Project data)
|
||||
{
|
||||
SetupUserWithoutPermission(sutProvider, data.OrganizationId);
|
||||
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(default).ReturnsForAnyArgs(data);
|
||||
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync(default, default, default)
|
||||
.Returns((false, false));
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetProjectPeopleAccessPoliciesAsync(id));
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
|
||||
.GetPeoplePoliciesByGrantedProjectIdAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void GetProjectPeopleAccessPolicies_ProjectsExist_UserWithoutPermission_Throws(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid id,
|
||||
Project data,
|
||||
UserProjectAccessPolicy resultAccessPolicy)
|
||||
{
|
||||
SetupUserWithoutPermission(sutProvider, data.OrganizationId);
|
||||
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(default).ReturnsForAnyArgs(data);
|
||||
sutProvider.GetDependency<IProjectRepository>().AccessToProjectAsync(default, default, default)
|
||||
.Returns((false, false));
|
||||
|
||||
sutProvider.GetDependency<IAccessPolicyRepository>().GetPeoplePoliciesByGrantedProjectIdAsync(default, default)
|
||||
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { resultAccessPolicy });
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetProjectPeopleAccessPoliciesAsync(id));
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
|
||||
.GetPeoplePoliciesByGrantedProjectIdAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PermissionType.RunAsAdmin)]
|
||||
[BitAutoData(PermissionType.RunAsUserWithPermission)]
|
||||
public async void GetProjectPeopleAccessPolicies_Success(
|
||||
PermissionType permissionType,
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid id,
|
||||
Project data,
|
||||
UserProjectAccessPolicy resultUserPolicy,
|
||||
GroupProjectAccessPolicy resultGroupPolicy)
|
||||
{
|
||||
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(default).ReturnsForAnyArgs(data);
|
||||
switch (permissionType)
|
||||
{
|
||||
case PermissionType.RunAsAdmin:
|
||||
SetupAdmin(sutProvider, data.OrganizationId);
|
||||
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)
|
||||
.Returns((true, true));
|
||||
break;
|
||||
}
|
||||
|
||||
sutProvider.GetDependency<IAccessPolicyRepository>().GetPeoplePoliciesByGrantedProjectIdAsync(default, default)
|
||||
.ReturnsForAnyArgs(new List<BaseAccessPolicy> { resultUserPolicy, resultGroupPolicy });
|
||||
|
||||
var result = await sutProvider.Sut.GetProjectPeopleAccessPoliciesAsync(id);
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
|
||||
.GetPeoplePoliciesByGrantedProjectIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), Arg.Any<Guid>());
|
||||
|
||||
Assert.NotEmpty(result.GroupAccessPolicies);
|
||||
Assert.NotEmpty(result.UserAccessPolicies);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void PutProjectPeopleAccessPolicies_ProjectDoesNotExist_Throws(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid id,
|
||||
PeopleAccessPoliciesRequestModel request)
|
||||
{
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.PutProjectPeopleAccessPoliciesAsync(id, request));
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
|
||||
.ReplaceProjectPeopleAsync(Arg.Any<ProjectPeopleAccessPolicies>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void PutProjectPeopleAccessPoliciesAsync_DuplicatePolicy_Throws(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Project project,
|
||||
PeopleAccessPoliciesRequestModel request)
|
||||
{
|
||||
var dup = new AccessPolicyRequest { GranteeId = Guid.NewGuid(), Read = true, Write = true };
|
||||
request.UserAccessPolicyRequests = new[] { dup, dup };
|
||||
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(default).ReturnsForAnyArgs(project);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.PutProjectPeopleAccessPoliciesAsync(project.Id, request));
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
|
||||
.ReplaceProjectPeopleAsync(Arg.Any<ProjectPeopleAccessPolicies>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void PutProjectPeopleAccessPoliciesAsync_NoAccess_Throws(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Project project,
|
||||
PeopleAccessPoliciesRequestModel request)
|
||||
{
|
||||
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(default).ReturnsForAnyArgs(project);
|
||||
var peoplePolicies = request.ToProjectPeopleAccessPolicies(project.Id, project.OrganizationId);
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), peoplePolicies,
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Failed());
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.PutProjectPeopleAccessPoliciesAsync(project.Id, request));
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
|
||||
.ReplaceProjectPeopleAsync(Arg.Any<ProjectPeopleAccessPolicies>(), Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void PutProjectPeopleAccessPoliciesAsync_Success(
|
||||
SutProvider<AccessPoliciesController> sutProvider,
|
||||
Guid userId,
|
||||
Project project,
|
||||
PeopleAccessPoliciesRequestModel request)
|
||||
{
|
||||
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(default).ReturnsForAnyArgs(project);
|
||||
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(userId);
|
||||
var peoplePolicies = request.ToProjectPeopleAccessPolicies(project.Id, project.OrganizationId);
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), peoplePolicies,
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
|
||||
|
||||
sutProvider.GetDependency<IAccessPolicyRepository>().ReplaceProjectPeopleAsync(peoplePolicies, Arg.Any<Guid>())
|
||||
.Returns(peoplePolicies.ToBaseAccessPolicies());
|
||||
|
||||
await sutProvider.Sut.PutProjectPeopleAccessPoliciesAsync(project.Id, request);
|
||||
|
||||
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
|
||||
.ReplaceProjectPeopleAsync(Arg.Any<ProjectPeopleAccessPolicies>(), Arg.Any<Guid>());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user