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

[SM-1293] Add endpoint to fetch secret's access policies (#4146)

* Add authz handling for secret access policy reads

* Add the ability to fetch secret access polices from the repository

* refactor response models

* Add new endpoint
This commit is contained in:
Thomas Avery 2024-06-07 12:08:38 -05:00 committed by GitHub
parent a1d609b208
commit 36705790ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 554 additions and 143 deletions

View File

@ -47,6 +47,9 @@ public class SecretAuthorizationHandler : AuthorizationHandler<SecretOperationRe
case not null when requirement == SecretOperations.Delete:
await CanDeleteSecretAsync(context, requirement, resource);
break;
case not null when requirement == SecretOperations.ReadAccessPolicies:
await CanReadAccessPoliciesAsync(context, requirement, resource);
break;
default:
throw new ArgumentException("Unsupported operation requirement type provided.", nameof(requirement));
}
@ -152,6 +155,26 @@ public class SecretAuthorizationHandler : AuthorizationHandler<SecretOperationRe
}
}
private async Task CanReadAccessPoliciesAsync(AuthorizationHandlerContext context,
SecretOperationRequirement requirement, Secret resource)
{
var (accessClient, userId) = await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId);
// Only users and admins can read access policies
if (accessClient != AccessClientType.User && accessClient != AccessClientType.NoAccessCheck)
{
return;
}
var access = await _secretRepository.AccessToSecretAsync(resource.Id, userId, accessClient);
if (access.Write)
{
context.Succeed(requirement);
}
}
private async Task<bool> GetAccessToUpdateSecretAsync(Secret resource, Guid userId, AccessClientType accessClient)
{
var newProject = resource.Projects?.FirstOrDefault();

View File

@ -20,7 +20,8 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
{
}
public async Task<List<Core.SecretsManager.Entities.BaseAccessPolicy>> CreateManyAsync(List<Core.SecretsManager.Entities.BaseAccessPolicy> baseAccessPolicies)
public async Task<List<Core.SecretsManager.Entities.BaseAccessPolicy>> CreateManyAsync(
List<Core.SecretsManager.Entities.BaseAccessPolicy> baseAccessPolicies)
{
await using var scope = ServiceScopeFactory.CreateAsyncScope();
var dbContext = GetDatabaseContext(scope);
@ -39,6 +40,13 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.UserSecretAccessPolicy accessPolicy:
{
var entity =
Mapper.Map<UserSecretAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy:
{
var entity =
@ -52,6 +60,12 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.GroupSecretAccessPolicy accessPolicy:
{
var entity = Mapper.Map<GroupSecretAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy:
{
var entity = Mapper.Map<GroupServiceAccountAccessPolicy>(accessPolicy);
@ -65,6 +79,13 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
serviceAccountIds.Add(entity.ServiceAccountId!.Value);
break;
}
case Core.SecretsManager.Entities.ServiceAccountSecretAccessPolicy accessPolicy:
{
var entity = Mapper.Map<ServiceAccountSecretAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
serviceAccountIds.Add(entity.ServiceAccountId!.Value);
break;
}
}
}
@ -395,6 +416,42 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
await transaction.CommitAsync();
}
public async Task<SecretAccessPolicies?> GetSecretAccessPoliciesAsync(
Guid secretId,
Guid userId)
{
await using var scope = ServiceScopeFactory.CreateAsyncScope();
var dbContext = GetDatabaseContext(scope);
var entities = await dbContext.AccessPolicies.Where(ap =>
((UserSecretAccessPolicy)ap).GrantedSecretId == secretId ||
((GroupSecretAccessPolicy)ap).GrantedSecretId == secretId ||
((ServiceAccountSecretAccessPolicy)ap).GrantedSecretId == secretId)
.Include(ap => ((UserSecretAccessPolicy)ap).OrganizationUser.User)
.Include(ap => ((GroupSecretAccessPolicy)ap).Group)
.Include(ap => ((ServiceAccountSecretAccessPolicy)ap).ServiceAccount)
.Select(ap => new
{
ap,
CurrentUserInGroup = ap is GroupSecretAccessPolicy &&
((GroupSecretAccessPolicy)ap).Group.GroupUsers.Any(g =>
g.OrganizationUser.UserId == userId)
})
.ToListAsync();
if (entities.Count == 0)
{
return null;
}
var organizationId = await dbContext.Secret.Where(s => s.Id == secretId)
.Select(s => s.OrganizationId)
.SingleAsync();
return new SecretAccessPolicies(secretId, organizationId,
entities.Select(e => MapToCore(e.ap, e.CurrentUserInGroup)).ToList());
}
private static async Task UpsertPeoplePoliciesAsync(DatabaseContext dbContext,
List<BaseAccessPolicy> policies, IReadOnlyCollection<AccessPolicy> userPolicyEntities,
IReadOnlyCollection<AccessPolicy> groupPolicyEntities)
@ -466,13 +523,17 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
baseAccessPolicyEntity switch
{
UserProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserProjectAccessPolicy>(ap),
GroupProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupProjectAccessPolicy>(ap),
ServiceAccountProjectAccessPolicy ap => Mapper
.Map<Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy>(ap),
UserSecretAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserSecretAccessPolicy>(ap),
UserServiceAccountAccessPolicy ap =>
Mapper.Map<Core.SecretsManager.Entities.UserServiceAccountAccessPolicy>(ap),
GroupProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupProjectAccessPolicy>(ap),
GroupSecretAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupSecretAccessPolicy>(ap),
GroupServiceAccountAccessPolicy ap => Mapper
.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap),
ServiceAccountProjectAccessPolicy ap => Mapper
.Map<Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy>(ap),
ServiceAccountSecretAccessPolicy ap => Mapper
.Map<Core.SecretsManager.Entities.ServiceAccountSecretAccessPolicy>(ap),
_ => throw new ArgumentException("Unsupported access policy type")
};
@ -482,20 +543,26 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
{
Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy => Mapper.Map<UserProjectAccessPolicy>(
accessPolicy),
Core.SecretsManager.Entities.UserSecretAccessPolicy accessPolicy => Mapper.Map<UserSecretAccessPolicy>(
accessPolicy),
Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy => Mapper
.Map<UserServiceAccountAccessPolicy>(accessPolicy),
Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy => Mapper.Map<GroupProjectAccessPolicy>(
accessPolicy),
Core.SecretsManager.Entities.GroupSecretAccessPolicy accessPolicy => Mapper.Map<GroupSecretAccessPolicy>(
accessPolicy),
Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy => Mapper
.Map<GroupServiceAccountAccessPolicy>(accessPolicy),
Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy => Mapper
.Map<ServiceAccountProjectAccessPolicy>(accessPolicy),
Core.SecretsManager.Entities.ServiceAccountSecretAccessPolicy accessPolicy => Mapper
.Map<ServiceAccountSecretAccessPolicy>(accessPolicy),
_ => throw new ArgumentException("Unsupported access policy type")
};
}
private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore(
BaseAccessPolicy baseAccessPolicyEntity, bool currentUserInGroup)
BaseAccessPolicy baseAccessPolicyEntity, bool currentUserInGroup)
{
switch (baseAccessPolicyEntity)
{
@ -505,6 +572,12 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
mapped.CurrentUserInGroup = currentUserInGroup;
return mapped;
}
case GroupSecretAccessPolicy ap:
{
var mapped = Mapper.Map<Core.SecretsManager.Entities.GroupSecretAccessPolicy>(ap);
mapped.CurrentUserInGroup = currentUserInGroup;
return mapped;
}
case GroupServiceAccountAccessPolicy ap:
{
var mapped = Mapper.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap);

View File

@ -517,4 +517,85 @@ public class SecretAuthorizationHandlerTests
Assert.Equal(expected, authzContext.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task CanReadAccessPolicies_AccessToSecretsManagerFalse_DoesNotSucceed(
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
ClaimsPrincipal claimsPrincipal)
{
var requirement = SecretOperations.ReadAccessPolicies;
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(secret.OrganizationId)
.Returns(false);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task CanReadAccessPolicies_NullResource_DoesNotSucceed(
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
ClaimsPrincipal claimsPrincipal,
Guid userId)
{
var requirement = SecretOperations.ReadAccessPolicies;
SetupPermission(sutProvider, PermissionType.RunAsAdmin, secret.OrganizationId, userId);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, null);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(AccessClientType.ServiceAccount)]
[BitAutoData(AccessClientType.Organization)]
public async Task CanReadAccessPolicies_UnsupportedClient_DoesNotSucceed(
AccessClientType clientType,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
ClaimsPrincipal claimsPrincipal)
{
var requirement = SecretOperations.ReadAccessPolicies;
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(secret.OrganizationId)
.Returns(true);
sutProvider.GetDependency<IAccessClientQuery>()
.GetAccessClientAsync(Arg.Any<ClaimsPrincipal>(), secret.OrganizationId)
.Returns((clientType, Guid.NewGuid()));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin, true, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true, true)]
public async Task CanReadAccessPolicies_AccessCheck(PermissionType permissionType, bool read, bool write,
bool expected,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
ClaimsPrincipal claimsPrincipal,
Guid userId)
{
var requirement = SecretOperations.ReadAccessPolicies;
SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId);
sutProvider.GetDependency<ISecretRepository>()
.AccessToSecretAsync(secret.Id, userId, Arg.Any<AccessClientType>())
.Returns((read, write));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.Equal(expected, authzContext.HasSucceeded);
}
}

