diff --git a/src/Api/Controllers/CollectionsController.cs b/src/Api/Controllers/CollectionsController.cs index 1d92226d8e..c4efe8d9f2 100644 --- a/src/Api/Controllers/CollectionsController.cs +++ b/src/Api/Controllers/CollectionsController.cs @@ -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 model) { Collection collection; + var users = model?.Select(g => g.ToSelectionReadOnly()).ToList() ?? + new List(); 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")] diff --git a/src/Core/Services/Implementations/CollectionService.cs b/src/Core/Services/Implementations/CollectionService.cs index 5e907e927c..ec99e0fb99 100644 --- a/src/Core/Services/Implementations/CollectionService.cs +++ b/src/Core/Services/Implementations/CollectionService.cs @@ -53,21 +53,40 @@ public class CollectionService : ICollectionService } var groupsList = groups?.ToList(); - var usersList = users?.ToList(); + var usersList = users?.ToList() ?? new List(); // 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) { diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 6343cc92de..b47520a6c8 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -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(); var limitedCollectionOrgUsers = new List<(OrganizationUser, IEnumerable)>(); var orgUserGroups = new List<(OrganizationUser, IEnumerable)>(); 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>> 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(); + 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)