mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-28 12:45:45 +01:00
[PM-5303] Fix issues found with SSO login (#7346)
* [PM-5303] Cannot login with SSO * [PM-5303] Adding documentation to newly created ContentMessageHandler class * [PM-5303] Updating manifest v3 implementation to use the newly scoped name * [PM-5303] Adding jest tests to implementation
This commit is contained in:
parent
a62f8cd652
commit
a1e649e809
@ -0,0 +1,6 @@
|
|||||||
|
interface ContentMessageHandler {
|
||||||
|
init(): void;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ContentMessageHandler };
|
@ -0,0 +1,12 @@
|
|||||||
|
import { setupExtensionDisconnectAction } from "../utils";
|
||||||
|
|
||||||
|
import ContentMessageHandler from "./content-message-handler";
|
||||||
|
|
||||||
|
(function (windowContext) {
|
||||||
|
if (!windowContext.bitwardenContentMessageHandler) {
|
||||||
|
windowContext.bitwardenContentMessageHandler = new ContentMessageHandler();
|
||||||
|
setupExtensionDisconnectAction(() => windowContext.bitwardenContentMessageHandler.destroy());
|
||||||
|
|
||||||
|
windowContext.bitwardenContentMessageHandler.init();
|
||||||
|
}
|
||||||
|
})(window);
|
@ -0,0 +1,91 @@
|
|||||||
|
import { postWindowMessage, sendExtensionRuntimeMessage } from "../jest/testing-utils";
|
||||||
|
|
||||||
|
import ContentMessageHandler from "./content-message-handler";
|
||||||
|
|
||||||
|
describe("ContentMessageHandler", () => {
|
||||||
|
let contentMessageHandler: ContentMessageHandler;
|
||||||
|
const sendMessageSpy = jest.spyOn(chrome.runtime, "sendMessage");
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
contentMessageHandler = new ContentMessageHandler();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
contentMessageHandler.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("init", () => {
|
||||||
|
it("should add event listeners", () => {
|
||||||
|
const addEventListenerSpy = jest.spyOn(window, "addEventListener");
|
||||||
|
const addListenerSpy = jest.spyOn(chrome.runtime.onMessage, "addListener");
|
||||||
|
|
||||||
|
contentMessageHandler.init();
|
||||||
|
|
||||||
|
expect(addEventListenerSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(addListenerSpy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("handleWindowMessage", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
contentMessageHandler.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores messages from other sources", () => {
|
||||||
|
postWindowMessage({ command: "authResult" }, "https://localhost/", null);
|
||||||
|
|
||||||
|
expect(sendMessageSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores messages without a command", () => {
|
||||||
|
postWindowMessage({});
|
||||||
|
|
||||||
|
expect(sendMessageSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends an authResult message", () => {
|
||||||
|
postWindowMessage({ command: "authResult", lastpass: true, code: "code", state: "state" });
|
||||||
|
|
||||||
|
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sendMessageSpy).toHaveBeenCalledWith({
|
||||||
|
command: "authResult",
|
||||||
|
code: "code",
|
||||||
|
state: "state",
|
||||||
|
lastpass: true,
|
||||||
|
referrer: "localhost",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends a webAuthnResult message", () => {
|
||||||
|
postWindowMessage({ command: "webAuthnResult", data: "data", remember: true });
|
||||||
|
|
||||||
|
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sendMessageSpy).toHaveBeenCalledWith({
|
||||||
|
command: "webAuthnResult",
|
||||||
|
data: "data",
|
||||||
|
remember: true,
|
||||||
|
referrer: "localhost",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("handleExtensionMessage", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
contentMessageHandler.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores the message to the extension background if it is not present in the forwardCommands list", () => {
|
||||||
|
sendExtensionRuntimeMessage({ command: "someOtherCommand" });
|
||||||
|
|
||||||
|
expect(sendMessageSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("forwards the message to the extension background if it is present in the forwardCommands list", () => {
|
||||||
|
sendExtensionRuntimeMessage({ command: "bgUnlockPopoutOpened" });
|
||||||
|
|
||||||
|
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sendMessageSpy).toHaveBeenCalledWith({ command: "bgUnlockPopoutOpened" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
74
apps/browser/src/autofill/content/content-message-handler.ts
Normal file
74
apps/browser/src/autofill/content/content-message-handler.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { ContentMessageHandler as ContentMessageHandlerInterface } from "./abstractions/content-message-handler";
|
||||||
|
|
||||||
|
class ContentMessageHandler implements ContentMessageHandlerInterface {
|
||||||
|
private forwardCommands = [
|
||||||
|
"bgUnlockPopoutOpened",
|
||||||
|
"addToLockedVaultPendingNotifications",
|
||||||
|
"unlockCompleted",
|
||||||
|
"addedCipher",
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the content message handler. Sets up
|
||||||
|
* a window message listener and a chrome runtime
|
||||||
|
* message listener.
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
window.addEventListener("message", this.handleWindowMessage, false);
|
||||||
|
chrome.runtime.onMessage.addListener(this.handleExtensionMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a message from the window. This implementation
|
||||||
|
* specifically handles the authResult and webAuthnResult
|
||||||
|
* commands. This facilitates single sign-on.
|
||||||
|
*
|
||||||
|
* @param event - The message event.
|
||||||
|
*/
|
||||||
|
private handleWindowMessage = (event: MessageEvent) => {
|
||||||
|
const { source, data } = event;
|
||||||
|
|
||||||
|
if (source !== window || !data?.command) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { command } = data;
|
||||||
|
const referrer = source.location.hostname;
|
||||||
|
|
||||||
|
if (command === "authResult") {
|
||||||
|
const { lastpass, code, state } = data;
|
||||||
|
chrome.runtime.sendMessage({ command, code, state, lastpass, referrer });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command === "webAuthnResult") {
|
||||||
|
const { remember } = data;
|
||||||
|
chrome.runtime.sendMessage({ command, data: data.data, remember, referrer });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a message from the extension. This
|
||||||
|
* implementation forwards the message to the
|
||||||
|
* extension background so that it can be received
|
||||||
|
* in other contexts of the background script.
|
||||||
|
*
|
||||||
|
* @param message - The message from the extension.
|
||||||
|
*/
|
||||||
|
private handleExtensionMessage = (message: any) => {
|
||||||
|
if (this.forwardCommands.includes(message.command)) {
|
||||||
|
chrome.runtime.sendMessage(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the content message handler. Removes
|
||||||
|
* the window message listener and the chrome
|
||||||
|
* runtime message listener.
|
||||||
|
*/
|
||||||
|
destroy = () => {
|
||||||
|
window.removeEventListener("message", this.handleWindowMessage);
|
||||||
|
chrome.runtime.onMessage.removeListener(this.handleExtensionMessage);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContentMessageHandler;
|
@ -1,65 +0,0 @@
|
|||||||
import { setupExtensionDisconnectAction } from "../utils";
|
|
||||||
|
|
||||||
const forwardCommands = [
|
|
||||||
"bgUnlockPopoutOpened",
|
|
||||||
"addToLockedVaultPendingNotifications",
|
|
||||||
"unlockCompleted",
|
|
||||||
"addedCipher",
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles sending extension messages to the background
|
|
||||||
* script based on window messages from the page.
|
|
||||||
*
|
|
||||||
* @param event - Window message event
|
|
||||||
*/
|
|
||||||
const handleWindowMessage = (event: MessageEvent) => {
|
|
||||||
if (event.source !== window) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.data.command && event.data.command === "authResult") {
|
|
||||||
chrome.runtime.sendMessage({
|
|
||||||
command: event.data.command,
|
|
||||||
code: event.data.code,
|
|
||||||
state: event.data.state,
|
|
||||||
lastpass: event.data.lastpass,
|
|
||||||
referrer: event.source.location.hostname,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.data.command && event.data.command === "webAuthnResult") {
|
|
||||||
chrome.runtime.sendMessage({
|
|
||||||
command: event.data.command,
|
|
||||||
data: event.data.data,
|
|
||||||
remember: event.data.remember,
|
|
||||||
referrer: event.source.location.hostname,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles forwarding any commands that need to trigger
|
|
||||||
* an action from one service of the extension background
|
|
||||||
* to another.
|
|
||||||
*
|
|
||||||
* @param message - Message from the extension
|
|
||||||
*/
|
|
||||||
const handleExtensionMessage = (message: any) => {
|
|
||||||
if (forwardCommands.includes(message.command)) {
|
|
||||||
chrome.runtime.sendMessage(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles cleaning up any event listeners that were
|
|
||||||
* added to the window or extension.
|
|
||||||
*/
|
|
||||||
const handleExtensionDisconnect = () => {
|
|
||||||
window.removeEventListener("message", handleWindowMessage);
|
|
||||||
chrome.runtime.onMessage.removeListener(handleExtensionMessage);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("message", handleWindowMessage, false);
|
|
||||||
chrome.runtime.onMessage.addListener(handleExtensionMessage);
|
|
||||||
setupExtensionDisconnectAction(handleExtensionDisconnect);
|
|
2
apps/browser/src/autofill/globals.d.ts
vendored
2
apps/browser/src/autofill/globals.d.ts
vendored
@ -1,7 +1,9 @@
|
|||||||
import { AutofillInit } from "./content/abstractions/autofill-init";
|
import { AutofillInit } from "./content/abstractions/autofill-init";
|
||||||
|
import ContentMessageHandler from "./content/content-message-handler";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
bitwardenAutofillInit?: AutofillInit;
|
bitwardenAutofillInit?: AutofillInit;
|
||||||
|
bitwardenContentMessageHandler?: ContentMessageHandler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,8 @@ function flushPromises() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function postWindowMessage(data: any, origin = "https://localhost/") {
|
function postWindowMessage(data: any, origin = "https://localhost/", source = window) {
|
||||||
globalThis.dispatchEvent(new MessageEvent("message", { data, origin }));
|
globalThis.dispatchEvent(new MessageEvent("message", { data, origin, source }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendExtensionRuntimeMessage(
|
function sendExtensionRuntimeMessage(
|
||||||
|
@ -246,6 +246,15 @@ describe("AutofillService", () => {
|
|||||||
...defaultExecuteScriptOptions,
|
...defaultExecuteScriptOptions,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("injects the bootstrap-content-message-handler script if not injecting on page load", async () => {
|
||||||
|
await autofillService.injectAutofillScripts(sender.tab, sender.frameId, false);
|
||||||
|
|
||||||
|
expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabMock.id, {
|
||||||
|
file: "content/bootstrap-content-message-handler.js",
|
||||||
|
...defaultExecuteScriptOptions,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getFormsWithPasswordFields", () => {
|
describe("getFormsWithPasswordFields", () => {
|
||||||
|
@ -109,9 +109,16 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const injectedScripts = [mainAutofillScript];
|
const injectedScripts = [mainAutofillScript];
|
||||||
|
|
||||||
if (triggeringOnPageLoad) {
|
if (triggeringOnPageLoad) {
|
||||||
injectedScripts.push("autofiller.js");
|
injectedScripts.push("autofiller.js");
|
||||||
|
} else {
|
||||||
|
await BrowserApi.executeScriptInTab(tab.id, {
|
||||||
|
file: "content/bootstrap-content-message-handler.js",
|
||||||
|
runAt: "document_start",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
injectedScripts.push("notificationBar.js", "contextMenuHandler.js");
|
injectedScripts.push("notificationBar.js", "contextMenuHandler.js");
|
||||||
|
|
||||||
for (const injectedScript of injectedScripts) {
|
for (const injectedScript of injectedScripts) {
|
||||||
@ -121,11 +128,6 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
runAt: "document_start",
|
runAt: "document_start",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await BrowserApi.executeScriptInTab(tab.id, {
|
|
||||||
file: "content/message_handler.js",
|
|
||||||
runAt: "document_start",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,6 +24,12 @@
|
|||||||
"matches": ["http://*/*", "https://*/*", "file:///*"],
|
"matches": ["http://*/*", "https://*/*", "file:///*"],
|
||||||
"run_at": "document_start"
|
"run_at": "document_start"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"all_frames": false,
|
||||||
|
"js": ["content/bootstrap-content-message-handler.js"],
|
||||||
|
"matches": ["http://*/*", "https://*/*", "file:///*"],
|
||||||
|
"run_at": "document_start"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"all_frames": true,
|
"all_frames": true,
|
||||||
"css": ["content/autofill.css"],
|
"css": ["content/autofill.css"],
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"all_frames": false,
|
"all_frames": false,
|
||||||
"js": ["content/message_handler.js"],
|
"js": ["content/bootstrap-content-message-handler.js"],
|
||||||
"matches": ["http://*/*", "https://*/*", "file:///*"],
|
"matches": ["http://*/*", "https://*/*", "file:///*"],
|
||||||
"run_at": "document_start"
|
"run_at": "document_start"
|
||||||
},
|
},
|
||||||
|
@ -170,7 +170,8 @@ const mainConfig = {
|
|||||||
"content/autofiller": "./src/autofill/content/autofiller.ts",
|
"content/autofiller": "./src/autofill/content/autofiller.ts",
|
||||||
"content/notificationBar": "./src/autofill/content/notification-bar.ts",
|
"content/notificationBar": "./src/autofill/content/notification-bar.ts",
|
||||||
"content/contextMenuHandler": "./src/autofill/content/context-menu-handler.ts",
|
"content/contextMenuHandler": "./src/autofill/content/context-menu-handler.ts",
|
||||||
"content/message_handler": "./src/autofill/content/message_handler.ts",
|
"content/bootstrap-content-message-handler":
|
||||||
|
"./src/autofill/content/bootstrap-content-message-handler.ts",
|
||||||
"content/fido2/trigger-fido2-content-script-injection":
|
"content/fido2/trigger-fido2-content-script-injection":
|
||||||
"./src/vault/fido2/content/trigger-fido2-content-script-injection.ts",
|
"./src/vault/fido2/content/trigger-fido2-content-script-injection.ts",
|
||||||
"content/fido2/content-script": "./src/vault/fido2/content/content-script.ts",
|
"content/fido2/content-script": "./src/vault/fido2/content/content-script.ts",
|
||||||
|
Loading…
Reference in New Issue
Block a user