mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
[AC-2522] Remove collection enhancements opt-in (#4110)
* Delete controller endpoint * Delete command * Drop sproc
This commit is contained in:
@ -7,7 +7,6 @@ using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
@ -46,7 +45,6 @@ public class OrganizationsControllerTests : IDisposable
|
||||
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IOrganizationEnableCollectionEnhancementsCommand _organizationEnableCollectionEnhancementsCommand;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IProviderBillingService _providerBillingService;
|
||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||
@ -70,7 +68,6 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_createOrganizationApiKeyCommand = Substitute.For<ICreateOrganizationApiKeyCommand>();
|
||||
_featureService = Substitute.For<IFeatureService>();
|
||||
_pushNotificationService = Substitute.For<IPushNotificationService>();
|
||||
_organizationEnableCollectionEnhancementsCommand = Substitute.For<IOrganizationEnableCollectionEnhancementsCommand>();
|
||||
_providerRepository = Substitute.For<IProviderRepository>();
|
||||
_providerBillingService = Substitute.For<IProviderBillingService>();
|
||||
_orgDeleteTokenDataFactory = Substitute.For<IDataProtectorTokenFactory<OrgDeleteTokenable>>();
|
||||
@ -91,7 +88,6 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_featureService,
|
||||
_globalSettings,
|
||||
_pushNotificationService,
|
||||
_organizationEnableCollectionEnhancementsCommand,
|
||||
_providerRepository,
|
||||
_providerBillingService,
|
||||
_orgDeleteTokenDataFactory);
|
||||
@ -162,48 +158,6 @@ public class OrganizationsControllerTests : IDisposable
|
||||
await _organizationService.Received(1).DeleteUserAsync(orgId, user.Id);
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task EnableCollectionEnhancements_Success(Organization organization)
|
||||
{
|
||||
organization.FlexibleCollections = false;
|
||||
var admin = new OrganizationUser { UserId = Guid.NewGuid(), Type = OrganizationUserType.Admin, Status = OrganizationUserStatusType.Confirmed };
|
||||
var owner = new OrganizationUser { UserId = Guid.NewGuid(), Type = OrganizationUserType.Owner, Status = OrganizationUserStatusType.Confirmed };
|
||||
var user = new OrganizationUser { UserId = Guid.NewGuid(), Type = OrganizationUserType.User, Status = OrganizationUserStatusType.Confirmed };
|
||||
var invited = new OrganizationUser
|
||||
{
|
||||
UserId = null,
|
||||
Type = OrganizationUserType.Admin,
|
||||
Email = "invited@example.com",
|
||||
Status = OrganizationUserStatusType.Invited
|
||||
};
|
||||
var orgUsers = new List<OrganizationUser> { admin, owner, user, invited };
|
||||
|
||||
_currentContext.OrganizationOwner(organization.Id).Returns(true);
|
||||
_organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
||||
_organizationUserRepository.GetManyByOrganizationAsync(organization.Id, null).Returns(orgUsers);
|
||||
|
||||
await _sut.EnableCollectionEnhancements(organization.Id);
|
||||
|
||||
await _organizationEnableCollectionEnhancementsCommand.Received(1).EnableCollectionEnhancements(organization);
|
||||
await _pushNotificationService.Received(1).PushSyncOrganizationsAsync(admin.UserId.Value);
|
||||
await _pushNotificationService.Received(1).PushSyncOrganizationsAsync(owner.UserId.Value);
|
||||
await _pushNotificationService.DidNotReceive().PushSyncOrganizationsAsync(user.UserId.Value);
|
||||
// Invited orgUser does not have a UserId we can use to assert here, but sut will throw if that null isn't handled
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task EnableCollectionEnhancements_WhenNotOwner_Throws(Organization organization)
|
||||
{
|
||||
organization.FlexibleCollections = false;
|
||||
_currentContext.OrganizationOwner(organization.Id).Returns(false);
|
||||
_organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await _sut.EnableCollectionEnhancements(organization.Id));
|
||||
|
||||
await _organizationEnableCollectionEnhancementsCommand.DidNotReceiveWithAnyArgs().EnableCollectionEnhancements(Arg.Any<Organization>());
|
||||
await _pushNotificationService.DidNotReceiveWithAnyArgs().PushSyncOrganizationsAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task Delete_OrganizationIsConsolidatedBillingClient_ScalesProvidersSeats(
|
||||
Provider provider,
|
||||
|
@ -1,46 +0,0 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationEnableCollectionEnhancementsCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task EnableCollectionEnhancements_Success(
|
||||
SutProvider<OrganizationEnableCollectionEnhancementsCommand> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
organization.FlexibleCollections = false;
|
||||
|
||||
await sutProvider.Sut.EnableCollectionEnhancements(organization);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).EnableCollectionEnhancements(organization.Id);
|
||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(
|
||||
Arg.Is<Organization>(o =>
|
||||
o.Id == organization.Id &&
|
||||
o.FlexibleCollections));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task EnableCollectionEnhancements_WhenAlreadyMigrated_Throws(
|
||||
SutProvider<OrganizationEnableCollectionEnhancementsCommand> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
organization.FlexibleCollections = true;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.EnableCollectionEnhancements(organization));
|
||||
Assert.Contains("has already been migrated", exception.Message);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().DidNotReceiveWithAnyArgs().EnableCollectionEnhancements(Arg.Any<Guid>());
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal class MssqlDatabaseDataAttribute : DatabaseDataAttribute
|
||||
{
|
||||
protected override IEnumerable<IServiceProvider> GetDatabaseProviders(IConfiguration config)
|
||||
{
|
||||
var configureLogging = (ILoggingBuilder builder) =>
|
||||
{
|
||||
if (!config.GetValue<bool>("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<IGlobalSettings>(globalSettings);
|
||||
dapperSqlServerCollection.AddSingleton(database);
|
||||
dapperSqlServerCollection.AddDataProtection();
|
||||
yield return dapperSqlServerCollection.BuildServiceProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,611 +0,0 @@
|
||||
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<User> 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<Group> 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<Organization> 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<OrganizationUser> 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<Collection> CreateCollection(Organization organization, ICollectionRepository collectionRepository,
|
||||
IEnumerable<CollectionAccessSelection>? groups = null, IEnumerable<CollectionAccessSelection>? 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 };
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user