1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 05:00:19 -05: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:
Thomas Avery 2023-11-08 11:42:40 -05:00 committed by GitHub
parent 35500b197d
commit 0ca65e3f9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1211 additions and 73 deletions

View File

@ -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);
}
}
}

View File

@ -35,6 +35,7 @@ public static class SecretsManagerCollectionExtensions
services.AddScoped<IAuthorizationHandler, SecretAuthorizationHandler>(); services.AddScoped<IAuthorizationHandler, SecretAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, ServiceAccountAuthorizationHandler>(); services.AddScoped<IAuthorizationHandler, ServiceAccountAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, AccessPolicyAuthorizationHandler>(); services.AddScoped<IAuthorizationHandler, AccessPolicyAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, ProjectPeopleAccessPoliciesAuthorizationHandler>();
services.AddScoped<IAccessClientQuery, AccessClientQuery>(); services.AddScoped<IAccessClientQuery, AccessClientQuery>();
services.AddScoped<IMaxProjectsQuery, MaxProjectsQuery>(); services.AddScoped<IMaxProjectsQuery, MaxProjectsQuery>();
services.AddScoped<IServiceAccountSecretsDetailsQuery, ServiceAccountSecretsDetailsQuery>(); services.AddScoped<IServiceAccountSecretsDetailsQuery, ServiceAccountSecretsDetailsQuery>();

View File

@ -1,8 +1,10 @@
using System.Linq.Expressions; using System.Linq.Expressions;
using AutoMapper; using AutoMapper;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.SecretsManager.Models.Data;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Infrastructure.EntityFramework.Repositories;
using Bit.Infrastructure.EntityFramework.SecretsManager.Discriminators;
using Bit.Infrastructure.EntityFramework.SecretsManager.Models; using Bit.Infrastructure.EntityFramework.SecretsManager.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -238,6 +240,153 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
return entities.Select(MapToCore); 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( private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore(
BaseAccessPolicy baseAccessPolicyEntity) => BaseAccessPolicy baseAccessPolicyEntity) =>
baseAccessPolicyEntity switch baseAccessPolicyEntity switch
@ -250,9 +399,27 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
Mapper.Map<Core.SecretsManager.Entities.UserServiceAccountAccessPolicy>(ap), Mapper.Map<Core.SecretsManager.Entities.UserServiceAccountAccessPolicy>(ap),
GroupServiceAccountAccessPolicy ap => Mapper GroupServiceAccountAccessPolicy ap => Mapper
.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap), .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( private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore(
BaseAccessPolicy baseAccessPolicyEntity, bool currentUserInGroup) BaseAccessPolicy baseAccessPolicyEntity, bool currentUserInGroup)
{ {

View File

@ -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);
}
}

View File