View File

@ -24,6 +24,7 @@ public class AccessPoliciesController : Controller
private readonly IAuthorizationService _authorizationService;
private readonly ICurrentContext _currentContext;
private readonly IProjectRepository _projectRepository;
private readonly ISecretRepository _secretRepository;
private readonly IServiceAccountGrantedPolicyUpdatesQuery _serviceAccountGrantedPolicyUpdatesQuery;
private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IUpdateServiceAccountGrantedPoliciesCommand _updateServiceAccountGrantedPoliciesCommand;
@ -41,6 +42,7 @@ public class AccessPoliciesController : Controller
IAccessPolicyRepository accessPolicyRepository,
IServiceAccountRepository serviceAccountRepository,
IProjectRepository projectRepository,
ISecretRepository secretRepository,
IAccessClientQuery accessClientQuery,
IServiceAccountGrantedPolicyUpdatesQuery serviceAccountGrantedPolicyUpdatesQuery,
IProjectServiceAccountsAccessPoliciesUpdatesQuery projectServiceAccountsAccessPoliciesUpdatesQuery,
@ -52,6 +54,7 @@ public class AccessPoliciesController : Controller
_currentContext = currentContext;
_serviceAccountRepository = serviceAccountRepository;
_projectRepository = projectRepository;
_secretRepository = secretRepository;
_accessPolicyRepository = accessPolicyRepository;
_updateServiceAccountGrantedPoliciesCommand = updateServiceAccountGrantedPoliciesCommand;
_accessClientQuery = accessClientQuery;
@ -259,6 +262,22 @@ public class AccessPoliciesController : Controller
return new ProjectServiceAccountsAccessPoliciesResponseModel(results);
}
[HttpGet("/secrets/{secretId}/access-policies")]
public async Task<SecretAccessPoliciesResponseModel> GetSecretAccessPoliciesAsync(Guid secretId)
{
var secret = await _secretRepository.GetByIdAsync(secretId);
var authorizationResult = await _authorizationService.AuthorizeAsync(User, secret, SecretOperations.ReadAccessPolicies);
if (!authorizationResult.Succeeded)
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User)!.Value;
var accessPolicies = await _accessPolicyRepository.GetSecretAccessPoliciesAsync(secretId, userId);
return new SecretAccessPoliciesResponseModel(accessPolicies, userId);
}
private async Task<(AccessClientType AccessClientType, Guid UserId)> CheckUserHasWriteAccessToProjectAsync(
Project project)
{

View File

@ -9,161 +9,133 @@ public abstract class BaseAccessPolicyResponseModel : ResponseModel
{
protected BaseAccessPolicyResponseModel(BaseAccessPolicy baseAccessPolicy, string obj) : base(obj)
{
Id = baseAccessPolicy.Id;
Read = baseAccessPolicy.Read;
Write = baseAccessPolicy.Write;
CreationDate = baseAccessPolicy.CreationDate;
RevisionDate = baseAccessPolicy.RevisionDate;
}
public Guid Id { get; set; }
public bool Read { get; set; }
public bool Write { get; set; }
public DateTime CreationDate { get; set; }
public DateTime RevisionDate { get; set; }
public string? GetUserDisplayName(User? user)
protected static string? GetUserDisplayName(User? user)
{
return string.IsNullOrWhiteSpace(user?.Name) ? user?.Email : user?.Name;
}
}
public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel
public class UserAccessPolicyResponseModel : BaseAccessPolicyResponseModel
{
private const string _objectName = "userProjectAccessPolicy";
private const string _objectName = "userAccessPolicy";
public UserProjectAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy) : base(accessPolicy, _objectName)
{
SetProperties(accessPolicy);
}
public UserProjectAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy, Guid currentUserId) : base(accessPolicy, _objectName)
public UserAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy, Guid currentUserId) : base(accessPolicy, _objectName)
{
CurrentUser = currentUserId == accessPolicy.User?.Id;
SetProperties(accessPolicy);
OrganizationUserId = accessPolicy.OrganizationUserId;
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
}
public UserProjectAccessPolicyResponseModel() : base(new UserProjectAccessPolicy(), _objectName)
public UserAccessPolicyResponseModel(UserServiceAccountAccessPolicy accessPolicy, Guid currentUserId) : base(accessPolicy, _objectName)
{
CurrentUser = currentUserId == accessPolicy.User?.Id;
OrganizationUserId = accessPolicy.OrganizationUserId;
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
}
public UserAccessPolicyResponseModel(UserSecretAccessPolicy accessPolicy, Guid currentUserId) : base(accessPolicy, _objectName)
{
CurrentUser = currentUserId == accessPolicy.User?.Id;
OrganizationUserId = accessPolicy.OrganizationUserId;
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
}
public UserAccessPolicyResponseModel() : base(new UserProjectAccessPolicy(), _objectName)
{
}
public Guid? OrganizationUserId { get; set; }
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
public class GroupAccessPolicyResponseModel : BaseAccessPolicyResponseModel
{
private const string _objectName = "userServiceAccountAccessPolicy";
private const string _objectName = "groupAccessPolicy";
public UserServiceAccountAccessPolicyResponseModel(UserServiceAccountAccessPolicy accessPolicy)
: base(accessPolicy, _objectName)
{
SetProperties(accessPolicy);
}
public UserServiceAccountAccessPolicyResponseModel(UserServiceAccountAccessPolicy accessPolicy, Guid userId)
: base(accessPolicy, _objectName)
{
SetProperties(accessPolicy);
CurrentUser = accessPolicy.User?.Id == userId;
}
public UserServiceAccountAccessPolicyResponseModel() : base(new UserServiceAccountAccessPolicy(), _objectName)
{
}
public Guid? OrganizationUserId { get; set; }
public string? OrganizationUserName { get; set; }
public Guid? UserId { get; set; }
public Guid? GrantedServiceAccountId { get; set; }
public bool CurrentUser { get; set; }
private void SetProperties(UserServiceAccountAccessPolicy accessPolicy)
{
OrganizationUserId = accessPolicy.OrganizationUserId;
GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId;
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
UserId = accessPolicy.User?.Id;
}
}
public class GroupProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel
{
private const string _objectName = "groupProjectAccessPolicy";
public GroupProjectAccessPolicyResponseModel(GroupProjectAccessPolicy accessPolicy)
public GroupAccessPolicyResponseModel(GroupProjectAccessPolicy accessPolicy)
: base(accessPolicy, _objectName)
{
GroupId = accessPolicy.GroupId;
GrantedProjectId = accessPolicy.GrantedProjectId;
GroupName = accessPolicy.Group?.Name;
CurrentUserInGroup = accessPolicy.CurrentUserInGroup;
}
public GroupProjectAccessPolicyResponseModel() : base(new GroupProjectAccessPolicy(), _objectName)
public GroupAccessPolicyResponseModel(GroupServiceAccountAccessPolicy accessPolicy)
: base(accessPolicy, _objectName)
{
GroupId = accessPolicy.GroupId;
GroupName = accessPolicy.Group?.Name;
CurrentUserInGroup = accessPolicy.CurrentUserInGroup;
}
public GroupAccessPolicyResponseModel(GroupSecretAccessPolicy accessPolicy)
: base(accessPolicy, _objectName)
{
GroupId = accessPolicy.GroupId;
GroupName = accessPolicy.Group?.Name;
CurrentUserInGroup = accessPolicy.CurrentUserInGroup;
}
public GroupAccessPolicyResponseModel() : base(new GroupProjectAccessPolicy(), _objectName)
{
}
public Guid? GroupId { get; set; }
public string? GroupName { get; set; }
public bool? CurrentUserInGroup { get; set; }
public Guid? GrantedProjectId { get; set; }
}
public class GroupServiceAccountAccessPolicyResponseModel : BaseAccessPolicyResponseModel
{
private const string _objectName = "groupServiceAccountAccessPolicy";
public GroupServiceAccountAccessPolicyResponseModel(GroupServiceAccountAccessPolicy accessPolicy)
: base(accessPolicy, _objectName)
{
GroupId = accessPolicy.GroupId;
GroupName = accessPolicy.Group?.Name;
GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId;
CurrentUserInGroup = accessPolicy.CurrentUserInGroup;
}
public GroupServiceAccountAccessPolicyResponseModel() : base(new GroupServiceAccountAccessPolicy(), _objectName)
{
}
public Guid? GroupId { get; set; }
public string? GroupName { get; set; }
public Guid? GrantedServiceAccountId { get; set; }
public bool? CurrentUserInGroup { get; set; }
}
public class ServiceAccountProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel
public class ServiceAccountAccessPolicyResponseModel : BaseAccessPolicyResponseModel
{
private const string _objectName = "serviceAccountProjectAccessPolicy";
public ServiceAccountProjectAccessPolicyResponseModel(ServiceAccountProjectAccessPolicy accessPolicy)
public ServiceAccountAccessPolicyResponseModel(ServiceAccountProjectAccessPolicy accessPolicy)
: base(accessPolicy, _objectName)
{
ServiceAccountId = accessPolicy.ServiceAccountId;
GrantedProjectId = accessPolicy.GrantedProjectId;
ServiceAccountName = accessPolicy.ServiceAccount?.Name;
GrantedProjectName = accessPolicy.GrantedProject?.Name;
}
public ServiceAccountProjectAccessPolicyResponseModel()
public ServiceAccountAccessPolicyResponseModel(ServiceAccountSecretAccessPolicy accessPolicy)
: base(accessPolicy, _objectName)
{
ServiceAccountId = accessPolicy.ServiceAccountId;
ServiceAccountName = accessPolicy.ServiceAccount?.Name;
}
public ServiceAccountAccessPolicyResponseModel()
: base(new ServiceAccountProjectAccessPolicy(), _objectName)
{
}
public Guid? ServiceAccountId { get; set; }
public string? ServiceAccountName { get; set; }
}
public class GrantedProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel
{
private const string _objectName = "grantedProjectAccessPolicy";
public GrantedProjectAccessPolicyResponseModel(ServiceAccountProjectAccessPolicy accessPolicy)
: base(accessPolicy, _objectName)
{
GrantedProjectId = accessPolicy.GrantedProjectId;
GrantedProjectName = accessPolicy.GrantedProject?.Name;
}
public GrantedProjectAccessPolicyResponseModel()
: base(new ServiceAccountProjectAccessPolicy(), _objectName)
{
}
public Guid? GrantedProjectId { get; set; }
public string? GrantedProjectName { get; set; }
}

