mirror of
https://github.com/bitwarden/browser.git
synced 2024-09-27 04:03:00 +02:00
Merge branch 'main' into ps/remove-browser-factory-methods
This commit is contained in:
commit
a16b7fa6ee
@ -3148,5 +3148,8 @@
|
|||||||
},
|
},
|
||||||
"errorAssigningTargetFolder": {
|
"errorAssigningTargetFolder": {
|
||||||
"message": "Error assigning target folder."
|
"message": "Error assigning target folder."
|
||||||
|
},
|
||||||
|
"new": {
|
||||||
|
"message": "New"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,7 @@ import { DefaultConfigService } from "@bitwarden/common/platform/services/config
|
|||||||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||||
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
|
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
|
||||||
|
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
|
||||||
import { Fido2AuthenticatorService } from "@bitwarden/common/platform/services/fido2/fido2-authenticator.service";
|
import { Fido2AuthenticatorService } from "@bitwarden/common/platform/services/fido2/fido2-authenticator.service";
|
||||||
import { Fido2ClientService } from "@bitwarden/common/platform/services/fido2/fido2-client.service";
|
import { Fido2ClientService } from "@bitwarden/common/platform/services/fido2/fido2-client.service";
|
||||||
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
|
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
|
||||||
@ -220,7 +221,6 @@ import { BrowserCryptoService } from "../platform/services/browser-crypto.servic
|
|||||||
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
||||||
import BrowserLocalStorageService from "../platform/services/browser-local-storage.service";
|
import BrowserLocalStorageService from "../platform/services/browser-local-storage.service";
|
||||||
import BrowserMemoryStorageService from "../platform/services/browser-memory-storage.service";
|
import BrowserMemoryStorageService from "../platform/services/browser-memory-storage.service";
|
||||||
import { BrowserMultithreadEncryptServiceImplementation } from "../platform/services/browser-multithread-encrypt.service.implementation";
|
|
||||||
import { BrowserScriptInjectorService } from "../platform/services/browser-script-injector.service";
|
import { BrowserScriptInjectorService } from "../platform/services/browser-script-injector.service";
|
||||||
import { DefaultBrowserStateService } from "../platform/services/default-browser-state.service";
|
import { DefaultBrowserStateService } from "../platform/services/default-browser-state.service";
|
||||||
import I18nService from "../platform/services/i18n.service";
|
import I18nService from "../platform/services/i18n.service";
|
||||||
@ -479,14 +479,14 @@ export default class MainBackground {
|
|||||||
storageServiceProvider,
|
storageServiceProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.encryptService = flagEnabled("multithreadDecryption")
|
this.encryptService =
|
||||||
? new BrowserMultithreadEncryptServiceImplementation(
|
flagEnabled("multithreadDecryption") && BrowserApi.isManifestVersion(2)
|
||||||
this.cryptoFunctionService,
|
? new MultithreadEncryptServiceImplementation(
|
||||||
this.logService,
|
this.cryptoFunctionService,
|
||||||
true,
|
this.logService,
|
||||||
this.offscreenDocumentService,
|
true,
|
||||||
)
|
)
|
||||||
: new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, true);
|
: new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, true);
|
||||||
|
|
||||||
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
||||||
storageServiceProvider,
|
storageServiceProvider,
|
||||||
|
@ -2,7 +2,6 @@ export type OffscreenDocumentExtensionMessage = {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
command: string;
|
command: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
decryptRequest?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type OffscreenExtensionMessageEventParams = {
|
type OffscreenExtensionMessageEventParams = {
|
||||||
@ -14,7 +13,6 @@ export type OffscreenDocumentExtensionMessageHandlers = {
|
|||||||
[key: string]: ({ message, sender }: OffscreenExtensionMessageEventParams) => any;
|
[key: string]: ({ message, sender }: OffscreenExtensionMessageEventParams) => any;
|
||||||
offscreenCopyToClipboard: ({ message }: OffscreenExtensionMessageEventParams) => any;
|
offscreenCopyToClipboard: ({ message }: OffscreenExtensionMessageEventParams) => any;
|
||||||
offscreenReadFromClipboard: () => any;
|
offscreenReadFromClipboard: () => any;
|
||||||
offscreenDecryptItems: ({ message }: OffscreenExtensionMessageEventParams) => Promise<string>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface OffscreenDocument {
|
export interface OffscreenDocument {
|
||||||
|
@ -1,25 +1,7 @@
|
|||||||
import { mock } from "jest-mock-extended";
|
|
||||||
|
|
||||||
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
|
|
||||||
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
|
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
|
||||||
|
|
||||||
import { flushPromises, sendExtensionRuntimeMessage } from "../../autofill/spec/testing-utils";
|
import { flushPromises, sendExtensionRuntimeMessage } from "../../autofill/spec/testing-utils";
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
import { BrowserApi } from "../browser/browser-api";
|
||||||
import BrowserClipboardService from "../services/browser-clipboard.service";
|
import BrowserClipboardService from "../services/browser-clipboard.service";
|
||||||
|
|
||||||
jest.mock(
|
|
||||||
"@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation",
|
|
||||||
() => ({
|
|
||||||
MultithreadEncryptServiceImplementation: class MultithreadEncryptServiceImplementation {
|
|
||||||
getDecryptedItemsFromWorker = async <T extends InitializerMetadata>(
|
|
||||||
items: Decryptable<T>[],
|
|
||||||
_key: SymmetricCryptoKey,
|
|
||||||
): Promise<string> => JSON.stringify(items);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
describe("OffscreenDocument", () => {
|
describe("OffscreenDocument", () => {
|
||||||
const browserApiMessageListenerSpy = jest.spyOn(BrowserApi, "messageListener");
|
const browserApiMessageListenerSpy = jest.spyOn(BrowserApi, "messageListener");
|
||||||
const browserClipboardServiceCopySpy = jest.spyOn(BrowserClipboardService, "copy");
|
const browserClipboardServiceCopySpy = jest.spyOn(BrowserClipboardService, "copy");
|
||||||
@ -78,37 +60,5 @@ describe("OffscreenDocument", () => {
|
|||||||
expect(browserClipboardServiceReadSpy).toHaveBeenCalledWith(window);
|
expect(browserClipboardServiceReadSpy).toHaveBeenCalledWith(window);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("handleOffscreenDecryptItems", () => {
|
|
||||||
it("returns an empty array as a string if the decrypt request is not present in the message", async () => {
|
|
||||||
let response: string | undefined;
|
|
||||||
sendExtensionRuntimeMessage(
|
|
||||||
{ command: "offscreenDecryptItems" },
|
|
||||||
mock<chrome.runtime.MessageSender>(),
|
|
||||||
(res: string) => (response = res),
|
|
||||||
);
|
|
||||||
await flushPromises();
|
|
||||||
|
|
||||||
expect(response).toBe("[]");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("decrypts the items and sends back the response as a string", async () => {
|
|
||||||
const items = [{ id: "test" }];
|
|
||||||
const key = { id: "test" };
|
|
||||||
const decryptRequest = JSON.stringify({ items, key });
|
|
||||||
let response: string | undefined;
|
|
||||||
|
|
||||||
sendExtensionRuntimeMessage(
|
|
||||||
{ command: "offscreenDecryptItems", decryptRequest },
|
|
||||||
mock<chrome.runtime.MessageSender>(),
|
|
||||||
(res: string) => {
|
|
||||||
response = res;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await flushPromises();
|
|
||||||
|
|
||||||
expect(response).toBe(JSON.stringify(items));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,35 +1,21 @@
|
|||||||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||||
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
|
|
||||||
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
|
||||||
|
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
import { BrowserApi } from "../browser/browser-api";
|
||||||
import BrowserClipboardService from "../services/browser-clipboard.service";
|
import BrowserClipboardService from "../services/browser-clipboard.service";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
OffscreenDocument as OffscreenDocumentInterface,
|
|
||||||
OffscreenDocumentExtensionMessage,
|
OffscreenDocumentExtensionMessage,
|
||||||
OffscreenDocumentExtensionMessageHandlers,
|
OffscreenDocumentExtensionMessageHandlers,
|
||||||
|
OffscreenDocument as OffscreenDocumentInterface,
|
||||||
} from "./abstractions/offscreen-document";
|
} from "./abstractions/offscreen-document";
|
||||||
|
|
||||||
class OffscreenDocument implements OffscreenDocumentInterface {
|
class OffscreenDocument implements OffscreenDocumentInterface {
|
||||||
private readonly consoleLogService: ConsoleLogService;
|
private consoleLogService: ConsoleLogService = new ConsoleLogService(false);
|
||||||
private encryptService: MultithreadEncryptServiceImplementation;
|
|
||||||
private readonly extensionMessageHandlers: OffscreenDocumentExtensionMessageHandlers = {
|
private readonly extensionMessageHandlers: OffscreenDocumentExtensionMessageHandlers = {
|
||||||
offscreenCopyToClipboard: ({ message }) => this.handleOffscreenCopyToClipboard(message),
|
offscreenCopyToClipboard: ({ message }) => this.handleOffscreenCopyToClipboard(message),
|
||||||
offscreenReadFromClipboard: () => this.handleOffscreenReadFromClipboard(),
|
offscreenReadFromClipboard: () => this.handleOffscreenReadFromClipboard(),
|
||||||
offscreenDecryptItems: ({ message }) => this.handleOffscreenDecryptItems(message),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
|
||||||
const cryptoFunctionService = new WebCryptoFunctionService(self);
|
|
||||||
this.consoleLogService = new ConsoleLogService(false);
|
|
||||||
this.encryptService = new MultithreadEncryptServiceImplementation(
|
|
||||||
cryptoFunctionService,
|
|
||||||
this.consoleLogService,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the offscreen document extension.
|
* Initializes the offscreen document extension.
|
||||||
*/
|
*/
|
||||||
@ -53,23 +39,6 @@ class OffscreenDocument implements OffscreenDocumentInterface {
|
|||||||
return await BrowserClipboardService.read(self);
|
return await BrowserClipboardService.read(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypts the items in the message using the encrypt service.
|
|
||||||
*
|
|
||||||
* @param message - The extension message containing the items to decrypt
|
|
||||||
*/
|
|
||||||
private async handleOffscreenDecryptItems(
|
|
||||||
message: OffscreenDocumentExtensionMessage,
|
|
||||||
): Promise<string> {
|
|
||||||
const { decryptRequest } = message;
|
|
||||||
if (!decryptRequest) {
|
|
||||||
return "[]";
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = JSON.parse(decryptRequest);
|
|
||||||
return await this.encryptService.getDecryptedItemsFromWorker(request.items, request.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the listener for extension messages.
|
* Sets up the listener for extension messages.
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,15 @@
|
|||||||
<ng-container *ngIf="show">
|
<ng-container *ngIf="show && !useRefreshVariant">
|
||||||
<button type="button" (click)="expand()" appA11yTitle="{{ 'popOutNewWindow' | i18n }}">
|
<button type="button" (click)="expand()" appA11yTitle="{{ 'popOutNewWindow' | i18n }}">
|
||||||
<i class="bwi bwi-external-link bwi-rotate-270 bwi-lg bwi-fw" aria-hidden="true"></i>
|
<i class="bwi bwi-external-link bwi-rotate-270 bwi-lg bwi-fw" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<ng-container *ngIf="show && useRefreshVariant">
|
||||||
|
<button
|
||||||
|
bitIconButton="bwi-popout"
|
||||||
|
size="small"
|
||||||
|
type="button"
|
||||||
|
appA11yTitle="{{ 'popOutNewWindow' | i18n }}"
|
||||||
|
[title]="'popOutNewWindow' | i18n"
|
||||||
|
(click)="expand()"
|
||||||
|
></button>
|
||||||
|
</ng-container>
|
||||||
|
@ -2,7 +2,10 @@ import { CommonModule } from "@angular/common";
|
|||||||
import { Component, Input, OnInit } from "@angular/core";
|
import { Component, Input, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
import { IconButtonModule } from "@bitwarden/components";
|
||||||
|
|
||||||
import BrowserPopupUtils from "../browser-popup-utils";
|
import BrowserPopupUtils from "../browser-popup-utils";
|
||||||
|
|
||||||
@ -10,14 +13,20 @@ import BrowserPopupUtils from "../browser-popup-utils";
|
|||||||
selector: "app-pop-out",
|
selector: "app-pop-out",
|
||||||
templateUrl: "pop-out.component.html",
|
templateUrl: "pop-out.component.html",
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, JslibModule],
|
imports: [CommonModule, JslibModule, IconButtonModule],
|
||||||
})
|
})
|
||||||
export class PopOutComponent implements OnInit {
|
export class PopOutComponent implements OnInit {
|
||||||
@Input() show = true;
|
@Input() show = true;
|
||||||
|
useRefreshVariant = false;
|
||||||
|
|
||||||
constructor(private platformUtilsService: PlatformUtilsService) {}
|
constructor(
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private configService: ConfigService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.useRefreshVariant = await this.configService.getFeatureFlag(FeatureFlag.ExtensionRefresh);
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
if (this.show) {
|
if (this.show) {
|
||||||
if (
|
if (
|
||||||
(BrowserPopupUtils.inSidebar(window) && this.platformUtilsService.isFirefox()) ||
|
(BrowserPopupUtils.inSidebar(window) && this.platformUtilsService.isFirefox()) ||
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
*ngIf="showBackButton"
|
*ngIf="showBackButton"
|
||||||
[title]="'back' | i18n"
|
[title]="'back' | i18n"
|
||||||
[ariaLabel]="'back' | i18n"
|
[ariaLabel]="'back' | i18n"
|
||||||
|
(click)="back()"
|
||||||
></button>
|
></button>
|
||||||
<h1 bitTypography="h3" class="!tw-mb-0.5 tw-text-headers">{{ pageTitle }}</h1>
|
<h1 bitTypography="h3" class="!tw-mb-0.5 tw-text-headers">{{ pageTitle }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
|
||||||
|
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
|
||||||
import { EncryptionType } from "@bitwarden/common/platform/enums";
|
|
||||||
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
|
|
||||||
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
|
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
|
||||||
import { InitializerKey } from "@bitwarden/common/platform/services/cryptography/initializer-key";
|
|
||||||
import { makeStaticByteArray } from "@bitwarden/common/spec";
|
|
||||||
|
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
|
||||||
import { OffscreenDocumentService } from "../offscreen-document/abstractions/offscreen-document";
|
|
||||||
|
|
||||||
import { BrowserMultithreadEncryptServiceImplementation } from "./browser-multithread-encrypt.service.implementation";
|
|
||||||
|
|
||||||
describe("BrowserMultithreadEncryptServiceImplementation", () => {
|
|
||||||
let cryptoFunctionServiceMock: MockProxy<CryptoFunctionService>;
|
|
||||||
let logServiceMock: MockProxy<LogService>;
|
|
||||||
let offscreenDocumentServiceMock: MockProxy<OffscreenDocumentService>;
|
|
||||||
let encryptService: BrowserMultithreadEncryptServiceImplementation;
|
|
||||||
const manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get");
|
|
||||||
const sendMessageWithResponseSpy = jest.spyOn(BrowserApi, "sendMessageWithResponse");
|
|
||||||
const encType = EncryptionType.AesCbc256_HmacSha256_B64;
|
|
||||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64, 100), encType);
|
|
||||||
const items: Decryptable<InitializerMetadata>[] = [
|
|
||||||
{
|
|
||||||
decrypt: jest.fn(),
|
|
||||||
initializerKey: InitializerKey.Cipher,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
cryptoFunctionServiceMock = mock<CryptoFunctionService>();
|
|
||||||
logServiceMock = mock<LogService>();
|
|
||||||
offscreenDocumentServiceMock = mock<OffscreenDocumentService>({
|
|
||||||
withDocument: jest.fn((_, __, callback) => callback() as any),
|
|
||||||
});
|
|
||||||
encryptService = new BrowserMultithreadEncryptServiceImplementation(
|
|
||||||
cryptoFunctionServiceMock,
|
|
||||||
logServiceMock,
|
|
||||||
false,
|
|
||||||
offscreenDocumentServiceMock,
|
|
||||||
);
|
|
||||||
manifestVersionSpy.mockReturnValue(3);
|
|
||||||
sendMessageWithResponseSpy.mockResolvedValue(JSON.stringify([]));
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("decrypts items using web workers if the chrome.offscreen API is not supported", async () => {
|
|
||||||
manifestVersionSpy.mockReturnValue(2);
|
|
||||||
|
|
||||||
await encryptService.decryptItems([], key);
|
|
||||||
|
|
||||||
expect(offscreenDocumentServiceMock.withDocument).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("decrypts items using the chrome.offscreen API if it is supported", async () => {
|
|
||||||
sendMessageWithResponseSpy.mockResolvedValue(JSON.stringify(items));
|
|
||||||
|
|
||||||
await encryptService.decryptItems(items, key);
|
|
||||||
|
|
||||||
expect(offscreenDocumentServiceMock.withDocument).toHaveBeenCalledWith(
|
|
||||||
[chrome.offscreen.Reason.WORKERS],
|
|
||||||
"Use web worker to decrypt items.",
|
|
||||||
expect.any(Function),
|
|
||||||
);
|
|
||||||
expect(BrowserApi.sendMessageWithResponse).toHaveBeenCalledWith("offscreenDecryptItems", {
|
|
||||||
decryptRequest: expect.any(String),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns an empty array if the passed items are not defined", async () => {
|
|
||||||
const result = await encryptService.decryptItems(null, key);
|
|
||||||
|
|
||||||
expect(result).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns an empty array if the offscreen document message returns an empty value", async () => {
|
|
||||||
sendMessageWithResponseSpy.mockResolvedValue("");
|
|
||||||
|
|
||||||
const result = await encryptService.decryptItems(items, key);
|
|
||||||
|
|
||||||
expect(result).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns an empty array if the offscreen document message returns an empty array", async () => {
|
|
||||||
sendMessageWithResponseSpy.mockResolvedValue("[]");
|
|
||||||
|
|
||||||
const result = await encryptService.decryptItems(items, key);
|
|
||||||
|
|
||||||
expect(result).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,91 +0,0 @@
|
|||||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
|
||||||
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
|
|
||||||
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
|
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
|
||||||
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
|
|
||||||
|
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
|
||||||
import { OffscreenDocumentService } from "../offscreen-document/abstractions/offscreen-document";
|
|
||||||
|
|
||||||
export class BrowserMultithreadEncryptServiceImplementation extends MultithreadEncryptServiceImplementation {
|
|
||||||
constructor(
|
|
||||||
cryptoFunctionService: CryptoFunctionService,
|
|
||||||
logService: LogService,
|
|
||||||
logMacFailures: boolean,
|
|
||||||
private offscreenDocumentService: OffscreenDocumentService,
|
|
||||||
) {
|
|
||||||
super(cryptoFunctionService, logService, logMacFailures);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles decryption of items, will use the offscreen document if supported.
|
|
||||||
*
|
|
||||||
* @param items - The items to decrypt.
|
|
||||||
* @param key - The key to use for decryption.
|
|
||||||
*/
|
|
||||||
async decryptItems<T extends InitializerMetadata>(
|
|
||||||
items: Decryptable<T>[],
|
|
||||||
key: SymmetricCryptoKey,
|
|
||||||
): Promise<T[]> {
|
|
||||||
if (!this.isOffscreenDocumentSupported()) {
|
|
||||||
return await super.decryptItems(items, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.decryptItemsInOffscreenDocument(items, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypts items using the offscreen document api.
|
|
||||||
*
|
|
||||||
* @param items - The items to decrypt.
|
|
||||||
* @param key - The key to use for decryption.
|
|
||||||
*/
|
|
||||||
private async decryptItemsInOffscreenDocument<T extends InitializerMetadata>(
|
|
||||||
items: Decryptable<T>[],
|
|
||||||
key: SymmetricCryptoKey,
|
|
||||||
): Promise<T[]> {
|
|
||||||
if (items == null || items.length < 1) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = {
|
|
||||||
id: Utils.newGuid(),
|
|
||||||
items: items,
|
|
||||||
key: key,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await this.offscreenDocumentService.withDocument(
|
|
||||||
[chrome.offscreen.Reason.WORKERS],
|
|
||||||
"Use web worker to decrypt items.",
|
|
||||||
async () => {
|
|
||||||
return (await BrowserApi.sendMessageWithResponse("offscreenDecryptItems", {
|
|
||||||
decryptRequest: JSON.stringify(request),
|
|
||||||
})) as string;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseItems = JSON.parse(response);
|
|
||||||
if (responseItems?.length < 1) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.initializeItems(responseItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the offscreen document api is supported.
|
|
||||||
*/
|
|
||||||
private isOffscreenDocumentSupported() {
|
|
||||||
return (
|
|
||||||
BrowserApi.isManifestVersion(3) &&
|
|
||||||
typeof chrome !== "undefined" &&
|
|
||||||
typeof chrome.offscreen !== "undefined"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +1,13 @@
|
|||||||
<h1>Vault V2 Extension Refresh</h1>
|
<popup-page>
|
||||||
|
<popup-header slot="header" [pageTitle]="'vault' | i18n">
|
||||||
|
<ng-container slot="end">
|
||||||
|
<!-- TODO PM-6826: add selectedVault query param -->
|
||||||
|
<a bitButton buttonType="primary" type="button" routerLink="/add-cipher">
|
||||||
|
<i class="bwi bwi-plus-f" aria-hidden="true"></i>
|
||||||
|
{{ "new" | i18n }}
|
||||||
|
</a>
|
||||||
|
<app-pop-out></app-pop-out>
|
||||||
|
<app-current-account></app-current-account>
|
||||||
|
</ng-container>
|
||||||
|
</popup-header>
|
||||||
|
</popup-page>
|
||||||
|
@ -19,36 +19,17 @@ export class MultithreadEncryptServiceImplementation extends EncryptServiceImple
|
|||||||
private clear$ = new Subject<void>();
|
private clear$ = new Subject<void>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypts items using a web worker if the environment supports it.
|
* Sends items to a web worker to decrypt them.
|
||||||
* Will fall back to the main thread if the window object is not available.
|
* This utilises multithreading to decrypt items faster without interrupting other operations (e.g. updating UI).
|
||||||
*/
|
*/
|
||||||
async decryptItems<T extends InitializerMetadata>(
|
async decryptItems<T extends InitializerMetadata>(
|
||||||
items: Decryptable<T>[],
|
items: Decryptable<T>[],
|
||||||
key: SymmetricCryptoKey,
|
key: SymmetricCryptoKey,
|
||||||
): Promise<T[]> {
|
): Promise<T[]> {
|
||||||
if (typeof window === "undefined") {
|
|
||||||
return super.decryptItems(items, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (items == null || items.length < 1) {
|
if (items == null || items.length < 1) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const decryptedItems = await this.getDecryptedItemsFromWorker(items, key);
|
|
||||||
const parsedItems = JSON.parse(decryptedItems);
|
|
||||||
|
|
||||||
return this.initializeItems(parsedItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends items to a web worker to decrypt them. This utilizes multithreading to decrypt items
|
|
||||||
* faster without interrupting other operations (e.g. updating UI). This method returns values
|
|
||||||
* prior to deserialization to support forwarding results to another party
|
|
||||||
*/
|
|
||||||
async getDecryptedItemsFromWorker<T extends InitializerMetadata>(
|
|
||||||
items: Decryptable<T>[],
|
|
||||||
key: SymmetricCryptoKey,
|
|
||||||
): Promise<string> {
|
|
||||||
this.logService.info("Starting decryption using multithreading");
|
this.logService.info("Starting decryption using multithreading");
|
||||||
|
|
||||||
this.worker ??= new Worker(
|
this.worker ??= new Worker(
|
||||||
@ -72,20 +53,19 @@ export class MultithreadEncryptServiceImplementation extends EncryptServiceImple
|
|||||||
return await firstValueFrom(
|
return await firstValueFrom(
|
||||||
fromEvent(this.worker, "message").pipe(
|
fromEvent(this.worker, "message").pipe(
|
||||||
filter((response: MessageEvent) => response.data?.id === request.id),
|
filter((response: MessageEvent) => response.data?.id === request.id),
|
||||||
map((response) => response.data.items),
|
map((response) => JSON.parse(response.data.items)),
|
||||||
|
map((items) =>
|
||||||
|
items.map((jsonItem: Jsonify<T>) => {
|
||||||
|
const initializer = getClassInitializer<T>(jsonItem.initializerKey);
|
||||||
|
return initializer(jsonItem);
|
||||||
|
}),
|
||||||
|
),
|
||||||
takeUntil(this.clear$),
|
takeUntil(this.clear$),
|
||||||
defaultIfEmpty("[]"),
|
defaultIfEmpty([]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initializeItems<T extends InitializerMetadata>(items: Jsonify<T>[]): T[] {
|
|
||||||
return items.map((jsonItem: Jsonify<T>) => {
|
|
||||||
const initializer = getClassInitializer<T>(jsonItem.initializerKey);
|
|
||||||
return initializer(jsonItem);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private clear() {
|
private clear() {
|
||||||
this.clear$.next();
|
this.clear$.next();
|
||||||
this.worker?.terminate();
|
this.worker?.terminate();
|
||||||
|
Loading…
Reference in New Issue
Block a user