mirror of
https://github.com/bitwarden/server.git
synced 2025-05-22 12:04:27 -05: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:
parent
a1d609b208
commit
36705790ad
@ -47,6 +47,9 @@ public class SecretAuthorizationHandler : AuthorizationHandler<SecretOperationRe
|
|||||||
case not null when requirement == SecretOperations.Delete:
|
case not null when requirement == SecretOperations.Delete:
|
||||||
await CanDeleteSecretAsync(context, requirement, resource);
|
await CanDeleteSecretAsync(context, requirement, resource);
|
||||||
break;
|
break;
|
||||||
|
case not null when requirement == SecretOperations.ReadAccessPolicies:
|
||||||
|
await CanReadAccessPoliciesAsync(context, requirement, resource);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException("Unsupported operation requirement type provided.", nameof(requirement));
|
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)
|
private async Task<bool> GetAccessToUpdateSecretAsync(Secret resource, Guid userId, AccessClientType accessClient)
|
||||||
{
|
{
|
||||||
var newProject = resource.Projects?.FirstOrDefault();
|
var newProject = resource.Projects?.FirstOrDefault();
|
||||||
|
@ -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();
|
await using var scope = ServiceScopeFactory.CreateAsyncScope();
|
||||||
var dbContext = GetDatabaseContext(scope);
|
var dbContext = GetDatabaseContext(scope);
|
||||||
@ -39,6 +40,13 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
|
|||||||
await dbContext.AddAsync(entity);
|
await dbContext.AddAsync(entity);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Core.SecretsManager.Entities.UserSecretAccessPolicy accessPolicy:
|
||||||
|
{
|
||||||
|
var entity =
|
||||||
|
Mapper.Map<UserSecretAccessPolicy>(accessPolicy);
|
||||||
|
await dbContext.AddAsync(entity);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy:
|
case Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy:
|
||||||
{
|
{
|
||||||
var entity =
|
var entity =
|
||||||
@ -52,6 +60,12 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
|
|||||||
await dbContext.AddAsync(entity);
|
await dbContext.AddAsync(entity);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Core.SecretsManager.Entities.GroupSecretAccessPolicy accessPolicy:
|
||||||
|
{
|
||||||
|
var entity = Mapper.Map<GroupSecretAccessPolicy>(accessPolicy);
|
||||||
|
await dbContext.AddAsync(entity);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy:
|
case Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy:
|
||||||
{
|
{
|
||||||
var entity = Mapper.Map<GroupServiceAccountAccessPolicy>(accessPolicy);
|
var entity = Mapper.Map<GroupServiceAccountAccessPolicy>(accessPolicy);
|
||||||
@ -65,6 +79,13 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
|
|||||||
serviceAccountIds.Add(entity.ServiceAccountId!.Value);
|
serviceAccountIds.Add(entity.ServiceAccountId!.Value);
|
||||||
break;
|
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();
|
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,
|
private static async Task UpsertPeoplePoliciesAsync(DatabaseContext dbContext,
|
||||||
List<BaseAccessPolicy> policies, IReadOnlyCollection<AccessPolicy> userPolicyEntities,
|
List<BaseAccessPolicy> policies, IReadOnlyCollection<AccessPolicy> userPolicyEntities,
|
||||||
IReadOnlyCollection<AccessPolicy> groupPolicyEntities)
|
IReadOnlyCollection<AccessPolicy> groupPolicyEntities)
|
||||||
@ -466,13 +523,17 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
|
|||||||
baseAccessPolicyEntity switch
|
baseAccessPolicyEntity switch
|
||||||
{
|
{
|
||||||
UserProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserProjectAccessPolicy>(ap),
|
UserProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserProjectAccessPolicy>(ap),
|
||||||
GroupProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupProjectAccessPolicy>(ap),
|
UserSecretAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserSecretAccessPolicy>(ap),
|
||||||
ServiceAccountProjectAccessPolicy ap => Mapper
|
|
||||||
.Map<Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy>(ap),
|
|
||||||
UserServiceAccountAccessPolicy ap =>
|
UserServiceAccountAccessPolicy ap =>
|
||||||
Mapper.Map<Core.SecretsManager.Entities.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
|
GroupServiceAccountAccessPolicy ap => Mapper
|
||||||
.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap),
|
.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")
|
_ => throw new ArgumentException("Unsupported access policy type")
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -482,20 +543,26 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
|
|||||||
{
|
{
|
||||||
Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy => Mapper.Map<UserProjectAccessPolicy>(
|
Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy => Mapper.Map<UserProjectAccessPolicy>(
|
||||||
accessPolicy),
|
accessPolicy),
|
||||||
|
Core.SecretsManager.Entities.UserSecretAccessPolicy accessPolicy => Mapper.Map<UserSecretAccessPolicy>(
|
||||||
|
accessPolicy),
|
||||||
Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy => Mapper
|
Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy => Mapper
|
||||||
.Map<UserServiceAccountAccessPolicy>(accessPolicy),
|
.Map<UserServiceAccountAccessPolicy>(accessPolicy),
|
||||||
Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy => Mapper.Map<GroupProjectAccessPolicy>(
|
Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy => Mapper.Map<GroupProjectAccessPolicy>(
|
||||||
accessPolicy),
|
accessPolicy),
|
||||||
|
Core.SecretsManager.Entities.GroupSecretAccessPolicy accessPolicy => Mapper.Map<GroupSecretAccessPolicy>(
|
||||||
|
accessPolicy),
|
||||||
Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy => Mapper
|
Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy => Mapper
|
||||||
.Map<GroupServiceAccountAccessPolicy>(accessPolicy),
|
.Map<GroupServiceAccountAccessPolicy>(accessPolicy),
|
||||||
Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy => Mapper
|
Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy => Mapper
|
||||||
.Map<ServiceAccountProjectAccessPolicy>(accessPolicy),
|
.Map<ServiceAccountProjectAccessPolicy>(accessPolicy),
|
||||||
|
Core.SecretsManager.Entities.ServiceAccountSecretAccessPolicy accessPolicy => Mapper
|
||||||
|
.Map<ServiceAccountSecretAccessPolicy>(accessPolicy),
|
||||||
_ => throw new ArgumentException("Unsupported access policy type")
|
_ => 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)
|
||||||
{
|
{
|
||||||
switch (baseAccessPolicyEntity)
|
switch (baseAccessPolicyEntity)
|
||||||
{
|
{
|
||||||
@ -505,6 +572,12 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
|
|||||||
mapped.CurrentUserInGroup = currentUserInGroup;
|
mapped.CurrentUserInGroup = currentUserInGroup;
|
||||||
return mapped;
|
return mapped;
|
||||||
}
|
}
|
||||||
|
case GroupSecretAccessPolicy ap:
|
||||||
|
{
|
||||||
|
var mapped = Mapper.Map<Core.SecretsManager.Entities.GroupSecretAccessPolicy>(ap);
|
||||||
|
mapped.CurrentUserInGroup = currentUserInGroup;
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
case GroupServiceAccountAccessPolicy ap:
|
case GroupServiceAccountAccessPolicy ap:
|
||||||
{
|
{
|
||||||
var mapped = Mapper.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap);
|
var mapped = Mapper.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap);
|
||||||
|
@ -517,4 +517,85 @@ public class SecretAuthorizationHandlerTests
|
|||||||
|
|
||||||
Assert.Equal(expected, authzContext.HasSucceeded);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ public class AccessPoliciesController : Controller
|
|||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private readonly IProjectRepository _projectRepository;
|
private readonly IProjectRepository _projectRepository;
|
||||||
|
private readonly ISecretRepository _secretRepository;
|
||||||
private readonly IServiceAccountGrantedPolicyUpdatesQuery _serviceAccountGrantedPolicyUpdatesQuery;
|
private readonly IServiceAccountGrantedPolicyUpdatesQuery _serviceAccountGrantedPolicyUpdatesQuery;
|
||||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||||
private readonly IUpdateServiceAccountGrantedPoliciesCommand _updateServiceAccountGrantedPoliciesCommand;
|
private readonly IUpdateServiceAccountGrantedPoliciesCommand _updateServiceAccountGrantedPoliciesCommand;
|
||||||
@ -41,6 +42,7 @@ public class AccessPoliciesController : Controller
|
|||||||
IAccessPolicyRepository accessPolicyRepository,
|
IAccessPolicyRepository accessPolicyRepository,
|
||||||
IServiceAccountRepository serviceAccountRepository,
|
IServiceAccountRepository serviceAccountRepository,
|
||||||
IProjectRepository projectRepository,
|
IProjectRepository projectRepository,
|
||||||
|
ISecretRepository secretRepository,
|
||||||
IAccessClientQuery accessClientQuery,
|
IAccessClientQuery accessClientQuery,
|
||||||
IServiceAccountGrantedPolicyUpdatesQuery serviceAccountGrantedPolicyUpdatesQuery,
|
IServiceAccountGrantedPolicyUpdatesQuery serviceAccountGrantedPolicyUpdatesQuery,
|
||||||
IProjectServiceAccountsAccessPoliciesUpdatesQuery projectServiceAccountsAccessPoliciesUpdatesQuery,
|
IProjectServiceAccountsAccessPoliciesUpdatesQuery projectServiceAccountsAccessPoliciesUpdatesQuery,
|
||||||
@ -52,6 +54,7 @@ public class AccessPoliciesController : Controller
|
|||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_serviceAccountRepository = serviceAccountRepository;
|
_serviceAccountRepository = serviceAccountRepository;
|
||||||
_projectRepository = projectRepository;
|
_projectRepository = projectRepository;
|
||||||
|
_secretRepository = secretRepository;
|
||||||
_accessPolicyRepository = accessPolicyRepository;
|
_accessPolicyRepository = accessPolicyRepository;
|
||||||
_updateServiceAccountGrantedPoliciesCommand = updateServiceAccountGrantedPoliciesCommand;
|
_updateServiceAccountGrantedPoliciesCommand = updateServiceAccountGrantedPoliciesCommand;
|
||||||
_accessClientQuery = accessClientQuery;
|
_accessClientQuery = accessClientQuery;
|
||||||
@ -259,6 +262,22 @@ public class AccessPoliciesController : Controller
|
|||||||
return new ProjectServiceAccountsAccessPoliciesResponseModel(results);
|
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(
|
private async Task<(AccessClientType AccessClientType, Guid UserId)> CheckUserHasWriteAccessToProjectAsync(
|
||||||
Project project)
|
Project project)
|
||||||
{
|
{
|
||||||
|
@ -9,161 +9,133 @@ public abstract class BaseAccessPolicyResponseModel : ResponseModel
|
|||||||
{
|
{
|
||||||
protected BaseAccessPolicyResponseModel(BaseAccessPolicy baseAccessPolicy, string obj) : base(obj)
|
protected BaseAccessPolicyResponseModel(BaseAccessPolicy baseAccessPolicy, string obj) : base(obj)
|
||||||
{
|
{
|
||||||
Id = baseAccessPolicy.Id;
|
|
||||||
Read = baseAccessPolicy.Read;
|
Read = baseAccessPolicy.Read;
|
||||||
Write = baseAccessPolicy.Write;
|
Write = baseAccessPolicy.Write;
|
||||||
CreationDate = baseAccessPolicy.CreationDate;
|
|
||||||
RevisionDate = baseAccessPolicy.RevisionDate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
public bool Read { get; set; }
|
public bool Read { get; set; }
|
||||||
public bool Write { 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;
|
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)
|
public UserAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy, Guid currentUserId) : base(accessPolicy, _objectName)
|
||||||
{
|
|
||||||
SetProperties(accessPolicy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserProjectAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy, Guid currentUserId) : base(accessPolicy, _objectName)
|
|
||||||
{
|
{
|
||||||
CurrentUser = currentUserId == accessPolicy.User?.Id;
|
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 Guid? OrganizationUserId { get; set; }
|
||||||
public string? OrganizationUserName { get; set; }
|
public string? OrganizationUserName { get; set; }
|
||||||
public Guid? UserId { get; set; }
|
|
||||||
public Guid? GrantedProjectId { get; set; }
|
|
||||||
public bool? CurrentUser { 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)
|
public GroupAccessPolicyResponseModel(GroupProjectAccessPolicy 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)
|
|
||||||
: base(accessPolicy, _objectName)
|
: base(accessPolicy, _objectName)
|
||||||
{
|
{
|
||||||
GroupId = accessPolicy.GroupId;
|
GroupId = accessPolicy.GroupId;
|
||||||
GrantedProjectId = accessPolicy.GrantedProjectId;
|
|
||||||
GroupName = accessPolicy.Group?.Name;
|
GroupName = accessPolicy.Group?.Name;
|
||||||
CurrentUserInGroup = accessPolicy.CurrentUserInGroup;
|
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 Guid? GroupId { get; set; }
|
||||||
public string? GroupName { get; set; }
|
public string? GroupName { get; set; }
|
||||||
public bool? CurrentUserInGroup { get; set; }
|
public bool? CurrentUserInGroup { get; set; }
|
||||||
public Guid? GrantedProjectId { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GroupServiceAccountAccessPolicyResponseModel : BaseAccessPolicyResponseModel
|
public class ServiceAccountAccessPolicyResponseModel : 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
|
|
||||||
{
|
{
|
||||||
private const string _objectName = "serviceAccountProjectAccessPolicy";
|
private const string _objectName = "serviceAccountProjectAccessPolicy";
|
||||||
|
|
||||||
public ServiceAccountProjectAccessPolicyResponseModel(ServiceAccountProjectAccessPolicy accessPolicy)
|
public ServiceAccountAccessPolicyResponseModel(ServiceAccountProjectAccessPolicy accessPolicy)
|
||||||
: base(accessPolicy, _objectName)
|
: base(accessPolicy, _objectName)
|
||||||
{
|
{
|
||||||
ServiceAccountId = accessPolicy.ServiceAccountId;
|
ServiceAccountId = accessPolicy.ServiceAccountId;
|
||||||
GrantedProjectId = accessPolicy.GrantedProjectId;
|
|
||||||
ServiceAccountName = accessPolicy.ServiceAccount?.Name;
|
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)
|
: base(new ServiceAccountProjectAccessPolicy(), _objectName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid? ServiceAccountId { get; set; }
|
public Guid? ServiceAccountId { get; set; }
|
||||||
public string? ServiceAccountName { 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 Guid? GrantedProjectId { get; set; }
|
||||||
public string? GrantedProjectName { get; set; }
|
public string? GrantedProjectName { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -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; }
|
||||||
|
}
|
@ -15,10 +15,10 @@ public class ProjectPeopleAccessPoliciesResponseModel : ResponseModel
|
|||||||
switch (baseAccessPolicy)
|
switch (baseAccessPolicy)
|
||||||
{
|
{
|
||||||
case UserProjectAccessPolicy accessPolicy:
|
case UserProjectAccessPolicy accessPolicy:
|
||||||
UserAccessPolicies.Add(new UserProjectAccessPolicyResponseModel(accessPolicy, userId));
|
UserAccessPolicies.Add(new UserAccessPolicyResponseModel(accessPolicy, userId));
|
||||||
break;
|
break;
|
||||||
case GroupProjectAccessPolicy accessPolicy:
|
case GroupProjectAccessPolicy accessPolicy:
|
||||||
GroupAccessPolicies.Add(new GroupProjectAccessPolicyResponseModel(accessPolicy));
|
GroupAccessPolicies.Add(new GroupAccessPolicyResponseModel(accessPolicy));
|
||||||
break;
|
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();
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,12 @@ public class ProjectServiceAccountsAccessPoliciesResponseModel : ResponseModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
ServiceAccountAccessPolicies = projectServiceAccountsAccessPolicies.ServiceAccountAccessPolicies
|
ServiceAccountAccessPolicies = projectServiceAccountsAccessPolicies.ServiceAccountAccessPolicies
|
||||||
.Select(x => new ServiceAccountProjectAccessPolicyResponseModel(x)).ToList();
|
.Select(x => new ServiceAccountAccessPolicyResponseModel(x)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProjectServiceAccountsAccessPoliciesResponseModel() : base(_objectName)
|
public ProjectServiceAccountsAccessPoliciesResponseModel() : base(_objectName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ServiceAccountProjectAccessPolicyResponseModel> ServiceAccountAccessPolicies { get; set; } = [];
|
public List<ServiceAccountAccessPolicyResponseModel> ServiceAccountAccessPolicies { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
@ -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; } = [];
|
||||||
|
|
||||||
|
}
|
@ -18,13 +18,13 @@ public class ServiceAccountGrantedPoliciesPermissionDetailsResponseModel : Respo
|
|||||||
}
|
}
|
||||||
|
|
||||||
GrantedProjectPolicies = grantedPoliciesPermissionDetails.ProjectGrantedPolicies
|
GrantedProjectPolicies = grantedPoliciesPermissionDetails.ProjectGrantedPolicies
|
||||||
.Select(x => new ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel(x)).ToList();
|
.Select(x => new GrantedProjectAccessPolicyPermissionDetailsResponseModel(x)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceAccountGrantedPoliciesPermissionDetailsResponseModel() : base(_objectName)
|
public ServiceAccountGrantedPoliciesPermissionDetailsResponseModel() : base(_objectName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel> GrantedProjectPolicies { get; set; } =
|
public List<GrantedProjectAccessPolicyPermissionDetailsResponseModel> GrantedProjectPolicies { get; set; } =
|
||||||
[];
|
[];
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,10 @@ public class ServiceAccountPeopleAccessPoliciesResponseModel : ResponseModel
|
|||||||
switch (baseAccessPolicy)
|
switch (baseAccessPolicy)
|
||||||
{
|
{
|
||||||
case UserServiceAccountAccessPolicy accessPolicy:
|
case UserServiceAccountAccessPolicy accessPolicy:
|
||||||
UserAccessPolicies.Add(new UserServiceAccountAccessPolicyResponseModel(accessPolicy, userId));
|
UserAccessPolicies.Add(new UserAccessPolicyResponseModel(accessPolicy, userId));
|
||||||
break;
|
break;
|
||||||
case GroupServiceAccountAccessPolicy accessPolicy:
|
case GroupServiceAccountAccessPolicy accessPolicy:
|
||||||
GroupAccessPolicies.Add(new GroupServiceAccountAccessPolicyResponseModel(accessPolicy));
|
GroupAccessPolicies.Add(new GroupAccessPolicyResponseModel(accessPolicy));
|
||||||
break;
|
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();
|
||||||
}
|
}
|
||||||
|
@ -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; }
|
|
||||||
}
|
|
@ -12,4 +12,5 @@ public static class SecretOperations
|
|||||||
public static readonly SecretOperationRequirement Read = new() { Name = nameof(Read) };
|
public static readonly SecretOperationRequirement Read = new() { Name = nameof(Read) };
|
||||||
public static readonly SecretOperationRequirement Update = new() { Name = nameof(Update) };
|
public static readonly SecretOperationRequirement Update = new() { Name = nameof(Update) };
|
||||||
public static readonly SecretOperationRequirement Delete = new() { Name = nameof(Delete) };
|
public static readonly SecretOperationRequirement Delete = new() { Name = nameof(Delete) };
|
||||||
|
public static readonly SecretOperationRequirement ReadAccessPolicies = new() { Name = nameof(ReadAccessPolicies) };
|
||||||
}
|
}
|
||||||
|
35
src/Core/SecretsManager/Models/Data/SecretAccessPolicies.cs
Normal file
35
src/Core/SecretsManager/Models/Data/SecretAccessPolicies.cs
Normal 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; } = [];
|
||||||
|
}
|
@ -20,4 +20,5 @@ public interface IAccessPolicyRepository
|
|||||||
Task UpdateServiceAccountGrantedPoliciesAsync(ServiceAccountGrantedPoliciesUpdates policyUpdates);
|
Task UpdateServiceAccountGrantedPoliciesAsync(ServiceAccountGrantedPoliciesUpdates policyUpdates);
|
||||||
Task<ProjectServiceAccountsAccessPolicies?> GetProjectServiceAccountsAccessPoliciesAsync(Guid projectId);
|
Task<ProjectServiceAccountsAccessPolicies?> GetProjectServiceAccountsAccessPoliciesAsync(Guid projectId);
|
||||||
Task UpdateProjectServiceAccountsAccessPoliciesAsync(ProjectServiceAccountsAccessPoliciesUpdates updates);
|
Task UpdateProjectServiceAccountsAccessPoliciesAsync(ProjectServiceAccountsAccessPoliciesUpdates updates);
|
||||||
|
Task<SecretAccessPolicies?> GetSecretAccessPoliciesAsync(Guid secretId, Guid userId);
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
|||||||
private readonly IGroupRepository _groupRepository;
|
private readonly IGroupRepository _groupRepository;
|
||||||
private readonly LoginHelper _loginHelper;
|
private readonly LoginHelper _loginHelper;
|
||||||
private readonly IProjectRepository _projectRepository;
|
private readonly IProjectRepository _projectRepository;
|
||||||
|
private readonly ISecretRepository _secretRepository;
|
||||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||||
private string _email = null!;
|
private string _email = null!;
|
||||||
private SecretsManagerOrganizationHelper _organizationHelper = null!;
|
private SecretsManagerOrganizationHelper _organizationHelper = null!;
|
||||||
@ -37,6 +38,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
|||||||
_client = _factory.CreateClient();
|
_client = _factory.CreateClient();
|
||||||
_accessPolicyRepository = _factory.GetService<IAccessPolicyRepository>();
|
_accessPolicyRepository = _factory.GetService<IAccessPolicyRepository>();
|
||||||
_serviceAccountRepository = _factory.GetService<IServiceAccountRepository>();
|
_serviceAccountRepository = _factory.GetService<IServiceAccountRepository>();
|
||||||
|
_secretRepository = _factory.GetService<ISecretRepository>();
|
||||||
_projectRepository = _factory.GetService<IProjectRepository>();
|
_projectRepository = _factory.GetService<IProjectRepository>();
|
||||||
_groupRepository = _factory.GetService<IGroupRepository>();
|
_groupRepository = _factory.GetService<IGroupRepository>();
|
||||||
_loginHelper = new LoginHelper(_factory, _client);
|
_loginHelper = new LoginHelper(_factory, _client);
|
||||||
@ -723,9 +725,8 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
|||||||
|
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.NotEmpty(result.GrantedProjectPolicies);
|
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.GrantedProjectName);
|
||||||
|
Assert.NotNull(result.GrantedProjectPolicies.First().AccessPolicy.GrantedProjectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -922,7 +923,6 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
|||||||
Assert.NotEmpty(result.ServiceAccountAccessPolicies);
|
Assert.NotEmpty(result.ServiceAccountAccessPolicies);
|
||||||
Assert.Equal(initData.ServiceAccountId, result.ServiceAccountAccessPolicies.First().ServiceAccountId);
|
Assert.Equal(initData.ServiceAccountId, result.ServiceAccountAccessPolicies.First().ServiceAccountId);
|
||||||
Assert.NotNull(result.ServiceAccountAccessPolicies.First().ServiceAccountName);
|
Assert.NotNull(result.ServiceAccountAccessPolicies.First().ServiceAccountName);
|
||||||
Assert.NotNull(result.ServiceAccountAccessPolicies.First().GrantedProjectName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -1038,6 +1038,89 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
|||||||
Assert.Single(result.ServiceAccountAccessPolicies);
|
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(
|
private async Task<(Guid ProjectId, Guid ServiceAccountId)> CreateServiceAccountProjectAccessPolicyAsync(
|
||||||
Guid organizationId)
|
Guid organizationId)
|
||||||
{
|
{
|
||||||
@ -1290,4 +1373,31 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
|||||||
|
|
||||||
return (project, request);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -827,7 +827,6 @@ public class AccessPoliciesControllerTests
|
|||||||
SutProvider<AccessPoliciesController> sutProvider,
|
SutProvider<AccessPoliciesController> sutProvider,
|
||||||
Project data)
|
Project data)
|
||||||
{
|
{
|
||||||
// FIX ME
|
|
||||||
SetupProjectAccessPoliciesTest(sutProvider, data, accessClientType);
|
SetupProjectAccessPoliciesTest(sutProvider, data, accessClientType);
|
||||||
|
|
||||||
sutProvider.GetDependency<IAccessPolicyRepository>()
|
sutProvider.GetDependency<IAccessPolicyRepository>()
|
||||||
@ -953,6 +952,61 @@ public class AccessPoliciesControllerTests
|
|||||||
.UpdateAsync(Arg.Any<ProjectServiceAccountsAccessPoliciesUpdates>());
|
.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)
|
private static PeopleAccessPoliciesRequestModel SetRequestToCanReadWrite(PeopleAccessPoliciesRequestModel request)
|
||||||
{
|
{
|
||||||
foreach (var ap in request.UserAccessPolicyRequests)
|
foreach (var ap in request.UserAccessPolicyRequests)
|
||||||
@ -1005,4 +1059,13 @@ public class AccessPoliciesControllerTests
|
|||||||
.GetAccessClientAsync(Arg.Any<ClaimsPrincipal>(), Arg.Any<Guid>())
|
.GetAccessClientAsync(Arg.Any<ClaimsPrincipal>(), Arg.Any<Guid>())
|
||||||
.ReturnsForAnyArgs((accessClientType, Guid.NewGuid()));
|
.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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user