View File

@ -0,0 +1,25 @@
#nullable enable
using Bit.Core.Models.Api;
using Bit.Core.SecretsManager.Models.Data;
namespace Bit.Api.SecretsManager.Models.Response;
public class GrantedProjectAccessPolicyPermissionDetailsResponseModel : ResponseModel
{
private const string _objectName = "grantedProjectAccessPolicyPermissionDetails";
public GrantedProjectAccessPolicyPermissionDetailsResponseModel(
ServiceAccountProjectAccessPolicyPermissionDetails apPermissionDetails, string obj = _objectName) : base(obj)
{
AccessPolicy = new GrantedProjectAccessPolicyResponseModel(apPermissionDetails.AccessPolicy);
HasPermission = apPermissionDetails.HasPermission;
}
public GrantedProjectAccessPolicyPermissionDetailsResponseModel()
: base(_objectName)
{
}
public GrantedProjectAccessPolicyResponseModel AccessPolicy { get; set; } = new();
public bool HasPermission { get; set; }
}

View File

@ -15,10 +15,10 @@ public class ProjectPeopleAccessPoliciesResponseModel : ResponseModel
switch (baseAccessPolicy)
{
case UserProjectAccessPolicy accessPolicy:
UserAccessPolicies.Add(new UserProjectAccessPolicyResponseModel(accessPolicy, userId));
UserAccessPolicies.Add(new UserAccessPolicyResponseModel(accessPolicy, userId));
break;
case GroupProjectAccessPolicy accessPolicy:
GroupAccessPolicies.Add(new GroupProjectAccessPolicyResponseModel(accessPolicy));
GroupAccessPolicies.Add(new GroupAccessPolicyResponseModel(accessPolicy));
break;
}
}
@ -28,7 +28,7 @@ public class ProjectPeopleAccessPoliciesResponseModel : ResponseModel
{
}
public List<UserProjectAccessPolicyResponseModel> UserAccessPolicies { get; set; } = new();
public List<UserAccessPolicyResponseModel> UserAccessPolicies { get; set; } = new();
public List<GroupProjectAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = new();
public List<GroupAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = new();
}

