mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 09:43:23 +00:00
Merge branch 'main' into autofill/pm-6546-blurring-of-autofilled-elements-causes-problems-in-blur-event-listeners
This commit is contained in:
@@ -109,6 +109,8 @@ type NotificationBackgroundExtensionMessageHandlers = {
|
||||
bgReopenUnlockPopout: ({ sender }: BackgroundSenderParam) => Promise<void>;
|
||||
checkNotificationQueue: ({ sender }: BackgroundSenderParam) => Promise<void>;
|
||||
collectPageDetailsResponse: ({ message }: BackgroundMessageParam) => Promise<void>;
|
||||
bgGetEnableChangedPasswordPrompt: () => Promise<boolean>;
|
||||
bgGetEnableAddedLoginPrompt: () => Promise<boolean>;
|
||||
getWebVaultUrlForNotification: () => string;
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { firstValueFrom } from "rxjs";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
||||
import { UserNotificationSettingsService } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -45,6 +46,7 @@ describe("NotificationBackground", () => {
|
||||
const policyService = mock<PolicyService>();
|
||||
const folderService = mock<FolderService>();
|
||||
const stateService = mock<BrowserStateService>();
|
||||
const userNotificationSettingsService = mock<UserNotificationSettingsService>();
|
||||
const environmentService = mock<EnvironmentService>();
|
||||
const logService = mock<LogService>();
|
||||
|
||||
@@ -56,6 +58,7 @@ describe("NotificationBackground", () => {
|
||||
policyService,
|
||||
folderService,
|
||||
stateService,
|
||||
userNotificationSettingsService,
|
||||
environmentService,
|
||||
logService,
|
||||
);
|
||||
@@ -235,8 +238,8 @@ describe("NotificationBackground", () => {
|
||||
let tab: chrome.tabs.Tab;
|
||||
let sender: chrome.runtime.MessageSender;
|
||||
let getAuthStatusSpy: jest.SpyInstance;
|
||||
let getDisableAddLoginNotificationSpy: jest.SpyInstance;
|
||||
let getDisableChangedPasswordNotificationSpy: jest.SpyInstance;
|
||||
let getEnableAddedLoginPromptSpy: jest.SpyInstance;
|
||||
let getEnableChangedPasswordPromptSpy: jest.SpyInstance;
|
||||
let pushAddLoginToQueueSpy: jest.SpyInstance;
|
||||
let pushChangePasswordToQueueSpy: jest.SpyInstance;
|
||||
let getAllDecryptedForUrlSpy: jest.SpyInstance;
|
||||
@@ -245,13 +248,13 @@ describe("NotificationBackground", () => {
|
||||
tab = createChromeTabMock();
|
||||
sender = mock<chrome.runtime.MessageSender>({ tab });
|
||||
getAuthStatusSpy = jest.spyOn(authService, "getAuthStatus");
|
||||
getDisableAddLoginNotificationSpy = jest.spyOn(
|
||||
stateService,
|
||||
"getDisableAddLoginNotification",
|
||||
getEnableAddedLoginPromptSpy = jest.spyOn(
|
||||
notificationBackground as any,
|
||||
"getEnableAddedLoginPrompt",
|
||||
);
|
||||
getDisableChangedPasswordNotificationSpy = jest.spyOn(
|
||||
stateService,
|
||||
"getDisableChangedPasswordNotification",
|
||||
getEnableChangedPasswordPromptSpy = jest.spyOn(
|
||||
notificationBackground as any,
|
||||
"getEnableChangedPasswordPrompt",
|
||||
);
|
||||
pushAddLoginToQueueSpy = jest.spyOn(notificationBackground as any, "pushAddLoginToQueue");
|
||||
pushChangePasswordToQueueSpy = jest.spyOn(
|
||||
@@ -272,7 +275,7 @@ describe("NotificationBackground", () => {
|
||||
await flushPromises();
|
||||
|
||||
expect(getAuthStatusSpy).toHaveBeenCalled();
|
||||
expect(getDisableAddLoginNotificationSpy).not.toHaveBeenCalled();
|
||||
expect(getEnableAddedLoginPromptSpy).not.toHaveBeenCalled();
|
||||
expect(pushAddLoginToQueueSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -287,7 +290,7 @@ describe("NotificationBackground", () => {
|
||||
await flushPromises();
|
||||
|
||||
expect(getAuthStatusSpy).toHaveBeenCalled();
|
||||
expect(getDisableAddLoginNotificationSpy).not.toHaveBeenCalled();
|
||||
expect(getEnableAddedLoginPromptSpy).not.toHaveBeenCalled();
|
||||
expect(pushAddLoginToQueueSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -297,13 +300,13 @@ describe("NotificationBackground", () => {
|
||||
login: { username: "test", password: "password", url: "https://example.com" },
|
||||
};
|
||||
getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Locked);
|
||||
getDisableAddLoginNotificationSpy.mockReturnValueOnce(true);
|
||||
getEnableAddedLoginPromptSpy.mockReturnValueOnce(false);
|
||||
|
||||
sendExtensionRuntimeMessage(message, sender);
|
||||
await flushPromises();
|
||||
|
||||
expect(getAuthStatusSpy).toHaveBeenCalled();
|
||||
expect(getDisableAddLoginNotificationSpy).toHaveBeenCalled();
|
||||
expect(getEnableAddedLoginPromptSpy).toHaveBeenCalled();
|
||||
expect(getAllDecryptedForUrlSpy).not.toHaveBeenCalled();
|
||||
expect(pushAddLoginToQueueSpy).not.toHaveBeenCalled();
|
||||
expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled();
|
||||
@@ -315,14 +318,14 @@ describe("NotificationBackground", () => {
|
||||
login: { username: "test", password: "password", url: "https://example.com" },
|
||||
};
|
||||
getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Unlocked);
|
||||
getDisableAddLoginNotificationSpy.mockReturnValueOnce(true);
|
||||
getEnableAddedLoginPromptSpy.mockReturnValueOnce(false);
|
||||
getAllDecryptedForUrlSpy.mockResolvedValueOnce([]);
|
||||
|
||||
sendExtensionRuntimeMessage(message, sender);
|
||||
await flushPromises();
|
||||
|
||||
expect(getAuthStatusSpy).toHaveBeenCalled();
|
||||
expect(getDisableAddLoginNotificationSpy).toHaveBeenCalled();
|
||||
expect(getEnableAddedLoginPromptSpy).toHaveBeenCalled();
|
||||
expect(getAllDecryptedForUrlSpy).toHaveBeenCalled();
|
||||
expect(pushAddLoginToQueueSpy).not.toHaveBeenCalled();
|
||||
expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled();
|
||||
@@ -334,8 +337,8 @@ describe("NotificationBackground", () => {
|
||||
login: { username: "test", password: "password", url: "https://example.com" },
|
||||
};
|
||||
getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Unlocked);
|
||||
getDisableAddLoginNotificationSpy.mockReturnValueOnce(false);
|
||||
getDisableChangedPasswordNotificationSpy.mockReturnValueOnce(true);
|
||||
getEnableAddedLoginPromptSpy.mockReturnValueOnce(true);
|
||||
getEnableChangedPasswordPromptSpy.mockReturnValueOnce(false);
|
||||
getAllDecryptedForUrlSpy.mockResolvedValueOnce([
|
||||
mock<CipherView>({ login: { username: "test", password: "oldPassword" } }),
|
||||
]);
|
||||
@@ -344,9 +347,9 @@ describe("NotificationBackground", () => {
|
||||
await flushPromises();
|
||||
|
||||
expect(getAuthStatusSpy).toHaveBeenCalled();
|
||||
expect(getDisableAddLoginNotificationSpy).toHaveBeenCalled();
|
||||
expect(getEnableAddedLoginPromptSpy).toHaveBeenCalled();
|
||||
expect(getAllDecryptedForUrlSpy).toHaveBeenCalled();
|
||||
expect(getDisableChangedPasswordNotificationSpy).toHaveBeenCalled();
|
||||
expect(getEnableChangedPasswordPromptSpy).toHaveBeenCalled();
|
||||
expect(pushAddLoginToQueueSpy).not.toHaveBeenCalled();
|
||||
expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -357,7 +360,7 @@ describe("NotificationBackground", () => {
|
||||
login: { username: "test", password: "password", url: "https://example.com" },
|
||||
};
|
||||
getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Unlocked);
|
||||
getDisableAddLoginNotificationSpy.mockReturnValueOnce(false);
|
||||
getEnableAddedLoginPromptSpy.mockReturnValueOnce(true);
|
||||
getAllDecryptedForUrlSpy.mockResolvedValueOnce([
|
||||
mock<CipherView>({ login: { username: "test", password: "password" } }),
|
||||
]);
|
||||
@@ -366,7 +369,7 @@ describe("NotificationBackground", () => {
|
||||
await flushPromises();
|
||||
|
||||
expect(getAuthStatusSpy).toHaveBeenCalled();
|
||||
expect(getDisableAddLoginNotificationSpy).toHaveBeenCalled();
|
||||
expect(getEnableAddedLoginPromptSpy).toHaveBeenCalled();
|
||||
expect(getAllDecryptedForUrlSpy).toHaveBeenCalled();
|
||||
expect(pushAddLoginToQueueSpy).not.toHaveBeenCalled();
|
||||
expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled();
|
||||
@@ -376,7 +379,7 @@ describe("NotificationBackground", () => {
|
||||
const login = { username: "test", password: "password", url: "https://example.com" };
|
||||
const message: NotificationBackgroundExtensionMessage = { command: "bgAddLogin", login };
|
||||
getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Locked);
|
||||
getDisableAddLoginNotificationSpy.mockReturnValueOnce(false);
|
||||
getEnableAddedLoginPromptSpy.mockReturnValueOnce(true);
|
||||
|
||||
sendExtensionRuntimeMessage(message, sender);
|
||||
await flushPromises();
|
||||
@@ -393,7 +396,7 @@ describe("NotificationBackground", () => {
|
||||
} as any;
|
||||
const message: NotificationBackgroundExtensionMessage = { command: "bgAddLogin", login };
|
||||
getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Unlocked);
|
||||
getDisableAddLoginNotificationSpy.mockReturnValueOnce(false);
|
||||
getEnableAddedLoginPromptSpy.mockReturnValueOnce(true);
|
||||
getAllDecryptedForUrlSpy.mockResolvedValueOnce([
|
||||
mock<CipherView>({ login: { username: "anotherTestUsername", password: "password" } }),
|
||||
]);
|
||||
@@ -409,7 +412,8 @@ describe("NotificationBackground", () => {
|
||||
const login = { username: "tEsT", password: "password", url: "https://example.com" };
|
||||
const message: NotificationBackgroundExtensionMessage = { command: "bgAddLogin", login };
|
||||
getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Unlocked);
|
||||
getDisableAddLoginNotificationSpy.mockReturnValueOnce(false);
|
||||
getEnableAddedLoginPromptSpy.mockResolvedValueOnce(true);
|
||||
getEnableChangedPasswordPromptSpy.mockResolvedValueOnce(true);
|
||||
getAllDecryptedForUrlSpy.mockResolvedValueOnce([
|
||||
mock<CipherView>({
|
||||
id: "cipher-id",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
@@ -57,6 +58,8 @@ export default class NotificationBackground {
|
||||
bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab),
|
||||
checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab),
|
||||
bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab),
|
||||
bgGetEnableChangedPasswordPrompt: () => this.getEnableChangedPasswordPrompt(),
|
||||
bgGetEnableAddedLoginPrompt: () => this.getEnableAddedLoginPrompt(),
|
||||
getWebVaultUrlForNotification: () => this.getWebVaultUrl(),
|
||||
};
|
||||
|
||||
@@ -67,6 +70,7 @@ export default class NotificationBackground {
|
||||
private policyService: PolicyService,
|
||||
private folderService: FolderService,
|
||||
private stateService: BrowserStateService,
|
||||
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
|
||||
private environmentService: EnvironmentService,
|
||||
private logService: LogService,
|
||||
) {}
|
||||
@@ -81,6 +85,20 @@ export default class NotificationBackground {
|
||||
this.cleanupNotificationQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the enableChangedPasswordPrompt setting from the user notification settings service.
|
||||
*/
|
||||
async getEnableChangedPasswordPrompt(): Promise<boolean> {
|
||||
return await firstValueFrom(this.userNotificationSettingsService.enableChangedPasswordPrompt$);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the enableAddedLoginPrompt setting from the user notification settings service.
|
||||
*/
|
||||
async getEnableAddedLoginPrompt(): Promise<boolean> {
|
||||
return await firstValueFrom(this.userNotificationSettingsService.enableAddedLoginPrompt$);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the notification queue for any messages that need to be sent to the
|
||||
* specified tab. If no tab is specified, the current tab will be used.
|
||||
@@ -194,9 +212,10 @@ export default class NotificationBackground {
|
||||
return;
|
||||
}
|
||||
|
||||
const disabledAddLogin = await this.stateService.getDisableAddLoginNotification();
|
||||
const addLoginIsEnabled = await this.getEnableAddedLoginPrompt();
|
||||
|
||||
if (authStatus === AuthenticationStatus.Locked) {
|
||||
if (!disabledAddLogin) {
|
||||
if (addLoginIsEnabled) {
|
||||
await this.pushAddLoginToQueue(loginDomain, loginInfo, sender.tab, true);
|
||||
}
|
||||
|
||||
@@ -207,14 +226,15 @@ export default class NotificationBackground {
|
||||
const usernameMatches = ciphers.filter(
|
||||
(c) => c.login.username != null && c.login.username.toLowerCase() === normalizedUsername,
|
||||
);
|
||||
if (!disabledAddLogin && usernameMatches.length === 0) {
|
||||
if (addLoginIsEnabled && usernameMatches.length === 0) {
|
||||
await this.pushAddLoginToQueue(loginDomain, loginInfo, sender.tab);
|
||||
return;
|
||||
}
|
||||
|
||||
const disabledChangePassword = await this.stateService.getDisableChangedPasswordNotification();
|
||||
const changePasswordIsEnabled = await this.getEnableChangedPasswordPrompt();
|
||||
|
||||
if (
|
||||
!disabledChangePassword &&
|
||||
changePasswordIsEnabled &&
|
||||
usernameMatches.length === 1 &&
|
||||
usernameMatches[0].login.password !== loginInfo.password
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { UserNotificationSettingsService } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||
|
||||
import {
|
||||
CachedServices,
|
||||
factory,
|
||||
FactoryOptions,
|
||||
} from "../../../platform/background/service-factories/factory-options";
|
||||
import {
|
||||
stateProviderFactory,
|
||||
StateProviderInitOptions,
|
||||
} from "../../../platform/background/service-factories/state-provider.factory";
|
||||
|
||||
export type UserNotificationSettingsServiceInitOptions = FactoryOptions & StateProviderInitOptions;
|
||||
|
||||
export function userNotificationSettingsServiceFactory(
|
||||
cache: { userNotificationSettingsService?: UserNotificationSettingsService } & CachedServices,
|
||||
opts: UserNotificationSettingsServiceInitOptions,
|
||||
): Promise<UserNotificationSettingsService> {
|
||||
return factory(
|
||||
cache,
|
||||
"userNotificationSettingsService",
|
||||
opts,
|
||||
async () => new UserNotificationSettingsService(await stateProviderFactory(cache, opts)),
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,11 @@ import { WatchedForm } from "../models/watched-form";
|
||||
import { NotificationBarIframeInitData } from "../notification/abstractions/notification-bar";
|
||||
import { FormData } from "../services/abstractions/autofill.service";
|
||||
import { GlobalSettings, UserSettings } from "../types";
|
||||
import { getFromLocalStorage, setupExtensionDisconnectAction } from "../utils";
|
||||
import {
|
||||
getFromLocalStorage,
|
||||
sendExtensionMessage,
|
||||
setupExtensionDisconnectAction,
|
||||
} from "../utils";
|
||||
|
||||
interface HTMLElementWithFormOpId extends HTMLElement {
|
||||
formOpId: string;
|
||||
@@ -86,12 +90,11 @@ async function loadNotificationBar() {
|
||||
]);
|
||||
const changePasswordButtonContainsNames = new Set(["pass", "change", "contras", "senha"]);
|
||||
|
||||
// These are preferences for whether to show the notification bar based on the user's settings
|
||||
// and they are set in the Settings > Options page in the browser extension.
|
||||
let disabledAddLoginNotification = false;
|
||||
let disabledChangedPasswordNotification = false;
|
||||
const enableChangedPasswordPrompt = await sendExtensionMessage(
|
||||
"bgGetEnableChangedPasswordPrompt",
|
||||
);
|
||||
const enableAddedLoginPrompt = await sendExtensionMessage("bgGetEnableAddedLoginPrompt");
|
||||
let showNotificationBar = true;
|
||||
|
||||
// Look up the active user id from storage
|
||||
const activeUserIdKey = "activeUserId";
|
||||
const globalStorageKey = "global";
|
||||
@@ -121,11 +124,7 @@ async function loadNotificationBar() {
|
||||
// Example: '{"bitwarden.com":null}'
|
||||
const excludedDomainsDict = globalSettings.neverDomains;
|
||||
if (!excludedDomainsDict || !(window.location.hostname in excludedDomainsDict)) {
|
||||
// Set local disabled preferences
|
||||
disabledAddLoginNotification = globalSettings.disableAddLoginNotification;
|
||||
disabledChangedPasswordNotification = globalSettings.disableChangedPasswordNotification;
|
||||
|
||||
if (!disabledAddLoginNotification || !disabledChangedPasswordNotification) {
|
||||
if (enableAddedLoginPrompt || enableChangedPasswordPrompt) {
|
||||
// If the user has not disabled both notifications, then handle the initial page change (null -> actual page)
|
||||
handlePageChange();
|
||||
}
|
||||
@@ -352,9 +351,7 @@ async function loadNotificationBar() {
|
||||
// to avoid missing any forms that are added after the page loads
|
||||
observeDom();
|
||||
|
||||
sendPlatformMessage({
|
||||
command: "checkNotificationQueue",
|
||||
});
|
||||
void sendExtensionMessage("checkNotificationQueue");
|
||||
}
|
||||
|
||||
// This is a safeguard in case the observer misses a SPA page change.
|
||||
@@ -392,10 +389,7 @@ async function loadNotificationBar() {
|
||||
*
|
||||
* */
|
||||
function collectPageDetails() {
|
||||
sendPlatformMessage({
|
||||
command: "bgCollectPageDetails",
|
||||
sender: "notificationBar",
|
||||
});
|
||||
void sendExtensionMessage("bgCollectPageDetails", { sender: "notificationBar" });
|
||||
}
|
||||
|
||||
// End Page Detail Collection Methods
|
||||
@@ -620,10 +614,9 @@ async function loadNotificationBar() {
|
||||
continue;
|
||||
}
|
||||
|
||||
const disabledBoth = disabledChangedPasswordNotification && disabledAddLoginNotification;
|
||||
// if user has not disabled both notifications and we have a username and password field,
|
||||
// if user has enabled either add login or change password notification, and we have a username and password field
|
||||
if (
|
||||
!disabledBoth &&
|
||||
(enableChangedPasswordPrompt || enableAddedLoginPrompt) &&
|
||||
watchedForms[i].usernameEl != null &&
|
||||
watchedForms[i].passwordEl != null
|
||||
) {
|
||||
@@ -639,10 +632,7 @@ async function loadNotificationBar() {
|
||||
const passwordPopulated = login.password != null && login.password !== "";
|
||||
if (userNamePopulated && passwordPopulated) {
|
||||
processedForm(form);
|
||||
sendPlatformMessage({
|
||||
command: "bgAddLogin",
|
||||
login,
|
||||
});
|
||||
void sendExtensionMessage("bgAddLogin", { login });
|
||||
break;
|
||||
} else if (
|
||||
userNamePopulated &&
|
||||
@@ -659,7 +649,7 @@ async function loadNotificationBar() {
|
||||
|
||||
// if user has not disabled the password changed notification and we have multiple password fields,
|
||||
// then check if the user has changed their password
|
||||
if (!disabledChangedPasswordNotification && watchedForms[i].passwordEls != null) {
|
||||
if (enableChangedPasswordPrompt && watchedForms[i].passwordEls != null) {
|
||||
// Get the values of the password fields
|
||||
const passwords: string[] = watchedForms[i].passwordEls
|
||||
.filter((el: HTMLInputElement) => el.value != null && el.value !== "")
|
||||
@@ -716,7 +706,7 @@ async function loadNotificationBar() {
|
||||
currentPassword: curPass,
|
||||
url: document.URL,
|
||||
};
|
||||
sendPlatformMessage({ command: "bgChangedPassword", data });
|
||||
void sendExtensionMessage("bgChangedPassword", { data });
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -954,9 +944,7 @@ async function loadNotificationBar() {
|
||||
switch (barType) {
|
||||
case "add":
|
||||
case "change":
|
||||
sendPlatformMessage({
|
||||
command: "bgRemoveTabFromNotificationQueue",
|
||||
});
|
||||
void sendExtensionMessage("bgRemoveTabFromNotificationQueue");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -981,12 +969,6 @@ async function loadNotificationBar() {
|
||||
// End Notification Bar Functions (open, close, height adjustment, etc.)
|
||||
|
||||
// Helper Functions
|
||||
function sendPlatformMessage(msg: any) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
chrome.runtime.sendMessage(msg);
|
||||
}
|
||||
|
||||
function isInIframe() {
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
|
||||
@@ -166,11 +166,10 @@ describe("AutofillService", () => {
|
||||
jest
|
||||
.spyOn(autofillService, "getOverlayVisibility")
|
||||
.mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus);
|
||||
jest.spyOn(autofillService, "getAutofillOnPageLoad").mockResolvedValue(true);
|
||||
});
|
||||
|
||||
it("accepts an extension message sender and injects the autofill scripts into the tab of the sender", async () => {
|
||||
jest.spyOn(autofillService, "getAutofillOnPageLoad").mockResolvedValue(true);
|
||||
|
||||
await autofillService.injectAutofillScripts(sender.tab, sender.frameId, true);
|
||||
|
||||
[autofillOverlayBootstrapScript, ...defaultAutofillScripts].forEach((scriptName) => {
|
||||
@@ -195,11 +194,6 @@ describe("AutofillService", () => {
|
||||
});
|
||||
|
||||
it("will inject the bootstrap-autofill-overlay script if the user has the autofill overlay enabled", async () => {
|
||||
jest
|
||||
.spyOn(autofillService, "getOverlayVisibility")
|
||||
.mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus);
|
||||
jest.spyOn(autofillService, "getAutofillOnPageLoad").mockResolvedValue(true);
|
||||
|
||||
await autofillService.injectAutofillScripts(sender.tab, sender.frameId);
|
||||
|
||||
expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabMock.id, {
|
||||
@@ -218,7 +212,6 @@ describe("AutofillService", () => {
|
||||
jest
|
||||
.spyOn(autofillService, "getOverlayVisibility")
|
||||
.mockResolvedValue(AutofillOverlayVisibility.Off);
|
||||
jest.spyOn(autofillService, "getAutofillOnPageLoad").mockResolvedValue(true);
|
||||
|
||||
await autofillService.injectAutofillScripts(sender.tab, sender.frameId);
|
||||
|
||||
@@ -235,8 +228,6 @@ describe("AutofillService", () => {
|
||||
});
|
||||
|
||||
it("injects the content-message-handler script if not injecting on page load", async () => {
|
||||
jest.spyOn(autofillService, "getAutofillOnPageLoad").mockResolvedValue(true);
|
||||
|
||||
await autofillService.injectAutofillScripts(sender.tab, sender.frameId, false);
|
||||
|
||||
expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabMock.id, {
|
||||
|
||||
@@ -39,10 +39,7 @@ export type UserSettings = {
|
||||
vaultTimeoutAction: VaultTimeoutAction;
|
||||
};
|
||||
|
||||
export type GlobalSettings = Pick<
|
||||
GlobalState,
|
||||
"disableAddLoginNotification" | "disableChangedPasswordNotification" | "neverDomains"
|
||||
>;
|
||||
export type GlobalSettings = Pick<GlobalState, "neverDomains">;
|
||||
|
||||
/**
|
||||
* A HTMLElement (usually a form element) with additional custom properties added by this script
|
||||
|
||||
@@ -55,6 +55,10 @@ import {
|
||||
BadgeSettingsServiceAbstraction,
|
||||
BadgeSettingsService,
|
||||
} from "@bitwarden/common/autofill/services/badge-settings.service";
|
||||
import {
|
||||
UserNotificationSettingsService,
|
||||
UserNotificationSettingsServiceAbstraction,
|
||||
} from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
|
||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
@@ -89,6 +93,7 @@ import { KeyGenerationService } from "@bitwarden/common/platform/services/key-ge
|
||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||
import { SystemService } from "@bitwarden/common/platform/services/system.service";
|
||||
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
||||
import {
|
||||
@@ -96,6 +101,7 @@ import {
|
||||
DerivedStateProvider,
|
||||
GlobalStateProvider,
|
||||
SingleUserStateProvider,
|
||||
StateEventRunnerService,
|
||||
StateProvider,
|
||||
} from "@bitwarden/common/platform/state";
|
||||
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally these should not be accessed */
|
||||
@@ -103,6 +109,7 @@ import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state
|
||||
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
|
||||
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
||||
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
||||
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
|
||||
/* eslint-enable import/no-restricted-paths */
|
||||
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
|
||||
import { ApiService } from "@bitwarden/common/services/api.service";
|
||||
@@ -248,6 +255,7 @@ export default class MainBackground {
|
||||
searchService: SearchServiceAbstraction;
|
||||
notificationsService: NotificationsServiceAbstraction;
|
||||
stateService: StateServiceAbstraction;
|
||||
userNotificationSettingsService: UserNotificationSettingsServiceAbstraction;
|
||||
autofillSettingsService: AutofillSettingsServiceAbstraction;
|
||||
badgeSettingsService: BadgeSettingsServiceAbstraction;
|
||||
systemService: SystemServiceAbstraction;
|
||||
@@ -294,11 +302,9 @@ export default class MainBackground {
|
||||
organizationVaultExportService: OrganizationVaultExportServiceAbstraction;
|
||||
vaultSettingsService: VaultSettingsServiceAbstraction;
|
||||
biometricStateService: BiometricStateService;
|
||||
stateEventRunnerService: StateEventRunnerService;
|
||||
ssoLoginService: SsoLoginServiceAbstraction;
|
||||
|
||||
// Passed to the popup for Safari to workaround issues with theming, downloading, etc.
|
||||
backgroundWindow = window;
|
||||
|
||||
onUpdatedRan: boolean;
|
||||
onReplacedRan: boolean;
|
||||
loginToAutoFill: CipherView = null;
|
||||
@@ -361,10 +367,24 @@ export default class MainBackground {
|
||||
this.keyGenerationService,
|
||||
)
|
||||
: new BackgroundMemoryStorageService();
|
||||
this.globalStateProvider = new DefaultGlobalStateProvider(
|
||||
this.memoryStorageForStateProviders,
|
||||
|
||||
const storageServiceProvider = new StorageServiceProvider(
|
||||
this.storageService as BrowserLocalStorageService,
|
||||
this.memoryStorageForStateProviders,
|
||||
);
|
||||
|
||||
this.globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
||||
|
||||
const stateEventRegistrarService = new StateEventRegistrarService(
|
||||
this.globalStateProvider,
|
||||
storageServiceProvider,
|
||||
);
|
||||
|
||||
this.stateEventRunnerService = new StateEventRunnerService(
|
||||
this.globalStateProvider,
|
||||
storageServiceProvider,
|
||||
);
|
||||
|
||||
this.encryptService = flagEnabled("multithreadDecryption")
|
||||
? new MultithreadEncryptServiceImplementation(
|
||||
this.cryptoFunctionService,
|
||||
@@ -374,8 +394,8 @@ export default class MainBackground {
|
||||
: new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, true);
|
||||
|
||||
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
||||
this.memoryStorageForStateProviders,
|
||||
this.storageService as BrowserLocalStorageService,
|
||||
storageServiceProvider,
|
||||
stateEventRegistrarService,
|
||||
);
|
||||
this.accountService = new AccountServiceImplementation(
|
||||
this.messagingService,
|
||||
@@ -384,8 +404,8 @@ export default class MainBackground {
|
||||
);
|
||||
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
|
||||
this.accountService,
|
||||
this.memoryStorageForStateProviders,
|
||||
this.storageService as BrowserLocalStorageService,
|
||||
storageServiceProvider,
|
||||
stateEventRegistrarService,
|
||||
);
|
||||
this.derivedStateProvider = new BackgroundDerivedStateProvider(
|
||||
this.memoryStorageForStateProviders,
|
||||
@@ -419,6 +439,7 @@ export default class MainBackground {
|
||||
this.environmentService,
|
||||
migrationRunner,
|
||||
);
|
||||
this.userNotificationSettingsService = new UserNotificationSettingsService(this.stateProvider);
|
||||
this.platformUtilsService = new BrowserPlatformUtilsService(
|
||||
this.messagingService,
|
||||
(clipboardValue, clearMs) => {
|
||||
@@ -660,6 +681,7 @@ export default class MainBackground {
|
||||
this.stateService,
|
||||
this.authService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
this.stateEventRunnerService,
|
||||
lockedCallback,
|
||||
logoutCallback,
|
||||
);
|
||||
@@ -675,7 +697,7 @@ export default class MainBackground {
|
||||
this.fileUploadService,
|
||||
this.sendService,
|
||||
);
|
||||
this.providerService = new ProviderService(this.stateService);
|
||||
this.providerService = new ProviderService(this.stateProvider);
|
||||
this.syncService = new SyncService(
|
||||
this.apiService,
|
||||
this.settingsService,
|
||||
@@ -846,6 +868,7 @@ export default class MainBackground {
|
||||
this.policyService,
|
||||
this.folderService,
|
||||
this.stateService,
|
||||
this.userNotificationSettingsService,
|
||||
this.environmentService,
|
||||
this.logService,
|
||||
);
|
||||
@@ -1088,9 +1111,11 @@ export default class MainBackground {
|
||||
this.keyConnectorService.clear(),
|
||||
this.vaultFilterService.clear(),
|
||||
this.biometricStateService.logout(userId),
|
||||
this.providerService.save(null, userId),
|
||||
/* We intentionally do not clear:
|
||||
* - autofillSettingsService
|
||||
* - badgeSettingsService
|
||||
* - userNotificationSettingsService
|
||||
*/
|
||||
]);
|
||||
|
||||
@@ -1104,6 +1129,8 @@ export default class MainBackground {
|
||||
this.searchService.clearIndex();
|
||||
}
|
||||
|
||||
await this.stateEventRunnerService.handleEvent("logout", currentUserId as UserId);
|
||||
|
||||
if (newActiveUser != null) {
|
||||
// we have a new active user, do not continue tearing down application
|
||||
await this.switchAccount(newActiveUser as UserId);
|
||||
|
||||
@@ -21,6 +21,10 @@ import {
|
||||
platformUtilsServiceFactory,
|
||||
PlatformUtilsServiceInitOptions,
|
||||
} from "../../platform/background/service-factories/platform-utils-service.factory";
|
||||
import {
|
||||
stateEventRunnerServiceFactory,
|
||||
StateEventRunnerServiceInitOptions,
|
||||
} from "../../platform/background/service-factories/state-event-runner-service.factory";
|
||||
import {
|
||||
StateServiceInitOptions,
|
||||
stateServiceFactory,
|
||||
@@ -62,7 +66,8 @@ export type VaultTimeoutServiceInitOptions = VaultTimeoutServiceFactoryOptions &
|
||||
SearchServiceInitOptions &
|
||||
StateServiceInitOptions &
|
||||
AuthServiceInitOptions &
|
||||
VaultTimeoutSettingsServiceInitOptions;
|
||||
VaultTimeoutSettingsServiceInitOptions &
|
||||
StateEventRunnerServiceInitOptions;
|
||||
|
||||
export function vaultTimeoutServiceFactory(
|
||||
cache: { vaultTimeoutService?: AbstractVaultTimeoutService } & CachedServices,
|
||||
@@ -84,6 +89,7 @@ export function vaultTimeoutServiceFactory(
|
||||
await stateServiceFactory(cache, opts),
|
||||
await authServiceFactory(cache, opts),
|
||||
await vaultTimeoutSettingsServiceFactory(cache, opts),
|
||||
await stateEventRunnerServiceFactory(cache, opts),
|
||||
opts.vaultTimeoutServiceOptions.lockedCallback,
|
||||
opts.vaultTimeoutServiceOptions.loggedOutCallback,
|
||||
),
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"web_accessible_resources": [
|
||||
"content/fido2/page-script.js",
|
||||
"content/lp-suppress-import-download.js",
|
||||
"notification/bar.html",
|
||||
"images/icon38.png",
|
||||
"images/icon38_locked.png",
|
||||
|
||||
@@ -31,6 +31,12 @@
|
||||
"matches": ["http://*/*", "https://*/*", "file:///*"],
|
||||
"run_at": "document_start"
|
||||
},
|
||||
{
|
||||
"all_frames": false,
|
||||
"js": ["content/lp-fileless-importer.js"],
|
||||
"matches": ["https://lastpass.com/export.php"],
|
||||
"run_at": "document_start"
|
||||
},
|
||||
{
|
||||
"all_frames": true,
|
||||
"css": ["content/autofill.css"],
|
||||
|
||||
@@ -9,18 +9,20 @@ import {
|
||||
|
||||
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
||||
import {
|
||||
DiskStorageServiceInitOptions,
|
||||
MemoryStorageServiceInitOptions,
|
||||
observableDiskStorageServiceFactory,
|
||||
observableMemoryStorageServiceFactory,
|
||||
} from "./storage-service.factory";
|
||||
StateEventRegistrarServiceInitOptions,
|
||||
stateEventRegistrarServiceFactory,
|
||||
} from "./state-event-registrar-service.factory";
|
||||
import {
|
||||
StorageServiceProviderInitOptions,
|
||||
storageServiceProviderFactory,
|
||||
} from "./storage-service-provider.factory";
|
||||
|
||||
type ActiveUserStateProviderFactory = FactoryOptions;
|
||||
|
||||
export type ActiveUserStateProviderInitOptions = ActiveUserStateProviderFactory &
|
||||
AccountServiceInitOptions &
|
||||
MemoryStorageServiceInitOptions &
|
||||
DiskStorageServiceInitOptions;
|
||||
StorageServiceProviderInitOptions &
|
||||
StateEventRegistrarServiceInitOptions;
|
||||
|
||||
export async function activeUserStateProviderFactory(
|
||||
cache: { activeUserStateProvider?: ActiveUserStateProvider } & CachedServices,
|
||||
@@ -33,8 +35,8 @@ export async function activeUserStateProviderFactory(
|
||||
async () =>
|
||||
new DefaultActiveUserStateProvider(
|
||||
await accountServiceFactory(cache, opts),
|
||||
await observableMemoryStorageServiceFactory(cache, opts),
|
||||
await observableDiskStorageServiceFactory(cache, opts),
|
||||
await storageServiceProviderFactory(cache, opts),
|
||||
await stateEventRegistrarServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,17 +4,14 @@ import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/imp
|
||||
|
||||
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
||||
import {
|
||||
DiskStorageServiceInitOptions,
|
||||
MemoryStorageServiceInitOptions,
|
||||
observableDiskStorageServiceFactory,
|
||||
observableMemoryStorageServiceFactory,
|
||||
} from "./storage-service.factory";
|
||||
StorageServiceProviderInitOptions,
|
||||
storageServiceProviderFactory,
|
||||
} from "./storage-service-provider.factory";
|
||||
|
||||
type GlobalStateProviderFactoryOptions = FactoryOptions;
|
||||
|
||||
export type GlobalStateProviderInitOptions = GlobalStateProviderFactoryOptions &
|
||||
MemoryStorageServiceInitOptions &
|
||||
DiskStorageServiceInitOptions;
|
||||
StorageServiceProviderInitOptions;
|
||||
|
||||
export async function globalStateProviderFactory(
|
||||
cache: { globalStateProvider?: GlobalStateProvider } & CachedServices,
|
||||
@@ -24,10 +21,6 @@ export async function globalStateProviderFactory(
|
||||
cache,
|
||||
"globalStateProvider",
|
||||
opts,
|
||||
async () =>
|
||||
new DefaultGlobalStateProvider(
|
||||
await observableMemoryStorageServiceFactory(cache, opts),
|
||||
await observableDiskStorageServiceFactory(cache, opts),
|
||||
),
|
||||
async () => new DefaultGlobalStateProvider(await storageServiceProviderFactory(cache, opts)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,17 +4,19 @@ import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state
|
||||
|
||||
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
||||
import {
|
||||
DiskStorageServiceInitOptions,
|
||||
MemoryStorageServiceInitOptions,
|
||||
observableDiskStorageServiceFactory,
|
||||
observableMemoryStorageServiceFactory,
|
||||
} from "./storage-service.factory";
|
||||
StateEventRegistrarServiceInitOptions,
|
||||
stateEventRegistrarServiceFactory,
|
||||
} from "./state-event-registrar-service.factory";
|
||||
import {
|
||||
StorageServiceProviderInitOptions,
|
||||
storageServiceProviderFactory,
|
||||
} from "./storage-service-provider.factory";
|
||||
|
||||
type SingleUserStateProviderFactoryOptions = FactoryOptions;
|
||||
|
||||
export type SingleUserStateProviderInitOptions = SingleUserStateProviderFactoryOptions &
|
||||
MemoryStorageServiceInitOptions &
|
||||
DiskStorageServiceInitOptions;
|
||||
StorageServiceProviderInitOptions &
|
||||
StateEventRegistrarServiceInitOptions;
|
||||
|
||||
export async function singleUserStateProviderFactory(
|
||||
cache: { singleUserStateProvider?: SingleUserStateProvider } & CachedServices,
|
||||
@@ -26,8 +28,8 @@ export async function singleUserStateProviderFactory(
|
||||
opts,
|
||||
async () =>
|
||||
new DefaultSingleUserStateProvider(
|
||||
await observableMemoryStorageServiceFactory(cache, opts),
|
||||
await observableDiskStorageServiceFactory(cache, opts),
|
||||
await storageServiceProviderFactory(cache, opts),
|
||||
await stateEventRegistrarServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { StateEventRunnerService } from "@bitwarden/common/platform/state";
|
||||
|
||||
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
||||
import {
|
||||
GlobalStateProviderInitOptions,
|
||||
globalStateProviderFactory,
|
||||
} from "./global-state-provider.factory";
|
||||
import {
|
||||
StorageServiceProviderInitOptions,
|
||||
storageServiceProviderFactory,
|
||||
} from "./storage-service-provider.factory";
|
||||
|
||||
type StateEventRunnerServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type StateEventRunnerServiceInitOptions = StateEventRunnerServiceFactoryOptions &
|
||||
GlobalStateProviderInitOptions &
|
||||
StorageServiceProviderInitOptions;
|
||||
|
||||
export function stateEventRunnerServiceFactory(
|
||||
cache: { stateEventRunnerService?: StateEventRunnerService } & CachedServices,
|
||||
opts: StateEventRunnerServiceInitOptions,
|
||||
): Promise<StateEventRunnerService> {
|
||||
return factory(
|
||||
cache,
|
||||
"stateEventRunnerService",
|
||||
opts,
|
||||
async () =>
|
||||
new StateEventRunnerService(
|
||||
await globalStateProviderFactory(cache, opts),
|
||||
await storageServiceProviderFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -320,6 +320,60 @@ describe("BrowserApi", () => {
|
||||
},
|
||||
files: [injectDetails.file],
|
||||
injectImmediately: true,
|
||||
world: "ISOLATED",
|
||||
});
|
||||
expect(result).toEqual(executeScriptResult);
|
||||
});
|
||||
|
||||
it("injects the script into a specified frameId when the extension is built for manifest v3", async () => {
|
||||
const tabId = 1;
|
||||
const frameId = 2;
|
||||
const injectDetails = mock<chrome.tabs.InjectDetails>({
|
||||
file: "file.js",
|
||||
allFrames: true,
|
||||
runAt: "document_start",
|
||||
frameId,
|
||||
});
|
||||
jest.spyOn(BrowserApi, "manifestVersion", "get").mockReturnValue(3);
|
||||
(chrome.scripting.executeScript as jest.Mock).mockResolvedValue(executeScriptResult);
|
||||
|
||||
await BrowserApi.executeScriptInTab(tabId, injectDetails);
|
||||
|
||||
expect(chrome.scripting.executeScript).toHaveBeenCalledWith({
|
||||
target: {
|
||||
tabId: tabId,
|
||||
allFrames: injectDetails.allFrames,
|
||||
frameIds: [frameId],
|
||||
},
|
||||
files: [injectDetails.file],
|
||||
injectImmediately: true,
|
||||
world: "ISOLATED",
|
||||
});
|
||||
});
|
||||
|
||||
it("injects the script into the MAIN world context when injecting a script for manifest v3", async () => {
|
||||
const tabId = 1;
|
||||
const injectDetails = mock<chrome.tabs.InjectDetails>({
|
||||
file: null,
|
||||
allFrames: true,
|
||||
runAt: "document_start",
|
||||
frameId: null,
|
||||
});
|
||||
const scriptingApiDetails = { world: "MAIN" as chrome.scripting.ExecutionWorld };
|
||||
jest.spyOn(BrowserApi, "manifestVersion", "get").mockReturnValue(3);
|
||||
(chrome.scripting.executeScript as jest.Mock).mockResolvedValue(executeScriptResult);
|
||||
|
||||
const result = await BrowserApi.executeScriptInTab(tabId, injectDetails, scriptingApiDetails);
|
||||
|
||||
expect(chrome.scripting.executeScript).toHaveBeenCalledWith({
|
||||
target: {
|
||||
tabId: tabId,
|
||||
allFrames: injectDetails.allFrames,
|
||||
frameIds: null,
|
||||
},
|
||||
files: null,
|
||||
injectImmediately: true,
|
||||
world: "MAIN",
|
||||
});
|
||||
expect(result).toEqual(executeScriptResult);
|
||||
});
|
||||
|
||||
@@ -475,12 +475,19 @@ export class BrowserApi {
|
||||
|
||||
/**
|
||||
* Extension API helper method used to execute a script in a tab.
|
||||
*
|
||||
* @see https://developer.chrome.com/docs/extensions/reference/tabs/#method-executeScript
|
||||
* @param {number} tabId
|
||||
* @param {chrome.tabs.InjectDetails} details
|
||||
* @returns {Promise<unknown>}
|
||||
* @param tabId - The id of the tab to execute the script in.
|
||||
* @param details {@link "InjectDetails" https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/extensionTypes/InjectDetails}
|
||||
* @param scriptingApiDetails {@link "ExecutionWorld" https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/ExecutionWorld}
|
||||
*/
|
||||
static executeScriptInTab(tabId: number, details: chrome.tabs.InjectDetails) {
|
||||
static executeScriptInTab(
|
||||
tabId: number,
|
||||
details: chrome.tabs.InjectDetails,
|
||||
scriptingApiDetails?: {
|
||||
world: chrome.scripting.ExecutionWorld;
|
||||
},
|
||||
): Promise<unknown> {
|
||||
if (BrowserApi.manifestVersion === 3) {
|
||||
return chrome.scripting.executeScript({
|
||||
target: {
|
||||
@@ -490,6 +497,7 @@ export class BrowserApi {
|
||||
},
|
||||
files: details.file ? [details.file] : null,
|
||||
injectImmediately: details.runAt === "document_start",
|
||||
world: scriptingApiDetails?.world || "ISOLATED",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
LoginStrategyServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||
@@ -25,7 +24,6 @@ import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaul
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
|
||||
@@ -42,6 +40,10 @@ import {
|
||||
AutofillSettingsService,
|
||||
AutofillSettingsServiceAbstraction,
|
||||
} from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||
import {
|
||||
UserNotificationSettingsService,
|
||||
UserNotificationSettingsServiceAbstraction,
|
||||
} from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
@@ -207,7 +209,6 @@ function getBgService<T>(service: keyof MainBackground) {
|
||||
},
|
||||
deps: [LogServiceAbstraction, I18nServiceAbstraction],
|
||||
},
|
||||
{ provide: AuditService, useFactory: getBgService<AuditService>("auditService"), deps: [] },
|
||||
{
|
||||
provide: CipherFileUploadService,
|
||||
useFactory: getBgService<CipherFileUploadService>("cipherFileUploadService"),
|
||||
@@ -434,11 +435,6 @@ function getBgService<T>(service: keyof MainBackground) {
|
||||
AccountServiceAbstraction,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: ProviderService,
|
||||
useFactory: getBgService<ProviderService>("providerService"),
|
||||
deps: [],
|
||||
},
|
||||
{
|
||||
provide: SECURE_STORAGE,
|
||||
useFactory: getBgService<AbstractStorageService>("secureStorageService"),
|
||||
@@ -513,13 +509,15 @@ function getBgService<T>(service: keyof MainBackground) {
|
||||
stateService: StateServiceAbstraction,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
) => {
|
||||
return new ThemingService(
|
||||
stateService,
|
||||
// Safari doesn't properly handle the (prefers-color-scheme) media query in the popup window, it always returns light.
|
||||
// In Safari we have to use the background page instead, which comes with limitations like not dynamically changing the extension theme when the system theme is changed.
|
||||
platformUtilsService.isSafari() ? getBgService<Window>("backgroundWindow")() : window,
|
||||
document,
|
||||
);
|
||||
// Safari doesn't properly handle the (prefers-color-scheme) media query in the popup window, it always returns light.
|
||||
// In Safari, we have to use the background page instead, which comes with limitations like not dynamically changing the extension theme when the system theme is changed.
|
||||
let windowContext = window;
|
||||
const backgroundWindow = BrowserApi.getBackgroundPage();
|
||||
if (platformUtilsService.isSafari() && backgroundWindow) {
|
||||
windowContext = backgroundWindow;
|
||||
}
|
||||
|
||||
return new ThemingService(stateService, windowContext, document);
|
||||
},
|
||||
deps: [StateServiceAbstraction, PlatformUtilsService],
|
||||
},
|
||||
@@ -551,6 +549,11 @@ function getBgService<T>(service: keyof MainBackground) {
|
||||
useClass: AutofillSettingsService,
|
||||
deps: [StateProvider, PolicyService],
|
||||
},
|
||||
{
|
||||
provide: UserNotificationSettingsServiceAbstraction,
|
||||
useClass: UserNotificationSettingsService,
|
||||
deps: [StateProvider],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class ServicesModule {}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AbstractThemingService } from "@bitwarden/angular/platform/services/the
|
||||
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
|
||||
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
@@ -47,6 +48,7 @@ export class OptionsComponent implements OnInit {
|
||||
constructor(
|
||||
private messagingService: MessagingService,
|
||||
private stateService: StateService,
|
||||
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
|
||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||
private badgeSettingsService: BadgeSettingsServiceAbstraction,
|
||||
i18nService: I18nService,
|
||||
@@ -95,10 +97,13 @@ export class OptionsComponent implements OnInit {
|
||||
this.autofillSettingsService.autofillOnPageLoadDefault$,
|
||||
);
|
||||
|
||||
this.enableAddLoginNotification = !(await this.stateService.getDisableAddLoginNotification());
|
||||
this.enableAddLoginNotification = await firstValueFrom(
|
||||
this.userNotificationSettingsService.enableAddedLoginPrompt$,
|
||||
);
|
||||
|
||||
this.enableChangedPasswordNotification =
|
||||
!(await this.stateService.getDisableChangedPasswordNotification());
|
||||
this.enableChangedPasswordNotification = await firstValueFrom(
|
||||
this.userNotificationSettingsService.enableChangedPasswordPrompt$,
|
||||
);
|
||||
|
||||
this.enableContextMenuItem = !(await this.stateService.getDisableContextMenuItem());
|
||||
|
||||
@@ -122,12 +127,14 @@ export class OptionsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async updateAddLoginNotification() {
|
||||
await this.stateService.setDisableAddLoginNotification(!this.enableAddLoginNotification);
|
||||
await this.userNotificationSettingsService.setEnableAddedLoginPrompt(
|
||||
this.enableAddLoginNotification,
|
||||
);
|
||||
}
|
||||
|
||||
async updateChangedPasswordNotification() {
|
||||
await this.stateService.setDisableChangedPasswordNotification(
|
||||
!this.enableChangedPasswordNotification,
|
||||
await this.userNotificationSettingsService.setEnableChangedPasswordPrompt(
|
||||
this.enableChangedPasswordNotification,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { FilelessImportTypeKeys } from "../../enums/fileless-import.enums";
|
||||
|
||||
type SuppressDownloadScriptInjectionConfig = {
|
||||
file: string;
|
||||
scriptingApiDetails?: { world: chrome.scripting.ExecutionWorld };
|
||||
};
|
||||
|
||||
type FilelessImportPortMessage = {
|
||||
command?: string;
|
||||
importType?: FilelessImportTypeKeys;
|
||||
@@ -27,6 +32,7 @@ interface FilelessImporterBackground {
|
||||
}
|
||||
|
||||
export {
|
||||
SuppressDownloadScriptInjectionConfig,
|
||||
FilelessImportPortMessage,
|
||||
ImportNotificationMessageHandlers,
|
||||
LpImporterMessageHandlers,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
@@ -14,10 +15,20 @@ import {
|
||||
sendPortMessage,
|
||||
triggerRuntimeOnConnectEvent,
|
||||
} from "../../autofill/spec/testing-utils";
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
import { FilelessImportPort, FilelessImportType } from "../enums/fileless-import.enums";
|
||||
|
||||
import FilelessImporterBackground from "./fileless-importer.background";
|
||||
|
||||
jest.mock("rxjs", () => {
|
||||
const rxjs = jest.requireActual("rxjs");
|
||||
const { firstValueFrom } = rxjs;
|
||||
return {
|
||||
...rxjs,
|
||||
firstValueFrom: jest.fn(firstValueFrom),
|
||||
};
|
||||
});
|
||||
|
||||
describe("FilelessImporterBackground ", () => {
|
||||
let filelessImporterBackground: FilelessImporterBackground;
|
||||
const configService = mock<ConfigService>();
|
||||
@@ -51,14 +62,17 @@ describe("FilelessImporterBackground ", () => {
|
||||
|
||||
describe("handle ports onConnect", () => {
|
||||
let lpImporterPort: chrome.runtime.Port;
|
||||
let manifestVersionSpy: jest.SpyInstance;
|
||||
let executeScriptInTabSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
lpImporterPort = createPortSpyMock(FilelessImportPort.LpImporter);
|
||||
manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get");
|
||||
executeScriptInTabSpy = jest.spyOn(BrowserApi, "executeScriptInTab").mockResolvedValue(null);
|
||||
jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Unlocked);
|
||||
jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(true);
|
||||
jest
|
||||
.spyOn(filelessImporterBackground as any, "removeIndividualVault")
|
||||
.mockResolvedValue(false);
|
||||
jest.spyOn(filelessImporterBackground as any, "removeIndividualVault");
|
||||
(firstValueFrom as jest.Mock).mockResolvedValue(false);
|
||||
});
|
||||
|
||||
it("ignores the port connection if the port name is not present in the set of filelessImportNames", async () => {
|
||||
@@ -83,9 +97,7 @@ describe("FilelessImporterBackground ", () => {
|
||||
});
|
||||
|
||||
it("posts a message to the port indicating that the fileless import feature is disabled if the user's policy removes individual vaults", async () => {
|
||||
jest
|
||||
.spyOn(filelessImporterBackground as any, "removeIndividualVault")
|
||||
.mockResolvedValue(true);
|
||||
(firstValueFrom as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
triggerRuntimeOnConnectEvent(lpImporterPort);
|
||||
await flushPromises();
|
||||
@@ -117,6 +129,35 @@ describe("FilelessImporterBackground ", () => {
|
||||
filelessImportEnabled: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("triggers an injection of the `lp-suppress-import-download.js` script in manifest v3", async () => {
|
||||
manifestVersionSpy.mockReturnValue(3);
|
||||
|
||||
triggerRuntimeOnConnectEvent(lpImporterPort);
|
||||
await flushPromises();
|
||||
|
||||
expect(executeScriptInTabSpy).toHaveBeenCalledWith(
|
||||
lpImporterPort.sender.tab.id,
|
||||
{ file: "content/lp-suppress-import-download.js", runAt: "document_start" },
|
||||
{ world: "MAIN" },
|
||||
);
|
||||
});
|
||||
|
||||
it("triggers an injection of the `lp-suppress-import-download-script-append-mv2.js` script in manifest v2", async () => {
|
||||
manifestVersionSpy.mockReturnValue(2);
|
||||
|
||||
triggerRuntimeOnConnectEvent(lpImporterPort);
|
||||
await flushPromises();
|
||||
|
||||
expect(executeScriptInTabSpy).toHaveBeenCalledWith(
|
||||
lpImporterPort.sender.tab.id,
|
||||
{
|
||||
file: "content/lp-suppress-import-download-script-append-mv2.js",
|
||||
runAt: "document_start",
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("port messages", () => {
|
||||
@@ -126,9 +167,7 @@ describe("FilelessImporterBackground ", () => {
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Unlocked);
|
||||
jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(true);
|
||||
jest
|
||||
.spyOn(filelessImporterBackground as any, "removeIndividualVault")
|
||||
.mockResolvedValue(false);
|
||||
(firstValueFrom as jest.Mock).mockResolvedValue(false);
|
||||
triggerRuntimeOnConnectEvent(createPortSpyMock(FilelessImportPort.NotificationBar));
|
||||
triggerRuntimeOnConnectEvent(createPortSpyMock(FilelessImportPort.LpImporter));
|
||||
await flushPromises();
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ImportServiceAbstraction } from "@bitwarden/importer/core";
|
||||
|
||||
import NotificationBackground from "../../autofill/background/notification.background";
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
import { FilelessImporterInjectedScriptsConfig } from "../config/fileless-importer-injected-scripts";
|
||||
import {
|
||||
FilelessImportPort,
|
||||
FilelessImportType,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
LpImporterMessageHandlers,
|
||||
FilelessImporterBackground as FilelessImporterBackgroundInterface,
|
||||
FilelessImportPortMessage,
|
||||
SuppressDownloadScriptInjectionConfig,
|
||||
} from "./abstractions/fileless-importer.background";
|
||||
|
||||
class FilelessImporterBackground implements FilelessImporterBackgroundInterface {
|
||||
@@ -108,6 +110,23 @@ class FilelessImporterBackground implements FilelessImporterBackgroundInterface
|
||||
await this.notificationBackground.requestFilelessImport(tab, importType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the script used to suppress the download of the LP importer export file.
|
||||
*
|
||||
* @param sender - The sender of the message.
|
||||
* @param injectionConfig - The configuration for the injection.
|
||||
*/
|
||||
private async injectScriptConfig(
|
||||
sender: chrome.runtime.MessageSender,
|
||||
injectionConfig: SuppressDownloadScriptInjectionConfig,
|
||||
) {
|
||||
await BrowserApi.executeScriptInTab(
|
||||
sender.tab.id,
|
||||
{ file: injectionConfig.file, runAt: "document_start" },
|
||||
injectionConfig.scriptingApiDetails,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the download of the CSV file from the LP importer. This is triggered
|
||||
* when the user opts to not save the export to Bitwarden within the notification bar.
|
||||
@@ -200,6 +219,12 @@ class FilelessImporterBackground implements FilelessImporterBackgroundInterface
|
||||
switch (port.name) {
|
||||
case FilelessImportPort.LpImporter:
|
||||
this.lpImporterPort = port;
|
||||
await this.injectScriptConfig(
|
||||
port.sender,
|
||||
BrowserApi.manifestVersion === 3
|
||||
? FilelessImporterInjectedScriptsConfig.LpSuppressImportDownload.mv3
|
||||
: FilelessImporterInjectedScriptsConfig.LpSuppressImportDownload.mv2,
|
||||
);
|
||||
break;
|
||||
case FilelessImportPort.NotificationBar:
|
||||
this.importNotificationsPort = port;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { SuppressDownloadScriptInjectionConfig } from "../background/abstractions/fileless-importer.background";
|
||||
|
||||
type FilelessImporterInjectedScriptsConfigurations = {
|
||||
LpSuppressImportDownload: {
|
||||
mv2: SuppressDownloadScriptInjectionConfig;
|
||||
mv3: SuppressDownloadScriptInjectionConfig;
|
||||
};
|
||||
};
|
||||
|
||||
const FilelessImporterInjectedScriptsConfig: FilelessImporterInjectedScriptsConfigurations = {
|
||||
LpSuppressImportDownload: {
|
||||
mv2: {
|
||||
file: "content/lp-suppress-import-download-script-append-mv2.js",
|
||||
},
|
||||
mv3: {
|
||||
file: "content/lp-suppress-import-download.js",
|
||||
scriptingApiDetails: { world: "MAIN" },
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export { FilelessImporterInjectedScriptsConfig };
|
||||
@@ -43,20 +43,6 @@ describe("LpFilelessImporter", () => {
|
||||
expect(portSpy.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("injects a script element that suppresses the download of the LastPass export", () => {
|
||||
const script = document.createElement("script");
|
||||
jest.spyOn(document, "createElement").mockReturnValue(script);
|
||||
jest.spyOn(document.documentElement, "appendChild");
|
||||
|
||||
lpFilelessImporter.handleFeatureFlagVerification({ filelessImportEnabled: true });
|
||||
|
||||
expect(document.createElement).toHaveBeenCalledWith("script");
|
||||
expect(document.documentElement.appendChild).toHaveBeenCalled();
|
||||
expect(script.textContent).toContain(
|
||||
"const defaultAppendChild = Element.prototype.appendChild;",
|
||||
);
|
||||
});
|
||||
|
||||
it("sets up an event listener for DOMContentLoaded that triggers the importer when the document ready state is `loading`", () => {
|
||||
Object.defineProperty(document, "readyState", {
|
||||
value: "loading",
|
||||
|
||||
@@ -36,7 +36,6 @@ class LpFilelessImporter implements LpFilelessImporterInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
this.suppressDownload();
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", this.loadImporter);
|
||||
return;
|
||||
@@ -52,46 +51,6 @@ class LpFilelessImporter implements LpFilelessImporterInterface {
|
||||
this.postWindowMessage({ command: "triggerCsvDownload" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppresses the download of the CSV file by overriding the `download` attribute of the
|
||||
* anchor element that is created by the LP importer. This is done by injecting a script
|
||||
* into the page that overrides the `appendChild` method of the `Element` prototype.
|
||||
*/
|
||||
private suppressDownload() {
|
||||
const script = document.createElement("script");
|
||||
script.textContent = `
|
||||
let csvDownload = '';
|
||||
let csvHref = '';
|
||||
const defaultAppendChild = Element.prototype.appendChild;
|
||||
Element.prototype.appendChild = function (newChild) {
|
||||
if (newChild.nodeName.toLowerCase() === 'a' && newChild.download) {
|
||||
csvDownload = newChild.download;
|
||||
csvHref = newChild.href;
|
||||
newChild.setAttribute('href', 'javascript:void(0)');
|
||||
newChild.setAttribute('download', '');
|
||||
Element.prototype.appendChild = defaultAppendChild;
|
||||
}
|
||||
|
||||
return defaultAppendChild.call(this, newChild);
|
||||
};
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
const command = event.data?.command;
|
||||
if (event.source !== window || command !== 'triggerCsvDownload') {
|
||||
return;
|
||||
}
|
||||
|
||||
const anchor = document.createElement('a');
|
||||
anchor.setAttribute('href', csvHref);
|
||||
anchor.setAttribute('download', csvDownload);
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
});
|
||||
`;
|
||||
document.documentElement.appendChild(script);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the importing mechanism used to import the CSV file into Bitwarden.
|
||||
* This is done by observing the DOM for the addition of the LP importer element.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
describe("LP Suppress Import Download for Manifest v2", () => {
|
||||
it("appends the `lp-suppress-import-download.js` script to the document element", () => {
|
||||
let createdScriptElement: HTMLScriptElement;
|
||||
jest.spyOn(window.document, "createElement");
|
||||
jest.spyOn(window.document.documentElement, "appendChild").mockImplementation((node) => {
|
||||
createdScriptElement = node as HTMLScriptElement;
|
||||
return node;
|
||||
});
|
||||
|
||||
require("./lp-suppress-import-download-script-append.mv2");
|
||||
|
||||
expect(window.document.createElement).toHaveBeenCalledWith("script");
|
||||
expect(chrome.runtime.getURL).toHaveBeenCalledWith("content/lp-suppress-import-download.js");
|
||||
expect(window.document.documentElement.appendChild).toHaveBeenCalledWith(
|
||||
expect.any(HTMLScriptElement),
|
||||
);
|
||||
expect(createdScriptElement.src).toBe(
|
||||
"chrome-extension://id/content/lp-suppress-import-download.js",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* This script handles injection of the LP suppress import download script into the document.
|
||||
* This is required for manifest v2, but will be removed when we migrate fully to manifest v3.
|
||||
*/
|
||||
(function (globalContext) {
|
||||
const script = globalContext.document.createElement("script");
|
||||
script.src = chrome.runtime.getURL("content/lp-suppress-import-download.js");
|
||||
globalContext.document.documentElement.appendChild(script);
|
||||
})(window);
|
||||
@@ -0,0 +1,81 @@
|
||||
import { flushPromises, postWindowMessage } from "../../autofill/spec/testing-utils";
|
||||
|
||||
describe("LP Suppress Import Download", () => {
|
||||
const downloadAttribute = "file.csv";
|
||||
const hrefAttribute = "https://example.com/file.csv";
|
||||
const overridenHrefAttribute = "javascript:void(0)";
|
||||
let anchor: HTMLAnchorElement;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Element.prototype, "appendChild");
|
||||
jest.spyOn(window, "addEventListener");
|
||||
|
||||
require("./lp-suppress-import-download");
|
||||
|
||||
anchor = document.createElement("a");
|
||||
anchor.download = downloadAttribute;
|
||||
anchor.href = hrefAttribute;
|
||||
anchor.click = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetModules();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("disables the automatic download anchor", () => {
|
||||
document.body.appendChild(anchor);
|
||||
|
||||
expect(anchor.href).toBe(overridenHrefAttribute);
|
||||
expect(anchor.download).toBe("");
|
||||
});
|
||||
|
||||
it("triggers the CSVDownload when receiving a `triggerCsvDownload` window message", async () => {
|
||||
window.document.createElement = jest.fn(() => anchor);
|
||||
jest.spyOn(window, "removeEventListener");
|
||||
|
||||
document.body.appendChild(anchor);
|
||||
|
||||
// Precondition - Ensure the anchor in the document has overridden href and download attributes
|
||||
expect(anchor.href).toBe(overridenHrefAttribute);
|
||||
expect(anchor.download).toBe("");
|
||||
|
||||
postWindowMessage({ command: "triggerCsvDownload" });
|
||||
await flushPromises();
|
||||
|
||||
expect(anchor.click).toHaveBeenCalled();
|
||||
expect(anchor.href).toEqual(hrefAttribute);
|
||||
expect(anchor.download).toEqual(downloadAttribute);
|
||||
expect(window.removeEventListener).toHaveBeenCalledWith("message", expect.any(Function));
|
||||
});
|
||||
|
||||
it("skips subsequent calls to trigger a CSVDownload", async () => {
|
||||
window.document.createElement = jest.fn(() => anchor);
|
||||
|
||||
document.body.appendChild(anchor);
|
||||
|
||||
postWindowMessage({ command: "triggerCsvDownload" });
|
||||
await flushPromises();
|
||||
|
||||
postWindowMessage({ command: "triggerCsvDownload" });
|
||||
await flushPromises();
|
||||
|
||||
expect(anchor.click).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("skips triggering the CSV download for window messages that do not have the correct command", () => {
|
||||
document.body.appendChild(anchor);
|
||||
|
||||
postWindowMessage({ command: "notTriggerCsvDownload" });
|
||||
|
||||
expect(anchor.click).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips triggering the CSV download for window messages that do not have a data value", () => {
|
||||
document.body.appendChild(anchor);
|
||||
|
||||
postWindowMessage(null);
|
||||
|
||||
expect(anchor.click).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Handles intercepting the injection of the CSV download link, and ensures the
|
||||
* download of the script is suppressed until the user opts to download the file.
|
||||
* The download is triggered by a window message sent from the LpFilelessImporter
|
||||
* content script.
|
||||
*/
|
||||
(function (globalContext) {
|
||||
let csvDownload = "";
|
||||
let csvHref = "";
|
||||
let isCsvDownloadTriggered = false;
|
||||
const defaultAppendChild = Element.prototype.appendChild;
|
||||
Element.prototype.appendChild = function (newChild: Node) {
|
||||
if (isAnchorElement(newChild) && newChild.download) {
|
||||
csvDownload = newChild.download;
|
||||
csvHref = newChild.href;
|
||||
newChild.setAttribute("href", "javascript:void(0)");
|
||||
newChild.setAttribute("download", "");
|
||||
Element.prototype.appendChild = defaultAppendChild;
|
||||
}
|
||||
|
||||
return defaultAppendChild.call(this, newChild);
|
||||
};
|
||||
|
||||
function isAnchorElement(node: Node): node is HTMLAnchorElement {
|
||||
return node.nodeName.toLowerCase() === "a";
|
||||
}
|
||||
|
||||
const handleWindowMessage = (event: MessageEvent) => {
|
||||
const command = event.data?.command;
|
||||
if (
|
||||
event.source !== globalContext ||
|
||||
command !== "triggerCsvDownload" ||
|
||||
isCsvDownloadTriggered
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
isCsvDownloadTriggered = true;
|
||||
globalContext.removeEventListener("message", handleWindowMessage);
|
||||
|
||||
const anchor = globalContext.document.createElement("a");
|
||||
anchor.setAttribute("href", csvHref);
|
||||
anchor.setAttribute("download", csvDownload);
|
||||
globalContext.document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
globalContext.document.body.removeChild(anchor);
|
||||
};
|
||||
|
||||
globalContext.addEventListener("message", handleWindowMessage);
|
||||
})(window);
|
||||
@@ -179,6 +179,7 @@ const mainConfig = {
|
||||
"overlay/list": "./src/autofill/overlay/pages/list/bootstrap-autofill-overlay-list.ts",
|
||||
"encrypt-worker": "../../libs/common/src/platform/services/cryptography/encrypt.worker.ts",
|
||||
"content/lp-fileless-importer": "./src/tools/content/lp-fileless-importer.ts",
|
||||
"content/lp-suppress-import-download": "./src/tools/content/lp-suppress-import-download.ts",
|
||||
},
|
||||
optimization: {
|
||||
minimize: ENV !== "development",
|
||||
@@ -276,6 +277,8 @@ if (manifestVersion == 2) {
|
||||
// Manifest V2 background pages can be run through the regular build pipeline.
|
||||
// Since it's a standard webpage.
|
||||
mainConfig.entry.background = "./src/platform/background.ts";
|
||||
mainConfig.entry["content/lp-suppress-import-download-script-append-mv2"] =
|
||||
"./src/tools/content/lp-suppress-import-download-script-append.mv2.ts";
|
||||
|
||||
configs.push(mainConfig);
|
||||
} else {
|
||||
|
||||
@@ -60,11 +60,13 @@ import { MigrationBuilderService } from "@bitwarden/common/platform/services/mig
|
||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||
import { NoopMessagingService } from "@bitwarden/common/platform/services/noop-messaging.service";
|
||||
import { StateService } from "@bitwarden/common/platform/services/state.service";
|
||||
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||
import {
|
||||
ActiveUserStateProvider,
|
||||
DerivedStateProvider,
|
||||
GlobalStateProvider,
|
||||
SingleUserStateProvider,
|
||||
StateEventRunnerService,
|
||||
StateProvider,
|
||||
} from "@bitwarden/common/platform/state";
|
||||
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally these should not be accessed */
|
||||
@@ -73,6 +75,7 @@ import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/im
|
||||
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
|
||||
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
||||
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
||||
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
|
||||
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
||||
/* eslint-enable import/no-restricted-paths */
|
||||
import { AuditService } from "@bitwarden/common/services/audit.service";
|
||||
@@ -208,6 +211,7 @@ export class Main {
|
||||
derivedStateProvider: DerivedStateProvider;
|
||||
stateProvider: StateProvider;
|
||||
loginStrategyService: LoginStrategyServiceAbstraction;
|
||||
stateEventRunnerService: StateEventRunnerService;
|
||||
biometricStateService: BiometricStateService;
|
||||
|
||||
constructor() {
|
||||
@@ -249,14 +253,26 @@ export class Main {
|
||||
this.memoryStorageService = new MemoryStorageService();
|
||||
this.memoryStorageForStateProviders = new MemoryStorageServiceForStateProviders();
|
||||
|
||||
this.globalStateProvider = new DefaultGlobalStateProvider(
|
||||
this.memoryStorageForStateProviders,
|
||||
const storageServiceProvider = new StorageServiceProvider(
|
||||
this.storageService,
|
||||
this.memoryStorageForStateProviders,
|
||||
);
|
||||
|
||||
this.globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
||||
|
||||
const stateEventRegistrarService = new StateEventRegistrarService(
|
||||
this.globalStateProvider,
|
||||
storageServiceProvider,
|
||||
);
|
||||
|
||||
this.stateEventRunnerService = new StateEventRunnerService(
|
||||
this.globalStateProvider,
|
||||
storageServiceProvider,
|
||||
);
|
||||
|
||||
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
||||
this.memoryStorageForStateProviders,
|
||||
this.storageService,
|
||||
storageServiceProvider,
|
||||
stateEventRegistrarService,
|
||||
);
|
||||
|
||||
this.messagingService = new NoopMessagingService();
|
||||
@@ -269,8 +285,8 @@ export class Main {
|
||||
|
||||
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
|
||||
this.accountService,
|
||||
this.memoryStorageForStateProviders,
|
||||
this.storageService,
|
||||
storageServiceProvider,
|
||||
stateEventRegistrarService,
|
||||
);
|
||||
|
||||
this.derivedStateProvider = new DefaultDerivedStateProvider(
|
||||
@@ -372,7 +388,7 @@ export class Main {
|
||||
this.stateProvider,
|
||||
);
|
||||
|
||||
this.providerService = new ProviderService(this.stateService);
|
||||
this.providerService = new ProviderService(this.stateProvider);
|
||||
|
||||
this.organizationService = new OrganizationService(this.stateService, this.stateProvider);
|
||||
|
||||
@@ -530,6 +546,7 @@ export class Main {
|
||||
this.stateService,
|
||||
this.authService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
this.stateEventRunnerService,
|
||||
lockedCallback,
|
||||
null,
|
||||
);
|
||||
@@ -638,7 +655,11 @@ export class Main {
|
||||
this.collectionService.clear(userId as UserId),
|
||||
this.policyService.clear(userId),
|
||||
this.passwordGenerationService.clear(),
|
||||
this.providerService.save(null, userId as UserId),
|
||||
]);
|
||||
|
||||
await this.stateEventRunnerService.handleEvent("logout", userId as UserId);
|
||||
|
||||
await this.stateService.clean();
|
||||
process.env.BW_SESSION = null;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { SettingsService } from "@bitwarden/common/abstractions/settings.service
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
@@ -39,6 +40,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
|
||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
import { StateEventRunnerService } from "@bitwarden/common/platform/state";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@@ -149,6 +151,8 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private configService: ConfigServiceAbstraction,
|
||||
private dialogService: DialogService,
|
||||
private biometricStateService: BiometricStateService,
|
||||
private stateEventRunnerService: StateEventRunnerService,
|
||||
private providerService: ProviderService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -219,13 +223,13 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
const currentUser = await this.stateService.getUserId();
|
||||
const accounts = await firstValueFrom(this.stateService.accounts$);
|
||||
await this.vaultTimeoutService.lock(currentUser);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
Promise.all(
|
||||
Object.keys(accounts)
|
||||
.filter((u) => u !== currentUser)
|
||||
.map((u) => this.vaultTimeoutService.lock(u)),
|
||||
);
|
||||
for (const account of Object.keys(accounts)) {
|
||||
if (account === currentUser) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.vaultTimeoutService.lock(account);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "locked":
|
||||
@@ -582,6 +586,9 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
await this.policyService.clear(userBeingLoggedOut);
|
||||
await this.keyConnectorService.clear();
|
||||
await this.biometricStateService.logout(userBeingLoggedOut as UserId);
|
||||
await this.providerService.save(null, userBeingLoggedOut as UserId);
|
||||
|
||||
await this.stateEventRunnerService.handleEvent("logout", userBeingLoggedOut as UserId);
|
||||
|
||||
preLogoutActiveUserId = this.activeUserId;
|
||||
await this.stateService.clean({ userId: userBeingLoggedOut });
|
||||
|
||||
@@ -13,11 +13,13 @@ import { MigrationBuilderService } from "@bitwarden/common/platform/services/mig
|
||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||
import { NoopMessagingService } from "@bitwarden/common/platform/services/noop-messaging.service";
|
||||
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally this should not be accessed */
|
||||
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
|
||||
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
|
||||
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
|
||||
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
||||
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
||||
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
|
||||
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
||||
/* eslint-enable import/no-restricted-paths */
|
||||
|
||||
@@ -104,10 +106,11 @@ export class Main {
|
||||
this.storageService = new ElectronStorageService(app.getPath("userData"), storageDefaults);
|
||||
this.memoryStorageService = new MemoryStorageService();
|
||||
this.memoryStorageForStateProviders = new MemoryStorageServiceForStateProviders();
|
||||
const globalStateProvider = new DefaultGlobalStateProvider(
|
||||
this.memoryStorageForStateProviders,
|
||||
const storageServiceProvider = new StorageServiceProvider(
|
||||
this.storageService,
|
||||
this.memoryStorageForStateProviders,
|
||||
);
|
||||
const globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
||||
|
||||
const accountService = new AccountServiceImplementation(
|
||||
new NoopMessagingService(),
|
||||
@@ -115,13 +118,18 @@ export class Main {
|
||||
globalStateProvider,
|
||||
);
|
||||
|
||||
const stateEventRegistrarService = new StateEventRegistrarService(
|
||||
globalStateProvider,
|
||||
storageServiceProvider,
|
||||
);
|
||||
|
||||
const stateProvider = new DefaultStateProvider(
|
||||
new DefaultActiveUserStateProvider(
|
||||
accountService,
|
||||
this.memoryStorageForStateProviders,
|
||||
this.storageService,
|
||||
storageServiceProvider,
|
||||
stateEventRegistrarService,
|
||||
),
|
||||
new DefaultSingleUserStateProvider(this.memoryStorageForStateProviders, this.storageService),
|
||||
new DefaultSingleUserStateProvider(storageServiceProvider, stateEventRegistrarService),
|
||||
globalStateProvider,
|
||||
new DefaultDerivedStateProvider(this.memoryStorageForStateProviders),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@bitwarden/web-vault",
|
||||
"version": "2024.2.3",
|
||||
"version": "2024.2.4",
|
||||
"scripts": {
|
||||
"build:oss": "webpack",
|
||||
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
canAccessBillingTab,
|
||||
canAccessGroupsTab,
|
||||
canAccessMembersTab,
|
||||
canAccessOrgAdmin,
|
||||
canAccessReportingTab,
|
||||
canAccessSettingsTab,
|
||||
canAccessVaultTab,
|
||||
@@ -43,7 +44,7 @@ import { AdminConsoleLogo } from "../../icons/admin-console-logo";
|
||||
export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
protected readonly logo = AdminConsoleLogo;
|
||||
|
||||
protected orgFilter = (org: Organization) => org.isAdmin;
|
||||
protected orgFilter = (org: Organization) => canAccessOrgAdmin(org);
|
||||
|
||||
organization$: Observable<Organization>;
|
||||
showPaymentAndHistory$: Observable<boolean>;
|
||||
|
||||
@@ -23,6 +23,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
import { StateEventRunnerService } from "@bitwarden/common/platform/state";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@@ -89,6 +90,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
private configService: ConfigServiceAbstraction,
|
||||
private dialogService: DialogService,
|
||||
private biometricStateService: BiometricStateService,
|
||||
private stateEventRunnerService: StateEventRunnerService,
|
||||
private paymentMethodWarningService: PaymentMethodWarningService,
|
||||
private organizationService: OrganizationService,
|
||||
) {}
|
||||
@@ -284,6 +286,8 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
this.paymentMethodWarningService.clear(),
|
||||
]);
|
||||
|
||||
await this.stateEventRunnerService.handleEvent("logout", userId as UserId);
|
||||
|
||||
this.searchService.clearIndex();
|
||||
this.authService.logOut(async () => {
|
||||
if (expired) {
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
} from "@bitwarden/angular/services/injection-tokens";
|
||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||
import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { LoginService } from "@bitwarden/common/auth/services/login.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
@@ -28,21 +27,16 @@ import { StateFactory } from "@bitwarden/common/platform/factories/state-factory
|
||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||
import {
|
||||
ActiveUserStateProvider,
|
||||
GlobalStateProvider,
|
||||
SingleUserStateProvider,
|
||||
} from "@bitwarden/common/platform/state";
|
||||
// eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage
|
||||
/* eslint-disable import/no-restricted-paths -- Implementation for memory storage */
|
||||
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
||||
/* eslint-enable import/no-restricted-paths -- Implementation for memory storage */
|
||||
|
||||
import { PolicyListService } from "../admin-console/core/policy-list.service";
|
||||
import { HtmlStorageService } from "../core/html-storage.service";
|
||||
import { I18nService } from "../core/i18n.service";
|
||||
import { WebActiveUserStateProvider } from "../platform/web-active-user-state.provider";
|
||||
import { WebGlobalStateProvider } from "../platform/web-global-state.provider";
|
||||
import { WebMigrationRunner } from "../platform/web-migration-runner";
|
||||
import { WebSingleUserStateProvider } from "../platform/web-single-user-state.provider";
|
||||
import { WebStorageServiceProvider } from "../platform/web-storage-service.provider";
|
||||
import { WindowStorageService } from "../platform/window-storage.service";
|
||||
import { CollectionAdminService } from "../vault/core/collection-admin.service";
|
||||
|
||||
@@ -124,24 +118,9 @@ import { WebPlatformUtilsService } from "./web-platform-utils.service";
|
||||
useFactory: () => new WindowStorageService(window.localStorage),
|
||||
},
|
||||
{
|
||||
provide: SingleUserStateProvider,
|
||||
useClass: WebSingleUserStateProvider,
|
||||
deps: [OBSERVABLE_MEMORY_STORAGE, OBSERVABLE_DISK_STORAGE, OBSERVABLE_DISK_LOCAL_STORAGE],
|
||||
},
|
||||
{
|
||||
provide: ActiveUserStateProvider,
|
||||
useClass: WebActiveUserStateProvider,
|
||||
deps: [
|
||||
AccountService,
|
||||
OBSERVABLE_MEMORY_STORAGE,
|
||||
OBSERVABLE_DISK_STORAGE,
|
||||
OBSERVABLE_DISK_LOCAL_STORAGE,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: GlobalStateProvider,
|
||||
useClass: WebGlobalStateProvider,
|
||||
deps: [OBSERVABLE_MEMORY_STORAGE, OBSERVABLE_DISK_STORAGE, OBSERVABLE_DISK_LOCAL_STORAGE],
|
||||
provide: StorageServiceProvider,
|
||||
useClass: WebStorageServiceProvider,
|
||||
deps: [OBSERVABLE_DISK_STORAGE, OBSERVABLE_MEMORY_STORAGE, OBSERVABLE_DISK_LOCAL_STORAGE],
|
||||
},
|
||||
{
|
||||
provide: MigrationRunner,
|
||||
|
||||
@@ -58,9 +58,9 @@ export class ProductSwitcherContentComponent {
|
||||
|
||||
// If the active route org doesn't have access to AC, find the first org that does.
|
||||
const acOrg =
|
||||
routeOrg != null && canAccessOrgAdmin(routeOrg) && routeOrg.enabled
|
||||
routeOrg != null && canAccessOrgAdmin(routeOrg)
|
||||
? routeOrg
|
||||
: orgs.find((o) => canAccessOrgAdmin(o) && o.enabled);
|
||||
: orgs.find((o) => canAccessOrgAdmin(o));
|
||||
|
||||
// TODO: This should be migrated to an Observable provided by the provider service and moved to the combineLatest above. See AC-2092.
|
||||
const providers = await this.providerService.getAll();
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { UserKeyDefinition } from "@bitwarden/common/platform/state";
|
||||
/* eslint-disable import/no-restricted-paths -- Needed to extend class & in platform owned code */
|
||||
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
|
||||
import { StateDefinition } from "@bitwarden/common/platform/state/state-definition";
|
||||
/* eslint-enable import/no-restricted-paths */
|
||||
|
||||
export class WebActiveUserStateProvider extends DefaultActiveUserStateProvider {
|
||||
constructor(
|
||||
accountService: AccountService,
|
||||
memoryStorage: AbstractMemoryStorageService & ObservableStorageService,
|
||||
sessionStorage: AbstractStorageService & ObservableStorageService,
|
||||
private readonly diskLocalStorage: AbstractStorageService & ObservableStorageService,
|
||||
) {
|
||||
super(accountService, memoryStorage, sessionStorage);
|
||||
}
|
||||
|
||||
protected override getLocationString(keyDefinition: UserKeyDefinition<unknown>): string {
|
||||
return (
|
||||
keyDefinition.stateDefinition.storageLocationOverrides["web"] ??
|
||||
keyDefinition.stateDefinition.defaultStorageLocation
|
||||
);
|
||||
}
|
||||
|
||||
protected override getLocation(
|
||||
stateDefinition: StateDefinition,
|
||||
): AbstractStorageService & ObservableStorageService {
|
||||
const location =
|
||||
stateDefinition.storageLocationOverrides["web"] ?? stateDefinition.defaultStorageLocation;
|
||||
switch (location) {
|
||||
case "disk":
|
||||
return this.diskStorage;
|
||||
case "memory":
|
||||
return this.memoryStorage;
|
||||
case "disk-local":
|
||||
return this.diskLocalStorage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { KeyDefinition } from "@bitwarden/common/platform/state";
|
||||
/* eslint-disable import/no-restricted-paths -- Needed to extend class & in platform owned code*/
|
||||
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
|
||||
import { StateDefinition } from "@bitwarden/common/platform/state/state-definition";
|
||||
/* eslint-enable import/no-restricted-paths */
|
||||
|
||||
export class WebGlobalStateProvider extends DefaultGlobalStateProvider {
|
||||
constructor(
|
||||
memoryStorage: AbstractMemoryStorageService & ObservableStorageService,
|
||||
sessionStorage: AbstractStorageService & ObservableStorageService,
|
||||
private readonly diskLocalStorage: AbstractStorageService & ObservableStorageService,
|
||||
) {
|
||||
super(memoryStorage, sessionStorage);
|
||||
}
|
||||
|
||||
protected getLocationString(keyDefinition: KeyDefinition<unknown>): string {
|
||||
return (
|
||||
keyDefinition.stateDefinition.storageLocationOverrides["web"] ??
|
||||
keyDefinition.stateDefinition.defaultStorageLocation
|
||||
);
|
||||
}
|
||||
|
||||
protected override getLocation(
|
||||
stateDefinition: StateDefinition,
|
||||
): AbstractStorageService & ObservableStorageService {
|
||||
const location =
|
||||
stateDefinition.storageLocationOverrides["web"] ?? stateDefinition.defaultStorageLocation;
|
||||
switch (location) {
|
||||
case "disk":
|
||||
return this.diskStorage;
|
||||
case "memory":
|
||||
return this.memoryStorage;
|
||||
case "disk-local":
|
||||
return this.diskLocalStorage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { UserKeyDefinition } from "@bitwarden/common/platform/state";
|
||||
/* eslint-disable import/no-restricted-paths -- Needed to extend service & and in platform owned file */
|
||||
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
||||
import { StateDefinition } from "@bitwarden/common/platform/state/state-definition";
|
||||
/* eslint-enable import/no-restricted-paths */
|
||||
|
||||
export class WebSingleUserStateProvider extends DefaultSingleUserStateProvider {
|
||||
constructor(
|
||||
memoryStorageService: AbstractMemoryStorageService & ObservableStorageService,
|
||||
sessionStorageService: AbstractStorageService & ObservableStorageService,
|
||||
private readonly diskLocalStorageService: AbstractStorageService & ObservableStorageService,
|
||||
) {
|
||||
super(memoryStorageService, sessionStorageService);
|
||||
}
|
||||
|
||||
protected override getLocationString(keyDefinition: UserKeyDefinition<unknown>): string {
|
||||
return (
|
||||
keyDefinition.stateDefinition.storageLocationOverrides["web"] ??
|
||||
keyDefinition.stateDefinition.defaultStorageLocation
|
||||
);
|
||||
}
|
||||
|
||||
protected override getLocation(
|
||||
stateDefinition: StateDefinition,
|
||||
): AbstractStorageService & ObservableStorageService {
|
||||
const location =
|
||||
stateDefinition.storageLocationOverrides["web"] ?? stateDefinition.defaultStorageLocation;
|
||||
|
||||
switch (location) {
|
||||
case "disk":
|
||||
return this.diskStorage;
|
||||
case "memory":
|
||||
return this.memoryStorage;
|
||||
case "disk-local":
|
||||
return this.diskLocalStorageService;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user