From 77698c3ee23156179989ef07999b68e8c8e45371 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Mon, 22 Jan 2024 08:56:20 +1000 Subject: [PATCH] [AC-2052] Block Manager role and AccessAll if using FlexibleCollections (#3671) * Also don't assign AccessAll to the first orgUser if using Flexible Collections --- .../AdminConsole/Services/ProviderService.cs | 5 +- .../Services/ProviderServiceTests.cs | 32 ++- .../Groups/CreateGroupCommand.cs | 11 +- .../Groups/UpdateGroupCommand.cs | 11 +- .../Implementations/OrganizationService.cs | 44 +++-- .../AutoFixture/OrganizationFixtures.cs | 13 +- .../Groups/CreateGroupCommandTests.cs | 26 ++- .../Groups/UpdateGroupCommandTests.cs | 25 ++- .../Services/OrganizationServiceTests.cs | 187 +++++++++++++++--- 9 files changed, 291 insertions(+), 63 deletions(-) diff --git a/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs b/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs index f9049de07..b6d53c0ad 100644 --- a/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs +++ b/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs @@ -515,7 +515,10 @@ public class ProviderService : IProviderService new OrganizationUserInvite { Emails = new[] { clientOwnerEmail }, - AccessAll = true, + + // If using Flexible Collections, AccessAll is deprecated and set to false. + // If not using Flexible Collections, set AccessAll to true (previous behavior) + AccessAll = !organization.FlexibleCollections, Type = OrganizationUserType.Owner, Permissions = null, Collections = Array.Empty(), diff --git a/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs b/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs index eea0ac53f..2d67c8c25 100644 --- a/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs @@ -12,6 +12,7 @@ using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -513,7 +514,7 @@ public class ProviderServiceTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogProviderOrganizationEventsAsync(default); } - [Theory, BitAutoData] + [Theory, OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task CreateOrganizationAsync_Success(Provider provider, OrganizationSignup organizationSignup, Organization organization, string clientOwnerEmail, User user, SutProvider sutProvider) { @@ -541,6 +542,35 @@ public class ProviderServiceTests t.First().Item2 == null)); } + [Theory, OrganizationCustomize(FlexibleCollections = true), BitAutoData] + public async Task CreateOrganizationAsync_WithFlexibleCollections_SetsAccessAllToFalse + (Provider provider, OrganizationSignup organizationSignup, Organization organization, string clientOwnerEmail, + User user, SutProvider sutProvider) + { + organizationSignup.Plan = PlanType.EnterpriseAnnually; + + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + var providerOrganizationRepository = sutProvider.GetDependency(); + sutProvider.GetDependency().SignUpAsync(organizationSignup, true) + .Returns(Tuple.Create(organization, null as OrganizationUser)); + + var providerOrganization = + await sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user); + + await providerOrganizationRepository.ReceivedWithAnyArgs().CreateAsync(default); + await sutProvider.GetDependency() + .Received().LogProviderOrganizationEventAsync(providerOrganization, + EventType.ProviderOrganization_Created); + await sutProvider.GetDependency() + .Received().InviteUsersAsync(organization.Id, user.Id, Arg.Is>( + t => t.Count() == 1 && + t.First().Item1.Emails.Count() == 1 && + t.First().Item1.Emails.First() == clientOwnerEmail && + t.First().Item1.Type == OrganizationUserType.Owner && + t.First().Item1.AccessAll == false && + t.First().Item2 == null)); + } + [Theory, BitAutoData] public async Task AddOrganization_CreateAfterNov162023_PlanTypeDoesNotUpdated(Provider provider, Organization organization, string key, SutProvider sutProvider) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommand.cs index 6c2dfd6b5..61321b2df 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommand.cs @@ -39,7 +39,7 @@ public class CreateGroupCommand : ICreateGroupCommand IEnumerable collections = null, IEnumerable users = null) { - Validate(organization); + Validate(organization, group); await GroupRepositoryCreateGroupAsync(group, organization, collections); if (users != null) @@ -54,7 +54,7 @@ public class CreateGroupCommand : ICreateGroupCommand IEnumerable collections = null, IEnumerable users = null) { - Validate(organization); + Validate(organization, group); await GroupRepositoryCreateGroupAsync(group, organization, collections); if (users != null) @@ -103,7 +103,7 @@ public class CreateGroupCommand : ICreateGroupCommand } } - private static void Validate(Organization organization) + private static void Validate(Organization organization, Group group) { if (organization == null) { @@ -114,5 +114,10 @@ public class CreateGroupCommand : ICreateGroupCommand { throw new BadRequestException("This organization cannot use groups."); } + + if (organization.FlexibleCollections && group.AccessAll) + { + throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the group to collections instead."); + } } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs index 3bc241221..fecc06be8 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs @@ -29,7 +29,7 @@ public class UpdateGroupCommand : IUpdateGroupCommand IEnumerable collections = null, IEnumerable userIds = null) { - Validate(organization); + Validate(organization, group); await GroupRepositoryUpdateGroupAsync(group, collections); if (userIds != null) @@ -44,7 +44,7 @@ public class UpdateGroupCommand : IUpdateGroupCommand IEnumerable collections = null, IEnumerable userIds = null) { - Validate(organization); + Validate(organization, group); await GroupRepositoryUpdateGroupAsync(group, collections); if (userIds != null) @@ -97,7 +97,7 @@ public class UpdateGroupCommand : IUpdateGroupCommand } } - private static void Validate(Organization organization) + private static void Validate(Organization organization, Group group) { if (organization == null) { @@ -108,5 +108,10 @@ public class UpdateGroupCommand : IUpdateGroupCommand { throw new BadRequestException("This organization cannot use groups."); } + + if (organization.FlexibleCollections && group.AccessAll) + { + throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the group to collections instead."); + } } } diff --git a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs index 7adf30f85..c3a6a06e6 100644 --- a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs +++ b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs @@ -673,7 +673,10 @@ public class OrganizationService : IOrganizationService AccessSecretsManager = organization.UseSecretsManager, Type = OrganizationUserType.Owner, Status = OrganizationUserStatusType.Confirmed, - AccessAll = true, + + // If using Flexible Collections, AccessAll is deprecated and set to false. + // If not using Flexible Collections, set AccessAll to true (previous behavior) + AccessAll = !organization.FlexibleCollections, CreationDate = organization.CreationDate, RevisionDate = organization.CreationDate }; @@ -885,6 +888,18 @@ public class OrganizationService : IOrganizationService throw new NotFoundException(); } + // If the organization is using Flexible Collections, prevent use of any deprecated permissions + if (organization.FlexibleCollections && invites.Any(i => i.invite.Type is OrganizationUserType.Manager)) + { + throw new BadRequestException("The Manager role has been deprecated by collection enhancements. Use the collection Can Manage permission instead."); + } + + if (organization.FlexibleCollections && invites.Any(i => i.invite.AccessAll)) + { + throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the user to collections instead."); + } + // End Flexible Collections + var existingEmails = new HashSet(await _organizationUserRepository.SelectKnownEmailsAsync( organizationId, invites.SelectMany(i => i.invite.Emails), false), StringComparer.InvariantCultureIgnoreCase); @@ -1377,6 +1392,19 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("Organization must have at least one confirmed owner."); } + // If the organization is using Flexible Collections, prevent use of any deprecated permissions + var organizationAbility = await _applicationCacheService.GetOrganizationAbilityAsync(user.OrganizationId); + if (organizationAbility?.FlexibleCollections == true && user.Type == OrganizationUserType.Manager) + { + throw new BadRequestException("The Manager role has been deprecated by collection enhancements. Use the collection Can Manage permission instead."); + } + + if (organizationAbility?.FlexibleCollections == true && user.AccessAll) + { + throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the user to collections instead."); + } + // End Flexible Collections + // Only autoscale (if required) after all validation has passed so that we know it's a valid request before // updating Stripe if (!originalUser.AccessSecretsManager && user.AccessSecretsManager) @@ -2027,15 +2055,6 @@ public class OrganizationService : IOrganizationService { throw new BadRequestException("Custom users can only grant the same custom permissions that they have."); } - - // TODO: pass in the whole organization object when this is refactored into a command/query - // See AC-2036 - var organizationAbility = await _applicationCacheService.GetOrganizationAbilityAsync(organizationId); - var flexibleCollectionsEnabled = organizationAbility?.FlexibleCollections ?? false; - if (flexibleCollectionsEnabled && newType == OrganizationUserType.Manager && oldType is not OrganizationUserType.Manager) - { - throw new BadRequestException("Manager role is deprecated after Flexible Collections."); - } } private async Task ValidateOrganizationCustomPermissionsEnabledAsync(Guid organizationId, OrganizationUserType newType) @@ -2451,7 +2470,10 @@ public class OrganizationService : IOrganizationService Key = null, Type = OrganizationUserType.Owner, Status = OrganizationUserStatusType.Invited, - AccessAll = true + + // If using Flexible Collections, AccessAll is deprecated and set to false. + // If not using Flexible Collections, set AccessAll to true (previous behavior) + AccessAll = !organization.FlexibleCollections, }; await _organizationUserRepository.CreateAsync(ownerOrganizationUser); diff --git a/test/Core.Test/AdminConsole/AutoFixture/OrganizationFixtures.cs b/test/Core.Test/AdminConsole/AutoFixture/OrganizationFixtures.cs index ef3889c6d..f549dd253 100644 --- a/test/Core.Test/AdminConsole/AutoFixture/OrganizationFixtures.cs +++ b/test/Core.Test/AdminConsole/AutoFixture/OrganizationFixtures.cs @@ -18,6 +18,7 @@ namespace Bit.Core.Test.AutoFixture.OrganizationFixtures; public class OrganizationCustomization : ICustomization { public bool UseGroups { get; set; } + public bool FlexibleCollections { get; set; } public void Customize(IFixture fixture) { @@ -27,7 +28,8 @@ public class OrganizationCustomization : ICustomization fixture.Customize(composer => composer .With(o => o.Id, organizationId) .With(o => o.MaxCollections, maxCollections) - .With(o => o.UseGroups, UseGroups)); + .With(o => o.UseGroups, UseGroups) + .With(o => o.FlexibleCollections, FlexibleCollections)); fixture.Customize(composer => composer @@ -181,10 +183,15 @@ internal class TeamsMonthlyWithAddOnsOrganizationCustomization : ICustomization } } -internal class OrganizationCustomizeAttribute : BitCustomizeAttribute +public class OrganizationCustomizeAttribute : BitCustomizeAttribute { public bool UseGroups { get; set; } - public override ICustomization GetCustomization() => new OrganizationCustomization() { UseGroups = UseGroups }; + public bool FlexibleCollections { get; set; } + public override ICustomization GetCustomization() => new OrganizationCustomization() + { + UseGroups = UseGroups, + FlexibleCollections = FlexibleCollections + }; } internal class PaidOrganizationCustomizeAttribute : BitCustomizeAttribute diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommandTests.cs index 9d28705e1..bac2630ed 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommandTests.cs @@ -20,7 +20,7 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Groups; [SutProviderCustomize] public class CreateGroupCommandTests { - [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] public async Task CreateGroup_Success(SutProvider sutProvider, Organization organization, Group group) { await sutProvider.Sut.CreateGroupAsync(group, organization); @@ -32,7 +32,7 @@ public class CreateGroupCommandTests AssertHelper.AssertRecent(group.RevisionDate); } - [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] public async Task CreateGroup_WithCollections_Success(SutProvider sutProvider, Organization organization, Group group, List collections) { await sutProvider.Sut.CreateGroupAsync(group, organization, collections); @@ -44,7 +44,7 @@ public class CreateGroupCommandTests AssertHelper.AssertRecent(group.RevisionDate); } - [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] public async Task CreateGroup_WithEventSystemUser_Success(SutProvider sutProvider, Organization organization, Group group, EventSystemUser eventSystemUser) { await sutProvider.Sut.CreateGroupAsync(group, organization, eventSystemUser); @@ -56,7 +56,7 @@ public class CreateGroupCommandTests AssertHelper.AssertRecent(group.RevisionDate); } - [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] public async Task CreateGroup_WithNullOrganization_Throws(SutProvider sutProvider, Group group, EventSystemUser eventSystemUser) { var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.CreateGroupAsync(group, null, eventSystemUser)); @@ -68,7 +68,7 @@ public class CreateGroupCommandTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RaiseEventAsync(default); } - [Theory, OrganizationCustomize(UseGroups = false), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = false, FlexibleCollections = false), BitAutoData] public async Task CreateGroup_WithUseGroupsAsFalse_Throws(SutProvider sutProvider, Organization organization, Group group, EventSystemUser eventSystemUser) { var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.CreateGroupAsync(group, organization, eventSystemUser)); @@ -79,4 +79,20 @@ public class CreateGroupCommandTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RaiseEventAsync(default); } + + [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = true), BitAutoData] + public async Task CreateGroup_WithFlexibleCollections_WithAccessAll_Throws( + SutProvider sutProvider, Organization organization, Group group) + { + group.AccessAll = true; + organization.FlexibleCollections = true; + + var exception = + await Assert.ThrowsAsync(async () => await sutProvider.Sut.CreateGroupAsync(group, organization)); + Assert.Contains("AccessAll property has been deprecated", exception.Message); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RaiseEventAsync(default); + } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs index 82ac484e7..1b21574fd 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs @@ -17,7 +17,7 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Groups; [SutProviderCustomize] public class UpdateGroupCommandTests { - [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] public async Task UpdateGroup_Success(SutProvider sutProvider, Group group, Organization organization) { await sutProvider.Sut.UpdateGroupAsync(group, organization); @@ -27,7 +27,7 @@ public class UpdateGroupCommandTests AssertHelper.AssertRecent(group.RevisionDate); } - [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] public async Task UpdateGroup_WithCollections_Success(SutProvider sutProvider, Group group, Organization organization, List collections) { await sutProvider.Sut.UpdateGroupAsync(group, organization, collections); @@ -37,7 +37,7 @@ public class UpdateGroupCommandTests AssertHelper.AssertRecent(group.RevisionDate); } - [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] public async Task UpdateGroup_WithEventSystemUser_Success(SutProvider sutProvider, Group group, Organization organization, EventSystemUser eventSystemUser) { await sutProvider.Sut.UpdateGroupAsync(group, organization, eventSystemUser); @@ -47,7 +47,7 @@ public class UpdateGroupCommandTests AssertHelper.AssertRecent(group.RevisionDate); } - [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] public async Task UpdateGroup_WithNullOrganization_Throws(SutProvider sutProvider, Group group, EventSystemUser eventSystemUser) { var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateGroupAsync(group, null, eventSystemUser)); @@ -58,7 +58,7 @@ public class UpdateGroupCommandTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); } - [Theory, OrganizationCustomize(UseGroups = false), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = false, FlexibleCollections = false), BitAutoData] public async Task UpdateGroup_WithUseGroupsAsFalse_Throws(SutProvider sutProvider, Organization organization, Group group, EventSystemUser eventSystemUser) { var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateGroupAsync(group, organization, eventSystemUser)); @@ -68,4 +68,19 @@ public class UpdateGroupCommandTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); } + + [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = true), BitAutoData] + public async Task UpdateGroup_WithFlexibleCollections_WithAccessAll_Throws( + SutProvider sutProvider, Organization organization, Group group) + { + group.AccessAll = true; + organization.FlexibleCollections = true; + + var exception = + await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateGroupAsync(group, organization)); + Assert.Contains("AccessAll property has been deprecated", exception.Message); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); + } } diff --git a/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs b/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs index d4888a63e..52dce5802 100644 --- a/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs +++ b/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs @@ -251,6 +251,64 @@ public class OrganizationServiceTests ); } + [Theory] + [BitAutoData(PlanType.FamiliesAnnually)] + public async Task SignUp_WithFlexibleCollections_SetsAccessAllToFalse + (PlanType planType, OrganizationSignup signup, SutProvider sutProvider) + { + signup.Plan = planType; + var plan = StaticStore.GetPlan(signup.Plan); + signup.AdditionalSeats = 0; + signup.PaymentMethodType = PaymentMethodType.Card; + signup.PremiumAccessAddon = false; + signup.UseSecretsManager = false; + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.FlexibleCollectionsSignup) + .Returns(true); + + var result = await sutProvider.Sut.SignUpAsync(signup); + + await sutProvider.GetDependency().Received(1).CreateAsync( + Arg.Is(o => + o.UserId == signup.Owner.Id && + o.AccessAll == false)); + + Assert.NotNull(result); + Assert.NotNull(result.Item1); + Assert.NotNull(result.Item2); + Assert.IsType>(result); + } + + [Theory] + [BitAutoData(PlanType.FamiliesAnnually)] + public async Task SignUp_WithoutFlexibleCollections_SetsAccessAllToTrue + (PlanType planType, OrganizationSignup signup, SutProvider sutProvider) + { + signup.Plan = planType; + var plan = StaticStore.GetPlan(signup.Plan); + signup.AdditionalSeats = 0; + signup.PaymentMethodType = PaymentMethodType.Card; + signup.PremiumAccessAddon = false; + signup.UseSecretsManager = false; + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.FlexibleCollectionsSignup) + .Returns(false); + + var result = await sutProvider.Sut.SignUpAsync(signup); + + await sutProvider.GetDependency().Received(1).CreateAsync( + Arg.Is(o => + o.UserId == signup.Owner.Id && + o.AccessAll == true)); + + Assert.NotNull(result); + Assert.NotNull(result.Item1); + Assert.NotNull(result.Item2); + Assert.IsType>(result); + } + [Theory] [BitAutoData(PlanType.EnterpriseAnnually)] [BitAutoData(PlanType.EnterpriseMonthly)] @@ -378,7 +436,7 @@ public class OrganizationServiceTests [Theory] [OrganizationInviteCustomize(InviteeUserType = OrganizationUserType.User, - InvitorUserType = OrganizationUserType.Owner), BitAutoData] + InvitorUserType = OrganizationUserType.Owner), OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_NoEmails_Throws(Organization organization, OrganizationUser invitor, OrganizationUserInvite invite, SutProvider sutProvider) { @@ -391,7 +449,7 @@ public class OrganizationServiceTests } [Theory] - [OrganizationInviteCustomize, BitAutoData] + [OrganizationInviteCustomize, OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_DuplicateEmails_PassesWithoutDuplicates(Organization organization, OrganizationUser invitor, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, OrganizationUserInvite invite, SutProvider sutProvider) @@ -434,7 +492,7 @@ public class OrganizationServiceTests } [Theory] - [OrganizationInviteCustomize, BitAutoData] + [OrganizationInviteCustomize, OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_SsoOrgWithNullSsoConfig_Passes(Organization organization, OrganizationUser invitor, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, OrganizationUserInvite invite, SutProvider sutProvider) @@ -483,7 +541,7 @@ public class OrganizationServiceTests } [Theory] - [OrganizationInviteCustomize, BitAutoData] + [OrganizationInviteCustomize, OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_SsoOrgWithNeverEnabledRequireSsoPolicy_Passes(Organization organization, SsoConfig ssoConfig, OrganizationUser invitor, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, OrganizationUserInvite invite, SutProvider sutProvider) @@ -537,7 +595,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.Admin, InvitorUserType = OrganizationUserType.Owner - ), BitAutoData] + ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_NoOwner_Throws(Organization organization, OrganizationUser invitor, OrganizationUserInvite invite, SutProvider sutProvider) { @@ -553,7 +611,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.Owner, InvitorUserType = OrganizationUserType.Admin - ), BitAutoData] + ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_NonOwnerConfiguringOwner_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -572,7 +630,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.Custom, InvitorUserType = OrganizationUserType.User - ), BitAutoData] + ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_NonAdminConfiguringAdmin_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -593,7 +651,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.Custom, InvitorUserType = OrganizationUserType.Admin - ), BitAutoData] + ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_WithCustomType_WhenUseCustomPermissionsIsFalse_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -620,7 +678,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.Custom, InvitorUserType = OrganizationUserType.Admin - ), BitAutoData] + ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_WithCustomType_WhenUseCustomPermissionsIsTrue_Passes(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -646,6 +704,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) } [Theory] + [OrganizationCustomize(FlexibleCollections = false)] [BitAutoData(OrganizationUserType.Admin)] [BitAutoData(OrganizationUserType.Manager)] [BitAutoData(OrganizationUserType.Owner)] @@ -679,7 +738,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.Manager, InvitorUserType = OrganizationUserType.Custom - ), BitAutoData] + ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_CustomUserWithoutManageUsersConfiguringUser_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -707,7 +766,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.Admin, InvitorUserType = OrganizationUserType.Custom - ), BitAutoData] + ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_CustomUserConfiguringAdmin_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -733,7 +792,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.User, InvitorUserType = OrganizationUserType.Owner - ), BitAutoData] + ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_NoPermissionsObject_Passes(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -759,7 +818,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.User, InvitorUserType = OrganizationUserType.Custom - ), BitAutoData] + ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_Passes(Organization organization, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites, OrganizationUser invitor, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, @@ -832,7 +891,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.User, InvitorUserType = OrganizationUserType.Custom - ), BitAutoData] + ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task InviteUser_WithEventSystemUser_Passes(Organization organization, EventSystemUser eventSystemUser, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites, OrganizationUser invitor, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, @@ -882,7 +941,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) await sutProvider.GetDependency().Received(1).LogOrganizationUserEventsAsync(Arg.Any>()); } - [Theory, BitAutoData, OrganizationInviteCustomize] + [Theory, BitAutoData, OrganizationCustomize(FlexibleCollections = false), OrganizationInviteCustomize] public async Task InviteUser_WithSecretsManager_Passes(Organization organization, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites, OrganizationUser savingUser, SutProvider sutProvider) @@ -916,7 +975,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) !update.MaxAutoscaleSmSeatsChanged)); } - [Theory, BitAutoData, OrganizationInviteCustomize] + [Theory, BitAutoData, OrganizationCustomize(FlexibleCollections = false), OrganizationInviteCustomize] public async Task InviteUser_WithSecretsManager_WhenErrorIsThrown_RevertsAutoscaling(Organization organization, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites, OrganizationUser savingUser, SutProvider sutProvider) @@ -972,26 +1031,48 @@ OrganizationUserInvite invite, SutProvider sutProvider) }); } - [Theory, BitAutoData] - public async Task InviteUser_WithFCEnabled_WhenInvitingManager_Throws(OrganizationAbility organizationAbility, + [Theory, OrganizationCustomize(FlexibleCollections = true), BitAutoData] + public async Task InviteUser_WithFlexibleCollections_WhenInvitingManager_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { invite.Type = OrganizationUserType.Manager; - organizationAbility.FlexibleCollections = true; + organization.FlexibleCollections = true; - sutProvider.GetDependency() - .GetOrganizationAbilityAsync(organizationAbility.Id) - .Returns(organizationAbility); + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); sutProvider.GetDependency() - .ManageUsers(organizationAbility.Id) + .ManageUsers(organization.Id) .Returns(true); var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.InviteUsersAsync(organizationAbility.Id, invitor.UserId, + () => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, new (OrganizationUserInvite, string)[] { (invite, null) })); - Assert.Contains("manager role is deprecated", exception.Message.ToLowerInvariant()); + Assert.Contains("manager role has been deprecated", exception.Message.ToLowerInvariant()); + } + + [Theory, OrganizationCustomize(FlexibleCollections = true), BitAutoData] + public async Task InviteUser_WithFlexibleCollections_WithAccessAll_Throws(Organization organization, + OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) + { + invite.Type = OrganizationUserType.User; + invite.AccessAll = true; + + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + sutProvider.GetDependency() + .ManageUsers(organization.Id) + .Returns(true); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, + new (OrganizationUserInvite, string)[] { (invite, null) })); + + Assert.Contains("accessall property has been deprecated", exception.Message.ToLowerInvariant()); } private void InviteUserHelper_ArrangeValidPermissions(Organization organization, OrganizationUser savingUser, @@ -1275,15 +1356,21 @@ OrganizationUserInvite invite, SutProvider sutProvider) } [Theory, BitAutoData] - public async Task SaveUser_WithFCEnabled_WhenUpgradingToManager_Throws( + public async Task SaveUser_WithFlexibleCollections_WhenUpgradingToManager_Throws( OrganizationAbility organizationAbility, [OrganizationUser(type: OrganizationUserType.User)] OrganizationUser oldUserData, [OrganizationUser(type: OrganizationUserType.Manager)] OrganizationUser newUserData, + [OrganizationUser(type: OrganizationUserType.Owner, status: OrganizationUserStatusType.Confirmed)] OrganizationUser savingUser, IEnumerable collections, IEnumerable groups, SutProvider sutProvider) { organizationAbility.FlexibleCollections = true; + newUserData.Id = oldUserData.Id; + newUserData.UserId = oldUserData.UserId; + newUserData.OrganizationId = oldUserData.OrganizationId = savingUser.OrganizationId = organizationAbility.Id; + newUserData.Permissions = CoreHelpers.ClassToJsonData(new Permissions()); + sutProvider.GetDependency() .GetOrganizationAbilityAsync(organizationAbility.Id) .Returns(organizationAbility); @@ -1296,15 +1383,53 @@ OrganizationUserInvite invite, SutProvider sutProvider) .GetByIdAsync(oldUserData.Id) .Returns(oldUserData); - newUserData.Id = oldUserData.Id; - newUserData.UserId = oldUserData.UserId; - newUserData.OrganizationId = oldUserData.OrganizationId = organizationAbility.Id; - newUserData.Permissions = CoreHelpers.ClassToJsonData(new Permissions()); + sutProvider.GetDependency() + .GetManyByOrganizationAsync(organizationAbility.Id, OrganizationUserType.Owner) + .Returns(new List { savingUser }); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SaveUserAsync(newUserData, oldUserData.UserId, collections, groups)); - Assert.Contains("manager role is deprecated", exception.Message.ToLowerInvariant()); + Assert.Contains("manager role has been deprecated", exception.Message.ToLowerInvariant()); + } + + [Theory, BitAutoData] + public async Task SaveUser_WithFlexibleCollections_WithAccessAll_Throws( + OrganizationAbility organizationAbility, + [OrganizationUser(type: OrganizationUserType.User)] OrganizationUser oldUserData, + [OrganizationUser(type: OrganizationUserType.User)] OrganizationUser newUserData, + [OrganizationUser(type: OrganizationUserType.Owner, status: OrganizationUserStatusType.Confirmed)] OrganizationUser savingUser, + IEnumerable collections, + IEnumerable groups, + SutProvider sutProvider) + { + organizationAbility.FlexibleCollections = true; + newUserData.Id = oldUserData.Id; + newUserData.UserId = oldUserData.UserId; + newUserData.OrganizationId = oldUserData.OrganizationId = savingUser.OrganizationId = organizationAbility.Id; + newUserData.Permissions = CoreHelpers.ClassToJsonData(new Permissions()); + newUserData.AccessAll = true; + + sutProvider.GetDependency() + .GetOrganizationAbilityAsync(organizationAbility.Id) + .Returns(organizationAbility); + + sutProvider.GetDependency() + .ManageUsers(organizationAbility.Id) + .Returns(true); + + sutProvider.GetDependency() + .GetByIdAsync(oldUserData.Id) + .Returns(oldUserData); + + sutProvider.GetDependency() + .GetManyByOrganizationAsync(organizationAbility.Id, OrganizationUserType.Owner) + .Returns(new List { savingUser }); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.SaveUserAsync(newUserData, oldUserData.UserId, collections, groups)); + + Assert.Contains("the accessall property has been deprecated", exception.Message.ToLowerInvariant()); } [Theory, BitAutoData]