1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-03 00:52:49 -05:00

[AC-1139] Lining up collection access data with Manage = true if feature flag is off

This commit is contained in:
Rui Tome
2023-10-22 11:58:45 +01:00
parent 76298829ed
commit 403e63ca11
3 changed files with 86 additions and 19 deletions

View File

@ -5,6 +5,7 @@ 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.Models.Data;
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;
@ -19,6 +20,7 @@ namespace Bit.Api.Controllers;
public class CollectionsController : Controller public class CollectionsController : Controller
{ {
private readonly ICollectionRepository _collectionRepository; private readonly ICollectionRepository _collectionRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly ICollectionService _collectionService; private readonly ICollectionService _collectionService;
private readonly IDeleteCollectionCommand _deleteCollectionCommand; private readonly IDeleteCollectionCommand _deleteCollectionCommand;
private readonly IUserService _userService; private readonly IUserService _userService;
@ -29,6 +31,7 @@ public class CollectionsController : Controller
public CollectionsController( public CollectionsController(
ICollectionRepository collectionRepository, ICollectionRepository collectionRepository,
IOrganizationUserRepository organizationUserRepository,
ICollectionService collectionService, ICollectionService collectionService,
IDeleteCollectionCommand deleteCollectionCommand, IDeleteCollectionCommand deleteCollectionCommand,
IUserService userService, IUserService userService,
@ -38,6 +41,7 @@ public class CollectionsController : Controller
IFeatureService featureService) IFeatureService featureService)
{ {
_collectionRepository = collectionRepository; _collectionRepository = collectionRepository;
_organizationUserRepository = organizationUserRepository;
_collectionService = collectionService; _collectionService = collectionService;
_deleteCollectionCommand = deleteCollectionCommand; _deleteCollectionCommand = deleteCollectionCommand;
_userService = userService; _userService = userService;
@ -296,6 +300,8 @@ public class CollectionsController : Controller
public async Task PutUsers(Guid orgId, Guid id, [FromBody] IEnumerable<SelectionReadOnlyRequestModel> model) public async Task PutUsers(Guid orgId, Guid id, [FromBody] IEnumerable<SelectionReadOnlyRequestModel> model)
{ {
Collection collection; Collection collection;
var users = model?.Select(g => g.ToSelectionReadOnly()).ToList() ??
new List<CollectionAccessSelection>();
if (FlexibleCollectionsIsEnabled) if (FlexibleCollectionsIsEnabled)
{ {
@ -314,9 +320,26 @@ public class CollectionsController : Controller
} }
collection = await GetCollectionAsync(id, orgId); collection = await GetCollectionAsync(id, orgId);
// If not using Flexible Collections
// all users with EditAnyCollection permission should have Can Manage permission for the collection
var organizationUsers = await _organizationUserRepository
.GetManyByOrganizationAsync(collection.OrganizationId, null);
foreach (var orgUser in organizationUsers.Where(ou => ou.GetPermissions()?.EditAnyCollection ?? false))
{
var user = users.FirstOrDefault(u => u.Id == orgUser.Id);
if (user != null)
{
user.Manage = true;
}
else
{
users.Add(new CollectionAccessSelection { Id = orgUser.Id, Manage = true });
}
}
} }
await _collectionRepository.UpdateUsersAsync(collection.Id, model?.Select(g => g.ToSelectionReadOnly())); await _collectionRepository.UpdateUsersAsync(collection.Id, users);
} }
[HttpPost("bulk-access")] [HttpPost("bulk-access")]

View File

@ -53,21 +53,40 @@ public class CollectionService : ICollectionService
} }
var groupsList = groups?.ToList(); var groupsList = groups?.ToList();
var usersList = users?.ToList(); var usersList = users?.ToList() ?? new List<CollectionAccessSelection>();
// If using Flexible Collections - a collection should always have someone with Can Manage permissions // If using Flexible Collections - a collection should always have someone with Can Manage permissions
if (_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext)) if (_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext))
{ {
var groupHasManageAccess = groupsList?.Any(g => g.Manage) ?? false; var groupHasManageAccess = groupsList?.Any(g => g.Manage) ?? false;
var userHasManageAccess = usersList?.Any(u => u.Manage) ?? false; var userHasManageAccess = usersList.Any(u => u.Manage);
if (!groupHasManageAccess && !userHasManageAccess) if (!groupHasManageAccess && !userHasManageAccess)
{ {
throw new BadRequestException( throw new BadRequestException(
"At least one member or group must have can manage permission."); "At least one member or group must have can manage permission.");
} }
} }
else
{
// If not using Flexible Collections
// all users with EditAnyCollection permission should have Can Manage permission for the collection
var organizationUsers = await _organizationUserRepository
.GetManyByOrganizationAsync(collection.OrganizationId, null);
foreach (var orgUser in organizationUsers.Where(ou => ou.GetPermissions()?.EditAnyCollection ?? false))
{
var user = usersList.FirstOrDefault(u => u.Id == orgUser.Id);
if (user != null)
{
user.Manage = true;
}
else
{
usersList.Add(new CollectionAccessSelection { Id = orgUser.Id, Manage = true });
}
}
}
if (collection.Id == default(Guid)) if (collection.Id == default)
{ {
if (org.MaxCollections.HasValue) if (org.MaxCollections.HasValue)
{ {

View File

@ -54,6 +54,9 @@ public class OrganizationService : IOrganizationService
private readonly IProviderUserRepository _providerUserRepository; private readonly IProviderUserRepository _providerUserRepository;
private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery; private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery;
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand; private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
private readonly IFeatureService _featureService;
private bool FlexibleCollectionsIsEnabled => _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
public OrganizationService( public OrganizationService(
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
@ -82,7 +85,8 @@ public class OrganizationService : IOrganizationService
IProviderOrganizationRepository providerOrganizationRepository, IProviderOrganizationRepository providerOrganizationRepository,
IProviderUserRepository providerUserRepository, IProviderUserRepository providerUserRepository,
ICountNewSmSeatsRequiredQuery countNewSmSeatsRequiredQuery, ICountNewSmSeatsRequiredQuery countNewSmSeatsRequiredQuery,
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand) IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
IFeatureService featureService)
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
@ -111,6 +115,7 @@ public class OrganizationService : IOrganizationService
_providerUserRepository = providerUserRepository; _providerUserRepository = providerUserRepository;
_countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery; _countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery;
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand; _updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
_featureService = featureService;
} }
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken, public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken,
@ -876,7 +881,6 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("Organization must have at least one confirmed owner."); throw new BadRequestException("Organization must have at least one confirmed owner.");
} }
var orgUsers = new List<OrganizationUser>();
var limitedCollectionOrgUsers = new List<(OrganizationUser, IEnumerable<CollectionAccessSelection>)>(); var limitedCollectionOrgUsers = new List<(OrganizationUser, IEnumerable<CollectionAccessSelection>)>();
var orgUserGroups = new List<(OrganizationUser, IEnumerable<Guid>)>(); var orgUserGroups = new List<(OrganizationUser, IEnumerable<Guid>)>();
var orgUserInvitedCount = 0; var orgUserInvitedCount = 0;
@ -915,14 +919,22 @@ public class OrganizationService : IOrganizationService
orgUser.Permissions = JsonSerializer.Serialize(invite.Permissions, JsonHelpers.CamelCase); orgUser.Permissions = JsonSerializer.Serialize(invite.Permissions, JsonHelpers.CamelCase);
} }
if (!orgUser.AccessAll && invite.Collections.Any()) var collections = invite.Collections;
if (!FlexibleCollectionsIsEnabled)
{ {
limitedCollectionOrgUsers.Add((orgUser, invite.Collections)); // If not using Flexible Collections - add access to all collections if user has EditAnyCollection or AccessAll permissions
} if (orgUser.GetPermissions()?.EditAnyCollection ?? false)
else {
{ var orgCollections = await _collectionRepository.GetManyByOrganizationIdAsync(orgUser.OrganizationId);
orgUsers.Add(orgUser); collections = orgCollections.Select(c => new CollectionAccessSelection { Id = c.Id, Manage = true });
}
else if (orgUser.AccessAll)
{
var orgCollections = await _collectionRepository.GetManyByOrganizationIdAsync(orgUser.OrganizationId);
collections = orgCollections.Select(c => new CollectionAccessSelection { Id = c.Id, ReadOnly = true });
}
} }
limitedCollectionOrgUsers.Add((orgUser, collections));
if (invite.Groups != null && invite.Groups.Any()) if (invite.Groups != null && invite.Groups.Any())
{ {
@ -947,7 +959,6 @@ public class OrganizationService : IOrganizationService
var prorationDate = DateTime.UtcNow; var prorationDate = DateTime.UtcNow;
try try
{ {
await _organizationUserRepository.CreateManyAsync(orgUsers);
foreach (var (orgUser, collections) in limitedCollectionOrgUsers) foreach (var (orgUser, collections) in limitedCollectionOrgUsers)
{ {
await _organizationUserRepository.CreateAsync(orgUser, collections); await _organizationUserRepository.CreateAsync(orgUser, collections);
@ -969,7 +980,7 @@ public class OrganizationService : IOrganizationService
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(smSubscriptionUpdate); await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(smSubscriptionUpdate);
} }
await AutoAddSeatsAsync(organization, newSeatsRequired, prorationDate); await AutoAddSeatsAsync(organization, newSeatsRequired, prorationDate);
await SendInvitesAsync(orgUsers.Concat(limitedCollectionOrgUsers.Select(u => u.Item1)), organization); await SendInvitesAsync(limitedCollectionOrgUsers.Select(u => u.Item1), organization);
await _referenceEventService.RaiseEventAsync( await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.InvitedUsers, organization, _currentContext) new ReferenceEvent(ReferenceEventType.InvitedUsers, organization, _currentContext)
@ -980,7 +991,7 @@ public class OrganizationService : IOrganizationService
catch (Exception e) catch (Exception e)
{ {
// Revert any added users. // Revert any added users.
var invitedOrgUserIds = orgUsers.Select(u => u.Id).Concat(limitedCollectionOrgUsers.Select(u => u.Item1.Id)); var invitedOrgUserIds = limitedCollectionOrgUsers.Select(u => u.Item1.Id);
await _organizationUserRepository.DeleteManyAsync(invitedOrgUserIds); await _organizationUserRepository.DeleteManyAsync(invitedOrgUserIds);
var currentOrganization = await _organizationRepository.GetByIdAsync(organization.Id); var currentOrganization = await _organizationRepository.GetByIdAsync(organization.Id);
@ -1010,7 +1021,7 @@ public class OrganizationService : IOrganizationService
throw new AggregateException("One or more errors occurred while inviting users.", exceptions); throw new AggregateException("One or more errors occurred while inviting users.", exceptions);
} }
return (orgUsers, events); return (limitedCollectionOrgUsers.Select(orgUser => orgUser.Item1).ToList(), events);
} }
public async Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, public async Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId,
@ -1436,11 +1447,25 @@ public class OrganizationService : IOrganizationService
} }
} }
if (user.AccessAll) // If not using Flexible Collections - add access to all collections if user has EditAnyCollection or AccessAll permissions
if (!FlexibleCollectionsIsEnabled)
{ {
// We don't need any collections if we're flagged to have all access. if (user.GetPermissions()?.EditAnyCollection ?? false)
collections = new List<CollectionAccessSelection>(); {
var orgCollections = await _collectionRepository.GetManyByOrganizationIdAsync(user.OrganizationId);
collections = orgCollections.Select(c => new CollectionAccessSelection { Id = c.Id, Manage = true });
}
else if (user.AccessAll)
{
var orgCollections = await _collectionRepository.GetManyByOrganizationIdAsync(user.OrganizationId);
collections = orgCollections.Select(c => new CollectionAccessSelection { Id = c.Id, ReadOnly = true });
}
else
{
collections = collections.Where(c => !c.Manage);
}
} }
await _organizationUserRepository.ReplaceAsync(user, collections); await _organizationUserRepository.ReplaceAsync(user, collections);
if (groups != null) if (groups != null)