View File

@ -18,12 +18,12 @@ public class ProjectServiceAccountsAccessPoliciesResponseModel : ResponseModel
}
ServiceAccountAccessPolicies = projectServiceAccountsAccessPolicies.ServiceAccountAccessPolicies
.Select(x => new ServiceAccountProjectAccessPolicyResponseModel(x)).ToList();
.Select(x => new ServiceAccountAccessPolicyResponseModel(x)).ToList();
}
public ProjectServiceAccountsAccessPoliciesResponseModel() : base(_objectName)
{
}
public List<ServiceAccountProjectAccessPolicyResponseModel> ServiceAccountAccessPolicies { get; set; } = [];
public List<ServiceAccountAccessPolicyResponseModel> ServiceAccountAccessPolicies { get; set; } = [];
}

View File

@ -0,0 +1,33 @@
#nullable enable
using Bit.Core.Models.Api;
using Bit.Core.SecretsManager.Models.Data;
namespace Bit.Api.SecretsManager.Models.Response;
public class SecretAccessPoliciesResponseModel : ResponseModel
{
private const string _objectName = "secretAccessPolicies";
public SecretAccessPoliciesResponseModel(SecretAccessPolicies? accessPolicies, Guid userId) :
base(_objectName)
{
if (accessPolicies == null)
{
return;
}
UserAccessPolicies = accessPolicies.UserAccessPolicies.Select(x => new UserAccessPolicyResponseModel(x, userId)).ToList();
GroupAccessPolicies = accessPolicies.GroupAccessPolicies.Select(x => new GroupAccessPolicyResponseModel(x)).ToList();
ServiceAccountAccessPolicies = accessPolicies.ServiceAccountAccessPolicies.Select(x => new ServiceAccountAccessPolicyResponseModel(x)).ToList();
}
public SecretAccessPoliciesResponseModel() : base(_objectName)
{
}
public List<UserAccessPolicyResponseModel> UserAccessPolicies { get; set; } = [];
public List<GroupAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = [];
public List<ServiceAccountAccessPolicyResponseModel> ServiceAccountAccessPolicies { get; set; } = [];
}

