mirror of
https://github.com/bitwarden/server.git
synced 2026-02-05 08:33:10 +08:00
336 lines
15 KiB
C#
336 lines
15 KiB
C#
|
|
using System.Text.Json;
|
|||
|
|
using Bit.Core.AdminConsole.Entities;
|
|||
|
|
using Bit.Core.Entities;
|
|||
|
|
using Bit.Core.Enums;
|
|||
|
|
using Bit.Core.Models.Data;
|
|||
|
|
using Bit.Core.Repositories;
|
|||
|
|
using Bit.Core.Utilities;
|
|||
|
|
using Bit.Infrastructure.IntegrationTest.Services;
|
|||
|
|
using Newtonsoft.Json.Linq;
|
|||
|
|
using Xunit;
|
|||
|
|
|
|||
|
|
namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Migrations;
|
|||
|
|
|
|||
|
|
public class FinalFlexibleCollectionsDataMigrationsTests
|
|||
|
|
{
|
|||
|
|
private const string _migrationName = "FinalFlexibleCollectionsDataMigrations";
|
|||
|
|
|
|||
|
|
[DatabaseTheory, DatabaseData(MigrationName = _migrationName)]
|
|||
|
|
public async Task RunMigration_WithEditAssignedCollections_WithCustomUserType_MigratesToUserNullPermissions(
|
|||
|
|
IUserRepository userRepository,
|
|||
|
|
IOrganizationRepository organizationRepository,
|
|||
|
|
IOrganizationUserRepository organizationUserRepository,
|
|||
|
|
IMigrationTesterService migrationTester)
|
|||
|
|
{
|
|||
|
|
// Setup data
|
|||
|
|
var orgUser = await SetupData(
|
|||
|
|
userRepository, organizationRepository, organizationUserRepository,
|
|||
|
|
OrganizationUserType.Custom, editAssignedCollections: true, deleteAssignedCollections: false);
|
|||
|
|
|
|||
|
|
// Run data migration
|
|||
|
|
migrationTester.ApplyMigration();
|
|||
|
|
|
|||
|
|
// Assert that the user was migrated to a User type with null permissions
|
|||
|
|
var migratedOrgUser = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
|||
|
|
Assert.NotNull(migratedOrgUser);
|
|||
|
|
Assert.Equal(orgUser.Id, migratedOrgUser.Id);
|
|||
|
|
Assert.Equal(OrganizationUserType.User, migratedOrgUser.Type);
|
|||
|
|
Assert.Null(migratedOrgUser.Permissions);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[DatabaseTheory, DatabaseData(MigrationName = _migrationName)]
|
|||
|
|
public async Task RunMigration_WithDeleteAssignedCollections_WithCustomUserType_MigratesToUserNullPermissions(
|
|||
|
|
IUserRepository userRepository,
|
|||
|
|
IOrganizationRepository organizationRepository,
|
|||
|
|
IOrganizationUserRepository organizationUserRepository,
|
|||
|
|
IMigrationTesterService migrationTester)
|
|||
|
|
{
|
|||
|
|
// Setup data
|
|||
|
|
var orgUser = await SetupData(
|
|||
|
|
userRepository, organizationRepository, organizationUserRepository,
|
|||
|
|
OrganizationUserType.Custom, editAssignedCollections: false, deleteAssignedCollections: true);
|
|||
|
|
|
|||
|
|
// Run data migration
|
|||
|
|
migrationTester.ApplyMigration();
|
|||
|
|
|
|||
|
|
// Assert that the user was migrated to a User type with null permissions
|
|||
|
|
var migratedOrgUser = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
|||
|
|
Assert.NotNull(migratedOrgUser);
|
|||
|
|
Assert.Equal(orgUser.Id, migratedOrgUser.Id);
|
|||
|
|
Assert.Equal(OrganizationUserType.User, migratedOrgUser.Type);
|
|||
|
|
Assert.Null(migratedOrgUser.Permissions);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[DatabaseTheory, DatabaseData(MigrationName = _migrationName)]
|
|||
|
|
public async Task RunMigration_WithEditAndDeleteAssignedCollections_WithCustomUserType_MigratesToUserNullPermissions(
|
|||
|
|
IUserRepository userRepository,
|
|||
|
|
IOrganizationRepository organizationRepository,
|
|||
|
|
IOrganizationUserRepository organizationUserRepository,
|
|||
|
|
IMigrationTesterService migrationTester)
|
|||
|
|
{
|
|||
|
|
// Setup data
|
|||
|
|
var orgUser = await SetupData(
|
|||
|
|
userRepository, organizationRepository, organizationUserRepository,
|
|||
|
|
OrganizationUserType.Custom, editAssignedCollections: true, deleteAssignedCollections: true);
|
|||
|
|
|
|||
|
|
// Run data migration
|
|||
|
|
migrationTester.ApplyMigration();
|
|||
|
|
|
|||
|
|
// Assert that the user was migrated to a User type with null permissions
|
|||
|
|
var migratedOrgUser = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
|||
|
|
Assert.NotNull(migratedOrgUser);
|
|||
|
|
Assert.Equal(orgUser.Id, migratedOrgUser.Id);
|
|||
|
|
Assert.Equal(OrganizationUserType.User, migratedOrgUser.Type);
|
|||
|
|
Assert.Null(migratedOrgUser.Permissions);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[DatabaseTheory, DatabaseData(MigrationName = _migrationName)]
|
|||
|
|
public async Task RunMigration_WithoutAssignedCollectionsPermissions_WithCustomUserType_RemovesAssignedCollectionsPermissions(
|
|||
|
|
IUserRepository userRepository,
|
|||
|
|
IOrganizationRepository organizationRepository,
|
|||
|
|
IOrganizationUserRepository organizationUserRepository,
|
|||
|
|
IMigrationTesterService migrationTester)
|
|||
|
|
{
|
|||
|
|
// Setup data
|
|||
|
|
var orgUser = await SetupData(
|
|||
|
|
userRepository, organizationRepository, organizationUserRepository, OrganizationUserType.Custom,
|
|||
|
|
editAssignedCollections: false, deleteAssignedCollections: false, accessEventLogs: true);
|
|||
|
|
|
|||
|
|
// Run data migration
|
|||
|
|
migrationTester.ApplyMigration();
|
|||
|
|
|
|||
|
|
// Assert that the user kept the accessEventLogs permission and lost the editAssignedCollections and deleteAssignedCollections permissions
|
|||
|
|
var migratedOrgUser = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
|||
|
|
Assert.NotNull(migratedOrgUser);
|
|||
|
|
Assert.Equal(orgUser.Id, migratedOrgUser.Id);
|
|||
|
|
Assert.Equal(OrganizationUserType.Custom, migratedOrgUser.Type);
|
|||
|
|
Assert.NotEqual(orgUser.Permissions, migratedOrgUser.Permissions);
|
|||
|
|
Assert.NotNull(migratedOrgUser.Permissions);
|
|||
|
|
Assert.Contains("accessEventLogs", orgUser.Permissions);
|
|||
|
|
Assert.Contains("editAssignedCollections", orgUser.Permissions);
|
|||
|
|
Assert.Contains("deleteAssignedCollections", orgUser.Permissions);
|
|||
|
|
|
|||
|
|
Assert.Contains("accessEventLogs", migratedOrgUser.Permissions);
|
|||
|
|
var migratedOrgUserPermissions = migratedOrgUser.GetPermissions();
|
|||
|
|
Assert.NotNull(migratedOrgUserPermissions);
|
|||
|
|
Assert.True(migratedOrgUserPermissions.AccessEventLogs);
|
|||
|
|
Assert.DoesNotContain("editAssignedCollections", migratedOrgUser.Permissions);
|
|||
|
|
Assert.DoesNotContain("deleteAssignedCollections", migratedOrgUser.Permissions);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[DatabaseTheory, DatabaseData(MigrationName = _migrationName)]
|
|||
|
|
public async Task RunMigration_WithAdminUserType_RemovesAssignedCollectionsPermissions(
|
|||
|
|
IUserRepository userRepository,
|
|||
|
|
IOrganizationRepository organizationRepository,
|
|||
|
|
IOrganizationUserRepository organizationUserRepository,
|
|||
|
|
IMigrationTesterService migrationTester)
|
|||
|
|
{
|
|||
|
|
// Setup data
|
|||
|
|
var orgUser = await SetupData(
|
|||
|
|
userRepository, organizationRepository, organizationUserRepository, OrganizationUserType.Admin,
|
|||
|
|
editAssignedCollections: false, deleteAssignedCollections: false, accessEventLogs: true);
|
|||
|
|
|
|||
|
|
// Run data migration
|
|||
|
|
migrationTester.ApplyMigration();
|
|||
|
|
|
|||
|
|
// Assert that the user kept the Admin type and lost the editAssignedCollections and deleteAssignedCollections
|
|||
|
|
// permissions but kept the accessEventLogs permission
|
|||
|
|
var migratedOrgUser = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
|||
|
|
Assert.NotNull(migratedOrgUser);
|
|||
|
|
Assert.Equal(orgUser.Id, migratedOrgUser.Id);
|
|||
|
|
Assert.Equal(OrganizationUserType.Admin, migratedOrgUser.Type);
|
|||
|
|
Assert.NotEqual(orgUser.Permissions, migratedOrgUser.Permissions);
|
|||
|
|
Assert.NotNull(migratedOrgUser.Permissions);
|
|||
|
|
Assert.Contains("accessEventLogs", orgUser.Permissions);
|
|||
|
|
Assert.Contains("editAssignedCollections", orgUser.Permissions);
|
|||
|
|
Assert.Contains("deleteAssignedCollections", orgUser.Permissions);
|
|||
|
|
|
|||
|
|
Assert.Contains("accessEventLogs", migratedOrgUser.Permissions);
|
|||
|
|
Assert.True(migratedOrgUser.GetPermissions().AccessEventLogs);
|
|||
|
|
Assert.DoesNotContain("editAssignedCollections", migratedOrgUser.Permissions);
|
|||
|
|
Assert.DoesNotContain("deleteAssignedCollections", migratedOrgUser.Permissions);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[DatabaseTheory, DatabaseData(MigrationName = _migrationName)]
|
|||
|
|
public async Task RunMigration_WithoutAssignedCollectionsPermissions_DoesNothing(
|
|||
|
|
IUserRepository userRepository,
|
|||
|
|
IOrganizationRepository organizationRepository,
|
|||
|
|
IOrganizationUserRepository organizationUserRepository,
|
|||
|
|
IMigrationTesterService migrationTester)
|
|||
|
|
{
|
|||
|
|
// Setup data
|
|||
|
|
var orgUser = await SetupData(
|
|||
|
|
userRepository, organizationRepository, organizationUserRepository, OrganizationUserType.Custom,
|
|||
|
|
editAssignedCollections: false, deleteAssignedCollections: false, accessEventLogs: false);
|
|||
|
|
// Remove the editAssignedCollections and deleteAssignedCollections permissions
|
|||
|
|
orgUser.Permissions = JsonSerializer.Serialize(new
|
|||
|
|
{
|
|||
|
|
AccessEventLogs = false,
|
|||
|
|
AccessImportExport = false,
|
|||
|
|
AccessReports = false,
|
|||
|
|
CreateNewCollections = false,
|
|||
|
|
EditAnyCollection = false,
|
|||
|
|
DeleteAnyCollection = false,
|
|||
|
|
ManageGroups = false,
|
|||
|
|
ManagePolicies = false,
|
|||
|
|
ManageSso = false,
|
|||
|
|
ManageUsers = false,
|
|||
|
|
ManageResetPassword = false,
|
|||
|
|
ManageScim = false
|
|||
|
|
}, JsonHelpers.CamelCase);
|
|||
|
|
await organizationUserRepository.ReplaceAsync(orgUser);
|
|||
|
|
|
|||
|
|
// Run data migration
|
|||
|
|
migrationTester.ApplyMigration();
|
|||
|
|
|
|||
|
|
// Assert that the user remained unchanged
|
|||
|
|
var migratedOrgUser = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
|||
|
|
Assert.NotNull(migratedOrgUser);
|
|||
|
|
Assert.Equal(orgUser.Id, migratedOrgUser.Id);
|
|||
|
|
Assert.Equal(OrganizationUserType.Custom, orgUser.Type);
|
|||
|
|
Assert.Equal(OrganizationUserType.Custom, migratedOrgUser.Type);
|
|||
|
|
Assert.NotNull(migratedOrgUser.Permissions);
|
|||
|
|
// Assert that the permissions remain unchanged by comparing JSON data, ignoring the order of properties
|
|||
|
|
Assert.True(JToken.DeepEquals(JObject.Parse(orgUser.Permissions), JObject.Parse(migratedOrgUser.Permissions)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[DatabaseTheory, DatabaseData(MigrationName = _migrationName)]
|
|||
|
|
public async Task RunMigration_HandlesNull(
|
|||
|
|
IUserRepository userRepository,
|
|||
|
|
IOrganizationRepository organizationRepository,
|
|||
|
|
IOrganizationUserRepository organizationUserRepository,
|
|||
|
|
IMigrationTesterService migrationTester)
|
|||
|
|
{
|
|||
|
|
// Setup data
|
|||
|
|
var orgUser = await SetupData(
|
|||
|
|
userRepository, organizationRepository, organizationUserRepository, OrganizationUserType.Custom,
|
|||
|
|
editAssignedCollections: false, deleteAssignedCollections: false, accessEventLogs: false);
|
|||
|
|
|
|||
|
|
orgUser.Permissions = null;
|
|||
|
|
await organizationUserRepository.ReplaceAsync(orgUser);
|
|||
|
|
|
|||
|
|
// Run data migration
|
|||
|
|
migrationTester.ApplyMigration();
|
|||
|
|
|
|||
|
|
// Assert no changes
|
|||
|
|
var migratedOrgUser = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
|||
|
|
Assert.NotNull(migratedOrgUser);
|
|||
|
|
Assert.Equal(orgUser.Id, migratedOrgUser.Id);
|
|||
|
|
Assert.Equal(orgUser.Type, migratedOrgUser.Type);
|
|||
|
|
Assert.Null(migratedOrgUser.Permissions);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[DatabaseTheory, DatabaseData(MigrationName = _migrationName)]
|
|||
|
|
public async Task RunMigration_HandlesNullString(
|
|||
|
|
IUserRepository userRepository,
|
|||
|
|
IOrganizationRepository organizationRepository,
|
|||
|
|
IOrganizationUserRepository organizationUserRepository,
|
|||
|
|
IMigrationTesterService migrationTester)
|
|||
|
|
{
|
|||
|
|
// Setup data
|
|||
|
|
var orgUser = await SetupData(
|
|||
|
|
userRepository, organizationRepository, organizationUserRepository, OrganizationUserType.Custom,
|
|||
|
|
editAssignedCollections: false, deleteAssignedCollections: false, accessEventLogs: false);
|
|||
|
|
|
|||
|
|
// We haven't tracked down the source of this yet but it does occur in our cloud database
|
|||
|
|
orgUser.Permissions = "NULL";
|
|||
|
|
await organizationUserRepository.ReplaceAsync(orgUser);
|
|||
|
|
|
|||
|
|
// Run data migration
|
|||
|
|
migrationTester.ApplyMigration();
|
|||
|
|
|
|||
|
|
// Assert no changes
|
|||
|
|
var migratedOrgUser = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
|||
|
|
Assert.NotNull(migratedOrgUser);
|
|||
|
|
Assert.Equal(orgUser.Id, migratedOrgUser.Id);
|
|||
|
|
Assert.Equal(orgUser.Type, migratedOrgUser.Type);
|
|||
|
|
Assert.Equal("NULL", migratedOrgUser.Permissions);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[DatabaseTheory, DatabaseData(MigrationName = _migrationName)]
|
|||
|
|
public async Task RunMigration_HandlesNonJsonValues(
|
|||
|
|
IUserRepository userRepository,
|
|||
|
|
IOrganizationRepository organizationRepository,
|
|||
|
|
IOrganizationUserRepository organizationUserRepository,
|
|||
|
|
IMigrationTesterService migrationTester)
|
|||
|
|
{
|
|||
|
|
// Setup data
|
|||
|
|
var orgUser = await SetupData(
|
|||
|
|
userRepository, organizationRepository, organizationUserRepository, OrganizationUserType.Custom,
|
|||
|
|
editAssignedCollections: false, deleteAssignedCollections: false, accessEventLogs: false);
|
|||
|
|
|
|||
|
|
orgUser.Permissions = "asdfasdfasfd";
|
|||
|
|
await organizationUserRepository.ReplaceAsync(orgUser);
|
|||
|
|
|
|||
|
|
// Run data migration
|
|||
|
|
migrationTester.ApplyMigration();
|
|||
|
|
|
|||
|
|
// Assert no changes
|
|||
|
|
var migratedOrgUser = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
|||
|
|
Assert.NotNull(migratedOrgUser);
|
|||
|
|
Assert.Equal(orgUser.Id, migratedOrgUser.Id);
|
|||
|
|
Assert.Equal(orgUser.Type, migratedOrgUser.Type);
|
|||
|
|
Assert.Equal("asdfasdfasfd", migratedOrgUser.Permissions);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task<OrganizationUser> SetupData(
|
|||
|
|
IUserRepository userRepository,
|
|||
|
|
IOrganizationRepository organizationRepository,
|
|||
|
|
IOrganizationUserRepository organizationUserRepository,
|
|||
|
|
OrganizationUserType organizationUserType,
|
|||
|
|
bool editAssignedCollections,
|
|||
|
|
bool deleteAssignedCollections,
|
|||
|
|
bool accessEventLogs = false)
|
|||
|
|
{
|
|||
|
|
var permissions = new Permissions
|
|||
|
|
{
|
|||
|
|
AccessEventLogs = accessEventLogs,
|
|||
|
|
AccessImportExport = false,
|
|||
|
|
AccessReports = false,
|
|||
|
|
CreateNewCollections = false,
|
|||
|
|
EditAnyCollection = false,
|
|||
|
|
DeleteAnyCollection = false,
|
|||
|
|
EditAssignedCollections = editAssignedCollections,
|
|||
|
|
DeleteAssignedCollections = deleteAssignedCollections,
|
|||
|
|
ManageGroups = false,
|
|||
|
|
ManagePolicies = false,
|
|||
|
|
ManageSso = false,
|
|||
|
|
ManageUsers = false,
|
|||
|
|
ManageResetPassword = false,
|
|||
|
|
ManageScim = false
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
var user = await userRepository.CreateAsync(new User
|
|||
|
|
{
|
|||
|
|
Name = "Test User 1",
|
|||
|
|
Email = $"test+{Guid.NewGuid()}@example.com",
|
|||
|
|
ApiKey = "TEST",
|
|||
|
|
SecurityStamp = "stamp",
|
|||
|
|
Kdf = KdfType.PBKDF2_SHA256,
|
|||
|
|
KdfIterations = 1,
|
|||
|
|
KdfMemory = 2,
|
|||
|
|
KdfParallelism = 3
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|||
|
|
{
|
|||
|
|
Name = "Test Org",
|
|||
|
|
BillingEmail = user.Email, // TODO: EF does not enforce this being NOT NULl
|
|||
|
|
Plan = "Test", // TODO: EF does not enforce this being NOT NULl
|
|||
|
|
PrivateKey = "privatekey",
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|||
|
|
{
|
|||
|
|
OrganizationId = organization.Id,
|
|||
|
|
UserId = user.Id,
|
|||
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|||
|
|
ResetPasswordKey = "resetpasswordkey1",
|
|||
|
|
Type = organizationUserType,
|
|||
|
|
Permissions = JsonSerializer.Serialize(permissions, JsonHelpers.CamelCase)
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return orgUser;
|
|||
|
|
}
|
|||
|
|
}
|