1
0
mirror of https://github.com/bitwarden/server synced 2025-12-14 23:33:41 +00:00

[SM-910] Add service account granted policies management endpoints (#3736)

* Add the ability to get multi projects access

* Add access policy helper + tests

* Add new data/request models

* Add access policy operations to repo

* Add authz handler for new operations

* Add new controller endpoints

* add updating service account revision
This commit is contained in:
Thomas Avery
2024-05-01 11:47:11 -05:00
committed by GitHub
parent a14646eaad
commit ebd88393c8
28 changed files with 1772 additions and 578 deletions

View File

@@ -7,6 +7,8 @@ using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.AuthorizationRequirements;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Queries.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Queries.Interfaces;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
@@ -26,6 +28,9 @@ public class AccessPoliciesController : Controller
private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IUpdateAccessPolicyCommand _updateAccessPolicyCommand;
private readonly IUpdateServiceAccountGrantedPoliciesCommand _updateServiceAccountGrantedPoliciesCommand;
private readonly IAccessClientQuery _accessClientQuery;
private readonly IServiceAccountGrantedPolicyUpdatesQuery _serviceAccountGrantedPolicyUpdatesQuery;
private readonly IUserService _userService;
private readonly IAuthorizationService _authorizationService;
@@ -36,6 +41,9 @@ public class AccessPoliciesController : Controller
IAccessPolicyRepository accessPolicyRepository,
IServiceAccountRepository serviceAccountRepository,
IProjectRepository projectRepository,
IAccessClientQuery accessClientQuery,
IServiceAccountGrantedPolicyUpdatesQuery serviceAccountGrantedPolicyUpdatesQuery,
IUpdateServiceAccountGrantedPoliciesCommand updateServiceAccountGrantedPoliciesCommand,
ICreateAccessPoliciesCommand createAccessPoliciesCommand,
IDeleteAccessPolicyCommand deleteAccessPolicyCommand,
IUpdateAccessPolicyCommand updateAccessPolicyCommand)
@@ -49,6 +57,9 @@ public class AccessPoliciesController : Controller
_createAccessPoliciesCommand = createAccessPoliciesCommand;
_deleteAccessPolicyCommand = deleteAccessPolicyCommand;
_updateAccessPolicyCommand = updateAccessPolicyCommand;
_updateServiceAccountGrantedPoliciesCommand = updateServiceAccountGrantedPoliciesCommand;
_accessClientQuery = accessClientQuery;
_serviceAccountGrantedPolicyUpdatesQuery = serviceAccountGrantedPolicyUpdatesQuery;
}
[HttpPost("/projects/{id}/access-policies")]
@@ -89,61 +100,6 @@ public class AccessPoliciesController : Controller
return new ProjectAccessPoliciesResponseModel(results);
}
[HttpPost("/service-accounts/{id}/granted-policies")]
public async Task<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>
CreateServiceAccountGrantedPoliciesAsync([FromRoute] Guid id,
[FromBody] List<GrantedAccessPolicyRequest> requests)
{
if (requests.Count > _maxBulkCreation)
{
throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once.");
}
if (requests.Count != requests.DistinctBy(request => request.GrantedId).Count())
{
throw new BadRequestException("Resources must be unique");
}
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
if (serviceAccount == null)
{
throw new NotFoundException();
}
var policies = requests.Select(request => request.ToServiceAccountProjectAccessPolicy(id, serviceAccount.OrganizationId)).ToList();
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(new List<BaseAccessPolicy>(policies));
var responses = results.Select(ap =>
new ServiceAccountProjectAccessPolicyResponseModel((ServiceAccountProjectAccessPolicy)ap));
return new ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>(responses);
}
[HttpGet("/service-accounts/{id}/granted-policies")]
public async Task<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>
GetServiceAccountGrantedPoliciesAsync([FromRoute] Guid id)
{
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
if (serviceAccount == null)
{
throw new NotFoundException();
}
var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId);
var results = await _accessPolicyRepository.GetManyByServiceAccountIdAsync(id, userId, accessClient);
var responses = results.Select(ap =>
new ServiceAccountProjectAccessPolicyResponseModel((ServiceAccountProjectAccessPolicy)ap));
return new ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>(responses);
}
[HttpPut("{id}")]
public async Task<BaseAccessPolicyResponseModel> UpdateAccessPolicyAsync([FromRoute] Guid id,
[FromBody] AccessPolicyUpdateRequest request)
@@ -303,6 +259,43 @@ public class AccessPoliciesController : Controller
return new ServiceAccountPeopleAccessPoliciesResponseModel(results, userId);
}
[HttpGet("/service-accounts/{id}/granted-policies")]
public async Task<ServiceAccountGrantedPoliciesPermissionDetailsResponseModel>
GetServiceAccountGrantedPoliciesAsync([FromRoute] Guid id)
{
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
var authorizationResult =
await _authorizationService.AuthorizeAsync(User, serviceAccount, ServiceAccountOperations.Update);
if (!authorizationResult.Succeeded)
{
throw new NotFoundException();
}
return await GetServiceAccountGrantedPoliciesAsync(serviceAccount);
}
[HttpPut("/service-accounts/{id}/granted-policies")]
public async Task<ServiceAccountGrantedPoliciesPermissionDetailsResponseModel>
PutServiceAccountGrantedPoliciesAsync([FromRoute] Guid id,
[FromBody] ServiceAccountGrantedPoliciesRequestModel request)
{
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id) ?? throw new NotFoundException();
var grantedPoliciesUpdates =
await _serviceAccountGrantedPolicyUpdatesQuery.GetAsync(request.ToGrantedPolicies(serviceAccount));
var authorizationResult = await _authorizationService.AuthorizeAsync(User, grantedPoliciesUpdates,
ServiceAccountGrantedPoliciesOperations.Updates);
if (!authorizationResult.Succeeded)
{
throw new NotFoundException();
}
await _updateServiceAccountGrantedPoliciesCommand.UpdateAsync(grantedPoliciesUpdates);
return await GetServiceAccountGrantedPoliciesAsync(serviceAccount);
}
private async Task<(AccessClientType AccessClientType, Guid UserId)> CheckUserHasWriteAccessToProjectAsync(Project project)
{
if (project == null)
@@ -355,4 +348,11 @@ public class AccessPoliciesController : Controller
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
return (accessClient, userId);
}
private async Task<ServiceAccountGrantedPoliciesPermissionDetailsResponseModel> GetServiceAccountGrantedPoliciesAsync(ServiceAccount serviceAccount)
{
var (accessClient, userId) = await _accessClientQuery.GetAccessClientAsync(User, serviceAccount.OrganizationId);
var results = await _accessPolicyRepository.GetServiceAccountGrantedPoliciesPermissionDetailsAsync(serviceAccount.Id, userId, accessClient);
return new ServiceAccountGrantedPoliciesPermissionDetailsResponseModel(results);
}
}