View File

@ -18,13 +18,13 @@ public class ServiceAccountGrantedPoliciesPermissionDetailsResponseModel : Respo
}
GrantedProjectPolicies = grantedPoliciesPermissionDetails.ProjectGrantedPolicies
.Select(x => new ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel(x)).ToList();
.Select(x => new GrantedProjectAccessPolicyPermissionDetailsResponseModel(x)).ToList();
}
public ServiceAccountGrantedPoliciesPermissionDetailsResponseModel() : base(_objectName)
{
}
public List<ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel> GrantedProjectPolicies { get; set; } =
public List<GrantedProjectAccessPolicyPermissionDetailsResponseModel> GrantedProjectPolicies { get; set; } =
[];
}

View File

@ -20,10 +20,10 @@ public class ServiceAccountPeopleAccessPoliciesResponseModel : ResponseModel
switch (baseAccessPolicy)
{
case UserServiceAccountAccessPolicy accessPolicy:
UserAccessPolicies.Add(new UserServiceAccountAccessPolicyResponseModel(accessPolicy, userId));
UserAccessPolicies.Add(new UserAccessPolicyResponseModel(accessPolicy, userId));
break;
case GroupServiceAccountAccessPolicy accessPolicy:
GroupAccessPolicies.Add(new GroupServiceAccountAccessPolicyResponseModel(accessPolicy));
GroupAccessPolicies.Add(new GroupAccessPolicyResponseModel(accessPolicy));
break;
}
}
@ -33,7 +33,7 @@ public class ServiceAccountPeopleAccessPoliciesResponseModel : ResponseModel
{
}
public List<UserServiceAccountAccessPolicyResponseModel> UserAccessPolicies { get; set; } = new();
public List<UserAccessPolicyResponseModel> UserAccessPolicies { get; set; } = new();
public List<GroupServiceAccountAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = new();
public List<GroupAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = new();
}

