From dd6f3d46cb986b91d4e261d9fa9a81f000424ed8 Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Mon, 8 Apr 2024 07:02:33 -0500 Subject: [PATCH] [PM-5189] Implementing a verification process to ensure we are receiving valid inline menu messages within the background script --- .../abstractions/overlay.background.ts | 1 + .../autofill/background/overlay.background.ts | 22 ++++++++++++-- .../abstractions/autofill-overlay-button.ts | 1 + .../abstractions/autofill-overlay-list.ts | 1 + .../pages/button/autofill-overlay-button.ts | 3 ++ .../pages/list/autofill-overlay-list.ts | 3 ++ .../message-connector/message-connector.ts | 12 ++++---- .../autofill-overlay-page-element.spec.ts | 1 + .../shared/autofill-overlay-page-element.ts | 10 ++++++- .../src/autofill/spec/autofill-mocks.ts | 2 ++ apps/browser/src/autofill/utils/index.ts | 29 ++++++++++--------- 11 files changed, 60 insertions(+), 25 deletions(-) diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 0b6516b3ae1..dd1bfca0ddf 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -48,6 +48,7 @@ type FocusedFieldData = { type OverlayBackgroundExtensionMessage = { command: string; + portKey?: string; tab?: chrome.tabs.Tab; sender?: string; details?: AutofillPageDetails; diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 24cdffc3636..d9f9269f839 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -27,6 +27,7 @@ import { openViewVaultItemPopout, } from "../../vault/popup/utils/vault-popout-window"; import { AutofillService } from "../services/abstractions/autofill.service"; +import { generateRandomChars } from "../utils"; import { AutofillOverlayElement, AutofillOverlayPort } from "../utils/autofill-overlay.enum"; import { LockedVaultPendingNotificationsData } from "./abstractions/notification.background"; @@ -56,6 +57,7 @@ class OverlayBackground implements OverlayBackgroundInterface { private userAuthStatus: AuthenticationStatus = AuthenticationStatus.LoggedOut; private overlayButtonPort: chrome.runtime.Port; private overlayListPort: chrome.runtime.Port; + private portKeyForTab: Record = {}; private focusedFieldData: FocusedFieldData; private isFieldCurrentlyFocused: boolean = false; private isFieldCurrentlyFilling: boolean = false; @@ -140,6 +142,10 @@ class OverlayBackground implements OverlayBackgroundInterface { this.subFrameOffsetsForTab[tabId].clear(); delete this.subFrameOffsetsForTab[tabId]; } + + if (this.portKeyForTab[tabId]) { + delete this.portKeyForTab[tabId]; + } } /** @@ -635,7 +641,7 @@ class OverlayBackground implements OverlayBackgroundInterface { */ private async getAuthStatus() { const formerAuthStatus = this.userAuthStatus; - this.userAuthStatus = await this.authService.getAuthStatus(); + this.userAuthStatus = await firstValueFrom(this.authService.activeAccountStatus$); if ( this.userAuthStatus !== formerAuthStatus && @@ -939,11 +945,15 @@ class OverlayBackground implements OverlayBackgroundInterface { const isOverlayListPort = port.name === AutofillOverlayPort.List; const isOverlayButtonPort = port.name === AutofillOverlayPort.Button; - if (!isOverlayListPort && !isOverlayButtonPort) { return; } + const tabId = port.sender.tab.id; + if (!this.portKeyForTab[tabId]) { + this.portKeyForTab[tabId] = generateRandomChars(12); + } + if (isOverlayListPort) { this.overlayListPort = port; } else { @@ -959,6 +969,7 @@ class OverlayBackground implements OverlayBackgroundInterface { translations: this.getTranslations(), ciphers: isOverlayListPort ? await this.getOverlayCipherData() : null, messageConnectorUrl: chrome.runtime.getURL("overlay/message-connector.html"), + portKey: this.portKeyForTab[tabId], }); void this.updateOverlayPosition( { @@ -980,7 +991,12 @@ class OverlayBackground implements OverlayBackgroundInterface { message: OverlayBackgroundExtensionMessage, port: chrome.runtime.Port, ) => { - const command = message?.command; + const tabId = port.sender.tab.id; + if (this.portKeyForTab[tabId] !== message?.portKey) { + return; + } + + const command = message.command; let handler: CallableFunction | undefined; if (port.name === AutofillOverlayPort.ButtonMessageConnector) { diff --git a/apps/browser/src/autofill/overlay/abstractions/autofill-overlay-button.ts b/apps/browser/src/autofill/overlay/abstractions/autofill-overlay-button.ts index 3fa34693d3a..b8164c6fe01 100644 --- a/apps/browser/src/autofill/overlay/abstractions/autofill-overlay-button.ts +++ b/apps/browser/src/autofill/overlay/abstractions/autofill-overlay-button.ts @@ -8,6 +8,7 @@ type InitAutofillOverlayButtonMessage = UpdateAuthStatusMessage & { styleSheetUrl: string; translations: Record; messageConnectorUrl: string; + portKey: string; }; type OverlayButtonWindowMessageHandlers = { diff --git a/apps/browser/src/autofill/overlay/abstractions/autofill-overlay-list.ts b/apps/browser/src/autofill/overlay/abstractions/autofill-overlay-list.ts index ce49d5c85c6..c5bb6698f79 100644 --- a/apps/browser/src/autofill/overlay/abstractions/autofill-overlay-list.ts +++ b/apps/browser/src/autofill/overlay/abstractions/autofill-overlay-list.ts @@ -15,6 +15,7 @@ type InitAutofillOverlayListMessage = OverlayListMessage & { translations: Record; ciphers?: OverlayCipherData[]; messageConnectorUrl: string; + portKey: string; }; type OverlayListWindowMessageHandlers = { diff --git a/apps/browser/src/autofill/overlay/pages/button/autofill-overlay-button.ts b/apps/browser/src/autofill/overlay/pages/button/autofill-overlay-button.ts index 57ea6f89d71..d226db6914a 100644 --- a/apps/browser/src/autofill/overlay/pages/button/autofill-overlay-button.ts +++ b/apps/browser/src/autofill/overlay/pages/button/autofill-overlay-button.ts @@ -47,18 +47,21 @@ class AutofillOverlayButton extends AutofillOverlayPageElement { * @param styleSheetUrl - The URL of the stylesheet to apply to the page * @param translations - The translations to apply to the page * @param messageConnectorUrl - The URL of the message connector to use + * @param portKey - Background generated key that allows the port to communicate with the background */ private async initAutofillOverlayButton({ authStatus, styleSheetUrl, translations, messageConnectorUrl, + portKey, }: InitAutofillOverlayButtonMessage) { const linkElement = await this.initOverlayPage( "button", styleSheetUrl, translations, messageConnectorUrl, + portKey, ); this.buttonElement.tabIndex = -1; this.buttonElement.type = "button"; diff --git a/apps/browser/src/autofill/overlay/pages/list/autofill-overlay-list.ts b/apps/browser/src/autofill/overlay/pages/list/autofill-overlay-list.ts index 4a5466ba3e8..a4ac103da9a 100644 --- a/apps/browser/src/autofill/overlay/pages/list/autofill-overlay-list.ts +++ b/apps/browser/src/autofill/overlay/pages/list/autofill-overlay-list.ts @@ -45,6 +45,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement { * @param authStatus - The current authentication status. * @param ciphers - The ciphers to display in the overlay list. * @param messageConnectorUrl - The URL of the message connector to use. + * @param portKey - Background generated key that allows the port to communicate with the background. */ private async initAutofillOverlayList({ translations, @@ -53,12 +54,14 @@ class AutofillOverlayList extends AutofillOverlayPageElement { authStatus, ciphers, messageConnectorUrl, + portKey, }: InitAutofillOverlayListMessage) { const linkElement = await this.initOverlayPage( "list", styleSheetUrl, translations, messageConnectorUrl, + portKey, ); const themeClass = `theme_${theme}`; diff --git a/apps/browser/src/autofill/overlay/pages/message-connector/message-connector.ts b/apps/browser/src/autofill/overlay/pages/message-connector/message-connector.ts index 9f6340cd2f6..37333a30157 100644 --- a/apps/browser/src/autofill/overlay/pages/message-connector/message-connector.ts +++ b/apps/browser/src/autofill/overlay/pages/message-connector/message-connector.ts @@ -12,25 +12,23 @@ export class AutofillOverlayMessageConnector { } private handleWindowMessage = (event: MessageEvent) => { + const message = event.data; if ( event.source !== globalThis.parent || - !this.isFromExtensionOrigin(event.origin.toLowerCase()) + !this.isFromExtensionOrigin(event.origin.toLowerCase()) || + !message.portKey ) { return; } - const message = event.data; - if (this.port) { this.port.postMessage(message); return; } - if (message.command !== "initAutofillOverlayPort") { - return; + if (message.command === "initAutofillOverlayPort") { + this.port = chrome.runtime.connect({ name: message.portName }); } - - this.port = chrome.runtime.connect({ name: message.portName }); }; /** diff --git a/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.spec.ts b/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.spec.ts index ce369b6b2d0..acdabae9206 100644 --- a/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.spec.ts +++ b/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.spec.ts @@ -37,6 +37,7 @@ describe("AutofillOverlayPageElement", () => { "https://jest-testing-website.com", translations, "https://jest-testing-website.com/message-connector", + "portKey", ); expect(globalThis.document.documentElement.setAttribute).toHaveBeenCalledWith( diff --git a/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.ts b/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.ts index 9dffd7801d7..e2b9ec536c4 100644 --- a/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.ts +++ b/apps/browser/src/autofill/overlay/pages/shared/autofill-overlay-page-element.ts @@ -11,6 +11,7 @@ class AutofillOverlayPageElement extends HTMLElement { protected messageOrigin: string; protected translations: Record; protected messageConnectorIframe: HTMLIFrameElement; + private portKey: string; protected windowMessageHandlers: WindowMessageHandlers; constructor() { @@ -27,13 +28,17 @@ class AutofillOverlayPageElement extends HTMLElement { * @param styleSheetUrl - The URL of the stylesheet to apply to the page * @param translations - The translations to apply to the page * @param messageConnectorUrl - The URL of the message connector to use + * @param portKey - Background generated key that allows the port to communicate with the background */ protected async initOverlayPage( elementName: "button" | "list", styleSheetUrl: string, translations: Record, messageConnectorUrl: string, + portKey: string, ): Promise { + this.portKey = portKey; + this.translations = translations; globalThis.document.documentElement.setAttribute("lang", this.getTranslation("locale")); globalThis.document.head.title = this.getTranslation(`${elementName}PageTitle`); @@ -79,7 +84,10 @@ class AutofillOverlayPageElement extends HTMLElement { return; } - this.messageConnectorIframe.contentWindow.postMessage(message, "*"); + this.messageConnectorIframe.contentWindow.postMessage( + { portKey: this.portKey, ...message }, + "*", + ); } /** diff --git a/apps/browser/src/autofill/spec/autofill-mocks.ts b/apps/browser/src/autofill/spec/autofill-mocks.ts index 3b87591ccc2..9ace6f1c102 100644 --- a/apps/browser/src/autofill/spec/autofill-mocks.ts +++ b/apps/browser/src/autofill/spec/autofill-mocks.ts @@ -174,6 +174,7 @@ function createInitAutofillOverlayButtonMessageMock( styleSheetUrl: "https://jest-testing-website.com", authStatus: AuthenticationStatus.Unlocked, messageConnectorUrl: "https://jest-testing-website.com/message-connector", + portKey: "portKey", ...customFields, }; } @@ -205,6 +206,7 @@ function createInitAutofillOverlayListMessageMock( theme: ThemeType.Light, authStatus: AuthenticationStatus.Unlocked, messageConnectorUrl: "https://jest-testing-website.com/message-connector", + portKey: "portKey", ciphers: [ createAutofillOverlayCipherDataMock(1, { icon: { diff --git a/apps/browser/src/autofill/utils/index.ts b/apps/browser/src/autofill/utils/index.ts index 99fae9a0289..1d7aa3ae308 100644 --- a/apps/browser/src/autofill/utils/index.ts +++ b/apps/browser/src/autofill/utils/index.ts @@ -1,24 +1,24 @@ import { AutofillPort } from "../enums/autofill-port.enums"; import { FillableFormFieldElement, FormFieldElement } from "../types"; +function generateRandomChars(length: number): string { + const chars = "abcdefghijklmnopqrstuvwxyz"; + const randomChars = []; + const randomBytes = new Uint8Array(length); + globalThis.crypto.getRandomValues(randomBytes); + + for (let byteIndex = 0; byteIndex < randomBytes.length; byteIndex++) { + const byte = randomBytes[byteIndex]; + randomChars.push(chars[byte % chars.length]); + } + + return randomChars.join(""); +} + /** * Generates a random string of characters that formatted as a custom element name. */ function generateRandomCustomElementName(): string { - const generateRandomChars = (length: number): string => { - const chars = "abcdefghijklmnopqrstuvwxyz"; - const randomChars = []; - const randomBytes = new Uint8Array(length); - globalThis.crypto.getRandomValues(randomBytes); - - for (let byteIndex = 0; byteIndex < randomBytes.length; byteIndex++) { - const byte = randomBytes[byteIndex]; - randomChars.push(chars[byte % chars.length]); - } - - return randomChars.join(""); - }; - const length = Math.floor(Math.random() * 5) + 8; // Between 8 and 12 characters const numHyphens = Math.min(Math.max(Math.floor(Math.random() * 4), 1), length - 1); // At least 1, maximum of 3 hyphens @@ -274,6 +274,7 @@ function nodeIsFormElement(node: Node): node is HTMLFormElement { } export { + generateRandomChars, generateRandomCustomElementName, buildSvgDomElement, sendExtensionMessage,