mirror of
https://github.com/bitwarden/server.git
synced 2024-11-21 12:05:42 +01:00
[AC-2655] Remove old permissions logic from CollectionsController (#4185)
* Replace all old methods with vNext methods * Remove remaining Flexible Collections checks and remove helper method * Remove unused private methods * Update tests
This commit is contained in:
parent
3ad4bc1cab
commit
c375c18257
@ -4,7 +4,6 @@ using Bit.Api.Utilities;
|
|||||||
using Bit.Api.Vault.AuthorizationHandlers.Collections;
|
using Bit.Api.Vault.AuthorizationHandlers.Collections;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces;
|
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces;
|
||||||
@ -26,8 +25,6 @@ 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 IOrganizationUserRepository _organizationUserRepository;
|
|
||||||
private readonly IApplicationCacheService _applicationCacheService;
|
|
||||||
|
|
||||||
public CollectionsController(
|
public CollectionsController(
|
||||||
ICollectionRepository collectionRepository,
|
ICollectionRepository collectionRepository,
|
||||||
@ -36,511 +33,21 @@ public class CollectionsController : Controller
|
|||||||
IUserService userService,
|
IUserService userService,
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
IBulkAddCollectionAccessCommand bulkAddCollectionAccessCommand,
|
IBulkAddCollectionAccessCommand bulkAddCollectionAccessCommand)
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
|
||||||
IApplicationCacheService applicationCacheService)
|
|
||||||
{
|
{
|
||||||
_collectionRepository = collectionRepository;
|
_collectionRepository = collectionRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
|
||||||
_collectionService = collectionService;
|
_collectionService = collectionService;
|
||||||
_deleteCollectionCommand = deleteCollectionCommand;
|
_deleteCollectionCommand = deleteCollectionCommand;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_bulkAddCollectionAccessCommand = bulkAddCollectionAccessCommand;
|
_bulkAddCollectionAccessCommand = bulkAddCollectionAccessCommand;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
|
||||||
_applicationCacheService = applicationCacheService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
public async Task<CollectionResponseModel> Get(Guid orgId, Guid id)
|
public async Task<CollectionResponseModel> Get(Guid orgId, Guid id)
|
||||||
{
|
{
|
||||||
if (await FlexibleCollectionsIsEnabledAsync(orgId))
|
var collection = await _collectionRepository.GetByIdAsync(id);
|
||||||
{
|
|
||||||
// New flexible collections logic
|
|
||||||
return await Get_vNext(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old pre-flexible collections logic follows
|
|
||||||
if (!await CanViewCollectionAsync(orgId, id))
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var collection = await GetCollectionAsync(id, orgId);
|
|
||||||
|
|
||||||
return new CollectionResponseModel(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id}/details")]
|
|
||||||
public async Task<CollectionAccessDetailsResponseModel> GetDetails(Guid orgId, Guid id)
|
|
||||||
{
|
|
||||||
if (await FlexibleCollectionsIsEnabledAsync(orgId))
|
|
||||||
{
|
|
||||||
// New flexible collections logic
|
|
||||||
return await GetDetails_vNext(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old pre-flexible collections logic follows
|
|
||||||
if (!await ViewAtLeastOneCollectionAsync(orgId) && !await _currentContext.ManageUsers(orgId))
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await _currentContext.ViewAllCollections(orgId))
|
|
||||||
{
|
|
||||||
(var collection, var access) = await _collectionRepository.GetByIdWithAccessAsync(id);
|
|
||||||
if (collection == null || collection.OrganizationId != orgId)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CollectionAccessDetailsResponseModel(collection, access.Groups, access.Users);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
(var collection, var access) = await _collectionRepository.GetByIdWithAccessAsync(id,
|
|
||||||
_currentContext.UserId.Value, false);
|
|
||||||
if (collection == null || collection.OrganizationId != orgId)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CollectionAccessDetailsResponseModel(collection, access.Groups, access.Users);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("details")]
|
|
||||||
public async Task<ListResponseModel<CollectionAccessDetailsResponseModel>> GetManyWithDetails(Guid orgId)
|
|
||||||
{
|
|
||||||
if (await FlexibleCollectionsIsEnabledAsync(orgId))
|
|
||||||
{
|
|
||||||
// New flexible collections logic
|
|
||||||
return await GetManyWithDetails_vNext(orgId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old pre-flexible collections logic follows
|
|
||||||
if (!await ViewAtLeastOneCollectionAsync(orgId) && !await _currentContext.ManageUsers(orgId) && !await _currentContext.ManageGroups(orgId))
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// We always need to know which collections the current user is assigned to
|
|
||||||
var assignedOrgCollections =
|
|
||||||
await _collectionRepository.GetManyByUserIdWithAccessAsync(_currentContext.UserId.Value, orgId,
|
|
||||||
false);
|
|
||||||
|
|
||||||
if (await _currentContext.ViewAllCollections(orgId) || await _currentContext.ManageUsers(orgId))
|
|
||||||
{
|
|
||||||
// The user can view all collections, but they may not always be assigned to all of them
|
|
||||||
var allOrgCollections = await _collectionRepository.GetManyByOrganizationIdWithAccessAsync(orgId);
|
|
||||||
|
|
||||||
return new ListResponseModel<CollectionAccessDetailsResponseModel>(allOrgCollections.Select(c =>
|
|
||||||
new CollectionAccessDetailsResponseModel(c.Item1, c.Item2.Groups, c.Item2.Users)
|
|
||||||
{
|
|
||||||
// Manually determine which collections they're assigned to
|
|
||||||
Assigned = assignedOrgCollections.Any(ac => ac.Item1.Id == c.Item1.Id)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ListResponseModel<CollectionAccessDetailsResponseModel>(assignedOrgCollections.Select(c =>
|
|
||||||
new CollectionAccessDetailsResponseModel(c.Item1, c.Item2.Groups, c.Item2.Users)
|
|
||||||
{
|
|
||||||
Assigned = true // Mapping from assignedOrgCollections implies they're all assigned
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("")]
|
|
||||||
public async Task<ListResponseModel<CollectionResponseModel>> Get(Guid orgId)
|
|
||||||
{
|
|
||||||
if (await FlexibleCollectionsIsEnabledAsync(orgId))
|
|
||||||
{
|
|
||||||
// New flexible collections logic
|
|
||||||
return await GetByOrgId_vNext(orgId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old pre-flexible collections logic follows
|
|
||||||
IEnumerable<Collection> orgCollections = null;
|
|
||||||
if (await _currentContext.ManageGroups(orgId))
|
|
||||||
{
|
|
||||||
// ManageGroups users need to see all collections to manage other users' collection access.
|
|
||||||
// This is not added to collectionService.GetOrganizationCollectionsAsync as that may have
|
|
||||||
// unintended consequences on other logic that also uses that method.
|
|
||||||
// This is a quick fix but it will be properly fixed by permission changes in Flexible Collections.
|
|
||||||
|
|
||||||
// Get all collections for organization
|
|
||||||
orgCollections = await _collectionRepository.GetManyByOrganizationIdAsync(orgId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Returns all collections or collections the user is assigned to, depending on permissions
|
|
||||||
orgCollections = await _collectionService.GetOrganizationCollectionsAsync(orgId);
|
|
||||||
}
|
|
||||||
|
|
||||||
var responses = orgCollections.Select(c => new CollectionResponseModel(c));
|
|
||||||
return new ListResponseModel<CollectionResponseModel>(responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("~/collections")]
|
|
||||||
public async Task<ListResponseModel<CollectionDetailsResponseModel>> GetUser()
|
|
||||||
{
|
|
||||||
var collections = await _collectionRepository.GetManyByUserIdAsync(
|
|
||||||
_userService.GetProperUserId(User).Value, false);
|
|
||||||
var responses = collections.Select(c => new CollectionDetailsResponseModel(c));
|
|
||||||
return new ListResponseModel<CollectionDetailsResponseModel>(responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id}/users")]
|
|
||||||
public async Task<IEnumerable<SelectionReadOnlyResponseModel>> GetUsers(Guid orgId, Guid id)
|
|
||||||
{
|
|
||||||
if (await FlexibleCollectionsIsEnabledAsync(orgId))
|
|
||||||
{
|
|
||||||
// New flexible collections logic
|
|
||||||
return await GetUsers_vNext(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old pre-flexible collections logic follows
|
|
||||||
var collection = await GetCollectionAsync(id, orgId);
|
|
||||||
var collectionUsers = await _collectionRepository.GetManyUsersByIdAsync(collection.Id);
|
|
||||||
var responses = collectionUsers.Select(cu => new SelectionReadOnlyResponseModel(cu));
|
|
||||||
return responses;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("")]
|
|
||||||
public async Task<CollectionResponseModel> Post(Guid orgId, [FromBody] CollectionRequestModel model)
|
|
||||||
{
|
|
||||||
if (await FlexibleCollectionsIsEnabledAsync(orgId))
|
|
||||||
{
|
|
||||||
// New flexible collections logic
|
|
||||||
return await Post_vNext(orgId, model);
|
|
||||||
}
|
|
||||||
|
|
||||||
var collection = model.ToCollection(orgId);
|
|
||||||
|
|
||||||
var authorized = await CanCreateCollection(orgId, collection.Id) || await CanEditCollectionAsync(orgId, collection.Id);
|
|
||||||
if (!authorized)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var groups = model.Groups?.Select(g => g.ToSelectionReadOnly());
|
|
||||||
var users = model.Users?.Select(g => g.ToSelectionReadOnly()).ToList() ?? new List<CollectionAccessSelection>();
|
|
||||||
|
|
||||||
// Pre-flexible collections logic assigned Managers to collections they create
|
|
||||||
var assignUserToCollection =
|
|
||||||
!await _currentContext.EditAnyCollection(orgId) &&
|
|
||||||
await _currentContext.EditAssignedCollections(orgId);
|
|
||||||
var isNewCollection = collection.Id == default;
|
|
||||||
|
|
||||||
if (assignUserToCollection && isNewCollection && _currentContext.UserId.HasValue)
|
|
||||||
{
|
|
||||||
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(orgId, _currentContext.UserId.Value);
|
|
||||||
// don't add duplicate access if the user has already specified it themselves
|
|
||||||
var existingAccess = users.Any(u => u.Id == orgUser.Id);
|
|
||||||
if (orgUser is { Status: OrganizationUserStatusType.Confirmed } && !existingAccess)
|
|
||||||
{
|
|
||||||
users.Add(new CollectionAccessSelection
|
|
||||||
{
|
|
||||||
Id = orgUser.Id,
|
|
||||||
ReadOnly = false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await _collectionService.SaveAsync(collection, groups, users);
|
|
||||||
|
|
||||||
if (!_currentContext.UserId.HasValue || await _currentContext.ProviderUserForOrgAsync(orgId))
|
|
||||||
{
|
|
||||||
return new CollectionResponseModel(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a user, fetch the collection to get the latest permission details
|
|
||||||
var userCollectionDetails = await _collectionRepository.GetByIdAsync(collection.Id,
|
|
||||||
_currentContext.UserId.Value, await FlexibleCollectionsIsEnabledAsync(collection.OrganizationId));
|
|
||||||
|
|
||||||
return userCollectionDetails == null
|
|
||||||
? new CollectionResponseModel(collection)
|
|
||||||
: new CollectionDetailsResponseModel(userCollectionDetails);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("{id}")]
|
|
||||||
[HttpPost("{id}")]
|
|
||||||
public async Task<CollectionResponseModel> Put(Guid orgId, Guid id, [FromBody] CollectionRequestModel model)
|
|
||||||
{
|
|
||||||
if (await FlexibleCollectionsIsEnabledAsync(orgId))
|
|
||||||
{
|
|
||||||
// New flexible collections logic
|
|
||||||
return await Put_vNext(id, model);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old pre-flexible collections logic follows
|
|
||||||
if (!await CanEditCollectionAsync(orgId, id))
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var collection = await GetCollectionAsync(id, orgId);
|
|
||||||
var groups = model.Groups?.Select(g => g.ToSelectionReadOnly());
|
|
||||||
var users = model.Users?.Select(g => g.ToSelectionReadOnly());
|
|
||||||
await _collectionService.SaveAsync(model.ToCollection(collection), groups, users);
|
|
||||||
|
|
||||||
if (!_currentContext.UserId.HasValue || await _currentContext.ProviderUserForOrgAsync(collection.OrganizationId))
|
|
||||||
{
|
|
||||||
return new CollectionResponseModel(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a user, fetch the collection details to get the latest permission details for the user
|
|
||||||
var updatedCollectionDetails = await _collectionRepository.GetByIdAsync(id, _currentContext.UserId.Value, await FlexibleCollectionsIsEnabledAsync(collection.OrganizationId));
|
|
||||||
|
|
||||||
return updatedCollectionDetails == null
|
|
||||||
? new CollectionResponseModel(collection)
|
|
||||||
: new CollectionDetailsResponseModel(updatedCollectionDetails);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("{id}/users")]
|
|
||||||
public async Task PutUsers(Guid orgId, Guid id, [FromBody] IEnumerable<SelectionReadOnlyRequestModel> model)
|
|
||||||
{
|
|
||||||
if (await FlexibleCollectionsIsEnabledAsync(orgId))
|
|
||||||
{
|
|
||||||
// New flexible collections logic
|
|
||||||
await PutUsers_vNext(id, model);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old pre-flexible collections logic follows
|
|
||||||
if (!await CanEditCollectionAsync(orgId, id))
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var collection = await GetCollectionAsync(id, orgId);
|
|
||||||
await _collectionRepository.UpdateUsersAsync(collection.Id, model?.Select(g => g.ToSelectionReadOnly()));
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("bulk-access")]
|
|
||||||
public async Task PostBulkCollectionAccess(Guid orgId, [FromBody] BulkCollectionAccessRequestModel model)
|
|
||||||
{
|
|
||||||
// Authorization logic assumes flexible collections is enabled
|
|
||||||
// Remove after all organizations have been migrated
|
|
||||||
if (!await FlexibleCollectionsIsEnabledAsync(orgId))
|
|
||||||
{
|
|
||||||
throw new NotFoundException("Feature disabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var collections = await _collectionRepository.GetManyByManyIdsAsync(model.CollectionIds);
|
|
||||||
if (collections.Count(c => c.OrganizationId == orgId) != model.CollectionIds.Count())
|
|
||||||
{
|
|
||||||
throw new NotFoundException("One or more collections not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await _authorizationService.AuthorizeAsync(User, collections,
|
|
||||||
new[] { BulkCollectionOperations.ModifyUserAccess, BulkCollectionOperations.ModifyGroupAccess });
|
|
||||||
|
|
||||||
if (!result.Succeeded)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _bulkAddCollectionAccessCommand.AddAccessAsync(
|
|
||||||
collections,
|
|
||||||
model.Users?.Select(u => u.ToSelectionReadOnly()).ToList(),
|
|
||||||
model.Groups?.Select(g => g.ToSelectionReadOnly()).ToList());
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("{id}")]
|
|
||||||
[HttpPost("{id}/delete")]
|
|
||||||
public async Task Delete(Guid orgId, Guid id)
|
|
||||||
{
|
|
||||||
if (await FlexibleCollectionsIsEnabledAsync(orgId))
|
|
||||||
{
|
|
||||||
// New flexible collections logic
|
|
||||||
await Delete_vNext(id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old pre-flexible collections logic follows
|
|
||||||
if (!await CanDeleteCollectionAsync(orgId, id))
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var collection = await GetCollectionAsync(id, orgId);
|
|
||||||
await _deleteCollectionCommand.DeleteAsync(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("")]
|
|
||||||
[HttpPost("delete")]
|
|
||||||
public async Task DeleteMany(Guid orgId, [FromBody] CollectionBulkDeleteRequestModel model)
|
|
||||||
{
|
|
||||||
if (await FlexibleCollectionsIsEnabledAsync(orgId))
|
|
||||||
{
|
|
||||||
// New flexible collections logic
|
|
||||||
var collections = await _collectionRepository.GetManyByManyIdsAsync(model.Ids);
|
|
||||||
var result = await _authorizationService.AuthorizeAsync(User, collections, BulkCollectionOperations.Delete);
|
|
||||||
if (!result.Succeeded)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _deleteCollectionCommand.DeleteManyAsync(collections);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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}")]
|
|
||||||
[HttpPost("{id}/delete-user/{orgUserId}")]
|
|
||||||
public async Task DeleteUser(Guid orgId, Guid id, Guid orgUserId)
|
|
||||||
{
|
|
||||||
if (await FlexibleCollectionsIsEnabledAsync(orgId))
|
|
||||||
{
|
|
||||||
// New flexible collections logic
|
|
||||||
await DeleteUser_vNext(id, orgUserId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old pre-flexible collections logic follows
|
|
||||||
var collection = await GetCollectionAsync(id, orgId);
|
|
||||||
await _collectionService.DeleteUserAsync(collection, orgUserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")]
|
|
||||||
private async Task<Collection> GetCollectionAsync(Guid id, Guid orgId)
|
|
||||||
{
|
|
||||||
Collection collection = default;
|
|
||||||
if (await _currentContext.ViewAllCollections(orgId))
|
|
||||||
{
|
|
||||||
collection = await _collectionRepository.GetByIdAsync(id);
|
|
||||||
}
|
|
||||||
else if (await _currentContext.ViewAssignedCollections(orgId))
|
|
||||||
{
|
|
||||||
collection = await _collectionRepository.GetByIdAsync(id, _currentContext.UserId.Value, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (collection == null || collection.OrganizationId != orgId)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")]
|
|
||||||
private async Task<bool> CanCreateCollection(Guid orgId, Guid collectionId)
|
|
||||||
{
|
|
||||||
if (collectionId != default)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await _currentContext.OrganizationManager(orgId) || (_currentContext.Organizations?.Any(o => o.Id == orgId &&
|
|
||||||
(o.Permissions?.CreateNewCollections ?? false)) ?? false);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")]
|
|
||||||
private async Task<bool> CanEditCollectionAsync(Guid orgId, Guid collectionId)
|
|
||||||
{
|
|
||||||
if (collectionId == default)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await _currentContext.EditAnyCollection(orgId))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await _currentContext.EditAssignedCollections(orgId))
|
|
||||||
{
|
|
||||||
var collectionDetails =
|
|
||||||
await _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value, false);
|
|
||||||
return collectionDetails != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")]
|
|
||||||
private async Task<bool> CanDeleteCollectionAsync(Guid orgId, Guid collectionId)
|
|
||||||
{
|
|
||||||
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, false);
|
|
||||||
return collectionDetails != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")]
|
|
||||||
private async Task<bool> DeleteAnyCollection(Guid orgId)
|
|
||||||
{
|
|
||||||
return await _currentContext.OrganizationAdmin(orgId) ||
|
|
||||||
(_currentContext.Organizations?.Any(o => o.Id == orgId
|
|
||||||
&& (o.Permissions?.DeleteAnyCollection ?? false)) ?? false);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")]
|
|
||||||
private async Task<bool> CanViewCollectionAsync(Guid orgId, Guid collectionId)
|
|
||||||
{
|
|
||||||
if (collectionId == default)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await _currentContext.ViewAllCollections(orgId))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await _currentContext.ViewAssignedCollections(orgId))
|
|
||||||
{
|
|
||||||
var collectionDetails =
|
|
||||||
await _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value, false);
|
|
||||||
return collectionDetails != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Obsolete("Pre-Flexible Collections logic. Will be replaced by CollectionsAuthorizationHandler.")]
|
|
||||||
private async Task<bool> ViewAtLeastOneCollectionAsync(Guid orgId)
|
|
||||||
{
|
|
||||||
return await _currentContext.ViewAllCollections(orgId) || await _currentContext.ViewAssignedCollections(orgId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<CollectionResponseModel> Get_vNext(Guid collectionId)
|
|
||||||
{
|
|
||||||
var collection = await _collectionRepository.GetByIdAsync(collectionId);
|
|
||||||
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Read)).Succeeded;
|
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Read)).Succeeded;
|
||||||
if (!authorized)
|
if (!authorized)
|
||||||
{
|
{
|
||||||
@ -550,9 +57,9 @@ public class CollectionsController : Controller
|
|||||||
return new CollectionResponseModel(collection);
|
return new CollectionResponseModel(collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CollectionAccessDetailsResponseModel> GetDetails_vNext(Guid id)
|
[HttpGet("{id}/details")]
|
||||||
|
public async Task<CollectionAccessDetailsResponseModel> GetDetails(Guid orgId, Guid id)
|
||||||
{
|
{
|
||||||
// New flexible collections logic
|
|
||||||
var collectionAdminDetails =
|
var collectionAdminDetails =
|
||||||
await _collectionRepository.GetByIdWithPermissionsAsync(id, _currentContext.UserId, true);
|
await _collectionRepository.GetByIdWithPermissionsAsync(id, _currentContext.UserId, true);
|
||||||
|
|
||||||
@ -565,7 +72,8 @@ public class CollectionsController : Controller
|
|||||||
return new CollectionAccessDetailsResponseModel(collectionAdminDetails);
|
return new CollectionAccessDetailsResponseModel(collectionAdminDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ListResponseModel<CollectionAccessDetailsResponseModel>> GetManyWithDetails_vNext(Guid orgId)
|
[HttpGet("details")]
|
||||||
|
public async Task<ListResponseModel<CollectionAccessDetailsResponseModel>> GetManyWithDetails(Guid orgId)
|
||||||
{
|
{
|
||||||
var allOrgCollections = await _collectionRepository.GetManyByOrganizationIdWithPermissionsAsync(
|
var allOrgCollections = await _collectionRepository.GetManyByOrganizationIdWithPermissionsAsync(
|
||||||
orgId, _currentContext.UserId.Value, true);
|
orgId, _currentContext.UserId.Value, true);
|
||||||
@ -587,7 +95,8 @@ public class CollectionsController : Controller
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ListResponseModel<CollectionResponseModel>> GetByOrgId_vNext(Guid orgId)
|
[HttpGet("")]
|
||||||
|
public async Task<ListResponseModel<CollectionResponseModel>> Get(Guid orgId)
|
||||||
{
|
{
|
||||||
IEnumerable<Collection> orgCollections;
|
IEnumerable<Collection> orgCollections;
|
||||||
|
|
||||||
@ -606,7 +115,17 @@ public class CollectionsController : Controller
|
|||||||
return new ListResponseModel<CollectionResponseModel>(responses);
|
return new ListResponseModel<CollectionResponseModel>(responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IEnumerable<SelectionReadOnlyResponseModel>> GetUsers_vNext(Guid id)
|
[HttpGet("~/collections")]
|
||||||
|
public async Task<ListResponseModel<CollectionDetailsResponseModel>> GetUser()
|
||||||
|
{
|
||||||
|
var collections = await _collectionRepository.GetManyByUserIdAsync(
|
||||||
|
_userService.GetProperUserId(User).Value, false);
|
||||||
|
var responses = collections.Select(c => new CollectionDetailsResponseModel(c));
|
||||||
|
return new ListResponseModel<CollectionDetailsResponseModel>(responses);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}/users")]
|
||||||
|
public async Task<IEnumerable<SelectionReadOnlyResponseModel>> GetUsers(Guid orgId, Guid id)
|
||||||
{
|
{
|
||||||
var collection = await _collectionRepository.GetByIdAsync(id);
|
var collection = await _collectionRepository.GetByIdAsync(id);
|
||||||
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ReadAccess)).Succeeded;
|
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ReadAccess)).Succeeded;
|
||||||
@ -620,7 +139,8 @@ public class CollectionsController : Controller
|
|||||||
return responses;
|
return responses;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CollectionAccessDetailsResponseModel> Post_vNext(Guid orgId, [FromBody] CollectionRequestModel model)
|
[HttpPost("")]
|
||||||
|
public async Task<CollectionResponseModel> Post(Guid orgId, [FromBody] CollectionRequestModel model)
|
||||||
{
|
{
|
||||||
var collection = model.ToCollection(orgId);
|
var collection = model.ToCollection(orgId);
|
||||||
|
|
||||||
@ -646,7 +166,9 @@ public class CollectionsController : Controller
|
|||||||
return new CollectionAccessDetailsResponseModel(collectionWithPermissions);
|
return new CollectionAccessDetailsResponseModel(collectionWithPermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CollectionAccessDetailsResponseModel> Put_vNext(Guid id, CollectionRequestModel model)
|
[HttpPut("{id}")]
|
||||||
|
[HttpPost("{id}")]
|
||||||
|
public async Task<CollectionResponseModel> Put(Guid orgId, Guid id, [FromBody] CollectionRequestModel model)
|
||||||
{
|
{
|
||||||
var collection = await _collectionRepository.GetByIdAsync(id);
|
var collection = await _collectionRepository.GetByIdAsync(id);
|
||||||
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Update)).Succeeded;
|
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Update)).Succeeded;
|
||||||
@ -670,7 +192,8 @@ public class CollectionsController : Controller
|
|||||||
return new CollectionAccessDetailsResponseModel(collectionWithPermissions);
|
return new CollectionAccessDetailsResponseModel(collectionWithPermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PutUsers_vNext(Guid id, IEnumerable<SelectionReadOnlyRequestModel> model)
|
[HttpPut("{id}/users")]
|
||||||
|
public async Task PutUsers(Guid orgId, Guid id, [FromBody] IEnumerable<SelectionReadOnlyRequestModel> model)
|
||||||
{
|
{
|
||||||
var collection = await _collectionRepository.GetByIdAsync(id);
|
var collection = await _collectionRepository.GetByIdAsync(id);
|
||||||
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyUserAccess)).Succeeded;
|
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyUserAccess)).Succeeded;
|
||||||
@ -682,7 +205,32 @@ public class CollectionsController : Controller
|
|||||||
await _collectionRepository.UpdateUsersAsync(collection.Id, model?.Select(g => g.ToSelectionReadOnly()));
|
await _collectionRepository.UpdateUsersAsync(collection.Id, model?.Select(g => g.ToSelectionReadOnly()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Delete_vNext(Guid id)
|
[HttpPost("bulk-access")]
|
||||||
|
public async Task PostBulkCollectionAccess(Guid orgId, [FromBody] BulkCollectionAccessRequestModel model)
|
||||||
|
{
|
||||||
|
var collections = await _collectionRepository.GetManyByManyIdsAsync(model.CollectionIds);
|
||||||
|
if (collections.Count(c => c.OrganizationId == orgId) != model.CollectionIds.Count())
|
||||||
|
{
|
||||||
|
throw new NotFoundException("One or more collections not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _authorizationService.AuthorizeAsync(User, collections,
|
||||||
|
new[] { BulkCollectionOperations.ModifyUserAccess, BulkCollectionOperations.ModifyGroupAccess });
|
||||||
|
|
||||||
|
if (!result.Succeeded)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _bulkAddCollectionAccessCommand.AddAccessAsync(
|
||||||
|
collections,
|
||||||
|
model.Users?.Select(u => u.ToSelectionReadOnly()).ToList(),
|
||||||
|
model.Groups?.Select(g => g.ToSelectionReadOnly()).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[HttpPost("{id}/delete")]
|
||||||
|
public async Task Delete(Guid orgId, Guid id)
|
||||||
{
|
{
|
||||||
var collection = await _collectionRepository.GetByIdAsync(id);
|
var collection = await _collectionRepository.GetByIdAsync(id);
|
||||||
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Delete)).Succeeded;
|
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Delete)).Succeeded;
|
||||||
@ -694,7 +242,23 @@ public class CollectionsController : Controller
|
|||||||
await _deleteCollectionCommand.DeleteAsync(collection);
|
await _deleteCollectionCommand.DeleteAsync(collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeleteUser_vNext(Guid id, Guid orgUserId)
|
[HttpDelete("")]
|
||||||
|
[HttpPost("delete")]
|
||||||
|
public async Task DeleteMany(Guid orgId, [FromBody] CollectionBulkDeleteRequestModel model)
|
||||||
|
{
|
||||||
|
var collections = await _collectionRepository.GetManyByManyIdsAsync(model.Ids);
|
||||||
|
var result = await _authorizationService.AuthorizeAsync(User, collections, BulkCollectionOperations.Delete);
|
||||||
|
if (!result.Succeeded)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _deleteCollectionCommand.DeleteManyAsync(collections);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id}/user/{orgUserId}")]
|
||||||
|
[HttpPost("{id}/delete-user/{orgUserId}")]
|
||||||
|
public async Task DeleteUser(Guid orgId, Guid id, Guid orgUserId)
|
||||||
{
|
{
|
||||||
var collection = await _collectionRepository.GetByIdAsync(id);
|
var collection = await _collectionRepository.GetByIdAsync(id);
|
||||||
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyUserAccess)).Succeeded;
|
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyUserAccess)).Succeeded;
|
||||||
@ -705,10 +269,4 @@ public class CollectionsController : Controller
|
|||||||
|
|
||||||
await _collectionService.DeleteUserAsync(collection, orgUserId);
|
await _collectionService.DeleteUserAsync(collection, orgUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> FlexibleCollectionsIsEnabledAsync(Guid organizationId)
|
|
||||||
{
|
|
||||||
var organizationAbility = await _applicationCacheService.GetOrganizationAbilityAsync(organizationId);
|
|
||||||
return organizationAbility?.FlexibleCollections ?? false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
using Bit.Api.Controllers;
|
using Bit.Api.Controllers;
|
||||||
using Bit.Api.Models.Request;
|
using Bit.Api.Models.Request;
|
||||||
using Bit.Api.Vault.AuthorizationHandlers.Collections;
|
using Bit.Api.Vault.AuthorizationHandlers.Collections;
|
||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
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.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Data.Organizations;
|
|
||||||
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;
|
||||||
@ -23,14 +23,12 @@ namespace Bit.Api.Test.Controllers;
|
|||||||
public class CollectionsControllerTests
|
public class CollectionsControllerTests
|
||||||
{
|
{
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task Post_Success(OrganizationAbility organizationAbility, CollectionRequestModel collectionRequest,
|
public async Task Post_Success(Organization organization, CollectionRequestModel collectionRequest,
|
||||||
SutProvider<CollectionsController> sutProvider)
|
SutProvider<CollectionsController> sutProvider)
|
||||||
{
|
{
|
||||||
ArrangeOrganizationAbility(sutProvider, organizationAbility);
|
|
||||||
|
|
||||||
Collection ExpectedCollection() => Arg.Is<Collection>(c =>
|
Collection ExpectedCollection() => Arg.Is<Collection>(c =>
|
||||||
c.Name == collectionRequest.Name && c.ExternalId == collectionRequest.ExternalId &&
|
c.Name == collectionRequest.Name && c.ExternalId == collectionRequest.ExternalId &&
|
||||||
c.OrganizationId == organizationAbility.Id);
|
c.OrganizationId == organization.Id);
|
||||||
|
|
||||||
sutProvider.GetDependency<IAuthorizationService>()
|
sutProvider.GetDependency<IAuthorizationService>()
|
||||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
|
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
|
||||||
@ -38,7 +36,7 @@ public class CollectionsControllerTests
|
|||||||
Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.Create)))
|
Arg.Is<IEnumerable<IAuthorizationRequirement>>(r => r.Contains(BulkCollectionOperations.Create)))
|
||||||
.Returns(AuthorizationResult.Success());
|
.Returns(AuthorizationResult.Success());
|
||||||
|
|
||||||
_ = await sutProvider.Sut.Post(organizationAbility.Id, collectionRequest);
|
_ = await sutProvider.Sut.Post(organization.Id, collectionRequest);
|
||||||
|
|
||||||
await sutProvider.GetDependency<ICollectionService>()
|
await sutProvider.GetDependency<ICollectionService>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
@ -48,11 +46,8 @@ public class CollectionsControllerTests
|
|||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task Put_Success(Collection collection, CollectionRequestModel collectionRequest,
|
public async Task Put_Success(Collection collection, CollectionRequestModel collectionRequest,
|
||||||
SutProvider<CollectionsController> sutProvider, OrganizationAbility organizationAbility)
|
SutProvider<CollectionsController> sutProvider)
|
||||||
{
|
{
|
||||||
ArrangeOrganizationAbility(sutProvider, organizationAbility);
|
|
||||||
collection.OrganizationId = organizationAbility.Id;
|
|
||||||
|
|
||||||
Collection ExpectedCollection() => Arg.Is<Collection>(c => c.Id == collection.Id &&
|
Collection ExpectedCollection() => Arg.Is<Collection>(c => c.Id == collection.Id &&
|
||||||
c.Name == collectionRequest.Name && c.ExternalId == collectionRequest.ExternalId &&
|
c.Name == collectionRequest.Name && c.ExternalId == collectionRequest.ExternalId &&
|
||||||
c.OrganizationId == collection.OrganizationId);
|
c.OrganizationId == collection.OrganizationId);
|
||||||
@ -77,11 +72,8 @@ public class CollectionsControllerTests
|
|||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task Put_WithNoCollectionPermission_ThrowsNotFound(Collection collection, CollectionRequestModel collectionRequest,
|
public async Task Put_WithNoCollectionPermission_ThrowsNotFound(Collection collection, CollectionRequestModel collectionRequest,
|
||||||
SutProvider<CollectionsController> sutProvider, OrganizationAbility organizationAbility)
|
SutProvider<CollectionsController> sutProvider)
|
||||||
{
|
{
|
||||||
ArrangeOrganizationAbility(sutProvider, organizationAbility);
|
|
||||||
collection.OrganizationId = organizationAbility.Id;
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IAuthorizationService>()
|
sutProvider.GetDependency<IAuthorizationService>()
|
||||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
|
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
|
||||||
collection,
|
collection,
|
||||||
@ -96,11 +88,9 @@ public class CollectionsControllerTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task GetOrganizationCollectionsWithGroups_WithReadAllPermissions_GetsAllCollections(OrganizationAbility organizationAbility,
|
public async Task GetOrganizationCollectionsWithGroups_WithReadAllPermissions_GetsAllCollections(Organization organization,
|
||||||
Guid userId, SutProvider<CollectionsController> sutProvider)
|
Guid userId, SutProvider<CollectionsController> sutProvider)
|
||||||
{
|
{
|
||||||
ArrangeOrganizationAbility(sutProvider, organizationAbility);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||||
|
|
||||||
sutProvider.GetDependency<IAuthorizationService>()
|
sutProvider.GetDependency<IAuthorizationService>()
|
||||||
@ -110,20 +100,20 @@ public class CollectionsControllerTests
|
|||||||
Arg.Is<IEnumerable<IAuthorizationRequirement>>(requirements =>
|
Arg.Is<IEnumerable<IAuthorizationRequirement>>(requirements =>
|
||||||
requirements.Cast<CollectionOperationRequirement>().All(operation =>
|
requirements.Cast<CollectionOperationRequirement>().All(operation =>
|
||||||
operation.Name == nameof(CollectionOperations.ReadAllWithAccess)
|
operation.Name == nameof(CollectionOperations.ReadAllWithAccess)
|
||||||
&& operation.OrganizationId == organizationAbility.Id)))
|
&& operation.OrganizationId == organization.Id)))
|
||||||
.Returns(AuthorizationResult.Success());
|
.Returns(AuthorizationResult.Success());
|
||||||
|
|
||||||
await sutProvider.Sut.GetManyWithDetails(organizationAbility.Id);
|
await sutProvider.Sut.GetManyWithDetails(organization.Id);
|
||||||
|
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().Received(1).GetManyByOrganizationIdWithPermissionsAsync(organizationAbility.Id, userId, true);
|
await sutProvider.GetDependency<ICollectionRepository>().Received(1).GetManyByOrganizationIdWithPermissionsAsync(organization.Id, userId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task GetOrganizationCollectionsWithGroups_MissingReadAllPermissions_GetsAssignedCollections(
|
public async Task GetOrganizationCollectionsWithGroups_MissingReadAllPermissions_GetsAssignedCollections(
|
||||||
OrganizationAbility organizationAbility, Guid userId, SutProvider<CollectionsController> sutProvider, List<CollectionAdminDetails> collections)
|
Organization organization, Guid userId, SutProvider<CollectionsController> sutProvider,
|
||||||
|
List<CollectionAdminDetails> collections)
|
||||||
{
|
{
|
||||||
ArrangeOrganizationAbility(sutProvider, organizationAbility);
|
collections.ForEach(c => c.OrganizationId = organization.Id);
|
||||||
collections.ForEach(c => c.OrganizationId = organizationAbility.Id);
|
|
||||||
collections.ForEach(c => c.Manage = false);
|
collections.ForEach(c => c.Manage = false);
|
||||||
|
|
||||||
var managedCollection = collections.First();
|
var managedCollection = collections.First();
|
||||||
@ -138,7 +128,7 @@ public class CollectionsControllerTests
|
|||||||
Arg.Is<IEnumerable<IAuthorizationRequirement>>(requirements =>
|
Arg.Is<IEnumerable<IAuthorizationRequirement>>(requirements =>
|
||||||
requirements.Cast<CollectionOperationRequirement>().All(operation =>
|
requirements.Cast<CollectionOperationRequirement>().All(operation =>
|
||||||
operation.Name == nameof(CollectionOperations.ReadAllWithAccess)
|
operation.Name == nameof(CollectionOperations.ReadAllWithAccess)
|
||||||
&& operation.OrganizationId == organizationAbility.Id)))
|
&& operation.OrganizationId == organization.Id)))
|
||||||
.Returns(AuthorizationResult.Failed());
|
.Returns(AuthorizationResult.Failed());
|
||||||
|
|
||||||
sutProvider.GetDependency<IAuthorizationService>()
|
sutProvider.GetDependency<IAuthorizationService>()
|
||||||
@ -151,23 +141,23 @@ public class CollectionsControllerTests
|
|||||||
.Returns(AuthorizationResult.Success());
|
.Returns(AuthorizationResult.Success());
|
||||||
|
|
||||||
sutProvider.GetDependency<ICollectionRepository>()
|
sutProvider.GetDependency<ICollectionRepository>()
|
||||||
.GetManyByOrganizationIdWithPermissionsAsync(organizationAbility.Id, userId, true)
|
.GetManyByOrganizationIdWithPermissionsAsync(organization.Id, userId, true)
|
||||||
.Returns(collections);
|
.Returns(collections);
|
||||||
|
|
||||||
var response = await sutProvider.Sut.GetManyWithDetails(organizationAbility.Id);
|
var response = await sutProvider.Sut.GetManyWithDetails(organization.Id);
|
||||||
|
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().Received(1).GetManyByOrganizationIdWithPermissionsAsync(organizationAbility.Id, userId, true);
|
await sutProvider.GetDependency<ICollectionRepository>().Received(1).GetManyByOrganizationIdWithPermissionsAsync(organization.Id, userId, true);
|
||||||
Assert.Single(response.Data);
|
Assert.Single(response.Data);
|
||||||
Assert.All(response.Data, c => Assert.Equal(organizationAbility.Id, c.OrganizationId));
|
Assert.All(response.Data, c => Assert.Equal(organization.Id, c.OrganizationId));
|
||||||
Assert.All(response.Data, c => Assert.Equal(managedCollection.Id, c.Id));
|
Assert.All(response.Data, c => Assert.Equal(managedCollection.Id, c.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task GetOrganizationCollections_WithReadAllPermissions_GetsAllCollections(
|
public async Task GetOrganizationCollections_WithReadAllPermissions_GetsAllCollections(
|
||||||
OrganizationAbility organizationAbility, List<Collection> collections, Guid userId, SutProvider<CollectionsController> sutProvider)
|
Organization organization, List<Collection> collections, Guid userId,
|
||||||
|
SutProvider<CollectionsController> sutProvider)
|
||||||
{
|
{
|
||||||
ArrangeOrganizationAbility(sutProvider, organizationAbility);
|
collections.ForEach(c => c.OrganizationId = organization.Id);
|
||||||
collections.ForEach(c => c.OrganizationId = organizationAbility.Id);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||||
|
|
||||||
@ -178,26 +168,25 @@ public class CollectionsControllerTests
|
|||||||
Arg.Is<IEnumerable<IAuthorizationRequirement>>(requirements =>
|
Arg.Is<IEnumerable<IAuthorizationRequirement>>(requirements =>
|
||||||
requirements.Cast<CollectionOperationRequirement>().All(operation =>
|
requirements.Cast<CollectionOperationRequirement>().All(operation =>
|
||||||
operation.Name == nameof(CollectionOperations.ReadAll)
|
operation.Name == nameof(CollectionOperations.ReadAll)
|
||||||
&& operation.OrganizationId == organizationAbility.Id)))
|
&& operation.OrganizationId == organization.Id)))
|
||||||
.Returns(AuthorizationResult.Success());
|
.Returns(AuthorizationResult.Success());
|
||||||
|
|
||||||
sutProvider.GetDependency<ICollectionRepository>()
|
sutProvider.GetDependency<ICollectionRepository>()
|
||||||
.GetManyByOrganizationIdAsync(organizationAbility.Id)
|
.GetManyByOrganizationIdAsync(organization.Id)
|
||||||
.Returns(collections);
|
.Returns(collections);
|
||||||
|
|
||||||
var response = await sutProvider.Sut.Get(organizationAbility.Id);
|
var response = await sutProvider.Sut.Get(organization.Id);
|
||||||
|
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().Received(1).GetManyByOrganizationIdAsync(organizationAbility.Id);
|
await sutProvider.GetDependency<ICollectionRepository>().Received(1).GetManyByOrganizationIdAsync(organization.Id);
|
||||||
|
|
||||||
Assert.Equal(collections.Count, response.Data.Count());
|
Assert.Equal(collections.Count, response.Data.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task GetOrganizationCollections_MissingReadAllPermissions_GetsManageableCollections(
|
public async Task GetOrganizationCollections_MissingReadAllPermissions_GetsManageableCollections(
|
||||||
OrganizationAbility organizationAbility, List<CollectionDetails> collections, Guid userId, SutProvider<CollectionsController> sutProvider)
|
Organization organization, List<CollectionDetails> collections, Guid userId, SutProvider<CollectionsController> sutProvider)
|
||||||
{
|
{
|
||||||
ArrangeOrganizationAbility(sutProvider, organizationAbility);
|
collections.ForEach(c => c.OrganizationId = organization.Id);
|
||||||
collections.ForEach(c => c.OrganizationId = organizationAbility.Id);
|
|
||||||
collections.ForEach(c => c.Manage = false);
|
collections.ForEach(c => c.Manage = false);
|
||||||
|
|
||||||
var managedCollection = collections.First();
|
var managedCollection = collections.First();
|
||||||
@ -212,7 +201,7 @@ public class CollectionsControllerTests
|
|||||||
Arg.Is<IEnumerable<IAuthorizationRequirement>>(requirements =>
|
Arg.Is<IEnumerable<IAuthorizationRequirement>>(requirements =>
|
||||||
requirements.Cast<CollectionOperationRequirement>().All(operation =>
|
requirements.Cast<CollectionOperationRequirement>().All(operation =>
|
||||||
operation.Name == nameof(CollectionOperations.ReadAll)
|
operation.Name == nameof(CollectionOperations.ReadAll)
|
||||||
&& operation.OrganizationId == organizationAbility.Id)))
|
&& operation.OrganizationId == organization.Id)))
|
||||||
.Returns(AuthorizationResult.Failed());
|
.Returns(AuthorizationResult.Failed());
|
||||||
|
|
||||||
sutProvider.GetDependency<IAuthorizationService>()
|
sutProvider.GetDependency<IAuthorizationService>()
|
||||||
@ -228,24 +217,22 @@ public class CollectionsControllerTests
|
|||||||
.GetManyByUserIdAsync(userId, false)
|
.GetManyByUserIdAsync(userId, false)
|
||||||
.Returns(collections);
|
.Returns(collections);
|
||||||
|
|
||||||
var result = await sutProvider.Sut.Get(organizationAbility.Id);
|
var result = await sutProvider.Sut.Get(organization.Id);
|
||||||
|
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceive().GetManyByOrganizationIdAsync(organizationAbility.Id);
|
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceive().GetManyByOrganizationIdAsync(organization.Id);
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().Received(1).GetManyByUserIdAsync(userId, false);
|
await sutProvider.GetDependency<ICollectionRepository>().Received(1).GetManyByUserIdAsync(userId, false);
|
||||||
|
|
||||||
Assert.Single(result.Data);
|
Assert.Single(result.Data);
|
||||||
Assert.All(result.Data, c => Assert.Equal(organizationAbility.Id, c.OrganizationId));
|
Assert.All(result.Data, c => Assert.Equal(organization.Id, c.OrganizationId));
|
||||||
Assert.All(result.Data, c => Assert.Equal(managedCollection.Id, c.Id));
|
Assert.All(result.Data, c => Assert.Equal(managedCollection.Id, c.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task DeleteMany_Success(OrganizationAbility organizationAbility, Collection collection1, Collection collection2,
|
public async Task DeleteMany_Success(Organization organization, Collection collection1, Collection collection2,
|
||||||
SutProvider<CollectionsController> sutProvider)
|
SutProvider<CollectionsController> sutProvider)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var orgId = organizationAbility.Id;
|
var orgId = organization.Id;
|
||||||
ArrangeOrganizationAbility(sutProvider, organizationAbility);
|
|
||||||
|
|
||||||
var model = new CollectionBulkDeleteRequestModel
|
var model = new CollectionBulkDeleteRequestModel
|
||||||
{
|
{
|
||||||
Ids = new[] { collection1.Id, collection2.Id }
|
Ids = new[] { collection1.Id, collection2.Id }
|
||||||
@ -285,13 +272,11 @@ public class CollectionsControllerTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task DeleteMany_PermissionDenied_ThrowsNotFound(OrganizationAbility organizationAbility, Collection collection1,
|
public async Task DeleteMany_PermissionDenied_ThrowsNotFound(Organization organization, Collection collection1,
|
||||||
Collection collection2, SutProvider<CollectionsController> sutProvider)
|
Collection collection2, SutProvider<CollectionsController> sutProvider)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var orgId = organizationAbility.Id;
|
var orgId = organization.Id;
|
||||||
ArrangeOrganizationAbility(sutProvider, organizationAbility);
|
|
||||||
|
|
||||||
var model = new CollectionBulkDeleteRequestModel
|
var model = new CollectionBulkDeleteRequestModel
|
||||||
{
|
{
|
||||||
Ids = new[] { collection1.Id, collection2.Id }
|
Ids = new[] { collection1.Id, collection2.Id }
|
||||||
@ -331,12 +316,10 @@ public class CollectionsControllerTests
|
|||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task PostBulkCollectionAccess_Success(User actingUser, List<Collection> collections,
|
public async Task PostBulkCollectionAccess_Success(User actingUser, List<Collection> collections,
|
||||||
OrganizationAbility organizationAbility, SutProvider<CollectionsController> sutProvider)
|
Organization organization, SutProvider<CollectionsController> sutProvider)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
ArrangeOrganizationAbility(sutProvider, organizationAbility);
|
collections.ForEach(c => c.OrganizationId = organization.Id);
|
||||||
collections.ForEach(c => c.OrganizationId = organizationAbility.Id);
|
|
||||||
|
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
var groupId = Guid.NewGuid();
|
var groupId = Guid.NewGuid();
|
||||||
var model = new BulkCollectionAccessRequestModel
|
var model = new BulkCollectionAccessRequestModel
|
||||||
@ -365,7 +348,7 @@ public class CollectionsControllerTests
|
|||||||
IEnumerable<Collection> ExpectedCollectionAccess() => Arg.Is<IEnumerable<Collection>>(cols => cols.SequenceEqual(collections));
|
IEnumerable<Collection> ExpectedCollectionAccess() => Arg.Is<IEnumerable<Collection>>(cols => cols.SequenceEqual(collections));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await sutProvider.Sut.PostBulkCollectionAccess(organizationAbility.Id, model);
|
await sutProvider.Sut.PostBulkCollectionAccess(organization.Id, model);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
await sutProvider.GetDependency<IAuthorizationService>().Received().AuthorizeAsync(
|
await sutProvider.GetDependency<IAuthorizationService>().Received().AuthorizeAsync(
|
||||||
@ -384,11 +367,10 @@ public class CollectionsControllerTests
|
|||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task PostBulkCollectionAccess_CollectionsNotFound_Throws(User actingUser,
|
public async Task PostBulkCollectionAccess_CollectionsNotFound_Throws(User actingUser,
|
||||||
OrganizationAbility organizationAbility, List<Collection> collections,
|
Organization organization, List<Collection> collections,
|
||||||
SutProvider<CollectionsController> sutProvider)
|
SutProvider<CollectionsController> sutProvider)
|
||||||
{
|
{
|
||||||
ArrangeOrganizationAbility(sutProvider, organizationAbility);
|
collections.ForEach(c => c.OrganizationId = organization.Id);
|
||||||
collections.ForEach(c => c.OrganizationId = organizationAbility.Id);
|
|
||||||
|
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
var groupId = Guid.NewGuid();
|
var groupId = Guid.NewGuid();
|
||||||
@ -407,7 +389,7 @@ public class CollectionsControllerTests
|
|||||||
.Returns(collections.Skip(1).ToList());
|
.Returns(collections.Skip(1).ToList());
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<NotFoundException>(
|
var exception = await Assert.ThrowsAsync<NotFoundException>(
|
||||||
() => sutProvider.Sut.PostBulkCollectionAccess(organizationAbility.Id, model));
|
() => sutProvider.Sut.PostBulkCollectionAccess(organization.Id, model));
|
||||||
|
|
||||||
Assert.Equal("One or more collections not found.", exception.Message);
|
Assert.Equal("One or more collections not found.", exception.Message);
|
||||||
await sutProvider.GetDependency<IAuthorizationService>().DidNotReceiveWithAnyArgs().AuthorizeAsync(
|
await sutProvider.GetDependency<IAuthorizationService>().DidNotReceiveWithAnyArgs().AuthorizeAsync(
|
||||||
@ -421,13 +403,11 @@ public class CollectionsControllerTests
|
|||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task PostBulkCollectionAccess_CollectionsBelongToDifferentOrganizations_Throws(User actingUser,
|
public async Task PostBulkCollectionAccess_CollectionsBelongToDifferentOrganizations_Throws(User actingUser,
|
||||||
OrganizationAbility organizationAbility, List<Collection> collections,
|
Organization organization, List<Collection> collections,
|
||||||
SutProvider<CollectionsController> sutProvider)
|
SutProvider<CollectionsController> sutProvider)
|
||||||
{
|
{
|
||||||
ArrangeOrganizationAbility(sutProvider, organizationAbility);
|
|
||||||
|
|
||||||
// First collection has a different orgId
|
// First collection has a different orgId
|
||||||
collections.Skip(1).ToList().ForEach(c => c.OrganizationId = organizationAbility.Id);
|
collections.Skip(1).ToList().ForEach(c => c.OrganizationId = organization.Id);
|
||||||
|
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
var groupId = Guid.NewGuid();
|
var groupId = Guid.NewGuid();
|
||||||
@ -446,7 +426,7 @@ public class CollectionsControllerTests
|
|||||||
.Returns(collections);
|
.Returns(collections);
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<NotFoundException>(
|
var exception = await Assert.ThrowsAsync<NotFoundException>(
|
||||||
() => sutProvider.Sut.PostBulkCollectionAccess(organizationAbility.Id, model));
|
() => sutProvider.Sut.PostBulkCollectionAccess(organization.Id, model));
|
||||||
|
|
||||||
Assert.Equal("One or more collections not found.", exception.Message);
|
Assert.Equal("One or more collections not found.", exception.Message);
|
||||||
await sutProvider.GetDependency<IAuthorizationService>().DidNotReceiveWithAnyArgs().AuthorizeAsync(
|
await sutProvider.GetDependency<IAuthorizationService>().DidNotReceiveWithAnyArgs().AuthorizeAsync(
|
||||||
@ -458,42 +438,11 @@ public class CollectionsControllerTests
|
|||||||
.AddAccessAsync(default, default, default);
|
.AddAccessAsync(default, default, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task PostBulkCollectionAccess_FlexibleCollectionsDisabled_Throws(OrganizationAbility organizationAbility, List<Collection> collections,
|
|
||||||
SutProvider<CollectionsController> sutProvider)
|
|
||||||
{
|
|
||||||
organizationAbility.FlexibleCollections = false;
|
|
||||||
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilityAsync(organizationAbility.Id)
|
|
||||||
.Returns(organizationAbility);
|
|
||||||
|
|
||||||
var userId = Guid.NewGuid();
|
|
||||||
var groupId = Guid.NewGuid();
|
|
||||||
var model = new BulkCollectionAccessRequestModel
|
|
||||||
{
|
|
||||||
CollectionIds = collections.Select(c => c.Id),
|
|
||||||
Users = new[] { new SelectionReadOnlyRequestModel { Id = userId, Manage = true } },
|
|
||||||
Groups = new[] { new SelectionReadOnlyRequestModel { Id = groupId, ReadOnly = true } },
|
|
||||||
};
|
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<NotFoundException>(
|
|
||||||
() => sutProvider.Sut.PostBulkCollectionAccess(organizationAbility.Id, model));
|
|
||||||
|
|
||||||
Assert.Equal("Feature disabled.", exception.Message);
|
|
||||||
await sutProvider.GetDependency<IAuthorizationService>().DidNotReceiveWithAnyArgs().AuthorizeAsync(
|
|
||||||
Arg.Any<ClaimsPrincipal>(),
|
|
||||||
Arg.Any<IEnumerable<Collection>>(),
|
|
||||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()
|
|
||||||
);
|
|
||||||
await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().DidNotReceiveWithAnyArgs()
|
|
||||||
.AddAccessAsync(default, default, default);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task PostBulkCollectionAccess_AccessDenied_Throws(User actingUser, List<Collection> collections,
|
public async Task PostBulkCollectionAccess_AccessDenied_Throws(User actingUser, List<Collection> collections,
|
||||||
OrganizationAbility organizationAbility, SutProvider<CollectionsController> sutProvider)
|
Organization organization, SutProvider<CollectionsController> sutProvider)
|
||||||
{
|
{
|
||||||
ArrangeOrganizationAbility(sutProvider, organizationAbility);
|
collections.ForEach(c => c.OrganizationId = organization.Id);
|
||||||
collections.ForEach(c => c.OrganizationId = organizationAbility.Id);
|
|
||||||
|
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
var groupId = Guid.NewGuid();
|
var groupId = Guid.NewGuid();
|
||||||
@ -522,7 +471,7 @@ public class CollectionsControllerTests
|
|||||||
|
|
||||||
IEnumerable<Collection> ExpectedCollectionAccess() => Arg.Is<IEnumerable<Collection>>(cols => cols.SequenceEqual(collections));
|
IEnumerable<Collection> ExpectedCollectionAccess() => Arg.Is<IEnumerable<Collection>>(cols => cols.SequenceEqual(collections));
|
||||||
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PostBulkCollectionAccess(organizationAbility.Id, model));
|
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PostBulkCollectionAccess(organization.Id, model));
|
||||||
await sutProvider.GetDependency<IAuthorizationService>().Received().AuthorizeAsync(
|
await sutProvider.GetDependency<IAuthorizationService>().Received().AuthorizeAsync(
|
||||||
Arg.Any<ClaimsPrincipal>(),
|
Arg.Any<ClaimsPrincipal>(),
|
||||||
ExpectedCollectionAccess(),
|
ExpectedCollectionAccess(),
|
||||||
@ -534,12 +483,4 @@ public class CollectionsControllerTests
|
|||||||
await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().DidNotReceiveWithAnyArgs()
|
await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().DidNotReceiveWithAnyArgs()
|
||||||
.AddAccessAsync(default, default, default);
|
.AddAccessAsync(default, default, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ArrangeOrganizationAbility(SutProvider<CollectionsController> sutProvider, OrganizationAbility organizationAbility)
|
|
||||||
{
|
|
||||||
organizationAbility.FlexibleCollections = true;
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilityAsync(organizationAbility.Id)
|
|
||||||
.Returns(organizationAbility);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,318 +0,0 @@
|
|||||||
using Bit.Api.Controllers;
|
|
||||||
using Bit.Api.Models.Request;
|
|
||||||
using Bit.Core.AdminConsole.Entities;
|
|
||||||
using Bit.Core.Context;
|
|
||||||
using Bit.Core.Entities;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Exceptions;
|
|
||||||
using Bit.Core.Models.Data;
|
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces;
|
|
||||||
using Bit.Core.Repositories;
|
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Test.Common.AutoFixture;
|
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
|
||||||
using NSubstitute;
|
|
||||||
using Xunit;
|
|
||||||
using Collection = Bit.Core.Entities.Collection;
|
|
||||||
using User = Bit.Core.Entities.User;
|
|
||||||
|
|
||||||
namespace Bit.Api.Test.Controllers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// CollectionsController tests that use pre-Flexible Collections logic. To be removed when the feature flag is removed.
|
|
||||||
/// Note the feature flag defaults to OFF so it is not explicitly set in these tests.
|
|
||||||
/// </summary>
|
|
||||||
[ControllerCustomize(typeof(CollectionsController))]
|
|
||||||
[SutProviderCustomize]
|
|
||||||
public class LegacyCollectionsControllerTests
|
|
||||||
{
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task Post_Manager_AssignsToCollection_Success(Guid orgId, OrganizationUser orgUser, SutProvider<CollectionsController> sutProvider)
|
|
||||||
{
|
|
||||||
orgUser.Type = OrganizationUserType.Manager;
|
|
||||||
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.OrganizationManager(orgId)
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.EditAnyCollection(orgId)
|
|
||||||
.Returns(false);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.EditAssignedCollections(orgId)
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().UserId = orgUser.UserId;
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetByOrganizationAsync(orgId, orgUser.UserId.Value)
|
|
||||||
.Returns(orgUser);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICollectionRepository>()
|
|
||||||
.GetByIdAsync(Arg.Any<Guid>(), orgUser.UserId.Value, Arg.Any<bool>())
|
|
||||||
.Returns(new CollectionDetails());
|
|
||||||
|
|
||||||
var collectionRequest = new CollectionRequestModel
|
|
||||||
{
|
|
||||||
Name = "encrypted_string",
|
|
||||||
ExternalId = "my_external_id"
|
|
||||||
};
|
|
||||||
|
|
||||||
_ = await sutProvider.Sut.Post(orgId, collectionRequest);
|
|
||||||
|
|
||||||
var test = sutProvider.GetDependency<ICollectionService>().ReceivedCalls();
|
|
||||||
await sutProvider.GetDependency<ICollectionService>()
|
|
||||||
.Received(1)
|
|
||||||
.SaveAsync(Arg.Any<Collection>(), Arg.Any<IEnumerable<CollectionAccessSelection>>(),
|
|
||||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(users => users.Any(u => u.Id == orgUser.Id && !u.ReadOnly && !u.HidePasswords && !u.Manage)));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task Post_Owner_DoesNotAssignToCollection_Success(Guid orgId, OrganizationUser orgUser, SutProvider<CollectionsController> sutProvider)
|
|
||||||
{
|
|
||||||
orgUser.Type = OrganizationUserType.Owner;
|
|
||||||
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.OrganizationManager(orgId)
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.EditAnyCollection(orgId)
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.EditAssignedCollections(orgId)
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().UserId = orgUser.UserId;
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetByOrganizationAsync(orgId, orgUser.UserId.Value)
|
|
||||||
.Returns(orgUser);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICollectionRepository>()
|
|
||||||
.GetByIdAsync(Arg.Any<Guid>(), orgUser.UserId.Value, Arg.Any<bool>())
|
|
||||||
.Returns(new CollectionDetails());
|
|
||||||
|
|
||||||
var collectionRequest = new CollectionRequestModel
|
|
||||||
{
|
|
||||||
Name = "encrypted_string",
|
|
||||||
ExternalId = "my_external_id"
|
|
||||||
};
|
|
||||||
|
|
||||||
_ = await sutProvider.Sut.Post(orgId, collectionRequest);
|
|
||||||
|
|
||||||
var test = sutProvider.GetDependency<ICollectionService>().ReceivedCalls();
|
|
||||||
await sutProvider.GetDependency<ICollectionService>()
|
|
||||||
.Received(1)
|
|
||||||
.SaveAsync(Arg.Any<Collection>(), Arg.Any<IEnumerable<CollectionAccessSelection>>(),
|
|
||||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(users => !users.Any()));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task Put_Success(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest,
|
|
||||||
SutProvider<CollectionsController> sutProvider)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.ViewAssignedCollections(orgId)
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.EditAssignedCollections(orgId)
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.UserId
|
|
||||||
.Returns(userId);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICollectionRepository>()
|
|
||||||
.GetByIdAsync(collectionId, userId, Arg.Any<bool>())
|
|
||||||
.Returns(new CollectionDetails
|
|
||||||
{
|
|
||||||
OrganizationId = orgId,
|
|
||||||
});
|
|
||||||
|
|
||||||
_ = await sutProvider.Sut.Put(orgId, collectionId, collectionRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task Put_CanNotEditAssignedCollection_ThrowsNotFound(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest,
|
|
||||||
SutProvider<CollectionsController> sutProvider)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.EditAssignedCollections(orgId)
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.UserId
|
|
||||||
.Returns(userId);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICollectionRepository>()
|
|
||||||
.GetByIdAsync(collectionId, userId, Arg.Any<bool>())
|
|
||||||
.Returns(Task.FromResult<CollectionDetails>(null));
|
|
||||||
|
|
||||||
_ = await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.Put(orgId, collectionId, collectionRequest));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task GetOrganizationCollectionsWithGroups_NoManagerPermissions_ThrowsNotFound(Organization organization, SutProvider<CollectionsController> sutProvider)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().ViewAssignedCollections(organization.Id).Returns(false);
|
|
||||||
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetManyWithDetails(organization.Id));
|
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdWithAccessAsync(default);
|
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().GetManyByUserIdWithAccessAsync(default, default, default);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task GetOrganizationCollectionsWithGroups_AdminPermissions_GetsAllCollections(Organization organization, User user, SutProvider<CollectionsController> sutProvider)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().ViewAllCollections(organization.Id).Returns(true);
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organization.Id).Returns(true);
|
|
||||||
|
|
||||||
await sutProvider.Sut.GetManyWithDetails(organization.Id);
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().Received().GetManyByOrganizationIdWithAccessAsync(organization.Id);
|
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().Received().GetManyByUserIdWithAccessAsync(user.Id, organization.Id, Arg.Any<bool>());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task GetOrganizationCollectionsWithGroups_MissingViewAllPermissions_GetsAssignedCollections(Organization organization, User user, SutProvider<CollectionsController> sutProvider)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().ViewAssignedCollections(organization.Id).Returns(true);
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().OrganizationManager(organization.Id).Returns(true);
|
|
||||||
|
|
||||||
await sutProvider.Sut.GetManyWithDetails(organization.Id);
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdWithAccessAsync(default);
|
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().Received().GetManyByUserIdWithAccessAsync(user.Id, organization.Id, Arg.Any<bool>());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task GetOrganizationCollectionsWithGroups_CustomUserWithManagerPermissions_GetsAssignedCollections(Organization organization, User user, SutProvider<CollectionsController> sutProvider)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().ViewAssignedCollections(organization.Id).Returns(true);
|
|
||||||
sutProvider.GetDependency<ICurrentContext>().EditAssignedCollections(organization.Id).Returns(true);
|
|
||||||
|
|
||||||
|
|
||||||
await sutProvider.Sut.GetManyWithDetails(organization.Id);
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdWithAccessAsync(default);
|
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().Received().GetManyByUserIdWithAccessAsync(user.Id, organization.Id, Arg.Any<bool>());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task DeleteMany_Success(Guid orgId, User user, Collection collection1, Collection collection2, SutProvider<CollectionsController> sutProvider)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var model = new CollectionBulkDeleteRequestModel
|
|
||||||
{
|
|
||||||
Ids = new[] { collection1.Id, collection2.Id },
|
|
||||||
};
|
|
||||||
|
|
||||||
var collections = new List<Collection>
|
|
||||||
{
|
|
||||||
new CollectionDetails
|
|
||||||
{
|
|
||||||
Id = collection1.Id,
|
|
||||||
OrganizationId = orgId,
|
|
||||||
},
|
|
||||||
new CollectionDetails
|
|
||||||
{
|
|
||||||
Id = collection2.Id,
|
|
||||||
OrganizationId = orgId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.DeleteAssignedCollections(orgId)
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.UserId
|
|
||||||
.Returns(user.Id);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICollectionService>()
|
|
||||||
.GetOrganizationCollectionsAsync(orgId)
|
|
||||||
.Returns(collections);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await sutProvider.Sut.DeleteMany(orgId, model);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
await sutProvider.GetDependency<IDeleteCollectionCommand>()
|
|
||||||
.Received(1)
|
|
||||||
.DeleteManyAsync(Arg.Is<IEnumerable<Collection>>(coll => coll.Select(c => c.Id).SequenceEqual(collections.Select(c => c.Id))));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task DeleteMany_CanNotDeleteAssignedCollection_ThrowsNotFound(Guid orgId, Collection collection1, Collection collection2, SutProvider<CollectionsController> sutProvider)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var model = new CollectionBulkDeleteRequestModel
|
|
||||||
{
|
|
||||||
Ids = new[] { collection1.Id, collection2.Id },
|
|
||||||
};
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.DeleteAssignedCollections(orgId)
|
|
||||||
.Returns(false);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
|
||||||
sutProvider.Sut.DeleteMany(orgId, model));
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IDeleteCollectionCommand>()
|
|
||||||
.DidNotReceiveWithAnyArgs()
|
|
||||||
.DeleteManyAsync((IEnumerable<Collection>)default);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task DeleteMany_UserCanNotAccessCollections_FiltersOutInvalid(Guid orgId, User user, Collection collection1, Collection collection2, SutProvider<CollectionsController> sutProvider)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var model = new CollectionBulkDeleteRequestModel
|
|
||||||
{
|
|
||||||
Ids = new[] { collection1.Id, collection2.Id },
|
|
||||||
};
|
|
||||||
|
|
||||||
var collections = new List<Collection>
|
|
||||||
{
|
|
||||||
new CollectionDetails
|
|
||||||
{
|
|
||||||
Id = collection2.Id,
|
|
||||||
OrganizationId = orgId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.DeleteAssignedCollections(orgId)
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
|
||||||
.UserId
|
|
||||||
.Returns(user.Id);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ICollectionService>()
|
|
||||||
.GetOrganizationCollectionsAsync(orgId)
|
|
||||||
.Returns(collections);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await sutProvider.Sut.DeleteMany(orgId, model);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
await sutProvider.GetDependency<IDeleteCollectionCommand>()
|
|
||||||
.Received(1)
|
|
||||||
.DeleteManyAsync(Arg.Is<IEnumerable<Collection>>(coll => coll.Select(c => c.Id).SequenceEqual(collections.Select(c => c.Id))));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user