1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-06-30 11:15:36 +02:00

[PM-5468] Ensure prototypes available on memory stored objects (#7399)

* Hide account switcher in addEdit generator

* Handle AddEditCipher deserialization

* Opaque types are not serializable

* Better handle jsonification of login uris

* Ensure we don't overwrite original with clone

* Ensure cipherView prototype is always restored if it exists
This commit is contained in:
Matt Gibson 2024-01-02 10:46:45 -05:00 committed by GitHub
parent 90b794c74d
commit a682f2a0ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 51 additions and 17 deletions

View File

@ -5,7 +5,7 @@
<ng-content select=".center"></ng-content> <ng-content select=".center"></ng-content>
<div class="right"> <div class="right">
<ng-content select=".right"></ng-content> <ng-content select=".right"></ng-content>
<ng-container *ngIf="authedAccounts$ | async"> <ng-container *ngIf="(authedAccounts$ | async) && !hideAccountSwitcher">
<app-current-account></app-current-account> <app-current-account></app-current-account>
</ng-container> </ng-container>
</div> </div>

View File

@ -12,6 +12,7 @@ import { flagEnabled } from "../flags";
}) })
export class HeaderComponent { export class HeaderComponent {
@Input() noTheme = false; @Input() noTheme = false;
@Input() hideAccountSwitcher = false;
authedAccounts$: Observable<boolean>; authedAccounts$: Observable<boolean>;
constructor(accountService: AccountService) { constructor(accountService: AccountService) {
this.authedAccounts$ = accountService.accounts$.pipe( this.authedAccounts$ = accountService.accounts$.pipe(

View File

@ -1,4 +1,4 @@
<app-header> <app-header [hideAccountSwitcher]="comingFromAddEdit">
<div class="left"> <div class="left">
<app-pop-out [show]="!comingFromAddEdit"></app-pop-out> <app-pop-out [show]="!comingFromAddEdit"></app-pop-out>
<button type="button" (click)="close()" *ngIf="comingFromAddEdit"> <button type="button" (click)="close()" *ngIf="comingFromAddEdit">

View File

@ -27,6 +27,7 @@ import { CollectionData } from "../../../vault/models/data/collection.data";
import { FolderData } from "../../../vault/models/data/folder.data"; import { FolderData } from "../../../vault/models/data/folder.data";
import { CipherView } from "../../../vault/models/view/cipher.view"; import { CipherView } from "../../../vault/models/view/cipher.view";
import { CollectionView } from "../../../vault/models/view/collection.view"; import { CollectionView } from "../../../vault/models/view/collection.view";
import { AddEditCipherInfo } from "../../../vault/types/add-edit-cipher-info";
import { KdfType } from "../../enums"; import { KdfType } from "../../enums";
import { Utils } from "../../misc/utils"; import { Utils } from "../../misc/utils";
import { ServerConfigData } from "../../models/data/server-config.data"; import { ServerConfigData } from "../../models/data/server-config.data";
@ -101,10 +102,23 @@ export class AccountData {
GeneratedPasswordHistory[], GeneratedPasswordHistory[],
GeneratedPasswordHistory[] GeneratedPasswordHistory[]
> = new EncryptionPair<GeneratedPasswordHistory[], GeneratedPasswordHistory[]>(); > = new EncryptionPair<GeneratedPasswordHistory[], GeneratedPasswordHistory[]>();
addEditCipherInfo?: any; addEditCipherInfo?: AddEditCipherInfo;
eventCollection?: EventData[]; eventCollection?: EventData[];
organizations?: { [id: string]: OrganizationData }; organizations?: { [id: string]: OrganizationData };
providers?: { [id: string]: ProviderData }; providers?: { [id: string]: ProviderData };
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,
},
});
}
} }
export class AccountKeys { export class AccountKeys {
@ -152,7 +166,7 @@ export class AccountKeys {
if (obj == null) { if (obj == null) {
return null; return null;
} }
return Object.assign(new AccountKeys(), { return Object.assign(new AccountKeys(), obj, {
userKey: SymmetricCryptoKey.fromJSON(obj?.userKey), userKey: SymmetricCryptoKey.fromJSON(obj?.userKey),
masterKey: SymmetricCryptoKey.fromJSON(obj?.masterKey), masterKey: SymmetricCryptoKey.fromJSON(obj?.masterKey),
deviceKey: obj?.deviceKey, deviceKey: obj?.deviceKey,
@ -451,6 +465,7 @@ export class Account {
return Object.assign(new Account({}), json, { return Object.assign(new Account({}), json, {
keys: AccountKeys.fromJSON(json?.keys), keys: AccountKeys.fromJSON(json?.keys),
data: AccountData.fromJSON(json?.data),
profile: AccountProfile.fromJSON(json?.profile), profile: AccountProfile.fromJSON(json?.profile),
settings: AccountSettings.fromJSON(json?.settings), settings: AccountSettings.fromJSON(json?.settings),
tokens: AccountTokens.fromJSON(json?.tokens), tokens: AccountTokens.fromJSON(json?.tokens),

View File

@ -40,7 +40,7 @@ export class EncString implements Encrypted {
} }
toJSON() { toJSON() {
return this.encryptedString; return this.encryptedString as string;
} }
static fromJSON(obj: Jsonify<EncString>): EncString { static fromJSON(obj: Jsonify<EncString>): EncString {

View File

@ -31,8 +31,8 @@ export class MemoryStorageService extends AbstractMemoryStorageService {
} }
// TODO: Remove once foreground/background contexts are separated in browser // TODO: Remove once foreground/background contexts are separated in browser
// Needed to ensure ownership of all memory by the context running the storage service // Needed to ensure ownership of all memory by the context running the storage service
obj = structuredClone(obj); const toStore = structuredClone(obj);
this.store.set(key, obj); this.store.set(key, toStore);
this.updatesSubject.next({ key, updateType: "save" }); this.updatesSubject.next({ key, updateType: "save" });
return Promise.resolve(); return Promise.resolve();
} }

View File

@ -280,9 +280,20 @@ export class StateService<
} }
async getAddEditCipherInfo(options?: StorageOptions): Promise<AddEditCipherInfo> { async getAddEditCipherInfo(options?: StorageOptions): Promise<AddEditCipherInfo> {
return ( const account = await this.getAccount(
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) this.reconcileOptions(options, await this.defaultInMemoryOptions()),
)?.data?.addEditCipherInfo; );
// 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> { async setAddEditCipherInfo(value: AddEditCipherInfo, options?: StorageOptions): Promise<void> {

View File

@ -1,8 +1,7 @@
import { Jsonify } from "type-fest";
import { View } from "../../../models/view/view"; import { View } from "../../../models/view/view";
import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface"; import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface";
import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; import { InitializerKey } from "../../../platform/services/cryptography/initializer-key";
import { DeepJsonify } from "../../../types/deep-jsonify";
import { LinkedIdType } from "../../enums"; import { LinkedIdType } from "../../enums";
import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
import { CipherType } from "../../enums/cipher-type"; import { CipherType } from "../../enums/cipher-type";
@ -141,7 +140,16 @@ export class CipherView implements View, InitializerMetadata {
return this.linkedFieldOptions.get(id)?.i18nKey; return this.linkedFieldOptions.get(id)?.i18nKey;
} }
static fromJSON(obj: Partial<Jsonify<CipherView>>): CipherView { // This is used as a marker to indicate that the cipher view object still has its prototype
toJSON() {
return this;
}
static fromJSON(obj: Partial<DeepJsonify<CipherView>>): CipherView {
if (obj == null) {
return null;
}
const view = new CipherView(); const view = new CipherView();
const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate);
const deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate); const deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate);

View File

@ -1,6 +1,5 @@
import { Jsonify } from "type-fest";
import { Utils } from "../../../platform/misc/utils"; import { Utils } from "../../../platform/misc/utils";
import { DeepJsonify } from "../../../types/deep-jsonify";
import { LoginLinkedId as LinkedId, UriMatchType } from "../../enums"; import { LoginLinkedId as LinkedId, UriMatchType } from "../../enums";
import { linkedFieldOption } from "../../linked-field-option.decorator"; import { linkedFieldOption } from "../../linked-field-option.decorator";
import { Login } from "../domain/login"; import { Login } from "../domain/login";
@ -81,10 +80,10 @@ export class LoginView extends ItemView {
return this.uris.some((uri) => uri.matchesUri(targetUri, equivalentDomains, defaultUriMatch)); return this.uris.some((uri) => uri.matchesUri(targetUri, equivalentDomains, defaultUriMatch));
} }
static fromJSON(obj: Partial<Jsonify<LoginView>>): LoginView { static fromJSON(obj: Partial<DeepJsonify<LoginView>>): LoginView {
const passwordRevisionDate = const passwordRevisionDate =
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
const uris = obj.uris.map((uri: any) => LoginUriView.fromJSON(uri)); const uris = obj.uris.map((uri) => LoginUriView.fromJSON(uri));
const fido2Credentials = obj.fido2Credentials?.map((key) => Fido2CredentialView.fromJSON(key)); const fido2Credentials = obj.fido2Credentials?.map((key) => Fido2CredentialView.fromJSON(key));
return Object.assign(new LoginView(), obj, { return Object.assign(new LoginView(), obj, {