1
0
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:
Thomas Avery
2023-12-07 15:35:16 -06:00
committed by GitHub
parent a589af3588
commit f9232bcbb0
19 changed files with 1154 additions and 626 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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