1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

Auth/PM-5501 - VaultTimeoutSettingsService State Provider Migration (#8604)

* PM-5501 - VaultTimeoutSettingsSvc - refactor var names in getVaultTimeoutAction

* PM-5501 - Add state definitions and key definitions + test deserialization of key defs.

* PM-5501 - Add state provider dep to VaultTimeoutSettingsSvc

* PM-5501 - Refactor getVaultTimeout

* PM-5501 - VaultTimeoutSettingsService - Build getMaxVaultTimeoutPolicyByUserId helper

* PM-5501 - (1) Update state definitions (2) convert KeyDefs to UserKeyDefs (2) Remove everBeenUnlocked as we won't need it

* PM-5501 - VaultTimeoutSettingsSvc - POC for getVaultTimeoutActionByUserId$ method + new private determineVaultTimeoutAction helper.

* PM-5501 - VaultTimeoutSettingsSvc - build set and observable get methods for vault timeout settings

* PM-5501 - Update web references to use new vault timeout setting service methods

* PM-5501 - VaultTimeoutSettingsSvc - write up abstraction js docs

* PM-5501 - VaultTimeoutSettingsSvc abstraction - finish tweaks

* PM-5501 - VaultTimeoutSettingsSvc - add catchError blocks to observables to protect outer observables and prevent cancellation in case of error.

* PM-5501 - Remove vault timeout settings from state service implementation.

* PM-5501 - VaultTimeoutSettingsServiceStateProviderMigrator first draft

* PM-5501 - WIP - replace some state service calls with calls to vault timeout settings svc.

* PM-5501 - Replace state service calls in login strategies to get vault timeout settings data with VaultTimeoutSettingsService calls.

* PM-5501 - Fix login strategy tests

* PM-5501 - Update login strategy tests to pass

* PM-5501 - CryptoSvc - share VaultTimeout user key def to allow crypto svc access to the vault timeout without creating a circular dep.

* PM-5501 - Fix dependency injections.

* PM-5501 - ApiSvc - replace state svc with vault timeout settings svc.

* PM-5501 - VaultTimeoutSettingsServiceStateProviderMigrator more cleanup

* PM-5501 - Test VaultTimeoutSettingsServiceStateProviderMigrator

* PM-5501 - VaultTimeoutSettingsSvc tests updated

* PM-5501 - Update all setVaultTimeoutOptions references

* PM-5501 - VaultTimeoutSettingsSvc - Update setVaultTimeoutOptions to remove unnecessary logic and clean up clearTokens condition.

* PM-5501 - Fix vault timeout service tests

* PM-5501 - Update VaultTimeoutSettings state tests to pass

* PM-5501 - Desktop - system svc - fix build by replacing use of removed method.

* PM-5501 - Fix CLI by properly configuring super class deps in NodeApiService

* PM-5501 - Actually finish getitng deps fixed to get CLI to build

* PM-5501 - VaultTimeoutSettingsSvc.determineVaultTimeoutAction - pass userId to getAvailableVaultTimeoutActions to prevent hang waiting for an active user.

* PM-5501 - VaultTimeoutSettingSvc test - enhance getVaultTimeoutActionByUserId$ to also test PIN scenarios as an unlock method

* PM-5501 - bump migration version

* PM-5501 - Refactor migration to ensure the migration persists null vault timeout values.

* PM-5501 - Bump migration version

* PM-5501 - Fix web build issues introduced by merging main.

* PM-5501 - Bump migration version

* PM-5501 - PreferencesComponent - revert dep change from InternalPolicyService to standard PolicyService abstraction

* PM-5501 - Address all PR feedback from Jake

Co-authored-by: Jake Fink <jfink@bitwarden.com>

* PM-5501 - VaultTimeoutSettingsSvc tests - add tests for setVaultTimeoutOptions

* PM-5501 - VaultTimeoutSettingsSvc - setVaultTimeoutOptions - Update tests to use platform's desired syntax.

* PM-5501 - Fix tests

* PM-5501 - Create new VaultTimeout type

* PM-5501 - Create new DEFAULT_VAULT_TIMEOUT to allow each client to inject their default timeout into the VaultTimeoutSettingService

* PM-5501 - Migrate client default vault timeout to new injection token

* PM-5501 - Update VaultTimeoutSettingsSvc to use VaultTimeout type and apply default vault timeout if it is null.

* PM-5501 - Update vaultTimeout: number to be vaultTimeout: VaultTimeout everywhere I could find it.

* PM-5501 - More changes based on changing vaultTimeout from number to VaultTimeout type.

* PM-5501 - VaultTimeoutSvc - Update shouldLock logic which previously checked for null (never) or any negative values (any strings except never) with a simple string type check.

* PM-5501 - More cleanup of vaultTimeout type change - replacing null checks with "never" checks

* PM-5501 - VaultTimeoutSettingsSvc - refactor determineVaultTimeout to properly treat string and numeric vault timeouts.

* PM-5501 - Update vault timeout settings service tests to reflect new VaultTimeout type.

* PM-5501 - VaultTimeoutSettingsService - add more test cases for getVaultTimeoutByUserId

* PM-5501 - (1) Remove "immediately" as 0 is numerically meaningful and can be used with Math.min (2) Add VaultTimeoutOption interface for use in all places we show the user a list of vault timeout options.

* PM-5501 - VaultTimeoutSettingSvc - update tests to use 0 as immediately.

* PM-5501 - VaultTimeoutInputComp - Add new types and update applyVaultTimeoutPolicy logic appropriately.

* PM-5501 - Add new types to all preferences and setting components across clients.

* PM-5501 - Fix bug on web where navigating to the preferences page throws an error b/c the validatorChange function isn't defined.

* PM-5501 - WIP on updating vault timeout setting migration and rollback + testing it.

* PM-5501 - Update VaultTimeoutSettingsSvc state provider migration and tests to map existing possible values into new VaultTImeout type.

* PM-5501 - Fix vault timeout settings state tests by changing number to new VaultTimeout type.

* PM-5501 - Fix crypto svc auto key refresh test to use "never" instead of null.

* PM-5501 - Add clarifying comment to vaulttimeout type

* PM-5501 - Desktop app comp - replace systemTimeoutOptions with vault timeout type.

* PM-5501 - Update vault timeout service tests to use VaultTimeout type.

* PM-5501 - VaultTimeoutSettingsSvc - (1) Fix bug where vault timeout action didn't have a default like it did before (2) Fix bug in userHasMasterPassword where it would incorrectly return the active user stream for a given user id as a fallback. There is no guarantee the given user would match the active user so the paths are mutually exclusive.

* PM-5501 - Login Strategy fix - Move retrieval of vault timeout settings and setting of the tokens until after account init and user decryption options set as those opts are needed to properly determine the user's available vault timeout actions.

* PM-5501 - Fix vault timeout settings svc tests

* PM-5501 - VaultTimeoutSettingSvc - move default logic to determine methods + refactor default vault timeout action to properly default to lock in scenarios the user has lock available.

* Update libs/angular/src/components/settings/vault-timeout-input.component.ts

Co-authored-by: Cesar Gonzalez <cesar.a.gonzalezcs@gmail.com>

* PM-5501 - Per PR feedback, cleanup commented out vault timeout options

* PM-5501 - Fix vault timeout input comp lint issues

* PM-5501 - Per PR feedback from Cesar, update VaultTimeout type to use const so we can avoid any magic string usage. Awesome.

Co-authored-by: Cesar Gonzalez <cesar.a.gonzalezcs@gmail.com>

* PM-5501 - CLI - use "never" as default vault timeout instead of null.

* PM-5501 - Fix broken tests

* PM-5501 - Bump migration version

* PM-5501 - Fix build errors after merging main.

* PM-5501 - Update mockMigrationHelper to pass along client type so tests will respect it.

* PM-5501 - Update VaultTimeoutSettingsServiceStateProviderMigrator and tests to use new CLI client type to convert undefined values to never so that CLI users don't lose their session upon running this migration.

* PM-5501 - Bump migration version

* PM-5501 - Fix migration tests to use new authenticated user format

* PM-5501 Update rollback tests

* PM-5501 - Adjust migration based on feedback.

* PM-5501 - Per Jake's find, fix missed -2

Co-authored-by: Jake Fink <jfink@bitwarden.com>

* PM-5501 - Add user id to needsStorageReseed.

Co-authored-by: Jake Fink <jfink@bitwarden.com>

* PM-5501 - Per PR feedback, setVaultTimeoutOptions shouldn't accept null for vault timeout anymore.

* PM-5501 - Per PR feedback, add null checks for set methods for setting vault timeout or vault timeout action.

* PM-5501 - Per PR feedback, add more context as to why we need vault timeout settings to persist after logout.

* PM-5501 - Per PR feedback, fix userHasMasterPassword

* PM-5501 - VaultTimeoutSettingsService - fix userHasMasterPassword check by checking for null decryption options.

* PM-5501 - Remove state service from vault timeout settings service (WOOO)

* PM-5501 - Bump migration version

* PM-5501 - Account Security comp - refactor to consider ease of debugging.

* PM-5501 - (1) Add checks for null vault timeout and vault timeout actions (2) Add tests for new scenarios.

* PM-5501 - VaultTimeoutSettingsSvc - setVaultTimeoutOptions - fix bug where nullish check would throw incorrectly if immediately (0) was picked as the timeout.

* PM-5501 - Per PR feedback, clean up remaining token service methods which accept null for timeout and add tests. .

* PM-5501 - Fix nit

---------

Co-authored-by: Jake Fink <jfink@bitwarden.com>
Co-authored-by: Cesar Gonzalez <cesar.a.gonzalezcs@gmail.com>
This commit is contained in:
Jared Snider
2024-05-13 15:56:04 -04:00
committed by GitHub
parent dd41bcb52e
commit 473c5311fa
59 changed files with 2306 additions and 390 deletions

View File

@@ -59,13 +59,14 @@ import { KdfConfigMigrator } from "./migrations/59-move-kdf-config-to-state-prov
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
import { KnownAccountsMigrator } from "./migrations/60-known-accounts";
import { PinStateMigrator } from "./migrations/61-move-pin-state-to-providers";
import { VaultTimeoutSettingsServiceStateProviderMigrator } from "./migrations/62-migrate-vault-timeout-settings-svc-to-state-provider";
import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account";
import { MoveStateVersionMigrator } from "./migrations/8-move-state-version";
import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global";
import { MinVersionMigrator } from "./migrations/min-version";
export const MIN_VERSION = 3;
export const CURRENT_VERSION = 61;
export const CURRENT_VERSION = 62;
export type MinVersion = typeof MIN_VERSION;
export function createMigrationBuilder() {
@@ -128,7 +129,8 @@ export function createMigrationBuilder() {
.with(RemoveRefreshTokenMigratedFlagMigrator, 57, 58)
.with(KdfConfigMigrator, 58, 59)
.with(KnownAccountsMigrator, 59, 60)
.with(PinStateMigrator, 60, CURRENT_VERSION);
.with(PinStateMigrator, 60, 61)
.with(VaultTimeoutSettingsServiceStateProviderMigrator, 61, CURRENT_VERSION);
}
export async function currentVersion(

View File

@@ -242,6 +242,7 @@ export function mockMigrationHelper(
mockHelper.remove.mockImplementation((key) => helper.remove(key));
mockHelper.type = helper.type;
mockHelper.clientType = helper.clientType;
return mockHelper;
}

View File

@@ -0,0 +1,669 @@
import { MockProxy, any } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import {
ClientType,
VAULT_TIMEOUT,
VAULT_TIMEOUT_ACTION,
VaultTimeoutSettingsServiceStateProviderMigrator,
} from "./62-migrate-vault-timeout-settings-svc-to-state-provider";
// Represents data in state service pre-migration
function preMigrationJson() {
return {
global: {
vaultTimeout: 30,
vaultTimeoutAction: "lock",
otherStuff: "otherStuff",
},
global_account_accounts: {
user1: {
email: "user1@email.com",
name: "User 1",
emailVerified: true,
},
user2: {
email: "user2@email.com",
name: "User 2",
emailVerified: true,
},
// create the same structure for user3, user4, user5, user6, user7 in the global_account_accounts
user3: {
email: "user3@email.com",
name: "User 3",
emailVerified: true,
},
user4: {
email: "user4@email.com",
name: "User 4",
emailVerified: true,
},
user5: {
email: "user5@email.com",
name: "User 5",
emailVerified: true,
},
user6: {
email: "user6@email.com",
name: "User 6",
emailVerified: true,
},
user7: {
email: "user7@email.com",
name: "User 7",
emailVerified: true,
},
},
user1: {
settings: {
vaultTimeout: 30,
vaultTimeoutAction: "lock",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
user2: {
settings: {
vaultTimeout: null as any,
vaultTimeoutAction: "logOut",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
user3: {
settings: {
vaultTimeout: -1, // onRestart
vaultTimeoutAction: "lock",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
user4: {
settings: {
vaultTimeout: -2, // onLocked
vaultTimeoutAction: "logOut",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
user5: {
settings: {
vaultTimeout: -3, // onSleep
vaultTimeoutAction: "lock",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
user6: {
settings: {
vaultTimeout: -4, // onIdle
vaultTimeoutAction: "logOut",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
user7: {
settings: {
// no vault timeout data to migrate
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
};
}
function rollbackJSON(cli: boolean = false) {
const rollbackJson: any = {
// User specific state provider data
// use pattern user_{userId}_{stateDefinitionName}_{keyDefinitionKey} for user data
// User1 migrated data
user_user1_vaultTimeoutSettings_vaultTimeout: 30,
user_user1_vaultTimeoutSettings_vaultTimeoutAction: "lock",
// User2 migrated data
user_user2_vaultTimeoutSettings_vaultTimeout: "never",
user_user2_vaultTimeoutSettings_vaultTimeoutAction: "logOut",
// User3 migrated data
user_user3_vaultTimeoutSettings_vaultTimeout: "onRestart",
user_user3_vaultTimeoutSettings_vaultTimeoutAction: "lock",
// User4 migrated data
user_user4_vaultTimeoutSettings_vaultTimeout: "onLocked",
user_user4_vaultTimeoutSettings_vaultTimeoutAction: "logOut",
// User5 migrated data
user_user5_vaultTimeoutSettings_vaultTimeout: "onSleep",
user_user5_vaultTimeoutSettings_vaultTimeoutAction: "lock",
// User6 migrated data
user_user6_vaultTimeoutSettings_vaultTimeout: "onIdle",
user_user6_vaultTimeoutSettings_vaultTimeoutAction: "logOut",
// User7 migrated data
// user_user7_vaultTimeoutSettings_vaultTimeout: null as any,
// user_user7_vaultTimeoutSettings_vaultTimeoutAction: null as any,
// Global state provider data
// use pattern global_{stateDefinitionName}_{keyDefinitionKey} for global data
// Not migrating global data
global: {
// no longer has vault timeout data
otherStuff: "otherStuff",
},
global_account_accounts: {
user1: {
email: "user1@email.com",
name: "User 1",
emailVerified: true,
},
user2: {
email: "user2@email.com",
name: "User 2",
emailVerified: true,
},
// create the same structure for user3, user4, user5, user6, user7 in the global_account_accounts
user3: {
email: "user3@email.com",
name: "User 3",
emailVerified: true,
},
user4: {
email: "user4@email.com",
name: "User 4",
emailVerified: true,
},
user5: {
email: "user5@email.com",
name: "User 5",
emailVerified: true,
},
user6: {
email: "user6@email.com",
name: "User 6",
emailVerified: true,
},
user7: {
email: "user7@email.com",
name: "User 7",
emailVerified: true,
},
},
user1: {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
user2: {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
user3: {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
user4: {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
user5: {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
user6: {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
user7: {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
},
};
if (cli) {
rollbackJson.user_user7_vaultTimeoutSettings_vaultTimeout = "never";
}
return rollbackJson;
}
describe("VaultTimeoutSettingsServiceStateProviderMigrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: VaultTimeoutSettingsServiceStateProviderMigrator;
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(preMigrationJson(), 61);
sut = new VaultTimeoutSettingsServiceStateProviderMigrator(61, 62);
});
it("should remove state service data from all accounts that have it", async () => {
await sut.migrate(helper);
// Global data
expect(helper.set).toHaveBeenCalledWith("global", {
// no longer has vault timeout data
otherStuff: "otherStuff",
});
// User data
expect(helper.set).toHaveBeenCalledWith("user1", {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user2", {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user3", {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user4", {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user5", {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user6", {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledTimes(7); // 6 users + 1 global
expect(helper.set).not.toHaveBeenCalledWith("user7", any());
});
it("should migrate data to state providers for defined accounts that have the data", async () => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledWith("user1", VAULT_TIMEOUT, 30);
expect(helper.setToUser).toHaveBeenCalledWith("user1", VAULT_TIMEOUT_ACTION, "lock");
expect(helper.setToUser).toHaveBeenCalledWith("user2", VAULT_TIMEOUT, "never");
expect(helper.setToUser).toHaveBeenCalledWith("user2", VAULT_TIMEOUT_ACTION, "logOut");
expect(helper.setToUser).toHaveBeenCalledWith("user3", VAULT_TIMEOUT, "onRestart");
expect(helper.setToUser).toHaveBeenCalledWith("user3", VAULT_TIMEOUT_ACTION, "lock");
expect(helper.setToUser).toHaveBeenCalledWith("user4", VAULT_TIMEOUT, "onLocked");
expect(helper.setToUser).toHaveBeenCalledWith("user4", VAULT_TIMEOUT_ACTION, "logOut");
expect(helper.setToUser).toHaveBeenCalledWith("user5", VAULT_TIMEOUT, "onSleep");
expect(helper.setToUser).toHaveBeenCalledWith("user5", VAULT_TIMEOUT_ACTION, "lock");
expect(helper.setToUser).toHaveBeenCalledWith("user6", VAULT_TIMEOUT, "onIdle");
expect(helper.setToUser).toHaveBeenCalledWith("user6", VAULT_TIMEOUT_ACTION, "logOut");
// Expect that we didn't migrate anything to user 7 or 8
expect(helper.setToUser).not.toHaveBeenCalledWith("user7", VAULT_TIMEOUT, any());
expect(helper.setToUser).not.toHaveBeenCalledWith("user7", VAULT_TIMEOUT_ACTION, any());
expect(helper.setToUser).not.toHaveBeenCalledWith("user8", VAULT_TIMEOUT, any());
expect(helper.setToUser).not.toHaveBeenCalledWith("user8", VAULT_TIMEOUT_ACTION, any());
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 62);
sut = new VaultTimeoutSettingsServiceStateProviderMigrator(61, 62);
});
it("should null out newly migrated entries in state provider framework", async () => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledWith("user1", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user1", VAULT_TIMEOUT_ACTION, null);
expect(helper.setToUser).toHaveBeenCalledWith("user2", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user2", VAULT_TIMEOUT_ACTION, null);
expect(helper.setToUser).toHaveBeenCalledWith("user3", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user3", VAULT_TIMEOUT_ACTION, null);
expect(helper.setToUser).toHaveBeenCalledWith("user4", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user4", VAULT_TIMEOUT_ACTION, null);
expect(helper.setToUser).toHaveBeenCalledWith("user5", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user5", VAULT_TIMEOUT_ACTION, null);
expect(helper.setToUser).toHaveBeenCalledWith("user6", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user6", VAULT_TIMEOUT_ACTION, null);
expect(helper.setToUser).toHaveBeenCalledWith("user7", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user7", VAULT_TIMEOUT_ACTION, null);
});
it("should add back data to all accounts that had migrated data (only user 1)", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledWith("user1", {
settings: {
vaultTimeout: 30,
vaultTimeoutAction: "lock",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user2", {
settings: {
vaultTimeout: null,
vaultTimeoutAction: "logOut",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user3", {
settings: {
vaultTimeout: -1, // onRestart
vaultTimeoutAction: "lock",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user4", {
settings: {
vaultTimeout: -2, // onLocked
vaultTimeoutAction: "logOut",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user5", {
settings: {
vaultTimeout: -3, // onSleep
vaultTimeoutAction: "lock",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user6", {
settings: {
vaultTimeout: -4, // onIdle
vaultTimeoutAction: "logOut",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
});
it("should not add back the global vault timeout data", async () => {
await sut.rollback(helper);
expect(helper.set).not.toHaveBeenCalledWith("global", any());
});
it("should not add data back if data wasn't migrated or acct doesn't exist", async () => {
await sut.rollback(helper);
// no data to add back for user7 (acct exists but no migrated data) and user8 (no acct)
expect(helper.set).not.toHaveBeenCalledWith("user7", any());
expect(helper.set).not.toHaveBeenCalledWith("user8", any());
});
});
});
describe("VaultTimeoutSettingsServiceStateProviderMigrator - CLI", () => {
let helper: MockProxy<MigrationHelper>;
let sut: VaultTimeoutSettingsServiceStateProviderMigrator;
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(preMigrationJson(), 61, "general", ClientType.Cli);
sut = new VaultTimeoutSettingsServiceStateProviderMigrator(61, 62);
});
it("should remove state service data from all accounts that have it", async () => {
await sut.migrate(helper);
// Global data
expect(helper.set).toHaveBeenCalledWith("global", {
// no longer has vault timeout data
otherStuff: "otherStuff",
});
// User data
expect(helper.set).toHaveBeenCalledWith("user1", {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user2", {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user3", {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user4", {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user5", {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user6", {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user7", {
settings: {
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledTimes(8); // 7 users + 1 global
expect(helper.set).not.toHaveBeenCalledWith("user8", any());
});
it("should migrate data to state providers for defined accounts that have the data with an exception for the vault timeout", async () => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledWith("user1", VAULT_TIMEOUT, 30);
expect(helper.setToUser).toHaveBeenCalledWith("user1", VAULT_TIMEOUT_ACTION, "lock");
expect(helper.setToUser).toHaveBeenCalledWith("user2", VAULT_TIMEOUT, "never");
expect(helper.setToUser).toHaveBeenCalledWith("user2", VAULT_TIMEOUT_ACTION, "logOut");
expect(helper.setToUser).toHaveBeenCalledWith("user3", VAULT_TIMEOUT, "onRestart");
expect(helper.setToUser).toHaveBeenCalledWith("user3", VAULT_TIMEOUT_ACTION, "lock");
expect(helper.setToUser).toHaveBeenCalledWith("user4", VAULT_TIMEOUT, "onLocked");
expect(helper.setToUser).toHaveBeenCalledWith("user4", VAULT_TIMEOUT_ACTION, "logOut");
expect(helper.setToUser).toHaveBeenCalledWith("user5", VAULT_TIMEOUT, "onSleep");
expect(helper.setToUser).toHaveBeenCalledWith("user5", VAULT_TIMEOUT_ACTION, "lock");
expect(helper.setToUser).toHaveBeenCalledWith("user6", VAULT_TIMEOUT, "onIdle");
expect(helper.setToUser).toHaveBeenCalledWith("user6", VAULT_TIMEOUT_ACTION, "logOut");
// User7 has an undefined vault timeout, but we should still migrate it to "never"
// b/c the CLI doesn't have a vault timeout
expect(helper.setToUser).toHaveBeenCalledWith("user7", VAULT_TIMEOUT, "never");
// Note: we don't have to worry about not migrating the vault timeout action b/c each client
// has a default value for the vault timeout action when it is retrieved via the vault timeout settings svc.
expect(helper.setToUser).not.toHaveBeenCalledWith("user7", VAULT_TIMEOUT_ACTION, any());
// Expect that we didn't migrate anything to user 8 b/c it doesn't exist
expect(helper.setToUser).not.toHaveBeenCalledWith("user8", VAULT_TIMEOUT, any());
expect(helper.setToUser).not.toHaveBeenCalledWith("user8", VAULT_TIMEOUT_ACTION, any());
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(true), 62, "general", ClientType.Cli);
sut = new VaultTimeoutSettingsServiceStateProviderMigrator(61, 62);
});
it("should null out newly migrated entries in state provider framework", async () => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledWith("user1", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user1", VAULT_TIMEOUT_ACTION, null);
expect(helper.setToUser).toHaveBeenCalledWith("user2", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user2", VAULT_TIMEOUT_ACTION, null);
expect(helper.setToUser).toHaveBeenCalledWith("user3", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user3", VAULT_TIMEOUT_ACTION, null);
expect(helper.setToUser).toHaveBeenCalledWith("user4", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user4", VAULT_TIMEOUT_ACTION, null);
expect(helper.setToUser).toHaveBeenCalledWith("user5", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user5", VAULT_TIMEOUT_ACTION, null);
expect(helper.setToUser).toHaveBeenCalledWith("user6", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user6", VAULT_TIMEOUT_ACTION, null);
expect(helper.setToUser).toHaveBeenCalledWith("user7", VAULT_TIMEOUT, null);
expect(helper.setToUser).toHaveBeenCalledWith("user7", VAULT_TIMEOUT_ACTION, null);
});
it("should add back data to all accounts that had migrated data (only user 1)", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledWith("user1", {
settings: {
vaultTimeout: 30,
vaultTimeoutAction: "lock",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user2", {
settings: {
vaultTimeout: null,
vaultTimeoutAction: "logOut",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user3", {
settings: {
vaultTimeout: -1, // onRestart
vaultTimeoutAction: "lock",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user4", {
settings: {
vaultTimeout: -2, // onLocked
vaultTimeoutAction: "logOut",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user5", {
settings: {
vaultTimeout: -3, // onSleep
vaultTimeoutAction: "lock",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user6", {
settings: {
vaultTimeout: -4, // onIdle
vaultTimeoutAction: "logOut",
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user7", {
settings: {
vaultTimeout: null,
// vaultTimeoutAction: null, // not migrated
otherStuff: "otherStuff",
},
otherStuff: "otherStuff",
});
});
it("should not add back the global vault timeout data", async () => {
await sut.rollback(helper);
expect(helper.set).not.toHaveBeenCalledWith("global", any());
});
it("should not add data back if data wasn't migrated or acct doesn't exist", async () => {
await sut.rollback(helper);
// no data to add back for user8 (no acct)
expect(helper.set).not.toHaveBeenCalledWith("user8", any());
});
});
});

View File

@@ -0,0 +1,174 @@
import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper";
import { Migrator } from "../migrator";
// Types to represent data as it is stored in JSON
type ExpectedAccountType = {
settings?: {
vaultTimeout?: number;
vaultTimeoutAction?: string;
};
};
type ExpectedGlobalType = {
vaultTimeout?: number;
vaultTimeoutAction?: string;
};
const VAULT_TIMEOUT_SETTINGS_STATE_DEF_LIKE: StateDefinitionLike = {
name: "vaultTimeoutSettings",
};
export const VAULT_TIMEOUT: KeyDefinitionLike = {
key: "vaultTimeout", // matches KeyDefinition.key
stateDefinition: VAULT_TIMEOUT_SETTINGS_STATE_DEF_LIKE,
};
export const VAULT_TIMEOUT_ACTION: KeyDefinitionLike = {
key: "vaultTimeoutAction", // matches KeyDefinition.key
stateDefinition: VAULT_TIMEOUT_SETTINGS_STATE_DEF_LIKE,
};
// Migrations are supposed to be frozen so we have to copy the type here.
export type VaultTimeout =
| number // 0 for immediately; otherwise positive numbers
| "never" // null
| "onRestart" // -1
| "onLocked" // -2
| "onSleep" // -3
| "onIdle"; // -4
// Define mapping of old values to new values for migration purposes
const vaultTimeoutTypeMigrateRecord: Record<any, VaultTimeout> = {
null: "never",
"-1": "onRestart",
"-2": "onLocked",
"-3": "onSleep",
"-4": "onIdle",
};
// define mapping of new values to old values for rollback purposes
const vaultTimeoutTypeRollbackRecord: Record<VaultTimeout, any> = {
never: null,
onRestart: -1,
onLocked: -2,
onSleep: -3,
onIdle: -4,
};
export enum ClientType {
Web = "web",
Browser = "browser",
Desktop = "desktop",
Cli = "cli",
}
export class VaultTimeoutSettingsServiceStateProviderMigrator extends Migrator<61, 62> {
async migrate(helper: MigrationHelper): Promise<void> {
const globalData = await helper.get<ExpectedGlobalType>("global");
const accounts = await helper.getAccounts<ExpectedAccountType>();
async function migrateAccount(
userId: string,
account: ExpectedAccountType | undefined,
): Promise<void> {
let updatedAccount = false;
// Migrate vault timeout
let existingVaultTimeout = account?.settings?.vaultTimeout;
if (helper.clientType === ClientType.Cli && existingVaultTimeout === undefined) {
// The CLI does not set a vault timeout by default so we need to set it to null
// so that the migration can migrate null to "never" as the CLI does not have a vault timeout.
existingVaultTimeout = null;
}
if (existingVaultTimeout !== undefined) {
// check undefined so that we allow null values (previously meant never timeout)
// Only migrate data that exists
if (existingVaultTimeout === null || existingVaultTimeout < 0) {
// Map null or negative values to new string values
const newVaultTimeout = vaultTimeoutTypeMigrateRecord[existingVaultTimeout];
await helper.setToUser(userId, VAULT_TIMEOUT, newVaultTimeout);
} else {
// Persist positive numbers as is
await helper.setToUser(userId, VAULT_TIMEOUT, existingVaultTimeout);
}
delete account?.settings?.vaultTimeout;
updatedAccount = true;
}
// Migrate vault timeout action
const existingVaultTimeoutAction = account?.settings?.vaultTimeoutAction;
if (existingVaultTimeoutAction != null) {
// Only migrate data that exists
await helper.setToUser(userId, VAULT_TIMEOUT_ACTION, existingVaultTimeoutAction);
delete account?.settings?.vaultTimeoutAction;
updatedAccount = true;
}
// Note: we are explicitly not worrying about mapping over the global fallback vault timeout / action
// into the new state provider framework. It was originally a fallback but hasn't been used for years
// so this migration will clean up the global properties fully.
if (updatedAccount) {
// Save the migrated account only if it was updated
await helper.set(userId, account);
}
}
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
// Delete global data
delete globalData?.vaultTimeout;
delete globalData?.vaultTimeoutAction;
await helper.set("global", globalData);
}
async rollback(helper: MigrationHelper): Promise<void> {
const accounts = await helper.getAccounts<ExpectedAccountType>();
async function rollbackAccount(userId: string, account: ExpectedAccountType): Promise<void> {
let updatedLegacyAccount = false;
// Rollback vault timeout
const migratedVaultTimeout = await helper.getFromUser<VaultTimeout>(userId, VAULT_TIMEOUT);
if (account?.settings && migratedVaultTimeout != null) {
if (typeof migratedVaultTimeout === "string") {
// Map new string values back to old values
account.settings.vaultTimeout = vaultTimeoutTypeRollbackRecord[migratedVaultTimeout];
} else {
// persist numbers as is
account.settings.vaultTimeout = migratedVaultTimeout;
}
updatedLegacyAccount = true;
}
await helper.setToUser(userId, VAULT_TIMEOUT, null);
// Rollback vault timeout action
const migratedVaultTimeoutAction = await helper.getFromUser<string>(
userId,
VAULT_TIMEOUT_ACTION,
);
if (account?.settings && migratedVaultTimeoutAction != null) {
account.settings.vaultTimeoutAction = migratedVaultTimeoutAction;
updatedLegacyAccount = true;
}
await helper.setToUser(userId, VAULT_TIMEOUT_ACTION, null);
if (updatedLegacyAccount) {
await helper.set(userId, account);
}
}
await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]);
}
}