View File

@ -1,25 +0,0 @@
#nullable enable
using Bit.Core.Models.Api;
using Bit.Core.SecretsManager.Models.Data;
namespace Bit.Api.SecretsManager.Models.Response;
public class ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel : ResponseModel
{
private const string _objectName = "serviceAccountProjectAccessPolicyPermissionDetails";
public ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel(
ServiceAccountProjectAccessPolicyPermissionDetails apPermissionDetails, string obj = _objectName) : base(obj)
{
AccessPolicy = new ServiceAccountProjectAccessPolicyResponseModel(apPermissionDetails.AccessPolicy);
HasPermission = apPermissionDetails.HasPermission;
}
public ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel()
: base(_objectName)
{
}
public ServiceAccountProjectAccessPolicyResponseModel AccessPolicy { get; set; } = new();
public bool HasPermission { get; set; }
}

View File

@ -12,4 +12,5 @@ public static class SecretOperations
public static readonly SecretOperationRequirement Read = new() { Name = nameof(Read) };
public static readonly SecretOperationRequirement Update = new() { Name = nameof(Update) };
public static readonly SecretOperationRequirement Delete = new() { Name = nameof(Delete) };
public static readonly SecretOperationRequirement ReadAccessPolicies = new() { Name = nameof(ReadAccessPolicies) };
}

View File

@ -0,0 +1,35 @@
#nullable enable
using Bit.Core.SecretsManager.Entities;
namespace Bit.Core.SecretsManager.Models.Data;
public class SecretAccessPolicies
{
public SecretAccessPolicies(Guid secretId, Guid organizationId, List<BaseAccessPolicy> policies)
{
SecretId = secretId;
OrganizationId = organizationId;
UserAccessPolicies = policies
.OfType<UserSecretAccessPolicy>()
.ToList();
GroupAccessPolicies = policies
.OfType<GroupSecretAccessPolicy>()
.ToList();
ServiceAccountAccessPolicies = policies
.OfType<ServiceAccountSecretAccessPolicy>()
.ToList();
}
public SecretAccessPolicies()
{
}
public Guid SecretId { get; set; }
public Guid OrganizationId { get; set; }
public IEnumerable<UserSecretAccessPolicy> UserAccessPolicies { get; set; } = [];
public IEnumerable<GroupSecretAccessPolicy> GroupAccessPolicies { get; set; } = [];
public IEnumerable<ServiceAccountSecretAccessPolicy> ServiceAccountAccessPolicies { get; set; } = [];
}

