mirror of
https://github.com/bitwarden/server.git
synced 2025-07-03 09:02:48 -05:00
Restore old logic behind flags
This commit is contained in:
@ -1,12 +1,14 @@
|
|||||||
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.Collections;
|
using Bit.Api.Vault.AuthorizationHandlers.Collections;
|
||||||
|
using Bit.Core;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces;
|
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@ -23,6 +25,7 @@ public class CollectionsController : Controller
|
|||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private readonly IBulkAddCollectionAccessCommand _bulkAddCollectionAccessCommand;
|
private readonly IBulkAddCollectionAccessCommand _bulkAddCollectionAccessCommand;
|
||||||
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
public CollectionsController(
|
public CollectionsController(
|
||||||
ICollectionRepository collectionRepository,
|
ICollectionRepository collectionRepository,
|
||||||
@ -31,7 +34,8 @@ public class CollectionsController : Controller
|
|||||||
IUserService userService,
|
IUserService userService,
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
IBulkAddCollectionAccessCommand bulkAddCollectionAccessCommand)
|
IBulkAddCollectionAccessCommand bulkAddCollectionAccessCommand,
|
||||||
|
IFeatureService featureService)
|
||||||
{
|
{
|
||||||
_collectionRepository = collectionRepository;
|
_collectionRepository = collectionRepository;
|
||||||
_collectionService = collectionService;
|
_collectionService = collectionService;
|
||||||
@ -40,6 +44,7 @@ public class CollectionsController : Controller
|
|||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_bulkAddCollectionAccessCommand = bulkAddCollectionAccessCommand;
|
_bulkAddCollectionAccessCommand = bulkAddCollectionAccessCommand;
|
||||||
|
_featureService = featureService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
@ -152,8 +157,10 @@ public class CollectionsController : Controller
|
|||||||
{
|
{
|
||||||
var collection = model.ToCollection(orgId);
|
var collection = model.ToCollection(orgId);
|
||||||
|
|
||||||
var result = await _authorizationService.AuthorizeAsync(User, collection, CollectionOperations.Create);
|
var authorized = FlexibleCollectionsIsEnabled()
|
||||||
if (!result.Succeeded)
|
? (await _authorizationService.AuthorizeAsync(User, collection, CollectionOperations.Create)).Succeeded
|
||||||
|
: await CanCreateCollection(orgId, collection.Id) && await CanEditCollectionAsync(orgId, collection.Id);
|
||||||
|
if (!authorized)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
@ -221,8 +228,11 @@ public class CollectionsController : Controller
|
|||||||
public async Task Delete(Guid orgId, Guid id)
|
public async Task Delete(Guid orgId, Guid id)
|
||||||
{
|
{
|
||||||
var collection = await GetCollectionAsync(id, orgId);
|
var collection = await GetCollectionAsync(id, orgId);
|
||||||
var result = await _authorizationService.AuthorizeAsync(User, collection, CollectionOperations.Delete);
|
|
||||||
if (!result.Succeeded)
|
var authorized = FlexibleCollectionsIsEnabled()
|
||||||
|
? (await _authorizationService.AuthorizeAsync(User, collection, CollectionOperations.Delete)).Succeeded
|
||||||
|
: await CanDeleteCollectionAsync(orgId, id);
|
||||||
|
if (!authorized)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
@ -232,8 +242,11 @@ public class CollectionsController : Controller
|
|||||||
|
|
||||||
[HttpDelete("")]
|
[HttpDelete("")]
|
||||||
[HttpPost("delete")]
|
[HttpPost("delete")]
|
||||||
public async Task DeleteMany([FromBody] CollectionBulkDeleteRequestModel model)
|
public async Task DeleteMany(Guid orgId, [FromBody] CollectionBulkDeleteRequestModel model)
|
||||||
{
|
{
|
||||||
|
if (FlexibleCollectionsIsEnabled())
|
||||||
|
{
|
||||||
|
// New flexible collections logic
|
||||||
var collections = await _collectionRepository.GetManyByManyIdsAsync(model.Ids);
|
var collections = await _collectionRepository.GetManyByManyIdsAsync(model.Ids);
|
||||||
var result = await _authorizationService.AuthorizeAsync(User, collections, CollectionOperations.Delete);
|
var result = await _authorizationService.AuthorizeAsync(User, collections, CollectionOperations.Delete);
|
||||||
if (!result.Succeeded)
|
if (!result.Succeeded)
|
||||||
@ -244,6 +257,24 @@ public class CollectionsController : Controller
|
|||||||
await _deleteCollectionCommand.DeleteManyAsync(collections);
|
await _deleteCollectionCommand.DeleteManyAsync(collections);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Old pre-flexible collections logic follows
|
||||||
|
if (!await _currentContext.DeleteAssignedCollections(orgId) && !await DeleteAnyCollection(orgId))
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var userCollections = await _collectionService.GetOrganizationCollectionsAsync(orgId);
|
||||||
|
var filteredCollections = userCollections
|
||||||
|
.Where(c => model.Ids.Contains(c.Id) && c.OrganizationId == orgId);
|
||||||
|
|
||||||
|
if (!filteredCollections.Any())
|
||||||
|
{
|
||||||
|
throw new BadRequestException("No collections found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await _deleteCollectionCommand.DeleteManyAsync(filteredCollections);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpDelete("{id}/user/{orgUserId}")]
|
[HttpDelete("{id}/user/{orgUserId}")]
|
||||||
[HttpPost("{id}/delete-user/{orgUserId}")]
|
[HttpPost("{id}/delete-user/{orgUserId}")]
|
||||||
public async Task Delete(string orgId, string id, string orgUserId)
|
public async Task Delete(string orgId, string id, string orgUserId)
|
||||||
@ -272,6 +303,33 @@ public class CollectionsController : Controller
|
|||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool FlexibleCollectionsIsEnabled()
|
||||||
|
{
|
||||||
|
return _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeprecatedPermissionsGuard()
|
||||||
|
{
|
||||||
|
if (FlexibleCollectionsIsEnabled())
|
||||||
|
{
|
||||||
|
throw new FeatureUnavailableException("Flexible Collections is ON when it should be OFF.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")]
|
||||||
|
private async Task<bool> CanCreateCollection(Guid orgId, Guid collectionId)
|
||||||
|
{
|
||||||
|
DeprecatedPermissionsGuard();
|
||||||
|
|
||||||
|
if (collectionId != default)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _currentContext.OrganizationManager(orgId) || (_currentContext.Organizations?.Any(o => o.Id == orgId &&
|
||||||
|
(o.Permissions?.CreateNewCollections ?? false)) ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<bool> CanEditCollectionAsync(Guid orgId, Guid collectionId)
|
private async Task<bool> CanEditCollectionAsync(Guid orgId, Guid collectionId)
|
||||||
{
|
{
|
||||||
if (collectionId == default)
|
if (collectionId == default)
|
||||||
@ -294,6 +352,41 @@ public class CollectionsController : Controller
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")]
|
||||||
|
private async Task<bool> CanDeleteCollectionAsync(Guid orgId, Guid collectionId)
|
||||||
|
{
|
||||||
|
DeprecatedPermissionsGuard();
|
||||||
|
|
||||||
|
if (collectionId == default)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await DeleteAnyCollection(orgId))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await _currentContext.DeleteAssignedCollections(orgId))
|
||||||
|
{
|
||||||
|
var collectionDetails =
|
||||||
|
await _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value);
|
||||||
|
return collectionDetails != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")]
|
||||||
|
private async Task<bool> DeleteAnyCollection(Guid orgId)
|
||||||
|
{
|
||||||
|
DeprecatedPermissionsGuard();
|
||||||
|
|
||||||
|
return await _currentContext.OrganizationAdmin(orgId) ||
|
||||||
|
(_currentContext.Organizations?.Any(o => o.Id == orgId
|
||||||
|
&& (o.Permissions?.DeleteAnyCollection ?? false)) ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<bool> CanViewCollectionAsync(Guid orgId, Guid collectionId)
|
private async Task<bool> CanViewCollectionAsync(Guid orgId, Guid collectionId)
|
||||||
{
|
{
|
||||||
if (collectionId == default)
|
if (collectionId == default)
|
||||||
|
@ -1,27 +1,42 @@
|
|||||||
using Bit.Core.Context;
|
using Bit.Core;
|
||||||
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
namespace Bit.Api.Vault.AuthorizationHandlers.Collections;
|
namespace Bit.Api.Vault.AuthorizationHandlers.Collections;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles authorization logic for Collection objects, including access permissions for users and groups.
|
||||||
|
/// This uses new logic implemented in the Flexible Collections initiative.
|
||||||
|
/// </summary>
|
||||||
public class CollectionAuthorizationHandler : BulkAuthorizationHandler<CollectionOperationRequirement, Collection>
|
public class CollectionAuthorizationHandler : BulkAuthorizationHandler<CollectionOperationRequirement, Collection>
|
||||||
{
|
{
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private readonly ICollectionRepository _collectionRepository;
|
private readonly ICollectionRepository _collectionRepository;
|
||||||
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
public CollectionAuthorizationHandler(ICurrentContext currentContext, ICollectionRepository collectionRepository)
|
public CollectionAuthorizationHandler(ICurrentContext currentContext, ICollectionRepository collectionRepository,
|
||||||
|
IFeatureService featureService)
|
||||||
{
|
{
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_collectionRepository = collectionRepository;
|
_collectionRepository = collectionRepository;
|
||||||
|
_featureService = featureService;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
|
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
|
||||||
CollectionOperationRequirement requirement, ICollection<Collection> resources)
|
CollectionOperationRequirement requirement, ICollection<Collection> 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
|
// Establish pattern of authorization handler null checking passed resources
|
||||||
if (resources == null || !resources.Any())
|
if (resources == null || !resources.Any())
|
||||||
{
|
{
|
||||||
|
@ -41,6 +41,7 @@ public static class FeatureFlagKeys
|
|||||||
public const string AutofillV2 = "autofill-v2";
|
public const string AutofillV2 = "autofill-v2";
|
||||||
public const string BrowserFilelessImport = "browser-fileless-import";
|
public const string BrowserFilelessImport = "browser-fileless-import";
|
||||||
public const string FlexibleCollections = "flexible-collections";
|
public const string FlexibleCollections = "flexible-collections";
|
||||||
|
public const string BulkCollectionAccess = "bulk-collection-access";
|
||||||
|
|
||||||
public static List<string> GetAllKeys()
|
public static List<string> GetAllKeys()
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user