1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 08:13:42 +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

@@ -6,10 +6,6 @@ import { GeneratorOptions } from "../../tools/generator/generator-options";
import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password";
import { UsernameGeneratorOptions } from "../../tools/generator/username";
import { UserId } from "../../types/guid";
import { CipherData } from "../../vault/models/data/cipher.data";
import { LocalData } from "../../vault/models/data/local.data";
import { CipherView } from "../../vault/models/view/cipher.view";
import { AddEditCipherInfo } from "../../vault/types/add-edit-cipher-info";
import { KdfType } from "../enums";
import { Account } from "../models/domain/account";
import { EncString } from "../models/domain/enc-string";
@@ -38,8 +34,6 @@ export abstract class StateService<T extends Account = Account> {
clean: (options?: StorageOptions) => Promise<UserId>;
init: (initOptions?: InitOptions) => Promise<void>;
getAddEditCipherInfo: (options?: StorageOptions) => Promise<AddEditCipherInfo>;
setAddEditCipherInfo: (value: AddEditCipherInfo, options?: StorageOptions) => Promise<void>;
/**
* Gets the user's auto key
*/
@@ -104,8 +98,6 @@ export abstract class StateService<T extends Account = Account> {
* @deprecated For migration purposes only, use setUserKeyBiometric instead
*/
setCryptoMasterKeyBiometric: (value: BiometricKey, options?: StorageOptions) => Promise<void>;
getDecryptedCiphers: (options?: StorageOptions) => Promise<CipherView[]>;
setDecryptedCiphers: (value: CipherView[], options?: StorageOptions) => Promise<void>;
getDecryptedPasswordGenerationHistory: (
options?: StorageOptions,
) => Promise<GeneratedPasswordHistory[]>;
@@ -134,11 +126,6 @@ export abstract class StateService<T extends Account = Account> {
value: boolean,
options?: StorageOptions,
) => Promise<void>;
getEncryptedCiphers: (options?: StorageOptions) => Promise<{ [id: string]: CipherData }>;
setEncryptedCiphers: (
value: { [id: string]: CipherData },
options?: StorageOptions,
) => Promise<void>;
getEncryptedPasswordGenerationHistory: (
options?: StorageOptions,
) => Promise<GeneratedPasswordHistory[]>;
@@ -165,11 +152,6 @@ export abstract class StateService<T extends Account = Account> {
setLastActive: (value: number, options?: StorageOptions) => Promise<void>;
getLastSync: (options?: StorageOptions) => Promise<string>;
setLastSync: (value: string, options?: StorageOptions) => Promise<void>;
getLocalData: (options?: StorageOptions) => Promise<{ [cipherId: string]: LocalData }>;
setLocalData: (
value: { [cipherId: string]: LocalData },
options?: StorageOptions,
) => Promise<void>;
getMinimizeOnCopyToClipboard: (options?: StorageOptions) => Promise<boolean>;
setMinimizeOnCopyToClipboard: (value: boolean, options?: StorageOptions) => Promise<void>;
getOrganizationInvitation: (options?: StorageOptions) => Promise<any>;

View File

@@ -8,9 +8,6 @@ import {
} from "../../../tools/generator/password";
import { UsernameGeneratorOptions } from "../../../tools/generator/username/username-generation-options";
import { DeepJsonify } from "../../../types/deep-jsonify";
import { CipherData } from "../../../vault/models/data/cipher.data";
import { CipherView } from "../../../vault/models/view/cipher.view";
import { AddEditCipherInfo } from "../../../vault/types/add-edit-cipher-info";
import { KdfType } from "../../enums";
import { Utils } from "../../misc/utils";
@@ -61,28 +58,17 @@ export class DataEncryptionPair<TEncrypted, TDecrypted> {
}
export class AccountData {
ciphers?: DataEncryptionPair<CipherData, CipherView> = new DataEncryptionPair<
CipherData,
CipherView
>();
localData?: any;
passwordGenerationHistory?: EncryptionPair<
GeneratedPasswordHistory[],
GeneratedPasswordHistory[]
> = new EncryptionPair<GeneratedPasswordHistory[], GeneratedPasswordHistory[]>();
addEditCipherInfo?: AddEditCipherInfo;
static fromJSON(obj: DeepJsonify<AccountData>): AccountData {
if (obj == null) {
return null;
}
return Object.assign(new AccountData(), obj, {
addEditCipherInfo: {
cipher: CipherView.fromJSON(obj?.addEditCipherInfo?.cipher),
collectionIds: obj?.addEditCipherInfo?.collectionIds,
},
});
return Object.assign(new AccountData(), obj);
}
}

View File

@@ -9,10 +9,6 @@ import { GeneratorOptions } from "../../tools/generator/generator-options";
import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password";
import { UsernameGeneratorOptions } from "../../tools/generator/username";
import { UserId } from "../../types/guid";
import { CipherData } from "../../vault/models/data/cipher.data";
import { LocalData } from "../../vault/models/data/local.data";
import { CipherView } from "../../vault/models/view/cipher.view";
import { AddEditCipherInfo } from "../../vault/types/add-edit-cipher-info";
import { EnvironmentService } from "../abstractions/environment.service";
import { LogService } from "../abstractions/log.service";
import {
@@ -221,34 +217,6 @@ export class StateService<
return currentUser as UserId;
}
async getAddEditCipherInfo(options?: StorageOptions): Promise<AddEditCipherInfo> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
);
// ensure prototype on cipher
const raw = account?.data?.addEditCipherInfo;
return raw == null
? null
: {
cipher:
raw?.cipher.toJSON != null
? raw.cipher
: CipherView.fromJSON(raw?.cipher as Jsonify<CipherView>),
collectionIds: raw?.collectionIds,
};
}
async setAddEditCipherInfo(value: AddEditCipherInfo, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
);
account.data.addEditCipherInfo = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
);
}
/**
* user key when using the "never" option of vault timeout
*/
@@ -465,24 +433,6 @@ export class StateService<
await this.saveSecureStorageKey(partialKeys.biometricKey, value, options);
}
@withPrototypeForArrayMembers(CipherView, CipherView.fromJSON)
async getDecryptedCiphers(options?: StorageOptions): Promise<CipherView[]> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
)?.data?.ciphers?.decrypted;
}
async setDecryptedCiphers(value: CipherView[], options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
);
account.data.ciphers.decrypted = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
);
}
@withPrototypeForArrayMembers(GeneratedPasswordHistory)
async getDecryptedPasswordGenerationHistory(
options?: StorageOptions,
@@ -621,27 +571,6 @@ export class StateService<
);
}
@withPrototypeForObjectValues(CipherData)
async getEncryptedCiphers(options?: StorageOptions): Promise<{ [id: string]: CipherData }> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()))
)?.data?.ciphers?.encrypted;
}
async setEncryptedCiphers(
value: { [id: string]: CipherData },
options?: StorageOptions,
): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()),
);
account.data.ciphers.encrypted = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()),
);
}
/**
* @deprecated Use UserKey instead
*/
@@ -805,26 +734,6 @@ export class StateService<
);
}
async getLocalData(options?: StorageOptions): Promise<{ [cipherId: string]: LocalData }> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.data?.localData;
}
async setLocalData(
value: { [cipherId: string]: LocalData },
options?: StorageOptions,
): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()),
);
account.data.localData = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()),
);
}
async getMinimizeOnCopyToClipboard(options?: StorageOptions): Promise<boolean> {
return (
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
@@ -1510,50 +1419,3 @@ function withPrototypeForArrayMembers<T>(
};
};
}
function withPrototypeForObjectValues<T>(
valuesConstructor: new (...args: any[]) => T,
valuesConverter: (input: any) => T = (i) => i,
): (
target: any,
propertyKey: string | symbol,
descriptor: PropertyDescriptor,
) => { value: (...args: any[]) => Promise<{ [key: string]: T }> } {
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
const originalMethod = descriptor.value;
return {
value: function (...args: any[]) {
const originalResult: Promise<{ [key: string]: T }> = originalMethod.apply(this, args);
if (!Utils.isPromise(originalResult)) {
throw new Error(
`Error applying prototype to stored value -- result is not a promise for method ${String(
propertyKey,
)}`,
);
}
return originalResult.then((result) => {
if (result == null) {
return null;
} else {
for (const [key, val] of Object.entries(result)) {
result[key] =
val == null || val.constructor.name === valuesConstructor.prototype.constructor.name
? valuesConverter(val)
: valuesConverter(
Object.create(
valuesConstructor.prototype,
Object.getOwnPropertyDescriptors(val),
),
);
}
return result as { [key: string]: T };
}
});
},
};
};
}

View File

@@ -135,3 +135,8 @@ export const VAULT_SETTINGS_DISK = new StateDefinition("vaultSettings", "disk",
});
export const VAULT_BROWSER_MEMORY = new StateDefinition("vaultBrowser", "memory");
export const VAULT_SEARCH_MEMORY = new StateDefinition("vaultSearch", "memory");
export const CIPHERS_DISK = new StateDefinition("ciphers", "disk", { web: "memory" });
export const CIPHERS_DISK_LOCAL = new StateDefinition("ciphersLocal", "disk", {
web: "disk-local",
});
export const CIPHERS_MEMORY = new StateDefinition("ciphersMemory", "memory");