1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 16:42:50 -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.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
@ -19,6 +20,7 @@ namespace Bit.Api.Controllers;
public class CollectionsController : Controller
{
private readonly ICollectionRepository _collectionRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly ICollectionService _collectionService;
private readonly IDeleteCollectionCommand _deleteCollectionCommand;
private readonly IUserService _userService;
@ -29,6 +31,7 @@ public class CollectionsController : Controller
public CollectionsController(
ICollectionRepository collectionRepository,
IOrganizationUserRepository organizationUserRepository,
ICollectionService collectionService,
IDeleteCollectionCommand deleteCollectionCommand,
IUserService userService,
@ -38,6 +41,7 @@ public class CollectionsController : Controller
IFeatureService featureService)
{
_collectionRepository = collectionRepository;
_organizationUserRepository = organizationUserRepository;
_collectionService = collectionService;
_deleteCollectionCommand = deleteCollectionCommand;
_userService = userService;
@ -296,6 +300,8 @@ public class CollectionsController : Controller
public async Task PutUsers(Guid orgId, Guid id, [FromBody] IEnumerable<SelectionReadOnlyRequestModel> model)
{
Collection collection;
var users = model?.Select(g => g.ToSelectionReadOnly()).ToList() ??
new List<CollectionAccessSelection>();
if (FlexibleCollectionsIsEnabled)
{
@ -314,9 +320,26 @@ public class CollectionsController : Controller
}
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")]

View File

@ -53,21 +53,40 @@ public class CollectionService : ICollectionService
}
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 (_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext))
{
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)
{
throw new BadRequestException(
"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)
{

View File

@ -54,6 +54,9 @@ public class OrganizationService : IOrganizationService
private readonly IProviderUserRepository _providerUserRepository;
private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery;
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
private readonly IFeatureService _featureService;
private bool FlexibleCollectionsIsEnabled => _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
public OrganizationService(
IOrganizationRepository organizationRepository,
@ -82,7 +85,8 @@ public class OrganizationService : IOrganizationService
IProviderOrganizationRepository providerOrganizationRepository,
IProviderUserRepository providerUserRepository,
ICountNewSmSeatsRequiredQuery countNewSmSeatsRequiredQuery,
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand)
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
IFeatureService featureService)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
@ -111,6 +115,7 @@ public class OrganizationService : IOrganizationService
_providerUserRepository = providerUserRepository;
_countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery;
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
_featureService = featureService;
}
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.");
}
var orgUsers = new List<OrganizationUser>();
var limitedCollectionOrgUsers = new List<(OrganizationUser, IEnumerable<CollectionAccessSelection>)>();
var orgUserGroups = new List<(OrganizationUser, IEnumerable<Guid>)>();
var orgUserInvitedCount = 0;
@ -915,14 +919,22 @@ public class OrganizationService : IOrganizationService
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));
}
else
{
orgUsers.Add(orgUser);
// If not using Flexible Collections - add access to all collections if user has EditAnyCollection or AccessAll permissions
if (orgUser.GetPermissions()?.EditAnyCollection ?? false)
{
var orgCollections = await _collectionRepository.GetManyByOrganizationIdAsync(orgUser.OrganizationId);
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())
{
@ -947,7 +959,6 @@ public class OrganizationService : IOrganizationService
var prorationDate = DateTime.UtcNow;
try
{
await _organizationUserRepository.CreateManyAsync(orgUsers);
foreach (var (orgUser, collections) in limitedCollectionOrgUsers)
{
await _organizationUserRepository.CreateAsync(orgUser, collections);
@ -969,7 +980,7 @@ public class OrganizationService : IOrganizationService
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(smSubscriptionUpdate);
}
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(
new ReferenceEvent(ReferenceEventType.InvitedUsers, organization, _currentContext)
@ -980,7 +991,7 @@ public class OrganizationService : IOrganizationService
catch (Exception e)
{
// 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);
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);
}
return (orgUsers, events);
return (limitedCollectionOrgUsers.Select(orgUser => orgUser.Item1).ToList(), events);
}
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.
collections = new List<CollectionAccessSelection>();
if (user.GetPermissions()?.EditAnyCollection ?? false)
{
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);
if (groups != null)