View File

@ -20,4 +20,5 @@ public interface IAccessPolicyRepository
Task UpdateServiceAccountGrantedPoliciesAsync(ServiceAccountGrantedPoliciesUpdates policyUpdates);
Task<ProjectServiceAccountsAccessPolicies?> GetProjectServiceAccountsAccessPoliciesAsync(Guid projectId);
Task UpdateProjectServiceAccountsAccessPoliciesAsync(ProjectServiceAccountsAccessPoliciesUpdates updates);
Task<SecretAccessPolicies?> GetSecretAccessPoliciesAsync(Guid secretId, Guid userId);
}

View File

@ -27,6 +27,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
private readonly IGroupRepository _groupRepository;
private readonly LoginHelper _loginHelper;
private readonly IProjectRepository _projectRepository;
private readonly ISecretRepository _secretRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;
private string _email = null!;
private SecretsManagerOrganizationHelper _organizationHelper = null!;
@ -37,6 +38,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
_client = _factory.CreateClient();
_accessPolicyRepository = _factory.GetService<IAccessPolicyRepository>();
_serviceAccountRepository = _factory.GetService<IServiceAccountRepository>();
_secretRepository = _factory.GetService<ISecretRepository>();
_projectRepository = _factory.GetService<IProjectRepository>();
_groupRepository = _factory.GetService<IGroupRepository>();
_loginHelper = new LoginHelper(_factory, _client);
@ -723,9 +725,8 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
Assert.NotNull(result);
Assert.NotEmpty(result.GrantedProjectPolicies);
Assert.Equal(initData.ServiceAccountId, result.GrantedProjectPolicies.First().AccessPolicy.ServiceAccountId);
Assert.NotNull(result.GrantedProjectPolicies.First().AccessPolicy.ServiceAccountName);
Assert.NotNull(result.GrantedProjectPolicies.First().AccessPolicy.GrantedProjectName);
Assert.NotNull(result.GrantedProjectPolicies.First().AccessPolicy.GrantedProjectId);
}
[Theory]
@ -922,7 +923,6 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
Assert.NotEmpty(result.ServiceAccountAccessPolicies);
Assert.Equal(initData.ServiceAccountId, result.ServiceAccountAccessPolicies.First().ServiceAccountId);
Assert.NotNull(result.ServiceAccountAccessPolicies.First().ServiceAccountName);
Assert.NotNull(result.ServiceAccountAccessPolicies.First().GrantedProjectName);
}
[Theory]
@ -1038,6 +1038,89 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
Assert.Single(result.ServiceAccountAccessPolicies);
}
[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 GetSecretAccessPoliciesAsync_SmAccessDenied_ReturnsNotFound(bool useSecrets,
bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await _loginHelper.LoginAsync(_email);
var secret = await _secretRepository.CreateAsync(new Secret
{
OrganizationId = org.Id,
Key = _mockEncryptedString,
Value = _mockEncryptedString,
Note = _mockEncryptedString
});
var response = await _client.GetAsync($"/secrets/{secret.Id}/access-policies");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetSecretAccessPoliciesAsync_NoAccessPolicies_ReturnsEmpty()
{
var (secretId, _) = await SetupSecretAccessPoliciesTest(PermissionType.RunAsAdmin);
var response = await _client.GetAsync($"/secrets/{secretId}/access-policies");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadFromJsonAsync<SecretAccessPoliciesResponseModel>();
Assert.NotNull(result);
Assert.Empty(result.UserAccessPolicies);
Assert.Empty(result.GroupAccessPolicies);
Assert.Empty(result.ServiceAccountAccessPolicies);
}
[Fact]
public async Task GetSecretAccessPoliciesAsync_UserDoesntHavePermission_ReturnsNotFound()
{
var (secretId, _) = await SetupSecretAccessPoliciesTest(PermissionType.RunAsUserWithPermission);
var response = await _client.GetAsync($"/secrets/{secretId}/access-policies");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
public async Task GetSecretAccessPoliciesAsync_Success(PermissionType permissionType)
{
var (secretId, currentOrgUser) = await SetupSecretAccessPoliciesTest(permissionType);
var accessPolicies = new List<BaseAccessPolicy>
{
new UserSecretAccessPolicy
{
GrantedSecretId = secretId, OrganizationUserId = currentOrgUser.Id, Read = true, Write = true
}
};
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
var response = await _client.GetAsync($"/secrets/{secretId}/access-policies");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadFromJsonAsync<SecretAccessPoliciesResponseModel>();
Assert.NotNull(result);
Assert.NotEmpty(result.UserAccessPolicies);
Assert.Empty(result.GroupAccessPolicies);
Assert.Empty(result.ServiceAccountAccessPolicies);
Assert.NotNull(result.UserAccessPolicies.First().OrganizationUserName);
Assert.NotNull(result.UserAccessPolicies.First().OrganizationUserId);
Assert.NotNull(result.UserAccessPolicies.First().CurrentUser);
Assert.Equal(currentOrgUser.Id, result.UserAccessPolicies.First().OrganizationUserId);
}
private async Task<(Guid ProjectId, Guid ServiceAccountId)> CreateServiceAccountProjectAccessPolicyAsync(
Guid organizationId)
{
@ -1290,4 +1373,31 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
return (project, request);
}
private async Task<(Guid SecretId, OrganizationUser currentOrgUser)> SetupSecretAccessPoliciesTest(
PermissionType permissionType)
{
var (org, orgAdmin) = await _organizationHelper.Initialize(true, true, true);
var currentOrgUser = orgAdmin;
if (permissionType == PermissionType.RunAsUserWithPermission)
{
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await _loginHelper.LoginAsync(email);
currentOrgUser = orgUser;
}
else
{
await _loginHelper.LoginAsync(_email);
}
var secret = await _secretRepository.CreateAsync(new Secret
{
OrganizationId = org.Id,
Key = _mockEncryptedString,
Value = _mockEncryptedString,
Note = _mockEncryptedString
});
return (secret.Id, currentOrgUser);
}
}

View File

@ -827,7 +827,6 @@ public class AccessPoliciesControllerTests
SutProvider<AccessPoliciesController> sutProvider,
Project data)
{
// FIX ME
SetupProjectAccessPoliciesTest(sutProvider, data, accessClientType);
sutProvider.GetDependency<IAccessPolicyRepository>()
@ -953,6 +952,61 @@ public class AccessPoliciesControllerTests
.UpdateAsync(Arg.Any<ProjectServiceAccountsAccessPoliciesUpdates>());
}
[Theory]
[BitAutoData]
public async Task GetSecretAccessPoliciesAsync_NoAccess_ThrowsNotFound(
SutProvider<AccessPoliciesController> sutProvider,
Secret data)
{
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(data.Id).Returns(data);
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data,
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Failed());
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.GetSecretAccessPoliciesAsync(data.Id));
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(0)
.GetSecretAccessPoliciesAsync(Arg.Any<Guid>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData]
public async Task GetSecretAccessPoliciesAsync_HasAccessNoPolicies_ReturnsEmptyList(
SutProvider<AccessPoliciesController> sutProvider,
Secret data)
{
SetupSecretAccessPoliciesTest(sutProvider, data);
sutProvider.GetDependency<IAccessPolicyRepository>()
.GetSecretAccessPoliciesAsync(Arg.Any<Guid>(), Arg.Any<Guid>())
.ReturnsNull();
var result = await sutProvider.Sut.GetSecretAccessPoliciesAsync(data.Id);
Assert.Empty(result.UserAccessPolicies);
Assert.Empty(result.GroupAccessPolicies);
Assert.Empty(result.ServiceAccountAccessPolicies);
}
[Theory]
[BitAutoData]
public async Task GetSecretAccessPoliciesAsync_HasAccess_Success(
SutProvider<AccessPoliciesController> sutProvider,
SecretAccessPolicies policies,
Secret data)
{
SetupSecretAccessPoliciesTest(sutProvider, data);
sutProvider.GetDependency<IAccessPolicyRepository>()
.GetSecretAccessPoliciesAsync(Arg.Any<Guid>(), Arg.Any<Guid>())
.Returns(policies);
var result = await sutProvider.Sut.GetSecretAccessPoliciesAsync(data.Id);
Assert.NotEmpty(result.UserAccessPolicies);
Assert.NotEmpty(result.GroupAccessPolicies);
Assert.NotEmpty(result.ServiceAccountAccessPolicies);
}
private static PeopleAccessPoliciesRequestModel SetRequestToCanReadWrite(PeopleAccessPoliciesRequestModel request)
{
foreach (var ap in request.UserAccessPolicyRequests)
@ -1005,4 +1059,13 @@ public class AccessPoliciesControllerTests
.GetAccessClientAsync(Arg.Any<ClaimsPrincipal>(), Arg.Any<Guid>())
.ReturnsForAnyArgs((accessClientType, Guid.NewGuid()));
}
private static void SetupSecretAccessPoliciesTest(SutProvider<AccessPoliciesController> sutProvider, Secret data)
{
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(data.Id).Returns(data);
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data,
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
sutProvider.GetDependency<IUserService>().GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(Guid.NewGuid());
}
}