1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-04 01:22:50 -05:00

[AC-1748] Created GroupAuthorizationHandler and modified GroupsController.Get to use it if flexible collections feature flag is enabled

This commit is contained in:
Rui Tome
2023-10-19 12:34:55 +01:00
parent 2f974d6b06
commit 09be61669f
4 changed files with 147 additions and 10 deletions

View File

@ -1,7 +1,11 @@
using Bit.Api.Models.Request; using Bit.Api.Models.Request;
using Bit.Api.Models.Response; using Bit.Api.Models.Response;
using Bit.Api.Vault.AuthorizationHandlers.Groups;
using Bit.Core;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.OrganizationFeatures.Groups.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
@ -21,6 +25,10 @@ public class GroupsController : Controller
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly ICreateGroupCommand _createGroupCommand; private readonly ICreateGroupCommand _createGroupCommand;
private readonly IUpdateGroupCommand _updateGroupCommand; private readonly IUpdateGroupCommand _updateGroupCommand;
private readonly IFeatureService _featureService;
private readonly IAuthorizationService _authorizationService;
private bool FlexibleCollectionsIsEnabled => _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
public GroupsController( public GroupsController(
IGroupRepository groupRepository, IGroupRepository groupRepository,
@ -29,7 +37,9 @@ public class GroupsController : Controller
ICurrentContext currentContext, ICurrentContext currentContext,
ICreateGroupCommand createGroupCommand, ICreateGroupCommand createGroupCommand,
IUpdateGroupCommand updateGroupCommand, IUpdateGroupCommand updateGroupCommand,
IDeleteGroupCommand deleteGroupCommand) IDeleteGroupCommand deleteGroupCommand,
IFeatureService featureService,
IAuthorizationService authorizationService)
{ {
_groupRepository = groupRepository; _groupRepository = groupRepository;
_groupService = groupService; _groupService = groupService;
@ -38,6 +48,8 @@ public class GroupsController : Controller
_createGroupCommand = createGroupCommand; _createGroupCommand = createGroupCommand;
_updateGroupCommand = updateGroupCommand; _updateGroupCommand = updateGroupCommand;
_deleteGroupCommand = deleteGroupCommand; _deleteGroupCommand = deleteGroupCommand;
_featureService = featureService;
_authorizationService = authorizationService;
} }
[HttpGet("{id}")] [HttpGet("{id}")]
@ -65,20 +77,34 @@ public class GroupsController : Controller
} }
[HttpGet("")] [HttpGet("")]
public async Task<ListResponseModel<GroupDetailsResponseModel>> Get(string orgId) public async Task<ListResponseModel<GroupDetailsResponseModel>> Get(Guid orgId)
{ {
var orgIdGuid = new Guid(orgId); ICollection<Tuple<Group, ICollection<CollectionAccessSelection>>> groups;
var canAccess = await _currentContext.ManageGroups(orgIdGuid) ||
await _currentContext.ViewAssignedCollections(orgIdGuid) || if (FlexibleCollectionsIsEnabled)
await _currentContext.ViewAllCollections(orgIdGuid) || {
await _currentContext.ManageUsers(orgIdGuid); groups = await _groupRepository.GetManyWithCollectionsByOrganizationIdAsync(orgId);
var authorized = (await _authorizationService.AuthorizeAsync(User, groups.Select(g => g.Item1), GroupOperations.Read)).Succeeded;
if (!authorized)
{
throw new NotFoundException();
}
}
else
{
var canAccess = await _currentContext.ManageGroups(orgId) ||
await _currentContext.ViewAssignedCollections(orgId) ||
await _currentContext.ViewAllCollections(orgId) ||
await _currentContext.ManageUsers(orgId);
if (!canAccess) if (!canAccess)
{ {
throw new NotFoundException(); throw new NotFoundException();
} }
var groups = await _groupRepository.GetManyWithCollectionsByOrganizationIdAsync(orgIdGuid); groups = await _groupRepository.GetManyWithCollectionsByOrganizationIdAsync(orgId);
}
var responses = groups.Select(g => new GroupDetailsResponseModel(g.Item1, g.Item2)); var responses = groups.Select(g => new GroupDetailsResponseModel(g.Item1, g.Item2));
return new ListResponseModel<GroupDetailsResponseModel>(responses); return new ListResponseModel<GroupDetailsResponseModel>(responses);
} }