@ -1,11 +1,9 @@
using Bit.Api.Models.Response; using Bit.Api.Models.Response;
using Bit.Api.SecretsManager.Models.Request; using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.SecretsManager.Models.Response; using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.AuthorizationRequirements; using Bit.Core.SecretsManager.AuthorizationRequirements;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
@ -25,8 +23,6 @@ public class AccessPoliciesController : Controller
private readonly ICreateAccessPoliciesCommand _createAccessPoliciesCommand; private readonly ICreateAccessPoliciesCommand _createAccessPoliciesCommand;
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly IDeleteAccessPolicyCommand _deleteAccessPolicyCommand; private readonly IDeleteAccessPolicyCommand _deleteAccessPolicyCommand;
private readonly IGroupRepository _groupRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProjectRepository _projectRepository; private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IUpdateAccessPolicyCommand _updateAccessPolicyCommand; private readonly IUpdateAccessPolicyCommand _updateAccessPolicyCommand;
@ -39,9 +35,7 @@ public class AccessPoliciesController : Controller
ICurrentContext currentContext, ICurrentContext currentContext,
IAccessPolicyRepository accessPolicyRepository, IAccessPolicyRepository accessPolicyRepository,
IServiceAccountRepository serviceAccountRepository, IServiceAccountRepository serviceAccountRepository,
IGroupRepository groupRepository,
IProjectRepository projectRepository, IProjectRepository projectRepository,
IOrganizationUserRepository organizationUserRepository,
ICreateAccessPoliciesCommand createAccessPoliciesCommand, ICreateAccessPoliciesCommand createAccessPoliciesCommand,
IDeleteAccessPolicyCommand deleteAccessPolicyCommand, IDeleteAccessPolicyCommand deleteAccessPolicyCommand,
IUpdateAccessPolicyCommand updateAccessPolicyCommand) IUpdateAccessPolicyCommand updateAccessPolicyCommand)
@ -51,8 +45,6 @@ public class AccessPoliciesController : Controller
_currentContext = currentContext; _currentContext = currentContext;
_serviceAccountRepository = serviceAccountRepository; _serviceAccountRepository = serviceAccountRepository;
_projectRepository = projectRepository; _projectRepository = projectRepository;
_groupRepository = groupRepository;
_organizationUserRepository = organizationUserRepository;
_accessPolicyRepository = accessPolicyRepository; _accessPolicyRepository = accessPolicyRepository;
_createAccessPoliciesCommand = createAccessPoliciesCommand; _createAccessPoliciesCommand = createAccessPoliciesCommand;
_deleteAccessPolicyCommand = deleteAccessPolicyCommand; _deleteAccessPolicyCommand = deleteAccessPolicyCommand;
@ -243,15 +235,11 @@ public class AccessPoliciesController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
var groups = await _groupRepository.GetManyByOrganizationIdAsync(id); var userId = _userService.GetProperUserId(User).Value;
var groupResponses = groups.Select(g => new PotentialGranteeResponseModel(g)); var peopleGrantees = await _accessPolicyRepository.GetPeopleGranteesAsync(id, userId);
var organizationUsers =
await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id);
var userResponses = organizationUsers
.Where(user => user.AccessSecretsManager && user.Status == OrganizationUserStatusType.Confirmed)
.Select(userDetails => new PotentialGranteeResponseModel(userDetails));
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)); return new ListResponseModel<PotentialGranteeResponseModel>(userResponses.Concat(groupResponses));
} }
@ -287,6 +275,40 @@ public class AccessPoliciesController : Controller
return new ListResponseModel<PotentialGranteeResponseModel>(projectResponses); 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) private async Task<(AccessClientType AccessClientType, Guid UserId)> CheckUserHasWriteAccessToProjectAsync(Project project)
{ {
if (project == null) if (project == null)

View File

@ -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
};
}
}

View File

@ -34,10 +34,13 @@ public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseMode
public UserProjectAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy) : base(accessPolicy, _objectName) public UserProjectAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy) : base(accessPolicy, _objectName)
{ {
OrganizationUserId = accessPolicy.OrganizationUserId; SetProperties(accessPolicy);
GrantedProjectId = accessPolicy.GrantedProjectId; }
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
UserId = accessPolicy.User?.Id; public UserProjectAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy, Guid currentUserId) : base(accessPolicy, _objectName)
{
CurrentUser = currentUserId == accessPolicy.User?.Id;
SetProperties(accessPolicy);
} }
public UserProjectAccessPolicyResponseModel() : base(new UserProjectAccessPolicy(), _objectName) public UserProjectAccessPolicyResponseModel() : base(new UserProjectAccessPolicy(), _objectName)
@ -48,6 +51,15 @@ public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseMode
public string? OrganizationUserName { get; set; } public string? OrganizationUserName { get; set; }
public Guid? UserId { get; set; } public Guid? UserId { get; set; }
public Guid? GrantedProjectId { 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 public class UserServiceAccountAccessPolicyResponseModel : BaseAccessPolicyResponseModel

View File

@ -1,7 +1,6 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.Models.Api;
using Bit.Core.Models.Api;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Models.Data;
namespace Bit.Api.SecretsManager.Models.Response; namespace Bit.Api.SecretsManager.Models.Response;
@ -9,31 +8,33 @@ public class PotentialGranteeResponseModel : ResponseModel
{ {
private const string _objectName = "potentialGrantee"; private const string _objectName = "potentialGrantee";
public PotentialGranteeResponseModel(Group group) public PotentialGranteeResponseModel(GroupGrantee grantee)
: base(_objectName) : 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"; Type = "group";
Id = grantee.GroupId;
Name = grantee.Name;
CurrentUserInGroup = grantee.CurrentUserInGroup;
} }
public PotentialGranteeResponseModel(OrganizationUserUserDetails user) public PotentialGranteeResponseModel(UserGrantee grantee)
: base(_objectName) : 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"; Type = "user";
Id = grantee.OrganizationUserId;
Name = grantee.Name;
Email = grantee.Email;
CurrentUser = grantee.CurrentUser;
} }
public PotentialGranteeResponseModel(ServiceAccount serviceAccount) public PotentialGranteeResponseModel(ServiceAccount serviceAccount)
@ -67,9 +68,9 @@ public class PotentialGranteeResponseModel : ResponseModel
} }
public Guid Id { get; set; } public Guid Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Type { get; set; } public string Type { get; set; }
public string Email { get; set; } public string Email { get; set; }
public bool CurrentUserInGroup { get; set; }
public bool CurrentUser { get; set; }
} }

