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:
@ -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")]
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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
|
|
||||||
{
|
{
|
||||||
orgUsers.Add(orgUser);
|
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())
|
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)
|
||||||
|
Reference in New Issue
Block a user