1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-21 10: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:
Carlos Gonçalves
2024-04-16 17:37:03 +01:00
committed by GitHub
parent 62ed7e5abc
commit 06acdefa91
29 changed files with 525 additions and 305 deletions

View File

@@ -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());
});
});
});

View File

@@ -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))]);
}
}