mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
[PM-5273] Migrate state in CipherService (#8314)
* PM-5273 Initial migration work for localData
* PM-5273 Encrypted and Decrypted ciphers migration to state provider
* pm-5273 Update references
* pm5273 Ensure prototype on cipher
* PM-5273 Add CipherId
* PM-5273 Remove migrated methods and updated references
* pm-5273 Fix versions
* PM-5273 Added missing options
* Conflict resolution
* Revert "Conflict resolution"
This reverts commit 0c0c2039ed.
* PM-5273 Fix PR comments
* Pm-5273 Fix comments
* PM-5273 Changed decryptedCiphers to use ActiveUserState
* PM-5273 Fix tests
* PM-5273 Fix pr comments
This commit is contained in:
@@ -53,6 +53,7 @@ import { DeviceTrustCryptoServiceStateProviderMigrator } from "./migrations/53-m
|
||||
import { SendMigrator } from "./migrations/54-move-encrypted-sends";
|
||||
import { MoveMasterKeyStateToProviderMigrator } from "./migrations/55-move-master-key-state-to-provider";
|
||||
import { AuthRequestMigrator } from "./migrations/56-move-auth-requests";
|
||||
import { CipherServiceMigrator } from "./migrations/57-move-cipher-service-to-state-provider";
|
||||
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
|
||||
import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account";
|
||||
import { MoveStateVersionMigrator } from "./migrations/8-move-state-version";
|
||||
@@ -60,8 +61,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting
|
||||
import { MinVersionMigrator } from "./migrations/min-version";
|
||||
|
||||
export const MIN_VERSION = 3;
|
||||
export const CURRENT_VERSION = 56;
|
||||
|
||||
export const CURRENT_VERSION = 57;
|
||||
export type MinVersion = typeof MIN_VERSION;
|
||||
|
||||
export function createMigrationBuilder() {
|
||||
@@ -119,7 +119,8 @@ export function createMigrationBuilder() {
|
||||
.with(DeviceTrustCryptoServiceStateProviderMigrator, 52, 53)
|
||||
.with(SendMigrator, 53, 54)
|
||||
.with(MoveMasterKeyStateToProviderMigrator, 54, 55)
|
||||
.with(AuthRequestMigrator, 55, CURRENT_VERSION);
|
||||
.with(AuthRequestMigrator, 55, 56)
|
||||
.with(CipherServiceMigrator, 56, CURRENT_VERSION);
|
||||
}
|
||||
|
||||
export async function currentVersion(
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
import { MockProxy, any } from "jest-mock-extended";
|
||||
|
||||
import { MigrationHelper } from "../migration-helper";
|
||||
import { mockMigrationHelper } from "../migration-helper.spec";
|
||||
|
||||
import {
|
||||
CIPHERS_DISK,
|
||||
CIPHERS_DISK_LOCAL,
|
||||
CipherServiceMigrator,
|
||||
} from "./57-move-cipher-service-to-state-provider";
|
||||
|
||||
function exampleJSON() {
|
||||
return {
|
||||
global: {
|
||||
otherStuff: "otherStuff1",
|
||||
},
|
||||
authenticatedAccounts: ["user1", "user2"],
|
||||
user1: {
|
||||
data: {
|
||||
localData: {
|
||||
"6865ba55-7966-4d63-b743-b12000d49631": {
|
||||
lastUsedDate: 1708950970632,
|
||||
},
|
||||
"f895f099-6739-4cca-9d61-b12200d04bfa": {
|
||||
lastUsedDate: 1709031916943,
|
||||
},
|
||||
},
|
||||
ciphers: {
|
||||
"cipher-id-10": {
|
||||
id: "cipher-id-10",
|
||||
},
|
||||
"cipher-id-11": {
|
||||
id: "cipher-id-11",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
user2: {
|
||||
data: {
|
||||
otherStuff: "otherStuff5",
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function rollbackJSON() {
|
||||
return {
|
||||
user_user1_ciphersLocal_localData: {
|
||||
"6865ba55-7966-4d63-b743-b12000d49631": {
|
||||
lastUsedDate: 1708950970632,
|
||||
},
|
||||
"f895f099-6739-4cca-9d61-b12200d04bfa": {
|
||||
lastUsedDate: 1709031916943,
|
||||
},
|
||||
},
|
||||
user_user1_ciphers_ciphers: {
|
||||
"cipher-id-10": {
|
||||
id: "cipher-id-10",
|
||||
},
|
||||
"cipher-id-11": {
|
||||
id: "cipher-id-11",
|
||||
},
|
||||
},
|
||||
global: {
|
||||
otherStuff: "otherStuff1",
|
||||
},
|
||||
authenticatedAccounts: ["user1", "user2"],
|
||||
user1: {
|
||||
data: {},
|
||||
},
|
||||
user2: {
|
||||
data: {
|
||||
localData: {
|
||||
otherStuff: "otherStuff3",
|
||||
},
|
||||
ciphers: {
|
||||
otherStuff: "otherStuff4",
|
||||
},
|
||||
otherStuff: "otherStuff5",
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("CipherServiceMigrator", () => {
|
||||
let helper: MockProxy<MigrationHelper>;
|
||||
let sut: CipherServiceMigrator;
|
||||
|
||||
describe("migrate", () => {
|
||||
beforeEach(() => {
|
||||
helper = mockMigrationHelper(exampleJSON(), 56);
|
||||
sut = new CipherServiceMigrator(56, 57);
|
||||
});
|
||||
|
||||
it("should remove local data and ciphers from all accounts", async () => {
|
||||
await sut.migrate(helper);
|
||||
expect(helper.set).toHaveBeenCalledWith("user1", {
|
||||
data: {},
|
||||
});
|
||||
});
|
||||
|
||||
it("should migrate localData and ciphers to state provider for accounts that have the data", async () => {
|
||||
await sut.migrate(helper);
|
||||
|
||||
expect(helper.setToUser).toHaveBeenCalledWith("user1", CIPHERS_DISK_LOCAL, {
|
||||
"6865ba55-7966-4d63-b743-b12000d49631": {
|
||||
lastUsedDate: 1708950970632,
|
||||
},
|
||||
"f895f099-6739-4cca-9d61-b12200d04bfa": {
|
||||
lastUsedDate: 1709031916943,
|
||||
},
|
||||
});
|
||||
expect(helper.setToUser).toHaveBeenCalledWith("user1", CIPHERS_DISK, {
|
||||
"cipher-id-10": {
|
||||
id: "cipher-id-10",
|
||||
},
|
||||
"cipher-id-11": {
|
||||
id: "cipher-id-11",
|
||||
},
|
||||
});
|
||||
|
||||
expect(helper.setToUser).not.toHaveBeenCalledWith("user2", CIPHERS_DISK_LOCAL, any());
|
||||
expect(helper.setToUser).not.toHaveBeenCalledWith("user2", CIPHERS_DISK, any());
|
||||
});
|
||||
});
|
||||
|
||||
describe("rollback", () => {
|
||||
beforeEach(() => {
|
||||
helper = mockMigrationHelper(rollbackJSON(), 57);
|
||||
sut = new CipherServiceMigrator(56, 57);
|
||||
});
|
||||
|
||||
it.each(["user1", "user2"])("should null out new values", async (userId) => {
|
||||
await sut.rollback(helper);
|
||||
expect(helper.setToUser).toHaveBeenCalledWith(userId, CIPHERS_DISK_LOCAL, null);
|
||||
expect(helper.setToUser).toHaveBeenCalledWith(userId, CIPHERS_DISK, null);
|
||||
});
|
||||
|
||||
it("should add back localData and ciphers to all accounts", async () => {
|
||||
await sut.rollback(helper);
|
||||
|
||||
expect(helper.set).toHaveBeenCalledWith("user1", {
|
||||
data: {
|
||||
localData: {
|
||||
"6865ba55-7966-4d63-b743-b12000d49631": {
|
||||
lastUsedDate: 1708950970632,
|
||||
},
|
||||
"f895f099-6739-4cca-9d61-b12200d04bfa": {
|
||||
lastUsedDate: 1709031916943,
|
||||
},
|
||||
},
|
||||
ciphers: {
|
||||
"cipher-id-10": {
|
||||
id: "cipher-id-10",
|
||||
},
|
||||
"cipher-id-11": {
|
||||
id: "cipher-id-11",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should not add data back if data wasn't migrated or acct doesn't exist", async () => {
|
||||
await sut.rollback(helper);
|
||||
|
||||
expect(helper.set).not.toHaveBeenCalledWith("user2", any());
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
|
||||
import { Migrator } from "../migrator";
|
||||
|
||||
type ExpectedAccountType = {
|
||||
data: {
|
||||
localData?: unknown;
|
||||
ciphers?: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
export const CIPHERS_DISK_LOCAL: KeyDefinitionLike = {
|
||||
key: "localData",
|
||||
stateDefinition: {
|
||||
name: "ciphersLocal",
|
||||
},
|
||||
};
|
||||
|
||||
export const CIPHERS_DISK: KeyDefinitionLike = {
|
||||
key: "ciphers",
|
||||
stateDefinition: {
|
||||
name: "ciphers",
|
||||
},
|
||||
};
|
||||
|
||||
export class CipherServiceMigrator extends Migrator<56, 57> {
|
||||
async migrate(helper: MigrationHelper): Promise<void> {
|
||||
const accounts = await helper.getAccounts<ExpectedAccountType>();
|
||||
async function migrateAccount(userId: string, account: ExpectedAccountType): Promise<void> {
|
||||
let updatedAccount = false;
|
||||
|
||||
//Migrate localData
|
||||
const localData = account?.data?.localData;
|
||||
if (localData != null) {
|
||||
await helper.setToUser(userId, CIPHERS_DISK_LOCAL, localData);
|
||||
delete account.data.localData;
|
||||
updatedAccount = true;
|
||||
}
|
||||
|
||||
//Migrate ciphers
|
||||
const ciphers = account?.data?.ciphers;
|
||||
if (ciphers != null) {
|
||||
await helper.setToUser(userId, CIPHERS_DISK, ciphers);
|
||||
delete account.data.ciphers;
|
||||
updatedAccount = true;
|
||||
}
|
||||
|
||||
if (updatedAccount) {
|
||||
await helper.set(userId, account);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
|
||||
}
|
||||
|
||||
async rollback(helper: MigrationHelper): Promise<void> {
|
||||
const accounts = await helper.getAccounts<ExpectedAccountType>();
|
||||
async function rollbackAccount(userId: string, account: ExpectedAccountType): Promise<void> {
|
||||
//rollback localData
|
||||
const localData = await helper.getFromUser(userId, CIPHERS_DISK_LOCAL);
|
||||
|
||||
if (account.data && localData != null) {
|
||||
account.data.localData = localData;
|
||||
await helper.set(userId, account);
|
||||
}
|
||||
await helper.setToUser(userId, CIPHERS_DISK_LOCAL, null);
|
||||
|
||||
//rollback ciphers
|
||||
const ciphers = await helper.getFromUser(userId, CIPHERS_DISK);
|
||||
|
||||
if (account.data && ciphers != null) {
|
||||
account.data.ciphers = ciphers;
|
||||
await helper.set(userId, account);
|
||||
}
|
||||
await helper.setToUser(userId, CIPHERS_DISK, null);
|
||||
}
|
||||
|
||||
await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user