View File

@ -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();
}

View File

@ -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) };
}

View 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; }
}

View File

@ -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;
}
}

View File

@ -1,6 +1,7 @@
#nullable enable #nullable enable
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Models.Data;
namespace Bit.Core.SecretsManager.Repositories; namespace Bit.Core.SecretsManager.Repositories;
@ -15,4 +16,7 @@ public interface IAccessPolicyRepository
AccessClientType accessType); AccessClientType accessType);
Task ReplaceAsync(BaseAccessPolicy baseAccessPolicy); Task ReplaceAsync(BaseAccessPolicy baseAccessPolicy);
Task DeleteAsync(Guid id); 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);
} }

View File

@ -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;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
@ -10,11 +11,11 @@ public class AccessPolicyEntityTypeConfiguration : IEntityTypeConfiguration<Acce
{ {
builder builder
.HasDiscriminator<string>("Discriminator") .HasDiscriminator<string>("Discriminator")
.HasValue<UserProjectAccessPolicy>("user_project") .HasValue<UserProjectAccessPolicy>(AccessPolicyDiscriminator.UserProject)
.HasValue<UserServiceAccountAccessPolicy>("user_service_account") .HasValue<UserServiceAccountAccessPolicy>(AccessPolicyDiscriminator.UserServiceAccount)
.HasValue<GroupProjectAccessPolicy>("group_project") .HasValue<GroupProjectAccessPolicy>(AccessPolicyDiscriminator.GroupProject)
.HasValue<GroupServiceAccountAccessPolicy>("group_service_account") .HasValue<GroupServiceAccountAccessPolicy>(AccessPolicyDiscriminator.GroupServiceAccount)
.HasValue<ServiceAccountProjectAccessPolicy>("service_account_project"); .HasValue<ServiceAccountProjectAccessPolicy>(AccessPolicyDiscriminator.ServiceAccountProject);
builder builder
.Property(s => s.Id) .Property(s => s.Id)

View File

@ -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";
}

View File

@ -5,6 +5,8 @@ using Bit.Api.IntegrationTest.SecretsManager.Enums;
using Bit.Api.Models.Response; using Bit.Api.Models.Response;
using Bit.Api.SecretsManager.Models.Request; using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.SecretsManager.Models.Response; using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
@ -25,6 +27,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
private readonly ApiApplicationFactory _factory; private readonly ApiApplicationFactory _factory;
private readonly IProjectRepository _projectRepository; private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IGroupRepository _groupRepository;
private string _email = null!; private string _email = null!;
private SecretsManagerOrganizationHelper _organizationHelper = null!; private SecretsManagerOrganizationHelper _organizationHelper = null!;
@ -35,6 +38,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
_accessPolicyRepository = _factory.GetService<IAccessPolicyRepository>(); _accessPolicyRepository = _factory.GetService<IAccessPolicyRepository>();
_serviceAccountRepository = _factory.GetService<IServiceAccountRepository>(); _serviceAccountRepository = _factory.GetService<IServiceAccountRepository>();
_projectRepository = _factory.GetService<IProjectRepository>(); _projectRepository = _factory.GetService<IProjectRepository>();
_groupRepository = _factory.GetService<IGroupRepository>();
} }
public async Task InitializeAsync() public async Task InitializeAsync()
@ -660,7 +664,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)] [InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateServiceAccountAccessPolicies_MismatchOrgId_NotFound(PermissionType permissionType) 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); await LoginAsync(_email);
var anotherOrg = await _organizationHelper.CreateSmOrganizationAsync(); var anotherOrg = await _organizationHelper.CreateSmOrganizationAsync();
@ -904,9 +908,8 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)] [InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateServiceAccountGrantedPolicies_MismatchedOrgId_NotFound(PermissionType permissionType) 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); await LoginAsync(_email);
var ownerOrgUserId = orgUser.Id;
var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id, true); var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id, true);
await SetupProjectAndServiceAccountPermissionAsync(permissionType, projectId, serviceAccountId); await SetupProjectAndServiceAccountPermissionAsync(permissionType, projectId, serviceAccountId);
@ -925,9 +928,8 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)] [InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateServiceAccountGrantedPolicies_Success(PermissionType permissionType) 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); await LoginAsync(_email);
var ownerOrgUserId = orgUser.Id;
var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id); var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id);
await SetupProjectAndServiceAccountPermissionAsync(permissionType, projectId, serviceAccountId); await SetupProjectAndServiceAccountPermissionAsync(permissionType, projectId, serviceAccountId);
@ -1051,6 +1053,183 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
Assert.NotNull(result.Data.First().GrantedProjectName); 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) private async Task<RequestSetupData> SetupAccessPolicyRequest(Guid organizationId)
{ {
var project = await _projectRepository.CreateAsync(new Project 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, private async Task<(Guid ProjectId, Guid ServiceAccountId)> CreateProjectAndServiceAccountAsync(Guid organizationId,
bool misMatchOrganization = false) bool misMatchOrganization = false)
{ {

View File

@ -2,14 +2,12 @@
using Bit.Api.SecretsManager.Controllers; using Bit.Api.SecretsManager.Controllers;
using Bit.Api.SecretsManager.Models.Request; using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.Test.SecretsManager.Enums; using Bit.Api.Test.SecretsManager.Enums;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Models.Data;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
@ -806,14 +804,17 @@ public class AccessPoliciesControllerTests
Guid id) Guid id)
{ {
SetupPermission(sutProvider, permissionType, 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); var result = await sutProvider.Sut.GetPeoplePotentialGranteesAsync(id);
await sutProvider.GetDependency<IGroupRepository>().Received(1) await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
.GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); .GetPeopleGranteesAsync(id, Arg.Any<Guid>());
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
.GetManyDetailsByOrganizationAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)));
Assert.Empty(result.Data); Assert.Empty(result.Data);
} }
@ -826,17 +827,17 @@ public class AccessPoliciesControllerTests
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(id).Returns(false); sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(id).Returns(false);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(false); sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(false);
sutProvider.GetDependency<IUserService>().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); 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 Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetPeoplePotentialGranteesAsync(id));
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
.GetManyByOrganizationIdAsync(Arg.Any<Guid>()); .GetPeopleGranteesAsync(id, 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>());
} }
[Theory] [Theory]
@ -846,19 +847,20 @@ public class AccessPoliciesControllerTests
PermissionType permissionType, PermissionType permissionType,
SutProvider<AccessPoliciesController> sutProvider, SutProvider<AccessPoliciesController> sutProvider,
Guid id, Guid id,
Group mockGroup) GroupGrantee groupGrantee)
{ {
SetupPermission(sutProvider, permissionType, id); SetupPermission(sutProvider, permissionType, id);
sutProvider.GetDependency<IGroupRepository>().GetManyByOrganizationIdAsync(default) sutProvider.GetDependency<IAccessPolicyRepository>().GetPeopleGranteesAsync(default, default)
.ReturnsForAnyArgs(new List<Group> { mockGroup }); .ReturnsForAnyArgs(new PeopleGrantees
{
UserGrantees = new List<UserGrantee>(),
GroupGrantees = new List<GroupGrantee> { groupGrantee }
});
var result = await sutProvider.Sut.GetPeoplePotentialGranteesAsync(id); var result = await sutProvider.Sut.GetPeoplePotentialGranteesAsync(id);
await sutProvider.GetDependency<IGroupRepository>().Received(1) await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
.GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); .GetPeopleGranteesAsync(id, Arg.Any<Guid>());
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
.GetManyDetailsByOrganizationAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)));
Assert.NotEmpty(result.Data); Assert.NotEmpty(result.Data);
} }
@ -980,4 +982,195 @@ public class AccessPoliciesControllerTests
Assert.NotEmpty(result.Data); 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>());
}
} }