mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
[SM-909] Add service-account people access policy management endpoints (#3324)
* refactoring replace logic * model for policies + authz handler + unit tests * update AP repository * add new endpoints to controller * update unit tests and integration tests --------- Co-authored-by: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com>
This commit is contained in:
@ -89,46 +89,6 @@ public class AccessPoliciesController : Controller
|
||||
return new ProjectAccessPoliciesResponseModel(results);
|
||||
}
|
||||
|
||||
[HttpPost("/service-accounts/{id}/access-policies")]
|
||||
public async Task<ServiceAccountAccessPoliciesResponseModel> CreateServiceAccountAccessPoliciesAsync(
|
||||
[FromRoute] Guid id,
|
||||
[FromBody] AccessPoliciesCreateRequest request)
|
||||
{
|
||||
if (request.Count() > _maxBulkCreation)
|
||||
{
|
||||
throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once.");
|
||||
}
|
||||
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
if (serviceAccount == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var policies = request.ToBaseAccessPoliciesForServiceAccount(id, serviceAccount.OrganizationId);
|
||||
foreach (var policy in policies)
|
||||
{
|
||||
var authorizationResult = await _authorizationService.AuthorizeAsync(User, policy, AccessPolicyOperations.Create);
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
var results = await _createAccessPoliciesCommand.CreateManyAsync(policies);
|
||||
return new ServiceAccountAccessPoliciesResponseModel(results);
|
||||
}
|
||||
|
||||
[HttpGet("/service-accounts/{id}/access-policies")]
|
||||
public async Task<ServiceAccountAccessPoliciesResponseModel> GetServiceAccountAccessPoliciesAsync(
|
||||
[FromRoute] Guid id)
|
||||
{
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
var (_, userId) = await CheckUserHasWriteAccessToServiceAccountAsync(serviceAccount);
|
||||
var results = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(id, userId);
|
||||
return new ServiceAccountAccessPoliciesResponseModel(results);
|
||||
}
|
||||
|
||||
[HttpPost("/service-accounts/{id}/granted-policies")]
|
||||
public async Task<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>
|
||||
CreateServiceAccountGrantedPoliciesAsync([FromRoute] Guid id,
|
||||
@ -308,6 +268,40 @@ public class AccessPoliciesController : Controller
|
||||
return new ProjectPeopleAccessPoliciesResponseModel(results, userId);
|
||||
}
|
||||
|
||||
[HttpGet("/service-accounts/{id}/access-policies/people")]
|
||||
public async Task<ServiceAccountPeopleAccessPoliciesResponseModel> GetServiceAccountPeopleAccessPoliciesAsync(
|
||||
[FromRoute] Guid id)
|
||||
{
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
var (_, userId) = await CheckUserHasWriteAccessToServiceAccountAsync(serviceAccount);
|
||||
var results = await _accessPolicyRepository.GetPeoplePoliciesByGrantedServiceAccountIdAsync(id, userId);
|
||||
return new ServiceAccountPeopleAccessPoliciesResponseModel(results, userId);
|
||||
}
|
||||
|
||||
[HttpPut("/service-accounts/{id}/access-policies/people")]
|
||||
public async Task<ServiceAccountPeopleAccessPoliciesResponseModel> PutServiceAccountPeopleAccessPoliciesAsync(
|
||||
[FromRoute] Guid id,
|
||||
[FromBody] PeopleAccessPoliciesRequestModel request)
|
||||
{
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
|
||||
if (serviceAccount == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var peopleAccessPolicies = request.ToServiceAccountPeopleAccessPolicies(id, serviceAccount.OrganizationId);
|
||||
|
||||
var authorizationResult = await _authorizationService.AuthorizeAsync(User, peopleAccessPolicies,
|
||||
ServiceAccountPeopleAccessPoliciesOperations.Replace);
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User)!.Value;
|
||||
var results = await _accessPolicyRepository.ReplaceServiceAccountPeopleAsync(peopleAccessPolicies, userId);
|
||||
return new ServiceAccountPeopleAccessPoliciesResponseModel(results, userId);
|
||||
}
|
||||
|
||||
private async Task<(AccessClientType AccessClientType, Guid UserId)> CheckUserHasWriteAccessToProjectAsync(Project project)
|
||||
{
|
||||
@ -345,6 +339,7 @@ public class AccessPoliciesController : Controller
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return (accessClient, userId);
|
||||
}
|
||||
|
||||
|
@ -61,4 +61,39 @@ public class PeopleAccessPoliciesRequestModel
|
||||
GroupAccessPolicies = groupAccessPolicies
|
||||
};
|
||||
}
|
||||
|
||||
public ServiceAccountPeopleAccessPolicies ToServiceAccountPeopleAccessPolicies(Guid grantedServiceAccountId, Guid organizationId)
|
||||
{
|
||||
var userAccessPolicies = UserAccessPolicyRequests?
|
||||
.Select(x => x.ToUserServiceAccountAccessPolicy(grantedServiceAccountId, organizationId)).ToList();
|
||||
|
||||
var groupAccessPolicies = GroupAccessPolicyRequests?
|
||||
.Select(x => x.ToGroupServiceAccountAccessPolicy(grantedServiceAccountId, organizationId)).ToList();
|
||||
|
||||
var policies = new List<BaseAccessPolicy>();
|
||||
if (userAccessPolicies != null)
|
||||
{
|
||||
policies.AddRange(userAccessPolicies);
|
||||
}
|
||||
|
||||
if (groupAccessPolicies != null)
|
||||
{
|
||||
policies.AddRange(groupAccessPolicies);
|
||||
}
|
||||
|
||||
CheckForDistinctAccessPolicies(policies);
|
||||
|
||||
if (!policies.All(ap => ap.Read && ap.Write))
|
||||
{
|
||||
throw new BadRequestException("Service account access must be Can read, write");
|
||||
}
|
||||
|
||||
return new ServiceAccountPeopleAccessPolicies
|
||||
{
|
||||
Id = grantedServiceAccountId,
|
||||
OrganizationId = organizationId,
|
||||
UserAccessPolicies = userAccessPolicies,
|
||||
GroupAccessPolicies = groupAccessPolicies
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -69,10 +69,14 @@ public class UserServiceAccountAccessPolicyResponseModel : BaseAccessPolicyRespo
|
||||
public UserServiceAccountAccessPolicyResponseModel(UserServiceAccountAccessPolicy accessPolicy)
|
||||
: base(accessPolicy, _objectName)
|
||||
{
|
||||
OrganizationUserId = accessPolicy.OrganizationUserId;
|
||||
GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId;
|
||||
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
|
||||
UserId = accessPolicy.User?.Id;
|
||||
SetProperties(accessPolicy);
|
||||
}
|
||||
|
||||
public UserServiceAccountAccessPolicyResponseModel(UserServiceAccountAccessPolicy accessPolicy, Guid userId)
|
||||
: base(accessPolicy, _objectName)
|
||||
{
|
||||
SetProperties(accessPolicy);
|
||||
CurrentUser = accessPolicy.User?.Id == userId;
|
||||
}
|
||||
|
||||
public UserServiceAccountAccessPolicyResponseModel() : base(new UserServiceAccountAccessPolicy(), _objectName)
|
||||
@ -83,6 +87,15 @@ public class UserServiceAccountAccessPolicyResponseModel : BaseAccessPolicyRespo
|
||||
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
|
||||
|
@ -3,11 +3,11 @@ using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class ServiceAccountAccessPoliciesResponseModel : ResponseModel
|
||||
public class ServiceAccountPeopleAccessPoliciesResponseModel : ResponseModel
|
||||
{
|
||||
private const string _objectName = "serviceAccountAccessPolicies";
|
||||
|
||||
public ServiceAccountAccessPoliciesResponseModel(IEnumerable<BaseAccessPolicy> baseAccessPolicies)
|
||||
public ServiceAccountPeopleAccessPoliciesResponseModel(IEnumerable<BaseAccessPolicy> baseAccessPolicies, Guid userId)
|
||||
: base(_objectName)
|
||||
{
|
||||
if (baseAccessPolicies == null)
|
||||
@ -20,7 +20,7 @@ public class ServiceAccountAccessPoliciesResponseModel : ResponseModel
|
||||
switch (baseAccessPolicy)
|
||||
{
|
||||
case UserServiceAccountAccessPolicy accessPolicy:
|
||||
UserAccessPolicies.Add(new UserServiceAccountAccessPolicyResponseModel(accessPolicy));
|
||||
UserAccessPolicies.Add(new UserServiceAccountAccessPolicyResponseModel(accessPolicy, userId));
|
||||
break;
|
||||
case GroupServiceAccountAccessPolicy accessPolicy:
|
||||
GroupAccessPolicies.Add(new GroupServiceAccountAccessPolicyResponseModel(accessPolicy));
|
||||
@ -29,7 +29,7 @@ public class ServiceAccountAccessPoliciesResponseModel : ResponseModel
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceAccountAccessPoliciesResponseModel() : base(_objectName)
|
||||
public ServiceAccountPeopleAccessPoliciesResponseModel() : base(_objectName)
|
||||
{
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||
|
||||
namespace Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||
|
||||
public class ServiceAccountPeopleAccessPoliciesOperationRequirement : OperationAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
|
||||
public static class ServiceAccountPeopleAccessPoliciesOperations
|
||||
{
|
||||
public static readonly ServiceAccountPeopleAccessPoliciesOperationRequirement Replace = new() { Name = nameof(Replace) };
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
public class ServiceAccountPeopleAccessPolicies
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public IEnumerable<UserServiceAccountAccessPolicy> UserAccessPolicies { get; set; }
|
||||
public IEnumerable<GroupServiceAccountAccessPolicy> GroupAccessPolicies { get; set; }
|
||||
|
||||
public IEnumerable<BaseAccessPolicy> ToBaseAccessPolicies()
|
||||
{
|
||||
var policies = new List<BaseAccessPolicy>();
|
||||
if (UserAccessPolicies != null && UserAccessPolicies.Any())
|
||||
{
|
||||
policies.AddRange(UserAccessPolicies);
|
||||
}
|
||||
|
||||
if (GroupAccessPolicies != null && GroupAccessPolicies.Any())
|
||||
{
|
||||
policies.AddRange(GroupAccessPolicies);
|
||||
}
|
||||
|
||||
return policies;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace Bit.Core.SecretsManager.Queries.AccessPolicies.Interfaces;
|
||||
|
||||
public interface ISameOrganizationQuery
|
||||
{
|
||||
Task<bool> OrgUsersInTheSameOrgAsync(List<Guid> organizationUserIds, Guid organizationId);
|
||||
Task<bool> GroupsInTheSameOrgAsync(List<Guid> groupIds, Guid organizationId);
|
||||
}
|
@ -11,7 +11,6 @@ public interface IAccessPolicyRepository
|
||||
Task<bool> AccessPolicyExists(BaseAccessPolicy baseAccessPolicy);
|
||||
Task<BaseAccessPolicy?> GetByIdAsync(Guid id);
|
||||
Task<IEnumerable<BaseAccessPolicy>> GetManyByGrantedProjectIdAsync(Guid id, Guid userId);
|
||||
Task<IEnumerable<BaseAccessPolicy>> GetManyByGrantedServiceAccountIdAsync(Guid id, Guid userId);
|
||||
Task<IEnumerable<BaseAccessPolicy>> GetManyByServiceAccountIdAsync(Guid id, Guid userId,
|
||||
AccessClientType accessType);
|
||||
Task ReplaceAsync(BaseAccessPolicy baseAccessPolicy);
|
||||
@ -19,4 +18,6 @@ public interface IAccessPolicyRepository
|
||||
Task<IEnumerable<BaseAccessPolicy>> GetPeoplePoliciesByGrantedProjectIdAsync(Guid id, Guid userId);
|
||||
Task<IEnumerable<BaseAccessPolicy>> ReplaceProjectPeopleAsync(ProjectPeopleAccessPolicies peopleAccessPolicies, Guid userId);
|
||||
Task<PeopleGrantees> GetPeopleGranteesAsync(Guid organizationId, Guid currentUserId);
|
||||
Task<IEnumerable<BaseAccessPolicy>> GetPeoplePoliciesByGrantedServiceAccountIdAsync(Guid id, Guid userId);
|
||||
Task<IEnumerable<BaseAccessPolicy>> ReplaceServiceAccountPeopleAsync(ServiceAccountPeopleAccessPolicies peopleAccessPolicies, Guid userId);
|
||||
}
|
||||
|
Reference in New Issue
Block a user