diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/MssqlDatabaseDataAttribute.cs b/test/Infrastructure.IntegrationTest/AdminConsole/MssqlDatabaseDataAttribute.cs new file mode 100644 index 0000000000..01180ca312 --- /dev/null +++ b/test/Infrastructure.IntegrationTest/AdminConsole/MssqlDatabaseDataAttribute.cs @@ -0,0 +1,54 @@ +using Bit.Core.Enums; +using Bit.Core.Settings; +using Bit.Infrastructure.Dapper; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Bit.Infrastructure.IntegrationTest.AdminConsole; + +/// +/// Used to test the mssql database only. +/// This is generally NOT what you want and is only used for Flexible Collections which has an opt-in method specific +/// to cloud (and therefore mssql) only. This should be deleted during cleanup so that others don't use it. +/// +internal class MssqlDatabaseDataAttribute : DatabaseDataAttribute +{ + protected override IEnumerable GetDatabaseProviders(IConfiguration config) + { + var configureLogging = (ILoggingBuilder builder) => + { + if (!config.GetValue("Quiet")) + { + builder.AddConfiguration(config); + builder.AddConsole(); + builder.AddDebug(); + } + }; + + var databases = config.GetDatabases(); + + foreach (var database in databases) + { + if (database.Type == SupportedDatabaseProviders.SqlServer && !database.UseEf) + { + var dapperSqlServerCollection = new ServiceCollection(); + dapperSqlServerCollection.AddLogging(configureLogging); + dapperSqlServerCollection.AddDapperRepositories(SelfHosted); + var globalSettings = new GlobalSettings + { + DatabaseProvider = "sqlServer", + SqlServer = new GlobalSettings.SqlSettings + { + ConnectionString = database.ConnectionString, + }, + }; + dapperSqlServerCollection.AddSingleton(globalSettings); + dapperSqlServerCollection.AddSingleton(globalSettings); + dapperSqlServerCollection.AddSingleton(database); + dapperSqlServerCollection.AddDataProtection(); + yield return dapperSqlServerCollection.BuildServiceProvider(); + } + } + } +} diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationEnableCollectionEnhancementTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationEnableCollectionEnhancementTests.cs new file mode 100644 index 0000000000..27f7419255 --- /dev/null +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationEnableCollectionEnhancementTests.cs @@ -0,0 +1,611 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Repositories; +using Bit.Core.Utilities; +using Xunit; + +namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories; + +public class OrganizationEnableCollectionEnhancementTests +{ + [DatabaseTheory, MssqlDatabaseData] + public async Task Migrate_User_WithAccessAll_GivesCanEditAccessToAllCollections( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + var user = await CreateUser(userRepository); + var organization = await CreateOrganization(organizationRepository); + var orgUser = await CreateOrganizationUser(user, organization, OrganizationUserType.User, accessAll: true, organizationUserRepository); + var collection1 = await CreateCollection(organization, collectionRepository); + var collection2 = await CreateCollection(organization, collectionRepository); + var collection3 = await CreateCollection(organization, collectionRepository); + + await organizationRepository.EnableCollectionEnhancements(organization.Id); + + var (updatedOrgUser, collectionAccessSelections) = await organizationUserRepository.GetDetailsByIdWithCollectionsAsync(orgUser.Id); + + Assert.False(updatedOrgUser.AccessAll); + + Assert.Equal(3, collectionAccessSelections.Count); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection1.Id && + CanEdit(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection2.Id && + CanEdit(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection3.Id && + CanEdit(cas)); + } + + [DatabaseTheory, MssqlDatabaseData] + public async Task Migrate_Group_WithAccessAll_GivesCanEditAccessToAllCollections( + IGroupRepository groupRepository, + IOrganizationRepository organizationRepository, + ICollectionRepository collectionRepository) + { + var organization = await CreateOrganization(organizationRepository); + var group = await CreateGroup(organization, accessAll: true, groupRepository); + var collection1 = await CreateCollection(organization, collectionRepository); + var collection2 = await CreateCollection(organization, collectionRepository); + var collection3 = await CreateCollection(organization, collectionRepository); + + await organizationRepository.EnableCollectionEnhancements(organization.Id); + + var (updatedGroup, collectionAccessSelections) = await groupRepository.GetByIdWithCollectionsAsync(group.Id); + + Assert.False(updatedGroup.AccessAll); + + Assert.Equal(3, collectionAccessSelections.Count); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection1.Id && + CanEdit(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection2.Id && + CanEdit(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection3.Id && + CanEdit(cas)); + } + + [DatabaseTheory, MssqlDatabaseData] + public async Task Migrate_Manager_WithAccessAll_GivesCanManageAccessToAllCollections( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + var user = await CreateUser(userRepository); + var organization = await CreateOrganization(organizationRepository); + var orgUser = await CreateOrganizationUser(user, organization, OrganizationUserType.Manager, accessAll: true, organizationUserRepository); + var collection1 = await CreateCollection(organization, collectionRepository); + var collection2 = await CreateCollection(organization, collectionRepository); + var collection3 = await CreateCollection(organization, collectionRepository); + + await organizationRepository.EnableCollectionEnhancements(organization.Id); + + var (updatedOrgUser, collectionAccessSelections) = await organizationUserRepository.GetDetailsByIdWithCollectionsAsync(orgUser.Id); + + Assert.False(updatedOrgUser.AccessAll); + Assert.Equal(OrganizationUserType.User, updatedOrgUser.Type); + + Assert.Equal(3, collectionAccessSelections.Count); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection1.Id && + CanManage(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection2.Id && + CanManage(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection3.Id && + CanManage(cas)); + } + + [DatabaseTheory, MssqlDatabaseData] + public async Task Migrate_Manager_WithoutAccessAll_GivesCanManageAccessToAssignedCollections( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + var user = await CreateUser(userRepository); + var organization = await CreateOrganization(organizationRepository); + var orgUser = await CreateOrganizationUser(user, organization, OrganizationUserType.Manager, accessAll: false, organizationUserRepository); + var collection1 = await CreateCollection(organization, collectionRepository, null, [new CollectionAccessSelection { Id = orgUser.Id, HidePasswords = true, ReadOnly = false, Manage = false }]); + var collection2 = await CreateCollection(organization, collectionRepository, null, [new CollectionAccessSelection { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = false }]); + var collection3 = await CreateCollection(organization, collectionRepository); // no access + + await organizationRepository.EnableCollectionEnhancements(organization.Id); + + var (updatedOrgUser, collectionAccessSelections) = await organizationUserRepository.GetDetailsByIdWithCollectionsAsync(orgUser.Id); + + Assert.Equal(OrganizationUserType.User, updatedOrgUser.Type); + + Assert.Equal(2, collectionAccessSelections.Count); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection1.Id && + CanManage(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection2.Id && + CanManage(cas)); + Assert.DoesNotContain(collectionAccessSelections, cas => + cas.Id == collection3.Id); + } + + [DatabaseTheory, MssqlDatabaseData] + public async Task Migrate_Manager_WithoutAccessAll_GivesCanManageAccess_ToGroupAssignedCollections( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository, + IGroupRepository groupRepository) + { + var user = await CreateUser(userRepository); + var organization = await CreateOrganization(organizationRepository); + var orgUser = await CreateOrganizationUser(user, organization, OrganizationUserType.Manager, accessAll: false, organizationUserRepository); + var group = await CreateGroup(organization, accessAll: false, groupRepository, orgUser); + + var collection1 = await CreateCollection(organization, collectionRepository, new[] { new CollectionAccessSelection { Id = group.Id, HidePasswords = false, Manage = false, ReadOnly = false } }); + var collection2 = await CreateCollection(organization, collectionRepository, new[] { new CollectionAccessSelection { Id = group.Id, HidePasswords = false, Manage = false, ReadOnly = false } }); + var collection3 = await CreateCollection(organization, collectionRepository); // no access + + await organizationRepository.EnableCollectionEnhancements(organization.Id); + + var (updatedOrgUser, updatedUserAccess) = await organizationUserRepository.GetDetailsByIdWithCollectionsAsync(orgUser.Id); + + // Assert: orgUser should be downgraded from Manager to User + // and given Can Manage permissions over all group assigned collections + Assert.Equal(OrganizationUserType.User, updatedOrgUser.Type); + Assert.Equal(2, updatedUserAccess.Count); + Assert.Contains(updatedUserAccess, cas => + cas.Id == collection1.Id && + CanManage(cas)); + Assert.Contains(updatedUserAccess, cas => + cas.Id == collection2.Id && + CanManage(cas)); + Assert.DoesNotContain(updatedUserAccess, cas => + cas.Id == collection3.Id); + + // Assert: group should only have Can Edit permissions (making sure no side-effects from the Manager migration) + var (updatedGroup, updatedGroupAccess) = await groupRepository.GetByIdWithCollectionsAsync(group.Id); + Assert.Equal(2, updatedGroupAccess.Count); + Assert.Contains(updatedGroupAccess, cas => + cas.Id == collection1.Id && + CanEdit(cas)); + Assert.Contains(updatedGroupAccess, cas => + cas.Id == collection2.Id && + CanEdit(cas)); + Assert.DoesNotContain(updatedGroupAccess, cas => + cas.Id == collection3.Id); + } + + [DatabaseTheory, MssqlDatabaseData] + public async Task Migrate_Manager_WithoutAccessAll_InGroupWithAccessAll_GivesCanManageAccessToAllCollections( + IUserRepository userRepository, + IGroupRepository groupRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + var user = await CreateUser(userRepository); + var organization = await CreateOrganization(organizationRepository); + var orgUser = await CreateOrganizationUser(user, organization, OrganizationUserType.Manager, accessAll: false, organizationUserRepository); + + // Use 2 groups to test for overlapping access + var group1 = await CreateGroup(organization, accessAll: true, groupRepository, orgUser); + var group2 = await CreateGroup(organization, accessAll: true, groupRepository, orgUser); + + var collection1 = await CreateCollection(organization, collectionRepository); + var collection2 = await CreateCollection(organization, collectionRepository); + var collection3 = await CreateCollection(organization, collectionRepository); + + await organizationRepository.EnableCollectionEnhancements(organization.Id); + + var (updatedOrgUser, collectionAccessSelections) = await organizationUserRepository.GetDetailsByIdWithCollectionsAsync(orgUser.Id); + + Assert.Equal(OrganizationUserType.User, updatedOrgUser.Type); + + // OrgUser has direct Can Manage access to all collections + Assert.Equal(3, collectionAccessSelections.Count); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection1.Id && + CanManage(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection2.Id && + CanManage(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection3.Id && + CanManage(cas)); + + // Assert: group should only have Can Edit permissions (making sure no side-effects from the Manager migration) + var (updatedGroup1, updatedGroupAccess1) = await groupRepository.GetByIdWithCollectionsAsync(group1.Id); + Assert.Equal(3, updatedGroupAccess1.Count); + Assert.Contains(updatedGroupAccess1, cas => + cas.Id == collection1.Id && + CanEdit(cas)); + Assert.Contains(updatedGroupAccess1, cas => + cas.Id == collection2.Id && + CanEdit(cas)); + Assert.Contains(updatedGroupAccess1, cas => + cas.Id == collection3.Id && + CanEdit(cas)); + + var (updatedGroup2, updatedGroupAccess2) = await groupRepository.GetByIdWithCollectionsAsync(group2.Id); + Assert.Equal(3, updatedGroupAccess2.Count); + Assert.Contains(updatedGroupAccess2, cas => + cas.Id == collection1.Id && + CanEdit(cas)); + Assert.Contains(updatedGroupAccess2, cas => + cas.Id == collection2.Id && + CanEdit(cas)); + Assert.Contains(updatedGroupAccess2, cas => + cas.Id == collection3.Id && + CanEdit(cas)); + } + + [DatabaseTheory, MssqlDatabaseData] + public async Task Migrate_CustomUser_WithEditAssignedCollections_WithAccessAll_GivesCanManageAccessToAllCollections( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + var user = await CreateUser(userRepository); + var organization = await CreateOrganization(organizationRepository); + var orgUser = await CreateOrganizationUser(user, organization, OrganizationUserType.Custom, accessAll: true, + organizationUserRepository, new Permissions { EditAssignedCollections = true }); + var collection1 = await CreateCollection(organization, collectionRepository); + var collection2 = await CreateCollection(organization, collectionRepository); + var collection3 = await CreateCollection(organization, collectionRepository); + + await organizationRepository.EnableCollectionEnhancements(organization.Id); + + var (updatedOrgUser, collectionAccessSelections) = await organizationUserRepository.GetDetailsByIdWithCollectionsAsync(orgUser.Id); + + Assert.False(updatedOrgUser.AccessAll); + // Note: custom users do not have their types changed yet, this was done in code with a migration to follow + Assert.Equal(OrganizationUserType.Custom, updatedOrgUser.Type); + + Assert.Equal(3, collectionAccessSelections.Count); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection1.Id && + CanManage(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection2.Id && + CanManage(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection3.Id && + CanManage(cas)); + } + + [DatabaseTheory, MssqlDatabaseData] + public async Task Migrate_CustomUser_WithEditAssignedCollections_WithoutAccessAll_GivesCanManageAccessToAssignedCollections( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + var user = await CreateUser(userRepository); + var organization = await CreateOrganization(organizationRepository); + var orgUser = await CreateOrganizationUser(user, organization, OrganizationUserType.Custom, accessAll: false, + organizationUserRepository, new Permissions { EditAssignedCollections = true }); + var collection1 = await CreateCollection(organization, collectionRepository, null, [new CollectionAccessSelection { Id = orgUser.Id, HidePasswords = true, ReadOnly = false, Manage = false }]); + var collection2 = await CreateCollection(organization, collectionRepository, null, [new CollectionAccessSelection { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = false }]); + var collection3 = await CreateCollection(organization, collectionRepository); // no access + + await organizationRepository.EnableCollectionEnhancements(organization.Id); + + var (updatedOrgUser, collectionAccessSelections) = await organizationUserRepository.GetDetailsByIdWithCollectionsAsync(orgUser.Id); + + Assert.Equal(OrganizationUserType.Custom, updatedOrgUser.Type); + + Assert.Equal(2, collectionAccessSelections.Count); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection1.Id && + CanManage(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection2.Id && + CanManage(cas)); + Assert.DoesNotContain(collectionAccessSelections, cas => + cas.Id == collection3.Id); + } + + [DatabaseTheory, MssqlDatabaseData] + public async Task Migrate_CustomUser_WithEditAssignedCollections_WithoutAccessAll_GivesCanManageAccess_ToGroupAssignedCollections( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository, + IGroupRepository groupRepository) + { + var user = await CreateUser(userRepository); + var organization = await CreateOrganization(organizationRepository); + var orgUser = await CreateOrganizationUser(user, organization, OrganizationUserType.Custom, accessAll: false, + organizationUserRepository, new Permissions { EditAssignedCollections = true }); + var group = await CreateGroup(organization, accessAll: false, groupRepository, orgUser); + + var collection1 = await CreateCollection(organization, collectionRepository, new[] { new CollectionAccessSelection { Id = group.Id, HidePasswords = false, Manage = false, ReadOnly = false } }); + var collection2 = await CreateCollection(organization, collectionRepository, new[] { new CollectionAccessSelection { Id = group.Id, HidePasswords = false, Manage = false, ReadOnly = false } }); + var collection3 = await CreateCollection(organization, collectionRepository); // no access + + await organizationRepository.EnableCollectionEnhancements(organization.Id); + + var (updatedOrgUser, updatedUserAccess) = await organizationUserRepository.GetDetailsByIdWithCollectionsAsync(orgUser.Id); + + // Assert: user should be given Can Manage permissions over all group assigned collections + Assert.Equal(OrganizationUserType.Custom, updatedOrgUser.Type); + Assert.Equal(2, updatedUserAccess.Count); + Assert.Contains(updatedUserAccess, cas => + cas.Id == collection1.Id && + CanManage(cas)); + Assert.Contains(updatedUserAccess, cas => + cas.Id == collection2.Id && + CanManage(cas)); + Assert.DoesNotContain(updatedUserAccess, cas => + cas.Id == collection3.Id); + + // Assert: group should only have Can Edit permissions (making sure no side-effects from the Manager migration) + var (updatedGroup, updatedGroupAccess) = await groupRepository.GetByIdWithCollectionsAsync(group.Id); + Assert.Equal(2, updatedGroupAccess.Count); + Assert.Contains(updatedGroupAccess, cas => + cas.Id == collection1.Id && + CanEdit(cas)); + Assert.Contains(updatedGroupAccess, cas => + cas.Id == collection2.Id && + CanEdit(cas)); + Assert.DoesNotContain(updatedGroupAccess, cas => + cas.Id == collection3.Id); + } + + [DatabaseTheory, MssqlDatabaseData] + public async Task Migrate_CustomUser_WithEditAssignedCollections_WithoutAccessAll_InGroupWithAccessAll_GivesCanManageAccessToAllCollections( + IUserRepository userRepository, + IGroupRepository groupRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + var user = await CreateUser(userRepository); + var organization = await CreateOrganization(organizationRepository); + var orgUser = await CreateOrganizationUser(user, organization, OrganizationUserType.Custom, accessAll: false, + organizationUserRepository, new Permissions { EditAssignedCollections = true }); + + // Use 2 groups to test for overlapping access + var group1 = await CreateGroup(organization, accessAll: true, groupRepository, orgUser); + var group2 = await CreateGroup(organization, accessAll: true, groupRepository, orgUser); + + var collection1 = await CreateCollection(organization, collectionRepository); + var collection2 = await CreateCollection(organization, collectionRepository); + var collection3 = await CreateCollection(organization, collectionRepository); + + await organizationRepository.EnableCollectionEnhancements(organization.Id); + + var (updatedOrgUser, collectionAccessSelections) = await organizationUserRepository.GetDetailsByIdWithCollectionsAsync(orgUser.Id); + + Assert.Equal(OrganizationUserType.Custom, updatedOrgUser.Type); + + // OrgUser has direct Can Manage access to all collections + Assert.Equal(3, collectionAccessSelections.Count); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection1.Id && + CanManage(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection2.Id && + CanManage(cas)); + Assert.Contains(collectionAccessSelections, cas => + cas.Id == collection3.Id && + CanManage(cas)); + + // Assert: group should only have Can Edit permissions (making sure no side-effects from the Manager migration) + var (updatedGroup1, updatedGroupAccess1) = await groupRepository.GetByIdWithCollectionsAsync(group1.Id); + Assert.Equal(3, updatedGroupAccess1.Count); + Assert.Contains(updatedGroupAccess1, cas => + cas.Id == collection1.Id && + CanEdit(cas)); + Assert.Contains(updatedGroupAccess1, cas => + cas.Id == collection2.Id && + CanEdit(cas)); + Assert.Contains(updatedGroupAccess1, cas => + cas.Id == collection3.Id && + CanEdit(cas)); + + var (updatedGroup2, updatedGroupAccess2) = await groupRepository.GetByIdWithCollectionsAsync(group2.Id); + Assert.Equal(3, updatedGroupAccess2.Count); + Assert.Contains(updatedGroupAccess2, cas => + cas.Id == collection1.Id && + CanEdit(cas)); + Assert.Contains(updatedGroupAccess2, cas => + cas.Id == collection2.Id && + CanEdit(cas)); + Assert.Contains(updatedGroupAccess2, cas => + cas.Id == collection3.Id && + CanEdit(cas)); + } + + [DatabaseTheory, MssqlDatabaseData] + public async Task Migrate_NonManagers_WithoutAccessAll_NoChangeToRoleOrCollectionAccess( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + var userUser = await CreateUser(userRepository); + var adminUser = await CreateUser(userRepository); + var ownerUser = await CreateUser(userRepository); + var customUser = await CreateUser(userRepository); + + var organization = await CreateOrganization(organizationRepository); + + // All roles that are unaffected by this change without AccessAll + var orgUser = await CreateOrganizationUser(userUser, organization, OrganizationUserType.User, accessAll: false, organizationUserRepository); + var admin = await CreateOrganizationUser(adminUser, organization, OrganizationUserType.Admin, accessAll: false, organizationUserRepository); + var owner = await CreateOrganizationUser(ownerUser, organization, OrganizationUserType.Owner, accessAll: false, organizationUserRepository); + var custom = await CreateOrganizationUser(customUser, organization, OrganizationUserType.Custom, accessAll: false, organizationUserRepository, new Permissions { DeleteAssignedCollections = true, AccessReports = true }); + + var collection1 = await CreateCollection(organization, collectionRepository, null, new[] + { + new CollectionAccessSelection {Id = orgUser.Id}, + new CollectionAccessSelection {Id = custom.Id, HidePasswords = true} + }); + var collection2 = await CreateCollection(organization, collectionRepository, null, new[] + { + new CollectionAccessSelection { Id = owner.Id, HidePasswords = true} , + new CollectionAccessSelection { Id = admin.Id, ReadOnly = true} + }); + var collection3 = await CreateCollection(organization, collectionRepository, null, new[] + { + new CollectionAccessSelection { Id = owner.Id } + }); + + await organizationRepository.EnableCollectionEnhancements(organization.Id); + + var (updatedOrgUser, orgUserAccess) = await organizationUserRepository + .GetDetailsByIdWithCollectionsAsync(orgUser.Id); + Assert.Equal(OrganizationUserType.User, updatedOrgUser.Type); + Assert.Equal(1, orgUserAccess.Count); + Assert.Contains(orgUserAccess, cas => + cas.Id == collection1.Id && + CanEdit(cas)); + + var (updatedAdmin, adminAccess) = await organizationUserRepository + .GetDetailsByIdWithCollectionsAsync(admin.Id); + Assert.Equal(OrganizationUserType.Admin, updatedAdmin.Type); + Assert.Equal(1, adminAccess.Count); + Assert.Contains(adminAccess, cas => + cas.Id == collection2.Id && + cas is { HidePasswords: false, ReadOnly: true, Manage: false }); + + var (updatedOwner, ownerAccess) = await organizationUserRepository + .GetDetailsByIdWithCollectionsAsync(owner.Id); + Assert.Equal(OrganizationUserType.Owner, updatedOwner.Type); + Assert.Equal(2, ownerAccess.Count); + Assert.Contains(ownerAccess, cas => + cas.Id == collection2.Id && + cas is { HidePasswords: true, ReadOnly: false, Manage: false }); + Assert.Contains(ownerAccess, cas => + cas.Id == collection3.Id && + CanEdit(cas)); + + var (updatedCustom, customAccess) = await organizationUserRepository + .GetDetailsByIdWithCollectionsAsync(custom.Id); + Assert.Equal(OrganizationUserType.Custom, updatedCustom.Type); + Assert.Equal(1, customAccess.Count); + Assert.Contains(customAccess, cas => + cas.Id == collection1.Id && + cas is { HidePasswords: true, ReadOnly: false, Manage: false }); + } + + [DatabaseTheory, MssqlDatabaseData] + public async Task Migrate_DoesNotAffect_OtherOrganizations( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + // Target organization to be migrated + var targetUser = await CreateUser(userRepository); + var targetOrganization = await CreateOrganization(organizationRepository); + await CreateOrganizationUser(targetUser, targetOrganization, OrganizationUserType.Manager, accessAll: true, organizationUserRepository); + await CreateCollection(targetOrganization, collectionRepository); + await CreateCollection(targetOrganization, collectionRepository); + await CreateCollection(targetOrganization, collectionRepository); + + // Unrelated organization + var user = await CreateUser(userRepository); + var organization = await CreateOrganization(organizationRepository); + var orgUser = await CreateOrganizationUser(user, organization, OrganizationUserType.Manager, accessAll: true, organizationUserRepository); + await CreateCollection(organization, collectionRepository); + await CreateCollection(organization, collectionRepository); + await CreateCollection(organization, collectionRepository); + + await organizationRepository.EnableCollectionEnhancements(targetOrganization.Id); + + var (updatedOrgUser, collectionAccessSelections) = await organizationUserRepository + .GetDetailsByIdWithCollectionsAsync(orgUser.Id); + + // OrgUser should not have changed + Assert.Equal(OrganizationUserType.Manager, updatedOrgUser.Type); + Assert.True(updatedOrgUser.AccessAll); + Assert.Equal(0, collectionAccessSelections.Count); + + var updatedOrganization = await organizationRepository.GetByIdAsync(organization.Id); + Assert.False(updatedOrganization.FlexibleCollections); + } + + private async Task CreateUser(IUserRepository userRepository) + { + return await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@example.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + } + + private async Task CreateGroup(Organization organization, bool accessAll, IGroupRepository groupRepository, + OrganizationUser? orgUser = null) + { + var group = await groupRepository.CreateAsync(new Group + { + Name = $"Test Group {Guid.NewGuid()}", + OrganizationId = organization.Id, + AccessAll = accessAll + }); + + if (orgUser != null) + { + await groupRepository.UpdateUsersAsync(group.Id, [orgUser.Id]); + } + + return group; + } + + private async Task CreateOrganization(IOrganizationRepository organizationRepository) + { + return await organizationRepository.CreateAsync(new Organization + { + Name = $"Test Org {Guid.NewGuid()}", + BillingEmail = "Billing Email", // TODO: EF does not enforce this being NOT NULL + Plan = "Test Plan", // TODO: EF does not enforce this being NOT NULl + }); + } + + private async Task CreateOrganizationUser(User user, Organization organization, + OrganizationUserType type, bool accessAll, IOrganizationUserRepository organizationUserRepository, + Permissions? permissions = null) + { + return await organizationUserRepository.CreateAsync(new OrganizationUser + { + OrganizationId = organization.Id, + UserId = user.Id, + Status = OrganizationUserStatusType.Confirmed, + Type = type, + AccessAll = accessAll, + Permissions = permissions == null ? null : CoreHelpers.ClassToJsonData(permissions) + }); + } + + private async Task CreateCollection(Organization organization, ICollectionRepository collectionRepository, + IEnumerable? groups = null, IEnumerable? users = null) + { + var collection = new Collection { Name = $"Test collection {Guid.NewGuid()}", OrganizationId = organization.Id }; + await collectionRepository.CreateAsync(collection, groups: groups, users: users); + return collection; + } + + private bool CanEdit(CollectionAccessSelection collectionAccess) + { + return collectionAccess is { HidePasswords: false, ReadOnly: false, Manage: false }; + } + + private bool CanManage(CollectionAccessSelection collectionAccess) + { + return collectionAccess is { HidePasswords: false, ReadOnly: false, Manage: true }; + } +} diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs index 631d5a1b81..539ac0856f 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs @@ -16,7 +16,7 @@ public class OrganizationUserRepositoryTests var user = await userRepository.CreateAsync(new User { Name = "Test User", - Email = $"test+{Guid.NewGuid()}@email.com", + Email = $"test+{Guid.NewGuid()}@example.com", ApiKey = "TEST", SecurityStamp = "stamp", }); diff --git a/test/Infrastructure.IntegrationTest/DatabaseDataAttribute.cs b/test/Infrastructure.IntegrationTest/DatabaseDataAttribute.cs index 6433fcb207..2c12890ca1 100644 --- a/test/Infrastructure.IntegrationTest/DatabaseDataAttribute.cs +++ b/test/Infrastructure.IntegrationTest/DatabaseDataAttribute.cs @@ -33,7 +33,7 @@ public class DatabaseDataAttribute : DataAttribute } } - private IEnumerable GetDatabaseProviders(IConfiguration config) + protected virtual IEnumerable GetDatabaseProviders(IConfiguration config) { var configureLogging = (ILoggingBuilder builder) => {