View File

@ -1,4 +1,6 @@
using Bit.Api.Vault.AuthorizationHandlers.Collections; using Bit.Api.Vault.AuthorizationHandlers.Collections;
using Bit.Api.Vault.AuthorizationHandlers.Groups;
using Bit.Api.Vault.AuthorizationHandlers.OrganizationUsers;
using Bit.Core.IdentityServer; using Bit.Core.IdentityServer;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@ -121,5 +123,7 @@ public static class ServiceCollectionExtensions
public static void AddAuthorizationHandlers(this IServiceCollection services) public static void AddAuthorizationHandlers(this IServiceCollection services)
{ {
services.AddScoped<IAuthorizationHandler, CollectionAuthorizationHandler>(); services.AddScoped<IAuthorizationHandler, CollectionAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, GroupAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, OrganizationUserAuthorizationHandler>();
} }
} }

View File

@ -0,0 +1,97 @@
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
namespace Bit.Api.Vault.AuthorizationHandlers.Groups;
/// <summary>
/// Handles authorization logic for Group objects.
/// This uses new logic implemented in the Flexible Collections initiative.
/// </summary>
public class GroupAuthorizationHandler : BulkAuthorizationHandler<GroupOperationRequirement, Group>
{
private readonly ICurrentContext _currentContext;
private readonly IFeatureService _featureService;
public GroupAuthorizationHandler(
ICurrentContext currentContext,
IFeatureService featureService)
{
_currentContext = currentContext;
_featureService = featureService;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
GroupOperationRequirement requirement, ICollection<Group> resources)
{
if (!_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext))
{
// Flexible collections is OFF, should not be using this handler
throw new FeatureUnavailableException("Flexible collections is OFF when it should be ON.");
}
// Establish pattern of authorization handler null checking passed resources
if (resources == null)
{
context.Fail();
return;
}
if (!resources.Any())
{
context.Succeed(requirement);
return;
}
if (!_currentContext.UserId.HasValue)
{
context.Fail();
return;
}
var targetOrganizationId = resources.First().OrganizationId;
// Ensure all target collections belong to the same organization
if (resources.Any(tc => tc.OrganizationId != targetOrganizationId))
{
throw new BadRequestException("Requested groups must belong to the same organization.");
}
// Acting user is not a member of the target organization, fail
var org = _currentContext.GetOrganization(targetOrganizationId);
if (org == null)
{
context.Fail();
return;
}
switch (requirement)
{
case not null when requirement == GroupOperations.Read:
await CanReadAsync(context, requirement, org);
break;
}
}
private async Task CanReadAsync(AuthorizationHandlerContext context, GroupOperationRequirement requirement,
CurrentContextOrganization org)
{
if (org.Type is OrganizationUserType.Owner or OrganizationUserType.Admin ||
org.Permissions.ManageGroups ||
org.Permissions.ManageUsers ||
org.Permissions.EditAnyCollection ||
org.Permissions.DeleteAnyCollection ||
await _currentContext.ProviderUserForOrgAsync(org.Id))
{
context.Succeed(requirement);
return;
}
context.Fail();
}
}

View File

@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Authorization.Infrastructure;
namespace Bit.Api.Vault.AuthorizationHandlers.Groups;
public class GroupOperationRequirement : OperationAuthorizationRequirement { }
public static class GroupOperations
{
public static readonly GroupOperationRequirement Read = new() { Name = nameof(Read) };
}