1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 01:03:35 +00:00

[PM-5189] Implementing a verification process to ensure we are receiving valid inline menu messages within the background script

This commit is contained in:
Cesar Gonzalez
2024-04-08 07:02:33 -05:00
parent a69d9d8f4a
commit dd6f3d46cb
11 changed files with 60 additions and 25 deletions

View File

@@ -48,6 +48,7 @@ type FocusedFieldData = {
type OverlayBackgroundExtensionMessage = {
command: string;
portKey?: string;
tab?: chrome.tabs.Tab;
sender?: string;
details?: AutofillPageDetails;

View File

@@ -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<number, string> = {};
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) {

View File

@@ -8,6 +8,7 @@ type InitAutofillOverlayButtonMessage = UpdateAuthStatusMessage & {
styleSheetUrl: string;
translations: Record<string, string>;
messageConnectorUrl: string;
portKey: string;
};
type OverlayButtonWindowMessageHandlers = {

View File

@@ -15,6 +15,7 @@ type InitAutofillOverlayListMessage = OverlayListMessage & {
translations: Record<string, string>;
ciphers?: OverlayCipherData[];
messageConnectorUrl: string;
portKey: string;
};
type OverlayListWindowMessageHandlers = {

View File

@@ -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";

View File

@@ -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}`;

View File

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

View File

@@ -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(

View File

@@ -11,6 +11,7 @@ class AutofillOverlayPageElement extends HTMLElement {
protected messageOrigin: string;
protected translations: Record<string, string>;
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<string, string>,
messageConnectorUrl: string,
portKey: string,
): Promise<HTMLLinkElement> {
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 },
"*",
);
}
/**

View File

@@ -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: {

View File

@@ -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,