mirror of
https://github.com/bitwarden/server.git
synced 2024-11-22 12:15:36 +01:00
[AC-2522] Remove collection enhancements opt-in (#4110)
* Delete controller endpoint * Delete command * Drop sproc
This commit is contained in:
parent
ba93c0008b
commit
be41865b59
@ -14,7 +14,6 @@ using Bit.Core.AdminConsole.Enums;
|
|||||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements.Interfaces;
|
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Repositories;
|
using Bit.Core.Auth.Repositories;
|
||||||
@ -53,7 +52,6 @@ public class OrganizationsController : Controller
|
|||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
private readonly IPushNotificationService _pushNotificationService;
|
private readonly IPushNotificationService _pushNotificationService;
|
||||||
private readonly IOrganizationEnableCollectionEnhancementsCommand _organizationEnableCollectionEnhancementsCommand;
|
|
||||||
private readonly IProviderRepository _providerRepository;
|
private readonly IProviderRepository _providerRepository;
|
||||||
private readonly IProviderBillingService _providerBillingService;
|
private readonly IProviderBillingService _providerBillingService;
|
||||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||||
@ -74,7 +72,6 @@ public class OrganizationsController : Controller
|
|||||||
IFeatureService featureService,
|
IFeatureService featureService,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
IPushNotificationService pushNotificationService,
|
IPushNotificationService pushNotificationService,
|
||||||
IOrganizationEnableCollectionEnhancementsCommand organizationEnableCollectionEnhancementsCommand,
|
|
||||||
IProviderRepository providerRepository,
|
IProviderRepository providerRepository,
|
||||||
IProviderBillingService providerBillingService,
|
IProviderBillingService providerBillingService,
|
||||||
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory)
|
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory)
|
||||||
@ -94,7 +91,6 @@ public class OrganizationsController : Controller
|
|||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_pushNotificationService = pushNotificationService;
|
_pushNotificationService = pushNotificationService;
|
||||||
_organizationEnableCollectionEnhancementsCommand = organizationEnableCollectionEnhancementsCommand;
|
|
||||||
_providerRepository = providerRepository;
|
_providerRepository = providerRepository;
|
||||||
_providerBillingService = providerBillingService;
|
_providerBillingService = providerBillingService;
|
||||||
_orgDeleteTokenDataFactory = orgDeleteTokenDataFactory;
|
_orgDeleteTokenDataFactory = orgDeleteTokenDataFactory;
|
||||||
@ -558,38 +554,4 @@ public class OrganizationsController : Controller
|
|||||||
await _organizationService.UpdateAsync(model.ToOrganization(organization), eventType: EventType.Organization_CollectionManagement_Updated);
|
await _organizationService.UpdateAsync(model.ToOrganization(organization), eventType: EventType.Organization_CollectionManagement_Updated);
|
||||||
return new OrganizationResponseModel(organization);
|
return new OrganizationResponseModel(organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Migrates user, collection, and group data to the new Flexible Collections permissions scheme,
|
|
||||||
/// then sets organization.FlexibleCollections to true to enable these new features for the organization.
|
|
||||||
/// This is irreversible.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="organizationId"></param>
|
|
||||||
/// <exception cref="NotFoundException"></exception>
|
|
||||||
[HttpPost("{id}/enable-collection-enhancements")]
|
|
||||||
[RequireFeature(FeatureFlagKeys.FlexibleCollectionsMigration)]
|
|
||||||
public async Task EnableCollectionEnhancements(Guid id)
|
|
||||||
{
|
|
||||||
if (!await _currentContext.OrganizationOwner(id))
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
|
||||||
if (organization == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _organizationEnableCollectionEnhancementsCommand.EnableCollectionEnhancements(organization);
|
|
||||||
|
|
||||||
// Force a vault sync for all owners and admins of the organization so that changes show immediately
|
|
||||||
// Custom users are intentionally not handled as they are likely to be less impacted and we want to limit simultaneous syncs
|
|
||||||
var orgUsers = await _organizationUserRepository.GetManyByOrganizationAsync(id, null);
|
|
||||||
await Task.WhenAll(orgUsers
|
|
||||||
.Where(ou => ou.UserId.HasValue &&
|
|
||||||
ou.Status == OrganizationUserStatusType.Confirmed &&
|
|
||||||
ou.Type is OrganizationUserType.Admin or OrganizationUserType.Owner)
|
|
||||||
.Select(ou => _pushNotificationService.PushSyncOrganizationsAsync(ou.UserId.Value)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements.Interfaces;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enable collection enhancements for an organization.
|
|
||||||
/// This command will be deprecated once all organizations have collection enhancements enabled.
|
|
||||||
/// </summary>
|
|
||||||
public interface IOrganizationEnableCollectionEnhancementsCommand
|
|
||||||
{
|
|
||||||
Task EnableCollectionEnhancements(Organization organization);
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using Bit.Core.AdminConsole.Entities;
|
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements.Interfaces;
|
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Exceptions;
|
|
||||||
using Bit.Core.Repositories;
|
|
||||||
using Bit.Core.Services;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements;
|
|
||||||
|
|
||||||
public class OrganizationEnableCollectionEnhancementsCommand : IOrganizationEnableCollectionEnhancementsCommand
|
|
||||||
{
|
|
||||||
private readonly ICollectionRepository _collectionRepository;
|
|
||||||
private readonly IGroupRepository _groupRepository;
|
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
|
||||||
private readonly IOrganizationService _organizationService;
|
|
||||||
private readonly ILogger<OrganizationEnableCollectionEnhancementsCommand> _logger;
|
|
||||||
|
|
||||||
public OrganizationEnableCollectionEnhancementsCommand(ICollectionRepository collectionRepository,
|
|
||||||
IGroupRepository groupRepository,
|
|
||||||
IOrganizationRepository organizationRepository,
|
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
|
||||||
IOrganizationService organizationService,
|
|
||||||
ILogger<OrganizationEnableCollectionEnhancementsCommand> logger)
|
|
||||||
{
|
|
||||||
_collectionRepository = collectionRepository;
|
|
||||||
_groupRepository = groupRepository;
|
|
||||||
_organizationRepository = organizationRepository;
|
|
||||||
_organizationUserRepository = organizationUserRepository;
|
|
||||||
_organizationService = organizationService;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task EnableCollectionEnhancements(Organization organization)
|
|
||||||
{
|
|
||||||
if (organization.FlexibleCollections)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("Organization has already been migrated to the new collection enhancements");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the Organization data that will change when the migration is complete
|
|
||||||
await LogPreMigrationDataAsync(organization.Id);
|
|
||||||
|
|
||||||
// Run the data migration script
|
|
||||||
await _organizationRepository.EnableCollectionEnhancements(organization.Id);
|
|
||||||
|
|
||||||
organization.FlexibleCollections = true;
|
|
||||||
await _organizationService.ReplaceAndUpdateCacheAsync(organization);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This method logs the data that will be migrated to the new collection enhancements so that it can be restored if needed
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="organizationId"></param>
|
|
||||||
private async Task LogPreMigrationDataAsync(Guid organizationId)
|
|
||||||
{
|
|
||||||
var groups = await _groupRepository.GetManyByOrganizationIdAsync(organizationId);
|
|
||||||
// Grab Group Ids that have AccessAll enabled as it will be removed in the data migration
|
|
||||||
var groupIdsWithAccessAllEnabled = groups
|
|
||||||
.Where(g => g.AccessAll)
|
|
||||||
.Select(g => g.Id)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var organizationUsers = await _organizationUserRepository.GetManyByOrganizationAsync(organizationId, type: null);
|
|
||||||
// Grab OrganizationUser Ids that have AccessAll enabled as it will be removed in the data migration
|
|
||||||
var organizationUserIdsWithAccessAllEnabled = organizationUsers
|
|
||||||
.Where(ou => ou.AccessAll)
|
|
||||||
.Select(ou => ou.Id)
|
|
||||||
.ToList();
|
|
||||||
// Grab OrganizationUser Ids of Manager users as that will be downgraded to User in the data migration
|
|
||||||
var migratedManagers = organizationUsers
|
|
||||||
.Where(ou => ou.Type == OrganizationUserType.Manager)
|
|
||||||
.Select(ou => ou.Id)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var usersEligibleToManageCollections = organizationUsers
|
|
||||||
.Where(ou =>
|
|
||||||
ou.Type == OrganizationUserType.Manager ||
|
|
||||||
(ou.Type == OrganizationUserType.Custom &&
|
|
||||||
!string.IsNullOrEmpty(ou.Permissions) &&
|
|
||||||
ou.GetPermissions().EditAssignedCollections)
|
|
||||||
)
|
|
||||||
.Select(ou => ou.Id)
|
|
||||||
.ToList();
|
|
||||||
var collectionUsers = await _collectionRepository.GetManyByOrganizationIdWithAccessAsync(organizationId);
|
|
||||||
// Grab CollectionUser permissions that will change in the data migration
|
|
||||||
var collectionUsersData = collectionUsers.SelectMany(tuple =>
|
|
||||||
tuple.Item2.Users.Select(user =>
|
|
||||||
new
|
|
||||||
{
|
|
||||||
CollectionId = tuple.Item1.Id,
|
|
||||||
OrganizationUserId = user.Id,
|
|
||||||
user.ReadOnly,
|
|
||||||
user.HidePasswords
|
|
||||||
}))
|
|
||||||
.Where(cud => usersEligibleToManageCollections.Any(ou => ou == cud.OrganizationUserId))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var logObject = new
|
|
||||||
{
|
|
||||||
OrganizationId = organizationId,
|
|
||||||
GroupAccessAll = groupIdsWithAccessAllEnabled,
|
|
||||||
UserAccessAll = organizationUserIdsWithAccessAllEnabled,
|
|
||||||
MigratedManagers = migratedManagers,
|
|
||||||
CollectionUsers = collectionUsersData
|
|
||||||
};
|
|
||||||
|
|
||||||
_logger.LogWarning("Flexible Collections data migration started. Backup data: {LogObject}", JsonSerializer.Serialize(logObject));
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,5 +15,4 @@ public interface IOrganizationRepository : IRepository<Organization, Guid>
|
|||||||
Task<SelfHostedOrganizationDetails> GetSelfHostedOrganizationDetailsById(Guid id);
|
Task<SelfHostedOrganizationDetails> GetSelfHostedOrganizationDetailsById(Guid id);
|
||||||
Task<ICollection<Organization>> SearchUnassignedToProviderAsync(string name, string ownerEmail, int skip, int take);
|
Task<ICollection<Organization>> SearchUnassignedToProviderAsync(string name, string ownerEmail, int skip, int take);
|
||||||
Task<IEnumerable<string>> GetOwnerEmailAddressesById(Guid organizationId);
|
Task<IEnumerable<string>> GetOwnerEmailAddressesById(Guid organizationId);
|
||||||
Task EnableCollectionEnhancements(Guid organizationId);
|
|
||||||
}
|
}
|
||||||
|
@ -114,11 +114,6 @@ public static class FeatureFlagKeys
|
|||||||
public const string ItemShare = "item-share";
|
public const string ItemShare = "item-share";
|
||||||
public const string KeyRotationImprovements = "key-rotation-improvements";
|
public const string KeyRotationImprovements = "key-rotation-improvements";
|
||||||
public const string DuoRedirect = "duo-redirect";
|
public const string DuoRedirect = "duo-redirect";
|
||||||
/// <summary>
|
|
||||||
/// Exposes a migration button in the web vault which allows users to migrate an existing organization to
|
|
||||||
/// flexible collections
|
|
||||||
/// </summary>
|
|
||||||
public const string FlexibleCollectionsMigration = "flexible-collections-migration";
|
|
||||||
public const string PM5766AutomaticTax = "PM-5766-automatic-tax";
|
public const string PM5766AutomaticTax = "PM-5766-automatic-tax";
|
||||||
public const string PM5864DollarThreshold = "PM-5864-dollar-threshold";
|
public const string PM5864DollarThreshold = "PM-5864-dollar-threshold";
|
||||||
public const string ShowPaymentMethodWarningBanners = "show-payment-method-warning-banners";
|
public const string ShowPaymentMethodWarningBanners = "show-payment-method-warning-banners";
|
||||||
|
@ -4,8 +4,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Groups;
|
|||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements;
|
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements.Interfaces;
|
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
|
||||||
@ -52,7 +50,6 @@ public static class OrganizationServiceCollectionExtensions
|
|||||||
services.AddOrganizationUserCommands();
|
services.AddOrganizationUserCommands();
|
||||||
services.AddOrganizationUserCommandsQueries();
|
services.AddOrganizationUserCommandsQueries();
|
||||||
services.AddBaseOrganizationSubscriptionCommandsQueries();
|
services.AddBaseOrganizationSubscriptionCommandsQueries();
|
||||||
services.AddOrganizationCollectionEnhancementsCommands();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddOrganizationConnectionCommands(this IServiceCollection services)
|
private static void AddOrganizationConnectionCommands(this IServiceCollection services)
|
||||||
@ -148,11 +145,6 @@ public static class OrganizationServiceCollectionExtensions
|
|||||||
services.AddScoped<IUpdateSecretsManagerSubscriptionCommand, UpdateSecretsManagerSubscriptionCommand>();
|
services.AddScoped<IUpdateSecretsManagerSubscriptionCommand, UpdateSecretsManagerSubscriptionCommand>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddOrganizationCollectionEnhancementsCommands(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddScoped<IOrganizationEnableCollectionEnhancementsCommand, OrganizationEnableCollectionEnhancementsCommand>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AddTokenizers(this IServiceCollection services)
|
private static void AddTokenizers(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddSingleton<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>(serviceProvider =>
|
services.AddSingleton<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>(serviceProvider =>
|
||||||
|
@ -169,16 +169,4 @@ public class OrganizationRepository : Repository<Organization, Guid>, IOrganizat
|
|||||||
new { OrganizationId = organizationId },
|
new { OrganizationId = organizationId },
|
||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EnableCollectionEnhancements(Guid organizationId)
|
|
||||||
{
|
|
||||||
using (var connection = new SqlConnection(ConnectionString))
|
|
||||||
{
|
|
||||||
await connection.ExecuteAsync(
|
|
||||||
"[dbo].[Organization_EnableCollectionEnhancements]",
|
|
||||||
new { OrganizationId = organizationId },
|
|
||||||
commandType: CommandType.StoredProcedure,
|
|
||||||
commandTimeout: 180);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,155 +0,0 @@
|
|||||||
CREATE PROCEDURE [dbo].[Organization_EnableCollectionEnhancements]
|
|
||||||
@OrganizationId UNIQUEIDENTIFIER
|
|
||||||
AS
|
|
||||||
BEGIN
|
|
||||||
SET NOCOUNT ON
|
|
||||||
|
|
||||||
-- Step 1: AccessAll migration for Groups
|
|
||||||
-- Create a temporary table to store the groups with AccessAll = 1
|
|
||||||
SELECT [Id] AS [GroupId], [OrganizationId]
|
|
||||||
INTO #TempGroupsAccessAll
|
|
||||||
FROM [dbo].[Group]
|
|
||||||
WHERE [OrganizationId] = @OrganizationId
|
|
||||||
AND [AccessAll] = 1;
|
|
||||||
|
|
||||||
-- Step 2: AccessAll migration for OrganizationUsers
|
|
||||||
-- Create a temporary table to store the OrganizationUsers with AccessAll = 1
|
|
||||||
SELECT [Id] AS [OrganizationUserId], [OrganizationId]
|
|
||||||
INTO #TempUsersAccessAll
|
|
||||||
FROM [dbo].[OrganizationUser]
|
|
||||||
WHERE [OrganizationId] = @OrganizationId
|
|
||||||
AND [AccessAll] = 1;
|
|
||||||
|
|
||||||
-- Step 3: For all OrganizationUsers with Manager role or 'EditAssignedCollections' permission update their existing CollectionUser rows and insert new rows with [Manage] = 1
|
|
||||||
-- and finally update all OrganizationUsers with Manager role to User role
|
|
||||||
-- Create a temporary table to store the OrganizationUsers with Manager role or 'EditAssignedCollections' permission
|
|
||||||
SELECT ou.[Id] AS [OrganizationUserId],
|
|
||||||
CASE WHEN ou.[Type] = 3 THEN 1 ELSE 0 END AS [IsManager]
|
|
||||||
INTO #TempUserManagers
|
|
||||||
FROM [dbo].[OrganizationUser] ou
|
|
||||||
WHERE ou.[OrganizationId] = @OrganizationId
|
|
||||||
AND (ou.[Type] = 3 OR (ou.[Permissions] IS NOT NULL
|
|
||||||
AND ISJSON(ou.[Permissions]) > 0 AND JSON_VALUE(ou.[Permissions], '$.editAssignedCollections') = 'true'));
|
|
||||||
|
|
||||||
-- Step 4: Bump AccountRevisionDate for all OrganizationUsers updated in the previous steps
|
|
||||||
-- Combine and union the distinct OrganizationUserIds from all steps into a single variable
|
|
||||||
DECLARE @OrgUsersToBump [dbo].[GuidIdArray]
|
|
||||||
INSERT INTO @OrgUsersToBump
|
|
||||||
SELECT DISTINCT [OrganizationUserId] AS Id
|
|
||||||
FROM (
|
|
||||||
-- Step 1
|
|
||||||
SELECT GU.[OrganizationUserId]
|
|
||||||
FROM [dbo].[GroupUser] GU
|
|
||||||
INNER JOIN #TempGroupsAccessAll TG ON GU.[GroupId] = TG.[GroupId]
|
|
||||||
|
|
||||||
UNION
|
|
||||||
|
|
||||||
-- Step 2
|
|
||||||
SELECT [OrganizationUserId]
|
|
||||||
FROM #TempUsersAccessAll
|
|
||||||
|
|
||||||
UNION
|
|
||||||
|
|
||||||
-- Step 3
|
|
||||||
SELECT [OrganizationUserId]
|
|
||||||
FROM #TempUserManagers
|
|
||||||
) AS CombinedOrgUsers;
|
|
||||||
|
|
||||||
BEGIN TRY
|
|
||||||
BEGIN TRANSACTION;
|
|
||||||
-- Step 1
|
|
||||||
-- Update existing rows in [dbo].[CollectionGroup]
|
|
||||||
UPDATE CG
|
|
||||||
SET
|
|
||||||
CG.[ReadOnly] = 0,
|
|
||||||
CG.[HidePasswords] = 0,
|
|
||||||
CG.[Manage] = 0
|
|
||||||
FROM [dbo].[CollectionGroup] CG
|
|
||||||
INNER JOIN [dbo].[Collection] C ON CG.[CollectionId] = C.[Id]
|
|
||||||
INNER JOIN #TempGroupsAccessAll TG ON CG.[GroupId] = TG.[GroupId]
|
|
||||||
WHERE C.[OrganizationId] = TG.[OrganizationId];
|
|
||||||
|
|
||||||
-- Insert new rows into [dbo].[CollectionGroup]
|
|
||||||
INSERT INTO [dbo].[CollectionGroup] ([CollectionId], [GroupId], [ReadOnly], [HidePasswords], [Manage])
|
|
||||||
SELECT C.[Id], TG.[GroupId], 0, 0, 0
|
|
||||||
FROM [dbo].[Collection] C
|
|
||||||
INNER JOIN #TempGroupsAccessAll TG ON C.[OrganizationId] = TG.[OrganizationId]
|
|
||||||
LEFT JOIN [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = TG.[GroupId]
|
|
||||||
WHERE CG.[CollectionId] IS NULL;
|
|
||||||
|
|
||||||
-- Update Group to clear AccessAll flag and update RevisionDate
|
|
||||||
UPDATE G
|
|
||||||
SET [AccessAll] = 0, [RevisionDate] = GETUTCDATE()
|
|
||||||
FROM [dbo].[Group] G
|
|
||||||
INNER JOIN #TempGroupsAccessAll TG ON G.[Id] = TG.[GroupId];
|
|
||||||
|
|
||||||
-- Step 2
|
|
||||||
-- Update existing rows in [dbo].[CollectionUser]
|
|
||||||
UPDATE target
|
|
||||||
SET
|
|
||||||
target.[ReadOnly] = 0,
|
|
||||||
target.[HidePasswords] = 0,
|
|
||||||
target.[Manage] = 0
|
|
||||||
FROM [dbo].[CollectionUser] AS target
|
|
||||||
INNER JOIN [dbo].[Collection] AS C ON target.[CollectionId] = C.[Id]
|
|
||||||
INNER JOIN #TempUsersAccessAll AS TU ON C.[OrganizationId] = TU.[OrganizationId] AND target.[OrganizationUserId] = TU.[OrganizationUserId];
|
|
||||||
|
|
||||||
-- Insert new rows into [dbo].[CollectionUser]
|
|
||||||
INSERT INTO [dbo].[CollectionUser] ([CollectionId], [OrganizationUserId], [ReadOnly], [HidePasswords], [Manage])
|
|
||||||
SELECT C.[Id] AS [CollectionId], TU.[OrganizationUserId], 0, 0, 0
|
|
||||||
FROM [dbo].[Collection] C
|
|
||||||
INNER JOIN #TempUsersAccessAll TU ON C.[OrganizationId] = TU.[OrganizationId]
|
|
||||||
LEFT JOIN [dbo].[CollectionUser] target
|
|
||||||
ON target.[CollectionId] = C.[Id] AND target.[OrganizationUserId] = TU.[OrganizationUserId]
|
|
||||||
WHERE target.[CollectionId] IS NULL;
|
|
||||||
|
|
||||||
-- Update OrganizationUser to clear AccessAll flag
|
|
||||||
UPDATE OU
|
|
||||||
SET [AccessAll] = 0, [RevisionDate] = GETUTCDATE()
|
|
||||||
FROM [dbo].[OrganizationUser] OU
|
|
||||||
INNER JOIN #TempUsersAccessAll TU ON OU.[Id] = TU.[OrganizationUserId];
|
|
||||||
|
|
||||||
-- Step 3
|
|
||||||
-- Update [dbo].[CollectionUser] with [Manage] = 1 using the temporary table
|
|
||||||
UPDATE CU
|
|
||||||
SET CU.[ReadOnly] = 0,
|
|
||||||
CU.[HidePasswords] = 0,
|
|
||||||
CU.[Manage] = 1
|
|
||||||
FROM [dbo].[CollectionUser] CU
|
|
||||||
INNER JOIN #TempUserManagers TUM ON CU.[OrganizationUserId] = TUM.[OrganizationUserId];
|
|
||||||
|
|
||||||
-- Insert rows to [dbo].[CollectionUser] with [Manage] = 1 using the temporary table
|
|
||||||
-- This is for orgUsers who are Managers / EditAssignedCollections but have access via a group
|
|
||||||
-- We cannot give the whole group Manage permissions so we have to give them a direct assignment
|
|
||||||
INSERT INTO [dbo].[CollectionUser] ([CollectionId], [OrganizationUserId], [ReadOnly], [HidePasswords], [Manage])
|
|
||||||
SELECT DISTINCT CG.[CollectionId], TUM.[OrganizationUserId], 0, 0, 1
|
|
||||||
FROM [dbo].[CollectionGroup] CG
|
|
||||||
INNER JOIN [dbo].[GroupUser] GU ON CG.[GroupId] = GU.[GroupId]
|
|
||||||
INNER JOIN #TempUserManagers TUM ON GU.[OrganizationUserId] = TUM.[OrganizationUserId]
|
|
||||||
WHERE NOT EXISTS (
|
|
||||||
SELECT 1 FROM [dbo].[CollectionUser] CU
|
|
||||||
WHERE CU.[CollectionId] = CG.[CollectionId] AND CU.[OrganizationUserId] = TUM.[OrganizationUserId]
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Update [dbo].[OrganizationUser] to migrate all OrganizationUsers with Manager role to User role
|
|
||||||
UPDATE OU
|
|
||||||
SET OU.[Type] = 2, OU.[RevisionDate] = GETUTCDATE() -- User
|
|
||||||
FROM [dbo].[OrganizationUser] OU
|
|
||||||
INNER JOIN #TempUserManagers TUM ON ou.[Id] = TUM.[OrganizationUserId]
|
|
||||||
WHERE TUM.[IsManager] = 1; -- Filter for Managers
|
|
||||||
|
|
||||||
-- Step 4
|
|
||||||
-- Execute User_BumpAccountRevisionDateByOrganizationUserIds for the distinct OrganizationUserIds
|
|
||||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationUserIds] @OrgUsersToBump;
|
|
||||||
COMMIT TRANSACTION;
|
|
||||||
END TRY
|
|
||||||
BEGIN CATCH
|
|
||||||
ROLLBACK TRANSACTION;
|
|
||||||
THROW;
|
|
||||||
END CATCH;
|
|
||||||
|
|
||||||
-- Drop the temporary table
|
|
||||||
DROP TABLE #TempGroupsAccessAll;
|
|
||||||
DROP TABLE #TempUsersAccessAll;
|
|
||||||
DROP TABLE #TempUserManagers;
|
|
||||||
END
|
|
@ -7,7 +7,6 @@ using Bit.Core.AdminConsole.Entities;
|
|||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationCollectionEnhancements.Interfaces;
|
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Auth.Entities;
|
using Bit.Core.Auth.Entities;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
@ -46,7 +45,6 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
|
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
private readonly IPushNotificationService _pushNotificationService;
|
private readonly IPushNotificationService _pushNotificationService;
|
||||||
private readonly IOrganizationEnableCollectionEnhancementsCommand _organizationEnableCollectionEnhancementsCommand;
|
|
||||||
private readonly IProviderRepository _providerRepository;
|
private readonly IProviderRepository _providerRepository;
|
||||||
private readonly IProviderBillingService _providerBillingService;
|
private readonly IProviderBillingService _providerBillingService;
|
||||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||||
@ -70,7 +68,6 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
_createOrganizationApiKeyCommand = Substitute.For<ICreateOrganizationApiKeyCommand>();
|
_createOrganizationApiKeyCommand = Substitute.For<ICreateOrganizationApiKeyCommand>();
|
||||||
_featureService = Substitute.For<IFeatureService>();
|
_featureService = Substitute.For<IFeatureService>();
|
||||||
_pushNotificationService = Substitute.For<IPushNotificationService>();
|
_pushNotificationService = Substitute.For<IPushNotificationService>();
|
||||||
_organizationEnableCollectionEnhancementsCommand = Substitute.For<IOrganizationEnableCollectionEnhancementsCommand>();
|
|
||||||
_providerRepository = Substitute.For<IProviderRepository>();
|
_providerRepository = Substitute.For<IProviderRepository>();
|
||||||
_providerBillingService = Substitute.For<IProviderBillingService>();
|
_providerBillingService = Substitute.For<IProviderBillingService>();
|
||||||
_orgDeleteTokenDataFactory = Substitute.For<IDataProtectorTokenFactory<OrgDeleteTokenable>>();
|
_orgDeleteTokenDataFactory = Substitute.For<IDataProtectorTokenFactory<OrgDeleteTokenable>>();
|
||||||
@ -91,7 +88,6 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
_featureService,
|
_featureService,
|
||||||
_globalSettings,
|
_globalSettings,
|
||||||
_pushNotificationService,
|
_pushNotificationService,
|
||||||
_organizationEnableCollectionEnhancementsCommand,
|
|
||||||
_providerRepository,
|
_providerRepository,
|
||||||
_providerBillingService,
|
_providerBillingService,
|
||||||
_orgDeleteTokenDataFactory);
|
_orgDeleteTokenDataFactory);
|
||||||
@ -162,48 +158,6 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
await _organizationService.Received(1).DeleteUserAsync(orgId, user.Id);
|
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]
|
[Theory, AutoData]
|
||||||
public async Task Delete_OrganizationIsConsolidatedBillingClient_ScalesProvidersSeats(
|
public async Task Delete_OrganizationIsConsolidatedBillingClient_ScalesProvidersSeats(
|
||||||
Provider provider,
|
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 };
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,5 @@
|
|||||||
|
IF OBJECT_ID('[dbo].[Organization_EnableCollectionEnhancements]') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
DROP PROCEDURE [dbo].[Organization_EnableCollectionEnhancements]
|
||||||
|
END
|
||||||
|
GO
|
Loading…
Reference in New Issue
Block a user