1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 13:53:34 +00:00

[PM-5560] Implement Autofill Settings state provider (#7767)

* Begin migration of autofill settings

Co-authored-by: Cesar Gonzalez <cagonzalezcs@users.noreply.github.com>
Co-authored-by: Thomas Avery <Thomas-Avery@users.noreply.github.com>
Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com>
Co-authored-by: Colton Hurst <coltonhurst@users.noreply.github.com>

* add browser dependency for AutofillSettingsService

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>

* update autofill settings service

* replace usages of stateService get/set autofillOnPageLoad with autofillSettingsService

* replace usages of stateService get/set autofillOnPageLoadDefault with autofillSettingsService

* replace usages of stateService get/set autoCopyTotp with autofillSettingsService

* replace usages of stateService get/set autoFillOnPageLoadCalloutIsDismissed with autofillSettingsService

* replace usages of stateService get/set activateAutoFillOnPageLoadFromPolicy with autofillSettingsService

* replace usages of get/set autoFillOverlayVisibility with autofillSettingsService

* inlineMenuVisibility should use global state

* add the AutofillSettingsService to background scripts

* fix typing

* replace additional usages of get/set autoFillOverlayVisibility and disableAutoTotpCopy with autofillSettingsService equivalents

* replace additional usages of get/set autofillOnPageLoadDefault with autofillSettingsService equivalent

* replace additional usages of get/set activateAutoFillOnPageLoadFromPolicy with autofillSettingsService equivalent

* remove additional deprecated and unused state service calls

* improve naming conventions and consistency

* fix missing mock for policy service test

* replace missing overlay background tests

* cleanup

* fix double inversion

* fix reference to wrong setter

* move handleActivateAutofillPolicy out of BrowserPolicyService

* create state migration script

* resolve linting issues

* remove migrated setting properties

* add AutofillSettingsSErvice to jslib-services

* handle conditional content script loading via autofillOnPageLoad check

* add deprecated note to getFromLocalStorage

* add jsdoc decorators to new autofill service methods

* handle undefined globalState

* move autofill settings out of BrowserPolicyService

* Move autofill settings code out of policyService

* fix tests

* fix typo in state definition

---------

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
Co-authored-by: Cesar Gonzalez <cagonzalezcs@users.noreply.github.com>
Co-authored-by: Thomas Avery <Thomas-Avery@users.noreply.github.com>
Co-authored-by: Colton Hurst <coltonhurst@users.noreply.github.com>
Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
This commit is contained in:
Jonathan Prusik
2024-02-12 17:11:04 -05:00
committed by GitHub
parent bf16682f42
commit c65e92f769
34 changed files with 975 additions and 306 deletions

View File

@@ -1,11 +1,8 @@
import { BehaviorSubject, filter, map, Observable, switchMap, tap } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { browserSession, sessionSync } from "../../platform/decorators/session-sync-observable"; import { browserSession, sessionSync } from "../../platform/decorators/session-sync-observable";
@@ -16,31 +13,4 @@ export class BrowserPolicyService extends PolicyService {
initializeAs: "array", initializeAs: "array",
}) })
protected _policies: BehaviorSubject<Policy[]>; protected _policies: BehaviorSubject<Policy[]>;
constructor(stateService: StateService, organizationService: OrganizationService) {
super(stateService, organizationService);
this._policies.pipe(this.handleActivateAutofillPolicy.bind(this)).subscribe();
}
/**
* If the ActivateAutofill policy is enabled, save a flag indicating if we need to
* enable Autofill on page load.
*/
private handleActivateAutofillPolicy(policies$: Observable<Policy[]>) {
return policies$.pipe(
map((policies) => policies.find((p) => p.type == PolicyType.ActivateAutofill && p.enabled)),
filter((p) => p != null),
switchMap(async (_) => [
await this.stateService.getActivateAutoFillOnPageLoadFromPolicy(),
await this.stateService.getEnableAutoFillOnPageLoad(),
]),
tap(([activated, autofillEnabled]) => {
if (activated === undefined) {
// 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
this.stateService.setActivateAutoFillOnPageLoadFromPolicy(!autofillEnabled);
}
}),
);
}
} }

View File

@@ -2,6 +2,7 @@ import { mock, mockReset } from "jest-mock-extended";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeType } from "@bitwarden/common/platform/enums";
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
import { I18nService } from "@bitwarden/common/platform/services/i18n.service"; import { I18nService } from "@bitwarden/common/platform/services/i18n.service";
@@ -47,21 +48,18 @@ describe("OverlayBackground", () => {
}); });
const settingsService = mock<SettingsService>(); const settingsService = mock<SettingsService>();
const stateService = mock<BrowserStateService>(); const stateService = mock<BrowserStateService>();
const autofillSettingsService = mock<AutofillSettingsService>();
const i18nService = mock<I18nService>(); const i18nService = mock<I18nService>();
const platformUtilsService = mock<BrowserPlatformUtilsService>(); const platformUtilsService = mock<BrowserPlatformUtilsService>();
const initOverlayElementPorts = (options = { initList: true, initButton: true }) => { const initOverlayElementPorts = async (options = { initList: true, initButton: true }) => {
const { initList, initButton } = options; const { initList, initButton } = options;
if (initButton) { if (initButton) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await overlayBackground["handlePortOnConnect"](createPortSpyMock(AutofillOverlayPort.Button));
// eslint-disable-next-line @typescript-eslint/no-floating-promises
overlayBackground["handlePortOnConnect"](createPortSpyMock(AutofillOverlayPort.Button));
buttonPortSpy = overlayBackground["overlayButtonPort"]; buttonPortSpy = overlayBackground["overlayButtonPort"];
} }
if (initList) { if (initList) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await overlayBackground["handlePortOnConnect"](createPortSpyMock(AutofillOverlayPort.List));
// eslint-disable-next-line @typescript-eslint/no-floating-promises
overlayBackground["handlePortOnConnect"](createPortSpyMock(AutofillOverlayPort.List));
listPortSpy = overlayBackground["overlayListPort"]; listPortSpy = overlayBackground["overlayListPort"];
} }
@@ -76,12 +74,16 @@ describe("OverlayBackground", () => {
environmentService, environmentService,
settingsService, settingsService,
stateService, stateService,
autofillSettingsService,
i18nService, i18nService,
platformUtilsService, platformUtilsService,
); );
// 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 jest
overlayBackground.init(); .spyOn(overlayBackground as any, "getOverlayVisibility")
.mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus);
void overlayBackground.init();
}); });
afterEach(() => { afterEach(() => {
@@ -570,8 +572,8 @@ describe("OverlayBackground", () => {
}); });
describe("autofillOverlayElementClosed message handler", () => { describe("autofillOverlayElementClosed message handler", () => {
beforeEach(() => { beforeEach(async () => {
initOverlayElementPorts(); await initOverlayElementPorts();
}); });
it("disconnects the button element port", () => { it("disconnects the button element port", () => {
@@ -635,7 +637,7 @@ describe("OverlayBackground", () => {
describe("getAutofillOverlayVisibility message handler", () => { describe("getAutofillOverlayVisibility message handler", () => {
beforeEach(() => { beforeEach(() => {
jest jest
.spyOn(overlayBackground["settingsService"], "getAutoFillOverlayVisibility") .spyOn(overlayBackground as any, "getOverlayVisibility")
.mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus); .mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus);
}); });
@@ -643,7 +645,7 @@ describe("OverlayBackground", () => {
sendExtensionRuntimeMessage({ command: "getAutofillOverlayVisibility" }); sendExtensionRuntimeMessage({ command: "getAutofillOverlayVisibility" });
await flushPromises(); await flushPromises();
expect(overlayBackground["overlayVisibility"]).toBe( expect(await overlayBackground["getOverlayVisibility"]()).toBe(
AutofillOverlayVisibility.OnFieldFocus, AutofillOverlayVisibility.OnFieldFocus,
); );
}); });
@@ -663,8 +665,8 @@ describe("OverlayBackground", () => {
}); });
describe("checkAutofillOverlayFocused message handler", () => { describe("checkAutofillOverlayFocused message handler", () => {
beforeEach(() => { beforeEach(async () => {
initOverlayElementPorts(); await initOverlayElementPorts();
}); });
it("will check if the overlay list is focused if the list port is open", () => { it("will check if the overlay list is focused if the list port is open", () => {
@@ -693,8 +695,8 @@ describe("OverlayBackground", () => {
}); });
describe("focusAutofillOverlayList message handler", () => { describe("focusAutofillOverlayList message handler", () => {
it("will send a `focusOverlayList` message to the overlay list port", () => { it("will send a `focusOverlayList` message to the overlay list port", async () => {
initOverlayElementPorts({ initList: true, initButton: false }); await initOverlayElementPorts({ initList: true, initButton: false });
sendExtensionRuntimeMessage({ command: "focusAutofillOverlayList" }); sendExtensionRuntimeMessage({ command: "focusAutofillOverlayList" });
@@ -703,14 +705,15 @@ describe("OverlayBackground", () => {
}); });
describe("updateAutofillOverlayPosition message handler", () => { describe("updateAutofillOverlayPosition message handler", () => {
beforeEach(() => { beforeEach(async () => {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await overlayBackground["handlePortOnConnect"](
// eslint-disable-next-line @typescript-eslint/no-floating-promises createPortSpyMock(AutofillOverlayPort.List),
overlayBackground["handlePortOnConnect"](createPortSpyMock(AutofillOverlayPort.List)); );
listPortSpy = overlayBackground["overlayListPort"]; listPortSpy = overlayBackground["overlayListPort"];
// 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 await overlayBackground["handlePortOnConnect"](
overlayBackground["handlePortOnConnect"](createPortSpyMock(AutofillOverlayPort.Button)); createPortSpyMock(AutofillOverlayPort.Button),
);
buttonPortSpy = overlayBackground["overlayButtonPort"]; buttonPortSpy = overlayBackground["overlayButtonPort"];
}); });
@@ -813,8 +816,8 @@ describe("OverlayBackground", () => {
}); });
describe("updateOverlayHidden", () => { describe("updateOverlayHidden", () => {
beforeEach(() => { beforeEach(async () => {
initOverlayElementPorts(); await initOverlayElementPorts();
}); });
it("returns early if the display value is not provided", () => { it("returns early if the display value is not provided", () => {
@@ -984,19 +987,17 @@ describe("OverlayBackground", () => {
jest.spyOn(overlayBackground as any, "getOverlayCipherData").mockImplementation(); jest.spyOn(overlayBackground as any, "getOverlayCipherData").mockImplementation();
}); });
it("skips setting up the overlay port if the port connection is not for an overlay element", () => { it("skips setting up the overlay port if the port connection is not for an overlay element", async () => {
const port = createPortSpyMock("not-an-overlay-element"); const port = createPortSpyMock("not-an-overlay-element");
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await overlayBackground["handlePortOnConnect"](port);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
overlayBackground["handlePortOnConnect"](port);
expect(port.onMessage.addListener).not.toHaveBeenCalled(); expect(port.onMessage.addListener).not.toHaveBeenCalled();
expect(port.postMessage).not.toHaveBeenCalled(); expect(port.postMessage).not.toHaveBeenCalled();
}); });
it("sets up the overlay list port if the port connection is for the overlay list", async () => { it("sets up the overlay list port if the port connection is for the overlay list", async () => {
initOverlayElementPorts({ initList: true, initButton: false }); await initOverlayElementPorts({ initList: true, initButton: false });
await flushPromises(); await flushPromises();
expect(overlayBackground["overlayButtonPort"]).toBeUndefined(); expect(overlayBackground["overlayButtonPort"]).toBeUndefined();
@@ -1012,7 +1013,7 @@ describe("OverlayBackground", () => {
}); });
it("sets up the overlay button port if the port connection is for the overlay button", async () => { it("sets up the overlay button port if the port connection is for the overlay button", async () => {
initOverlayElementPorts({ initList: false, initButton: true }); await initOverlayElementPorts({ initList: false, initButton: true });
await flushPromises(); await flushPromises();
expect(overlayBackground["overlayListPort"]).toBeUndefined(); expect(overlayBackground["overlayListPort"]).toBeUndefined();
@@ -1029,7 +1030,7 @@ describe("OverlayBackground", () => {
it("gets the system theme", async () => { it("gets the system theme", async () => {
jest.spyOn(overlayBackground["stateService"], "getTheme").mockResolvedValue(ThemeType.System); jest.spyOn(overlayBackground["stateService"], "getTheme").mockResolvedValue(ThemeType.System);
initOverlayElementPorts({ initList: true, initButton: false }); await initOverlayElementPorts({ initList: true, initButton: false });
await flushPromises(); await flushPromises();
expect(listPortSpy.postMessage).toHaveBeenCalledWith( expect(listPortSpy.postMessage).toHaveBeenCalledWith(
@@ -1039,8 +1040,8 @@ describe("OverlayBackground", () => {
}); });
describe("handleOverlayElementPortMessage", () => { describe("handleOverlayElementPortMessage", () => {
beforeEach(() => { beforeEach(async () => {
initOverlayElementPorts(); await initOverlayElementPorts();
overlayBackground["userAuthStatus"] = AuthenticationStatus.Unlocked; overlayBackground["userAuthStatus"] = AuthenticationStatus.Unlocked;
}); });

View File

@@ -1,6 +1,9 @@
import { firstValueFrom } from "rxjs";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service"; import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -21,7 +24,11 @@ import {
} from "../../vault/popup/utils/vault-popout-window"; } from "../../vault/popup/utils/vault-popout-window";
import { SHOW_AUTOFILL_BUTTON } from "../constants"; import { SHOW_AUTOFILL_BUTTON } from "../constants";
import { AutofillService, PageDetail } from "../services/abstractions/autofill.service"; import { AutofillService, PageDetail } from "../services/abstractions/autofill.service";
import { AutofillOverlayElement, AutofillOverlayPort } from "../utils/autofill-overlay.enum"; import {
InlineMenuVisibilitySetting,
AutofillOverlayElement,
AutofillOverlayPort,
} from "../utils/autofill-overlay.enum";
import { LockedVaultPendingNotificationsData } from "./abstractions/notification.background"; import { LockedVaultPendingNotificationsData } from "./abstractions/notification.background";
import { import {
@@ -41,7 +48,6 @@ class OverlayBackground implements OverlayBackgroundInterface {
private readonly openUnlockPopout = openUnlockPopout; private readonly openUnlockPopout = openUnlockPopout;
private readonly openViewVaultItemPopout = openViewVaultItemPopout; private readonly openViewVaultItemPopout = openViewVaultItemPopout;
private readonly openAddEditVaultItemPopout = openAddEditVaultItemPopout; private readonly openAddEditVaultItemPopout = openAddEditVaultItemPopout;
private overlayVisibility: number;
private overlayLoginCiphers: Map<string, CipherView> = new Map(); private overlayLoginCiphers: Map<string, CipherView> = new Map();
private pageDetailsForTab: Record<number, PageDetail[]> = {}; private pageDetailsForTab: Record<number, PageDetail[]> = {};
private userAuthStatus: AuthenticationStatus = AuthenticationStatus.LoggedOut; private userAuthStatus: AuthenticationStatus = AuthenticationStatus.LoggedOut;
@@ -90,6 +96,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
private settingsService: SettingsService, private settingsService: SettingsService,
private stateService: StateService, private stateService: StateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private i18nService: I18nService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
) { ) {
@@ -455,10 +462,8 @@ class OverlayBackground implements OverlayBackgroundInterface {
/** /**
* Gets the overlay's visibility setting from the settings service. * Gets the overlay's visibility setting from the settings service.
*/ */
private async getOverlayVisibility(): Promise<number> { private async getOverlayVisibility(): Promise<InlineMenuVisibilitySetting> {
this.overlayVisibility = await this.settingsService.getAutoFillOverlayVisibility(); return await firstValueFrom(this.autofillSettingsService.inlineMenuVisibility$);
return this.overlayVisibility;
} }
/** /**

View File

@@ -34,11 +34,17 @@ import {
import { AutofillService as AbstractAutoFillService } from "../../services/abstractions/autofill.service"; import { AutofillService as AbstractAutoFillService } from "../../services/abstractions/autofill.service";
import AutofillService from "../../services/autofill.service"; import AutofillService from "../../services/autofill.service";
import {
AutofillSettingsServiceInitOptions,
autofillSettingsServiceFactory,
} from "./autofill-settings-service.factory";
type AutoFillServiceOptions = FactoryOptions; type AutoFillServiceOptions = FactoryOptions;
export type AutoFillServiceInitOptions = AutoFillServiceOptions & export type AutoFillServiceInitOptions = AutoFillServiceOptions &
CipherServiceInitOptions & CipherServiceInitOptions &
StateServiceInitOptions & StateServiceInitOptions &
AutofillSettingsServiceInitOptions &
TotpServiceInitOptions & TotpServiceInitOptions &
EventCollectionServiceInitOptions & EventCollectionServiceInitOptions &
LogServiceInitOptions & LogServiceInitOptions &
@@ -57,6 +63,7 @@ export function autofillServiceFactory(
new AutofillService( new AutofillService(
await cipherServiceFactory(cache, opts), await cipherServiceFactory(cache, opts),
await stateServiceFactory(cache, opts), await stateServiceFactory(cache, opts),
await autofillSettingsServiceFactory(cache, opts),
await totpServiceFactory(cache, opts), await totpServiceFactory(cache, opts),
await eventCollectionServiceFactory(cache, opts), await eventCollectionServiceFactory(cache, opts),
await logServiceFactory(cache, opts), await logServiceFactory(cache, opts),

View File

@@ -0,0 +1,35 @@
import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service";
import {
policyServiceFactory,
PolicyServiceInitOptions,
} from "../../../admin-console/background/service-factories/policy-service.factory";
import {
CachedServices,
factory,
FactoryOptions,
} from "../../../platform/background/service-factories/factory-options";
import {
stateProviderFactory,
StateProviderInitOptions,
} from "../../../platform/background/service-factories/state-provider.factory";
export type AutofillSettingsServiceInitOptions = FactoryOptions &
StateProviderInitOptions &
PolicyServiceInitOptions;
export function autofillSettingsServiceFactory(
cache: { autofillSettingsService?: AutofillSettingsService } & CachedServices,
opts: AutofillSettingsServiceInitOptions,
): Promise<AutofillSettingsService> {
return factory(
cache,
"autofillSettingsService",
opts,
async () =>
new AutofillSettingsService(
await stateProviderFactory(cache, opts),
await policyServiceFactory(cache, opts),
),
);
}

View File

@@ -1,4 +1,4 @@
import { getFromLocalStorage, setupExtensionDisconnectAction } from "../utils"; import { setupExtensionDisconnectAction } from "../utils";
if (document.readyState === "loading") { if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", loadAutofiller); document.addEventListener("DOMContentLoaded", loadAutofiller);
@@ -22,19 +22,11 @@ function loadAutofiller() {
}; };
setupExtensionEventListeners(); setupExtensionEventListeners();
// 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
triggerUserFillOnLoad(); triggerUserFillOnLoad();
async function triggerUserFillOnLoad() { function triggerUserFillOnLoad() {
const activeUserIdKey = "activeUserId"; clearDoFillInterval();
const userKeyStorage = await getFromLocalStorage(activeUserIdKey); doFillInterval = setInterval(() => doFillIfNeeded(), 500);
const activeUserId = userKeyStorage[activeUserIdKey];
const activeUserStorage = await getFromLocalStorage(activeUserId);
if (activeUserStorage?.[activeUserId]?.settings?.enableAutoFillOnPageLoad === true) {
clearDoFillInterval();
doFillInterval = setInterval(() => doFillIfNeeded(), 500);
}
} }
function doFillIfNeeded(force = false) { function doFillIfNeeded(force = false) {

View File

@@ -1,7 +1,8 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service"; import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
@@ -11,7 +12,10 @@ import { DialogService } from "@bitwarden/components";
import { BrowserApi } from "../../../platform/browser/browser-api"; import { BrowserApi } from "../../../platform/browser/browser-api";
import { enableAccountSwitching } from "../../../platform/flags"; import { enableAccountSwitching } from "../../../platform/flags";
import { AutofillService } from "../../services/abstractions/autofill.service"; import { AutofillService } from "../../services/abstractions/autofill.service";
import { AutofillOverlayVisibility } from "../../utils/autofill-overlay.enum"; import {
AutofillOverlayVisibility,
InlineMenuVisibilitySetting,
} from "../../utils/autofill-overlay.enum";
@Component({ @Component({
selector: "app-autofill", selector: "app-autofill",
@@ -20,7 +24,7 @@ import { AutofillOverlayVisibility } from "../../utils/autofill-overlay.enum";
export class AutofillComponent implements OnInit { export class AutofillComponent implements OnInit {
protected canOverrideBrowserAutofillSetting = false; protected canOverrideBrowserAutofillSetting = false;
protected defaultBrowserAutofillDisabled = false; protected defaultBrowserAutofillDisabled = false;
protected autoFillOverlayVisibility: number; protected autoFillOverlayVisibility: InlineMenuVisibilitySetting;
protected autoFillOverlayVisibilityOptions: any[]; protected autoFillOverlayVisibilityOptions: any[];
protected disablePasswordManagerLink: string; protected disablePasswordManagerLink: string;
enableAutoFillOnPageLoad = false; enableAutoFillOnPageLoad = false;
@@ -35,10 +39,10 @@ export class AutofillComponent implements OnInit {
private stateService: StateService, private stateService: StateService,
private i18nService: I18nService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private configService: ConfigServiceAbstraction,
private settingsService: SettingsService, private settingsService: SettingsService,
private autofillService: AutofillService, private autofillService: AutofillService,
private dialogService: DialogService, private dialogService: DialogService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
) { ) {
this.autoFillOverlayVisibilityOptions = [ this.autoFillOverlayVisibilityOptions = [
{ {
@@ -80,12 +84,17 @@ export class AutofillComponent implements OnInit {
this.defaultBrowserAutofillDisabled = await this.browserAutofillSettingCurrentlyOverridden(); this.defaultBrowserAutofillDisabled = await this.browserAutofillSettingCurrentlyOverridden();
this.autoFillOverlayVisibility = this.autoFillOverlayVisibility = await firstValueFrom(
(await this.settingsService.getAutoFillOverlayVisibility()) || AutofillOverlayVisibility.Off; this.autofillSettingsService.inlineMenuVisibility$,
);
this.enableAutoFillOnPageLoad = await this.stateService.getEnableAutoFillOnPageLoad(); this.enableAutoFillOnPageLoad = await firstValueFrom(
this.autoFillOnPageLoadDefault = this.autofillSettingsService.autofillOnPageLoad$,
(await this.stateService.getAutoFillOnPageLoadDefault()) ?? true; );
this.autoFillOnPageLoadDefault = await firstValueFrom(
this.autofillSettingsService.autofillOnPageLoadDefault$,
);
const defaultUriMatch = await this.stateService.getDefaultUriMatch(); const defaultUriMatch = await this.stateService.getDefaultUriMatch();
this.defaultUriMatch = defaultUriMatch == null ? UriMatchType.Domain : defaultUriMatch; this.defaultUriMatch = defaultUriMatch == null ? UriMatchType.Domain : defaultUriMatch;
@@ -95,19 +104,20 @@ export class AutofillComponent implements OnInit {
} }
async updateAutoFillOverlayVisibility() { async updateAutoFillOverlayVisibility() {
const previousAutoFillOverlayVisibility = const previousAutoFillOverlayVisibility = await firstValueFrom(
await this.settingsService.getAutoFillOverlayVisibility(); this.autofillSettingsService.inlineMenuVisibility$,
await this.settingsService.setAutoFillOverlayVisibility(this.autoFillOverlayVisibility); );
await this.autofillSettingsService.setInlineMenuVisibility(this.autoFillOverlayVisibility);
await this.handleUpdatingAutofillOverlayContentScripts(previousAutoFillOverlayVisibility); await this.handleUpdatingAutofillOverlayContentScripts(previousAutoFillOverlayVisibility);
await this.requestPrivacyPermission(); await this.requestPrivacyPermission();
} }
async updateAutoFillOnPageLoad() { async updateAutoFillOnPageLoad() {
await this.stateService.setEnableAutoFillOnPageLoad(this.enableAutoFillOnPageLoad); await this.autofillSettingsService.setAutofillOnPageLoad(this.enableAutoFillOnPageLoad);
} }
async updateAutoFillOnPageLoadDefault() { async updateAutoFillOnPageLoadDefault() {
await this.stateService.setAutoFillOnPageLoadDefault(this.autoFillOnPageLoadDefault); await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autoFillOnPageLoadDefault);
} }
async saveDefaultUriMatch() { async saveDefaultUriMatch() {

View File

@@ -1,6 +1,7 @@
import { mock, mockReset } from "jest-mock-extended"; import { mock, mockReset } from "jest-mock-extended";
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service"; import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { EventType } from "@bitwarden/common/enums"; import { EventType } from "@bitwarden/common/enums";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
@@ -50,6 +51,7 @@ describe("AutofillService", () => {
let autofillService: AutofillService; let autofillService: AutofillService;
const cipherService = mock<CipherService>(); const cipherService = mock<CipherService>();
const stateService = mock<BrowserStateService>(); const stateService = mock<BrowserStateService>();
const autofillSettingsService = mock<AutofillSettingsService>();
const totpService = mock<TotpService>(); const totpService = mock<TotpService>();
const eventCollectionService = mock<EventCollectionService>(); const eventCollectionService = mock<EventCollectionService>();
const logService = mock<LogService>(); const logService = mock<LogService>();
@@ -60,6 +62,7 @@ describe("AutofillService", () => {
autofillService = new AutofillService( autofillService = new AutofillService(
cipherService, cipherService,
stateService, stateService,
autofillSettingsService,
totpService, totpService,
eventCollectionService, eventCollectionService,
logService, logService,
@@ -83,6 +86,10 @@ describe("AutofillService", () => {
tab2 = createChromeTabMock({ id: 2, url: "http://some-url.com" }); tab2 = createChromeTabMock({ id: 2, url: "http://some-url.com" });
tab3 = createChromeTabMock({ id: 3, url: "chrome-extension://some-extension-route" }); tab3 = createChromeTabMock({ id: 3, url: "chrome-extension://some-extension-route" });
jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValueOnce([tab1, tab2]); jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValueOnce([tab1, tab2]);
jest
.spyOn(autofillService, "getOverlayVisibility")
.mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus);
jest.spyOn(autofillService, "getAutofillOnPageLoad").mockResolvedValue(true);
}); });
it("queries all browser tabs and injects the autofill scripts into them", async () => { it("queries all browser tabs and injects the autofill scripts into them", async () => {
@@ -134,6 +141,7 @@ describe("AutofillService", () => {
it("re-injects the autofill scripts in all tabs", () => { it("re-injects the autofill scripts in all tabs", () => {
autofillService["autofillScriptPortsSet"] = new Set([mock<chrome.runtime.Port>()]); autofillService["autofillScriptPortsSet"] = new Set([mock<chrome.runtime.Port>()]);
jest.spyOn(autofillService as any, "injectAutofillScriptsInAllTabs"); jest.spyOn(autofillService as any, "injectAutofillScriptsInAllTabs");
jest.spyOn(autofillService, "getAutofillOnPageLoad").mockResolvedValue(true);
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // 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 // eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -155,9 +163,14 @@ describe("AutofillService", () => {
tabMock = createChromeTabMock(); tabMock = createChromeTabMock();
sender = { tab: tabMock, frameId: 1 }; sender = { tab: tabMock, frameId: 1 };
jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation(); jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation();
jest
.spyOn(autofillService, "getOverlayVisibility")
.mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus);
}); });
it("accepts an extension message sender and injects the autofill scripts into the tab of the sender", async () => { 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); await autofillService.injectAutofillScripts(sender.tab, sender.frameId, true);
[autofillOverlayBootstrapScript, ...defaultAutofillScripts].forEach((scriptName) => { [autofillOverlayBootstrapScript, ...defaultAutofillScripts].forEach((scriptName) => {
@@ -169,10 +182,23 @@ describe("AutofillService", () => {
}); });
}); });
it("skips injecting autofiller script when autofill on load setting is disabled", async () => {
jest.spyOn(autofillService, "getAutofillOnPageLoad").mockResolvedValue(false);
await autofillService.injectAutofillScripts(sender.tab, sender.frameId, true);
expect(BrowserApi.executeScriptInTab).not.toHaveBeenCalledWith(tabMock.id, {
file: "content/autofiller.js",
frameId: sender.frameId,
...defaultExecuteScriptOptions,
});
});
it("will inject the bootstrap-autofill-overlay script if the user has the autofill overlay enabled", async () => { it("will inject the bootstrap-autofill-overlay script if the user has the autofill overlay enabled", async () => {
jest jest
.spyOn(autofillService["settingsService"], "getAutoFillOverlayVisibility") .spyOn(autofillService, "getOverlayVisibility")
.mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus); .mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus);
jest.spyOn(autofillService, "getAutofillOnPageLoad").mockResolvedValue(true);
await autofillService.injectAutofillScripts(sender.tab, sender.frameId); await autofillService.injectAutofillScripts(sender.tab, sender.frameId);
@@ -190,8 +216,9 @@ describe("AutofillService", () => {
it("will inject the bootstrap-autofill script if the user does not have the autofill overlay enabled", async () => { it("will inject the bootstrap-autofill script if the user does not have the autofill overlay enabled", async () => {
jest jest
.spyOn(autofillService["settingsService"], "getAutoFillOverlayVisibility") .spyOn(autofillService, "getOverlayVisibility")
.mockResolvedValue(AutofillOverlayVisibility.Off); .mockResolvedValue(AutofillOverlayVisibility.Off);
jest.spyOn(autofillService, "getAutofillOnPageLoad").mockResolvedValue(true);
await autofillService.injectAutofillScripts(sender.tab, sender.frameId); await autofillService.injectAutofillScripts(sender.tab, sender.frameId);
@@ -208,6 +235,8 @@ describe("AutofillService", () => {
}); });
it("injects the content-message-handler script if not injecting on page load", async () => { 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); await autofillService.injectAutofillScripts(sender.tab, sender.frameId, false);
expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabMock.id, { expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabMock.id, {
@@ -622,12 +651,12 @@ describe("AutofillService", () => {
const totpCode = "123456"; const totpCode = "123456";
autofillOptions.cipher.login.totp = "totp"; autofillOptions.cipher.login.totp = "totp";
jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(true); jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(true);
jest.spyOn(stateService, "getDisableAutoTotpCopy").mockResolvedValue(false); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(true);
jest.spyOn(totpService, "getCode").mockResolvedValue(totpCode); jest.spyOn(totpService, "getCode").mockResolvedValue(totpCode);
const autofillResult = await autofillService.doAutoFill(autofillOptions); const autofillResult = await autofillService.doAutoFill(autofillOptions);
expect(stateService.getDisableAutoTotpCopy).toHaveBeenCalled(); expect(autofillService.getShouldAutoCopyTotp).toHaveBeenCalled();
expect(totpService.getCode).toHaveBeenCalledWith(autofillOptions.cipher.login.totp); expect(totpService.getCode).toHaveBeenCalledWith(autofillOptions.cipher.login.totp);
expect(autofillResult).toBe(totpCode); expect(autofillResult).toBe(totpCode);
}); });
@@ -635,11 +664,11 @@ describe("AutofillService", () => {
it("does not return a TOTP value if the user does not have premium features", async () => { it("does not return a TOTP value if the user does not have premium features", async () => {
autofillOptions.cipher.login.totp = "totp"; autofillOptions.cipher.login.totp = "totp";
jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(false); jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(false);
jest.spyOn(stateService, "getDisableAutoTotpCopy").mockResolvedValue(false); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(true);
const autofillResult = await autofillService.doAutoFill(autofillOptions); const autofillResult = await autofillService.doAutoFill(autofillOptions);
expect(stateService.getDisableAutoTotpCopy).not.toHaveBeenCalled(); expect(autofillService.getShouldAutoCopyTotp).not.toHaveBeenCalled();
expect(totpService.getCode).not.toHaveBeenCalled(); expect(totpService.getCode).not.toHaveBeenCalled();
expect(autofillResult).toBeNull(); expect(autofillResult).toBeNull();
}); });
@@ -655,12 +684,12 @@ describe("AutofillService", () => {
it("returns a null value if the login does not contain a TOTP value", async () => { it("returns a null value if the login does not contain a TOTP value", async () => {
autofillOptions.cipher.login.totp = undefined; autofillOptions.cipher.login.totp = undefined;
jest.spyOn(stateService, "getDisableAutoTotpCopy"); jest.spyOn(autofillService, "getShouldAutoCopyTotp");
jest.spyOn(totpService, "getCode"); jest.spyOn(totpService, "getCode");
const autofillResult = await autofillService.doAutoFill(autofillOptions); const autofillResult = await autofillService.doAutoFill(autofillOptions);
expect(stateService.getDisableAutoTotpCopy).not.toHaveBeenCalled(); expect(autofillService.getShouldAutoCopyTotp).not.toHaveBeenCalled();
expect(totpService.getCode).not.toHaveBeenCalled(); expect(totpService.getCode).not.toHaveBeenCalled();
expect(autofillResult).toBeNull(); expect(autofillResult).toBeNull();
}); });
@@ -679,13 +708,13 @@ describe("AutofillService", () => {
autofillOptions.cipher.login.totp = "totp"; autofillOptions.cipher.login.totp = "totp";
autofillOptions.cipher.organizationUseTotp = true; autofillOptions.cipher.organizationUseTotp = true;
jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(true); jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(true);
jest.spyOn(stateService, "getDisableAutoTotpCopy").mockResolvedValue(true); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(false);
jest.spyOn(totpService, "getCode"); jest.spyOn(totpService, "getCode");
const autofillResult = await autofillService.doAutoFill(autofillOptions); const autofillResult = await autofillService.doAutoFill(autofillOptions);
expect(stateService.getCanAccessPremium).toHaveBeenCalled(); expect(stateService.getCanAccessPremium).toHaveBeenCalled();
expect(stateService.getDisableAutoTotpCopy).toHaveBeenCalled(); expect(autofillService.getShouldAutoCopyTotp).toHaveBeenCalled();
expect(totpService.getCode).not.toHaveBeenCalled(); expect(totpService.getCode).not.toHaveBeenCalled();
expect(autofillResult).toBeNull(); expect(autofillResult).toBeNull();
}); });

View File

@@ -1,6 +1,9 @@
import { firstValueFrom } from "rxjs";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service"; import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { EventType } from "@bitwarden/common/enums"; import { EventType } from "@bitwarden/common/enums";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -17,7 +20,7 @@ import { AutofillPort } from "../enums/autofill-port.enums";
import AutofillField from "../models/autofill-field"; import AutofillField from "../models/autofill-field";
import AutofillPageDetails from "../models/autofill-page-details"; import AutofillPageDetails from "../models/autofill-page-details";
import AutofillScript from "../models/autofill-script"; import AutofillScript from "../models/autofill-script";
import { AutofillOverlayVisibility } from "../utils/autofill-overlay.enum"; import { InlineMenuVisibilitySetting } from "../utils/autofill-overlay.enum";
import { import {
AutoFillOptions, AutoFillOptions,
@@ -41,6 +44,7 @@ export default class AutofillService implements AutofillServiceInterface {
constructor( constructor(
private cipherService: CipherService, private cipherService: CipherService,
private stateService: BrowserStateService, private stateService: BrowserStateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private totpService: TotpService, private totpService: TotpService,
private eventCollectionService: EventCollectionService, private eventCollectionService: EventCollectionService,
private logService: LogService, private logService: LogService,
@@ -93,15 +97,15 @@ export default class AutofillService implements AutofillServiceInterface {
frameId = 0, frameId = 0,
triggeringOnPageLoad = true, triggeringOnPageLoad = true,
): Promise<void> { ): Promise<void> {
const isUsingAutofillOverlay = const mainAutofillScript = (await this.getOverlayVisibility())
(await this.settingsService.getAutoFillOverlayVisibility()) !== AutofillOverlayVisibility.Off;
const mainAutofillScript = isUsingAutofillOverlay
? "bootstrap-autofill-overlay.js" ? "bootstrap-autofill-overlay.js"
: "bootstrap-autofill.js"; : "bootstrap-autofill.js";
const injectedScripts = [mainAutofillScript]; const injectedScripts = [mainAutofillScript];
if (triggeringOnPageLoad) { const autoFillOnPageLoadIsEnabled = await this.getAutofillOnPageLoad();
if (triggeringOnPageLoad && autoFillOnPageLoadIsEnabled) {
injectedScripts.push("autofiller.js"); injectedScripts.push("autofiller.js");
} else { } else {
await BrowserApi.executeScriptInTab(tab.id, { await BrowserApi.executeScriptInTab(tab.id, {
@@ -190,6 +194,27 @@ export default class AutofillService implements AutofillServiceInterface {
return formData; return formData;
} }
/**
* Gets the overlay's visibility setting from the autofill settings service.
*/
async getOverlayVisibility(): Promise<InlineMenuVisibilitySetting> {
return await firstValueFrom(this.autofillSettingsService.inlineMenuVisibility$);
}
/**
* Gets the setting for automatically copying TOTP upon autofill from the autofill settings service.
*/
async getShouldAutoCopyTotp(): Promise<boolean> {
return await firstValueFrom(this.autofillSettingsService.autoCopyTotp$);
}
/**
* Gets the autofill on page load setting from the autofill settings service.
*/
async getAutofillOnPageLoad(): Promise<boolean> {
return await firstValueFrom(this.autofillSettingsService.autofillOnPageLoad$);
}
/** /**
* Autofill a given tab with a given login item * Autofill a given tab with a given login item
* @param {AutoFillOptions} options Instructions about the autofill operation, including tab and login item * @param {AutoFillOptions} options Instructions about the autofill operation, including tab and login item
@@ -275,12 +300,11 @@ export default class AutofillService implements AutofillServiceInterface {
return; return;
} }
totp = await this.stateService.getDisableAutoTotpCopy().then((disabled) => { const shouldAutoCopyTotp = await this.getShouldAutoCopyTotp();
if (!disabled) {
return this.totpService.getCode(options.cipher.login.totp); totp = shouldAutoCopyTotp
} ? await this.totpService.getCode(options.cipher.login.totp)
return null; : null;
});
}), }),
); );

View File

@@ -20,9 +20,13 @@ const AutofillOverlayVisibility = {
OnFieldFocus: 2, OnFieldFocus: 2,
} as const; } as const;
type InlineMenuVisibilitySetting =
(typeof AutofillOverlayVisibility)[keyof typeof AutofillOverlayVisibility];
export { export {
AutofillOverlayElement, AutofillOverlayElement,
AutofillOverlayPort, AutofillOverlayPort,
RedirectFocusDirection, RedirectFocusDirection,
AutofillOverlayVisibility, AutofillOverlayVisibility,
InlineMenuVisibilitySetting,
}; };

View File

@@ -109,6 +109,7 @@ function setElementStyles(
* Get data from local storage based on the keys provided. * Get data from local storage based on the keys provided.
* *
* @param keys - String or array of strings of keys to get from local storage * @param keys - String or array of strings of keys to get from local storage
* @deprecated Do not call this, use state-relevant services instead
*/ */
async function getFromLocalStorage(keys: string | string[]): Promise<Record<string, any>> { async function getFromLocalStorage(keys: string | string[]): Promise<Record<string, any>> {
return new Promise((resolve) => { return new Promise((resolve) => {

View File

@@ -43,6 +43,10 @@ import { TokenService } from "@bitwarden/common/auth/services/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service";
import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service"; import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service";
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service"; import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
import {
AutofillSettingsServiceAbstraction,
AutofillSettingsService,
} from "@bitwarden/common/autofill/services/autofill-settings.service";
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.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 { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
@@ -228,6 +232,7 @@ export default class MainBackground {
searchService: SearchServiceAbstraction; searchService: SearchServiceAbstraction;
notificationsService: NotificationsServiceAbstraction; notificationsService: NotificationsServiceAbstraction;
stateService: StateServiceAbstraction; stateService: StateServiceAbstraction;
autofillSettingsService: AutofillSettingsServiceAbstraction;
systemService: SystemServiceAbstraction; systemService: SystemServiceAbstraction;
eventCollectionService: EventCollectionServiceAbstraction; eventCollectionService: EventCollectionServiceAbstraction;
eventUploadService: EventUploadServiceAbstraction; eventUploadService: EventUploadServiceAbstraction;
@@ -447,6 +452,10 @@ export default class MainBackground {
this.stateProvider, this.stateProvider,
); );
this.policyService = new BrowserPolicyService(this.stateService, this.organizationService); this.policyService = new BrowserPolicyService(this.stateService, this.organizationService);
this.autofillSettingsService = new AutofillSettingsService(
this.stateProvider,
this.policyService,
);
this.policyApiService = new PolicyApiService( this.policyApiService = new PolicyApiService(
this.policyService, this.policyService,
this.apiService, this.apiService,
@@ -548,6 +557,7 @@ export default class MainBackground {
this.i18nService, this.i18nService,
this.searchService, this.searchService,
this.stateService, this.stateService,
this.autofillSettingsService,
this.encryptService, this.encryptService,
this.cipherFileUploadService, this.cipherFileUploadService,
this.configService, this.configService,
@@ -661,6 +671,7 @@ export default class MainBackground {
this.autofillService = new AutofillService( this.autofillService = new AutofillService(
this.cipherService, this.cipherService,
this.stateService, this.stateService,
this.autofillSettingsService,
this.totpService, this.totpService,
this.eventCollectionService, this.eventCollectionService,
this.logService, this.logService,
@@ -759,13 +770,13 @@ export default class MainBackground {
this.i18nService, this.i18nService,
this.notificationsService, this.notificationsService,
this.stateService, this.stateService,
this.autofillSettingsService,
this.systemService, this.systemService,
this.environmentService, this.environmentService,
this.messagingService, this.messagingService,
this.logService, this.logService,
this.configService, this.configService,
this.fido2Service, this.fido2Service,
this.settingsService,
); );
this.nativeMessagingBackground = new NativeMessagingBackground( this.nativeMessagingBackground = new NativeMessagingBackground(
this.cryptoService, this.cryptoService,
@@ -802,6 +813,7 @@ export default class MainBackground {
this.environmentService, this.environmentService,
this.settingsService, this.settingsService,
this.stateService, this.stateService,
this.autofillSettingsService,
this.i18nService, this.i18nService,
this.platformUtilsService, this.platformUtilsService,
); );
@@ -1030,6 +1042,7 @@ export default class MainBackground {
this.vaultTimeoutSettingsService.clear(userId), this.vaultTimeoutSettingsService.clear(userId),
this.keyConnectorService.clear(), this.keyConnectorService.clear(),
this.vaultFilterService.clear(), this.vaultFilterService.clear(),
// We intentionally do not clear the autofillSettingsService
]); ]);
//Needs to be checked before state is cleaned //Needs to be checked before state is cleaned

View File

@@ -1,5 +1,5 @@
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -39,13 +39,13 @@ export default class RuntimeBackground {
private i18nService: I18nService, private i18nService: I18nService,
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private stateService: BrowserStateService, private stateService: BrowserStateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private systemService: SystemService, private systemService: SystemService,
private environmentService: BrowserEnvironmentService, private environmentService: BrowserEnvironmentService,
private messagingService: MessagingService, private messagingService: MessagingService,
private logService: LogService, private logService: LogService,
private configService: ConfigServiceAbstraction, private configService: ConfigServiceAbstraction,
private fido2Service: Fido2Service, private fido2Service: Fido2Service,
private settingsService: SettingsService,
) { ) {
// onInstalled listener must be wired up before anything else, so we do it in the ctor // onInstalled listener must be wired up before anything else, so we do it in the ctor
chrome.runtime.onInstalled.addListener((details: any) => { chrome.runtime.onInstalled.addListener((details: any) => {
@@ -338,7 +338,7 @@ export default class RuntimeBackground {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // 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 // eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserApi.createNewTab("https://bitwarden.com/browser-start/"); BrowserApi.createNewTab("https://bitwarden.com/browser-start/");
await this.settingsService.setAutoFillOverlayVisibility( await this.autofillSettingsService.setInlineMenuVisibility(
AutofillOverlayVisibility.OnFieldFocus, AutofillOverlayVisibility.OnFieldFocus,
); );

View File

@@ -40,6 +40,10 @@ import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { LoginService } from "@bitwarden/common/auth/services/login.service"; import { LoginService } from "@bitwarden/common/auth/services/login.service";
import {
AutofillSettingsService,
AutofillSettingsServiceAbstraction,
} from "@bitwarden/common/autofill/services/autofill-settings.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
@@ -556,6 +560,11 @@ function getBgService<T>(service: keyof MainBackground) {
useClass: ForegroundDerivedStateProvider, useClass: ForegroundDerivedStateProvider,
deps: [OBSERVABLE_MEMORY_STORAGE, NgZone], deps: [OBSERVABLE_MEMORY_STORAGE, NgZone],
}, },
{
provide: AutofillSettingsServiceAbstraction,
useClass: AutofillSettingsService,
deps: [StateProvider, PolicyService],
},
], ],
}) })
export class ServicesModule {} export class ServicesModule {}

View File

@@ -3,11 +3,11 @@ import { firstValueFrom } from "rxjs";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service"; import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeType } from "@bitwarden/common/platform/enums";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import { UriMatchType } from "@bitwarden/common/vault/enums"; import { UriMatchType } from "@bitwarden/common/vault/enums";
@@ -45,7 +45,7 @@ export class OptionsComponent implements OnInit {
constructor( constructor(
private messagingService: MessagingService, private messagingService: MessagingService,
private stateService: StateService, private stateService: StateService,
private totpService: TotpService, private autofillSettingsService: AutofillSettingsServiceAbstraction,
i18nService: I18nService, i18nService: I18nService,
private themingService: AbstractThemingService, private themingService: AbstractThemingService,
private settingsService: SettingsService, private settingsService: SettingsService,
@@ -84,10 +84,13 @@ export class OptionsComponent implements OnInit {
} }
async ngOnInit() { async ngOnInit() {
this.enableAutoFillOnPageLoad = await this.stateService.getEnableAutoFillOnPageLoad(); this.enableAutoFillOnPageLoad = await firstValueFrom(
this.autofillSettingsService.autofillOnPageLoad$,
);
this.autoFillOnPageLoadDefault = this.autoFillOnPageLoadDefault = await firstValueFrom(
(await this.stateService.getAutoFillOnPageLoadDefault()) ?? true; this.autofillSettingsService.autofillOnPageLoadDefault$,
);
this.enableAddLoginNotification = !(await this.stateService.getDisableAddLoginNotification()); this.enableAddLoginNotification = !(await this.stateService.getDisableAddLoginNotification());
@@ -99,7 +102,7 @@ export class OptionsComponent implements OnInit {
this.showCardsCurrentTab = !(await this.stateService.getDontShowCardsCurrentTab()); this.showCardsCurrentTab = !(await this.stateService.getDontShowCardsCurrentTab());
this.showIdentitiesCurrentTab = !(await this.stateService.getDontShowIdentitiesCurrentTab()); this.showIdentitiesCurrentTab = !(await this.stateService.getDontShowIdentitiesCurrentTab());
this.enableAutoTotpCopy = !(await this.stateService.getDisableAutoTotpCopy()); this.enableAutoTotpCopy = await firstValueFrom(this.autofillSettingsService.autoCopyTotp$);
this.enableFavicon = !this.settingsService.getDisableFavicon(); this.enableFavicon = !this.settingsService.getDisableFavicon();
@@ -135,15 +138,15 @@ export class OptionsComponent implements OnInit {
} }
async updateAutoTotpCopy() { async updateAutoTotpCopy() {
await this.stateService.setDisableAutoTotpCopy(!this.enableAutoTotpCopy); await this.autofillSettingsService.setAutoCopyTotp(this.enableAutoTotpCopy);
} }
async updateAutoFillOnPageLoad() { async updateAutoFillOnPageLoad() {
await this.stateService.setEnableAutoFillOnPageLoad(this.enableAutoFillOnPageLoad); await this.autofillSettingsService.setAutofillOnPageLoad(this.enableAutoFillOnPageLoad);
} }
async updateAutoFillOnPageLoadDefault() { async updateAutoFillOnPageLoadDefault() {
await this.stateService.setAutoFillOnPageLoadDefault(this.autoFillOnPageLoadDefault); await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autoFillOnPageLoadDefault);
} }
async updateFavicon() { async updateFavicon() {

View File

@@ -1,6 +1,10 @@
import { CipherService as AbstractCipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService as AbstractCipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
import {
AutofillSettingsServiceInitOptions,
autofillSettingsServiceFactory,
} from "../../../autofill/background/service_factories/autofill-settings-service.factory";
import { import {
CipherFileUploadServiceInitOptions, CipherFileUploadServiceInitOptions,
cipherFileUploadServiceFactory, cipherFileUploadServiceFactory,
@@ -53,6 +57,7 @@ export type CipherServiceInitOptions = CipherServiceFactoryOptions &
I18nServiceInitOptions & I18nServiceInitOptions &
SearchServiceInitOptions & SearchServiceInitOptions &
StateServiceInitOptions & StateServiceInitOptions &
AutofillSettingsServiceInitOptions &
EncryptServiceInitOptions & EncryptServiceInitOptions &
ConfigServiceInitOptions; ConfigServiceInitOptions;
@@ -72,6 +77,7 @@ export function cipherServiceFactory(
await i18nServiceFactory(cache, opts), await i18nServiceFactory(cache, opts),
await searchServiceFactory(cache, opts), await searchServiceFactory(cache, opts),
await stateServiceFactory(cache, opts), await stateServiceFactory(cache, opts),
await autofillSettingsServiceFactory(cache, opts),
await encryptServiceFactory(cache, opts), await encryptServiceFactory(cache, opts),
await cipherFileUploadServiceFactory(cache, opts), await cipherFileUploadServiceFactory(cache, opts),
await configServiceFactory(cache, opts), await configServiceFactory(cache, opts),

View File

@@ -10,6 +10,7 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; 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 { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -53,6 +54,7 @@ export class AddEditComponent extends BaseAddEditComponent {
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
auditService: AuditService, auditService: AuditService,
stateService: StateService, stateService: StateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
collectionService: CollectionService, collectionService: CollectionService,
messagingService: MessagingService, messagingService: MessagingService,
private route: ActivatedRoute, private route: ActivatedRoute,
@@ -160,7 +162,7 @@ export class AddEditComponent extends BaseAddEditComponent {
await super.load(); await super.load();
this.showAutoFillOnPageLoadOptions = this.showAutoFillOnPageLoadOptions =
this.cipher.type === CipherType.Login && this.cipher.type === CipherType.Login &&
(await this.stateService.getEnableAutoFillOnPageLoad()); (await firstValueFrom(this.autofillSettingsService.autofillOnPageLoad$));
} }
async submit(): Promise<boolean> { async submit(): Promise<boolean> {

View File

@@ -1,10 +1,11 @@
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { Subject } from "rxjs"; import { Subject, firstValueFrom } from "rxjs";
import { debounceTime, takeUntil } from "rxjs/operators"; import { debounceTime, takeUntil } from "rxjs/operators";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -65,6 +66,7 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
private syncService: SyncService, private syncService: SyncService,
private searchService: SearchService, private searchService: SearchService,
private stateService: StateService, private stateService: StateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private passwordRepromptService: PasswordRepromptService, private passwordRepromptService: PasswordRepromptService,
private organizationService: OrganizationService, private organizationService: OrganizationService,
private vaultFilterService: VaultFilterService, private vaultFilterService: VaultFilterService,
@@ -122,9 +124,9 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
.subscribe(() => this.searchVault()); .subscribe(() => this.searchVault());
// activate autofill on page load if policy is set // activate autofill on page load if policy is set
if (await this.stateService.getActivateAutoFillOnPageLoadFromPolicy()) { if (await this.getActivateAutofillOnPageLoadFromPolicy()) {
await this.stateService.setEnableAutoFillOnPageLoad(true); await this.autofillSettingsService.setAutofillOnPageLoad(true);
await this.stateService.setActivateAutoFillOnPageLoadFromPolicy(false); await this.autofillSettingsService.setActivateAutofillOnPageLoadFromPolicy(false);
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"info", "info",
null, null,
@@ -301,17 +303,25 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
this.router.navigate(["autofill"]); this.router.navigate(["autofill"]);
} }
private async getActivateAutofillOnPageLoadFromPolicy(): Promise<boolean> {
return await firstValueFrom(this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$);
}
async dismissCallout() { async dismissCallout() {
await this.stateService.setDismissedAutofillCallout(true); await this.autofillSettingsService.setAutofillOnPageLoadCalloutIsDismissed(true);
this.showHowToAutofill = false; this.showHowToAutofill = false;
} }
private async setCallout() { private async setCallout() {
const inlineMenuVisibilityIsOff =
(await firstValueFrom(this.autofillSettingsService.inlineMenuVisibility$)) ===
AutofillOverlayVisibility.Off;
this.showHowToAutofill = this.showHowToAutofill =
this.loginCiphers.length > 0 && this.loginCiphers.length > 0 &&
(await this.stateService.getAutoFillOverlayVisibility()) === AutofillOverlayVisibility.Off && inlineMenuVisibilityIsOff &&
!(await this.stateService.getEnableAutoFillOnPageLoad()) && !(await firstValueFrom(this.autofillSettingsService.autofillOnPageLoad$)) &&
!(await this.stateService.getDismissedAutofillCallout()); !(await firstValueFrom(this.autofillSettingsService.autofillOnPageLoadCalloutIsDismissed$));
if (this.showHowToAutofill) { if (this.showHowToAutofill) {
const autofillCommand = await this.platformUtilsService.getAutofillKeyboardShortcut(); const autofillCommand = await this.platformUtilsService.getAutofillKeyboardShortcut();

View File

@@ -35,6 +35,7 @@ import { TokenService } from "@bitwarden/common/auth/services/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service";
import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service"; import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service";
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service"; import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { ClientType } from "@bitwarden/common/enums"; import { ClientType } from "@bitwarden/common/enums";
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums"; import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums";
@@ -176,6 +177,7 @@ export class Main {
userVerificationService: UserVerificationService; userVerificationService: UserVerificationService;
pinCryptoService: PinCryptoServiceAbstraction; pinCryptoService: PinCryptoServiceAbstraction;
stateService: StateService; stateService: StateService;
autofillSettingsService: AutofillSettingsServiceAbstraction;
organizationService: OrganizationService; organizationService: OrganizationService;
providerService: ProviderService; providerService: ProviderService;
twoFactorService: TwoFactorService; twoFactorService: TwoFactorService;
@@ -444,6 +446,7 @@ export class Main {
this.i18nService, this.i18nService,
this.searchService, this.searchService,
this.stateService, this.stateService,
this.autofillSettingsService,
this.encryptService, this.encryptService,
this.cipherFileUploadService, this.cipherFileUploadService,
this.configService, this.configService,

View File

@@ -82,6 +82,10 @@ import { UserVerificationService } from "@bitwarden/common/auth/services/user-ve
import { WebAuthnLoginApiService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-api.service"; import { WebAuthnLoginApiService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-api.service";
import { WebAuthnLoginPrfCryptoService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-prf-crypto.service"; import { WebAuthnLoginPrfCryptoService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-prf-crypto.service";
import { WebAuthnLoginService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login.service"; import { WebAuthnLoginService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login.service";
import {
AutofillSettingsServiceAbstraction,
AutofillSettingsService,
} from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
import { BillingBannerServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-banner.service.abstraction"; import { BillingBannerServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-banner.service.abstraction";
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions/organization-billing.service"; import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions/organization-billing.service";
@@ -334,6 +338,7 @@ import { ModalService } from "./modal.service";
i18nService: I18nServiceAbstraction, i18nService: I18nServiceAbstraction,
searchService: SearchServiceAbstraction, searchService: SearchServiceAbstraction,
stateService: StateServiceAbstraction, stateService: StateServiceAbstraction,
autofillSettingsService: AutofillSettingsServiceAbstraction,
encryptService: EncryptService, encryptService: EncryptService,
fileUploadService: CipherFileUploadServiceAbstraction, fileUploadService: CipherFileUploadServiceAbstraction,
configService: ConfigServiceAbstraction, configService: ConfigServiceAbstraction,
@@ -345,6 +350,7 @@ import { ModalService } from "./modal.service";
i18nService, i18nService,
searchService, searchService,
stateService, stateService,
autofillSettingsService,
encryptService, encryptService,
fileUploadService, fileUploadService,
configService, configService,
@@ -356,6 +362,7 @@ import { ModalService } from "./modal.service";
I18nServiceAbstraction, I18nServiceAbstraction,
SearchServiceAbstraction, SearchServiceAbstraction,
StateServiceAbstraction, StateServiceAbstraction,
AutofillSettingsServiceAbstraction,
EncryptService, EncryptService,
CipherFileUploadServiceAbstraction, CipherFileUploadServiceAbstraction,
ConfigServiceAbstraction, ConfigServiceAbstraction,
@@ -908,6 +915,11 @@ import { ModalService } from "./modal.service";
OrganizationApiServiceAbstraction, OrganizationApiServiceAbstraction,
], ],
}, },
{
provide: AutofillSettingsServiceAbstraction,
useClass: AutofillSettingsService,
deps: [StateProvider, PolicyServiceAbstraction],
},
{ {
provide: BiometricStateService, provide: BiometricStateService,
useClass: DefaultBiometricStateService, useClass: DefaultBiometricStateService,

View File

@@ -10,7 +10,5 @@ export abstract class SettingsService {
getEquivalentDomains: (url: string) => Set<string>; getEquivalentDomains: (url: string) => Set<string>;
setDisableFavicon: (value: boolean) => Promise<any>; setDisableFavicon: (value: boolean) => Promise<any>;
getDisableFavicon: () => boolean; getDisableFavicon: () => boolean;
setAutoFillOverlayVisibility: (value: number) => Promise<void>;
getAutoFillOverlayVisibility: () => Promise<number>;
clear: (userId?: string) => Promise<void>; clear: (userId?: string) => Promise<void>;
} }

View File

@@ -1,22 +1,22 @@
import { mock, MockProxy } from "jest-mock-extended"; import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom } from "rxjs"; import { BehaviorSubject, firstValueFrom } from "rxjs";
import { OrganizationService } from "../admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "../../../admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationUserStatusType, PolicyType } from "../admin-console/enums"; import { OrganizationUserStatusType, PolicyType } from "../../../admin-console/enums";
import { PermissionsApi } from "../admin-console/models/api/permissions.api"; import { PermissionsApi } from "../../../admin-console/models/api/permissions.api";
import { OrganizationData } from "../admin-console/models/data/organization.data"; import { OrganizationData } from "../../../admin-console/models/data/organization.data";
import { PolicyData } from "../admin-console/models/data/policy.data"; import { PolicyData } from "../../../admin-console/models/data/policy.data";
import { MasterPasswordPolicyOptions } from "../admin-console/models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "../../../admin-console/models/domain/master-password-policy-options";
import { Organization } from "../admin-console/models/domain/organization"; import { Organization } from "../../../admin-console/models/domain/organization";
import { Policy } from "../admin-console/models/domain/policy"; import { Policy } from "../../../admin-console/models/domain/policy";
import { ResetPasswordPolicyOptions } from "../admin-console/models/domain/reset-password-policy-options"; import { ResetPasswordPolicyOptions } from "../../../admin-console/models/domain/reset-password-policy-options";
import { PolicyResponse } from "../admin-console/models/response/policy.response"; import { PolicyResponse } from "../../../admin-console/models/response/policy.response";
import { PolicyService } from "../admin-console/services/policy/policy.service"; import { PolicyService } from "../../../admin-console/services/policy/policy.service";
import { ListResponse } from "../models/response/list.response"; import { ListResponse } from "../../../models/response/list.response";
import { CryptoService } from "../platform/abstractions/crypto.service"; import { CryptoService } from "../../../platform/abstractions/crypto.service";
import { EncryptService } from "../platform/abstractions/encrypt.service"; import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { ContainerService } from "../platform/services/container.service"; import { ContainerService } from "../../../platform/services/container.service";
import { StateService } from "../platform/services/state.service"; import { StateService } from "../../../platform/services/state.service";
describe("PolicyService", () => { describe("PolicyService", () => {
let policyService: PolicyService; let policyService: PolicyService;

View File

@@ -0,0 +1,174 @@
import { filter, switchMap, tap, firstValueFrom, map, Observable } from "rxjs";
import {
AutofillOverlayVisibility,
InlineMenuVisibilitySetting,
} from "../../../../../apps/browser/src/autofill/utils/autofill-overlay.enum";
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "../../admin-console/enums/index";
import { Policy } from "../../admin-console/models/domain/policy";
import {
AUTOFILL_SETTINGS_DISK,
AUTOFILL_SETTINGS_DISK_LOCAL,
ActiveUserState,
GlobalState,
KeyDefinition,
StateProvider,
} from "../../platform/state";
const AUTOFILL_ON_PAGE_LOAD = new KeyDefinition(AUTOFILL_SETTINGS_DISK, "autofillOnPageLoad", {
deserializer: (value: boolean) => value ?? false,
});
const AUTOFILL_ON_PAGE_LOAD_DEFAULT = new KeyDefinition(
AUTOFILL_SETTINGS_DISK,
"autofillOnPageLoadDefault",
{
deserializer: (value: boolean) => value ?? false,
},
);
const AUTO_COPY_TOTP = new KeyDefinition(AUTOFILL_SETTINGS_DISK, "autoCopyTotp", {
deserializer: (value: boolean) => value ?? false,
});
const AUTOFILL_ON_PAGE_LOAD_CALLOUT_DISMISSED = new KeyDefinition(
AUTOFILL_SETTINGS_DISK,
"autofillOnPageLoadCalloutIsDismissed",
{
deserializer: (value: boolean) => value ?? false,
},
);
const ACTIVATE_AUTOFILL_ON_PAGE_LOAD_FROM_POLICY = new KeyDefinition(
AUTOFILL_SETTINGS_DISK_LOCAL,
"activateAutofillOnPageLoadFromPolicy",
{
deserializer: (value: boolean) => value ?? false,
},
);
const INLINE_MENU_VISIBILITY = new KeyDefinition(
AUTOFILL_SETTINGS_DISK_LOCAL,
"inlineMenuVisibility",
{
deserializer: (value: InlineMenuVisibilitySetting) => value ?? AutofillOverlayVisibility.Off,
},
);
export abstract class AutofillSettingsServiceAbstraction {
autofillOnPageLoad$: Observable<boolean>;
setAutofillOnPageLoad: (newValue: boolean) => Promise<void>;
autofillOnPageLoadDefault$: Observable<boolean>;
setAutofillOnPageLoadDefault: (newValue: boolean) => Promise<void>;
autoCopyTotp$: Observable<boolean>;
setAutoCopyTotp: (newValue: boolean) => Promise<void>;
autofillOnPageLoadCalloutIsDismissed$: Observable<boolean>;
setAutofillOnPageLoadCalloutIsDismissed: (newValue: boolean) => Promise<void>;
activateAutofillOnPageLoadFromPolicy$: Observable<boolean>;
setActivateAutofillOnPageLoadFromPolicy: (newValue: boolean) => Promise<void>;
inlineMenuVisibility$: Observable<InlineMenuVisibilitySetting>;
setInlineMenuVisibility: (newValue: InlineMenuVisibilitySetting) => Promise<void>;
handleActivateAutofillPolicy: (policies: Observable<Policy[]>) => Observable<boolean[]>;
}
export class AutofillSettingsService implements AutofillSettingsServiceAbstraction {
private autofillOnPageLoadState: ActiveUserState<boolean>;
readonly autofillOnPageLoad$: Observable<boolean>;
private autofillOnPageLoadDefaultState: ActiveUserState<boolean>;
readonly autofillOnPageLoadDefault$: Observable<boolean>;
private autoCopyTotpState: ActiveUserState<boolean>;
readonly autoCopyTotp$: Observable<boolean>;
private autofillOnPageLoadCalloutIsDismissedState: ActiveUserState<boolean>;
readonly autofillOnPageLoadCalloutIsDismissed$: Observable<boolean>;
private activateAutofillOnPageLoadFromPolicyState: ActiveUserState<boolean>;
readonly activateAutofillOnPageLoadFromPolicy$: Observable<boolean>;
private inlineMenuVisibilityState: GlobalState<InlineMenuVisibilitySetting>;
readonly inlineMenuVisibility$: Observable<InlineMenuVisibilitySetting>;
constructor(
private stateProvider: StateProvider,
policyService: PolicyService,
) {
this.autofillOnPageLoadState = this.stateProvider.getActive(AUTOFILL_ON_PAGE_LOAD);
this.autofillOnPageLoad$ = this.autofillOnPageLoadState.state$.pipe(map((x) => x ?? false));
this.autofillOnPageLoadDefaultState = this.stateProvider.getActive(
AUTOFILL_ON_PAGE_LOAD_DEFAULT,
);
this.autofillOnPageLoadDefault$ = this.autofillOnPageLoadDefaultState.state$.pipe(
map((x) => x ?? true),
);
this.autoCopyTotpState = this.stateProvider.getActive(AUTO_COPY_TOTP);
this.autoCopyTotp$ = this.autoCopyTotpState.state$.pipe(map((x) => x ?? false));
this.autofillOnPageLoadCalloutIsDismissedState = this.stateProvider.getActive(
AUTOFILL_ON_PAGE_LOAD_CALLOUT_DISMISSED,
);
this.autofillOnPageLoadCalloutIsDismissed$ =
this.autofillOnPageLoadCalloutIsDismissedState.state$.pipe(map((x) => x ?? false));
this.activateAutofillOnPageLoadFromPolicyState = this.stateProvider.getActive(
ACTIVATE_AUTOFILL_ON_PAGE_LOAD_FROM_POLICY,
);
this.activateAutofillOnPageLoadFromPolicy$ =
this.activateAutofillOnPageLoadFromPolicyState.state$.pipe(map((x) => x ?? false));
this.inlineMenuVisibilityState = this.stateProvider.getGlobal(INLINE_MENU_VISIBILITY);
this.inlineMenuVisibility$ = this.inlineMenuVisibilityState.state$.pipe(
map((x) => x ?? AutofillOverlayVisibility.Off),
);
policyService.policies$.pipe(this.handleActivateAutofillPolicy.bind(this)).subscribe();
}
async setAutofillOnPageLoad(newValue: boolean): Promise<void> {
await this.autofillOnPageLoadState.update(() => newValue);
}
async setAutofillOnPageLoadDefault(newValue: boolean): Promise<void> {
await this.autofillOnPageLoadDefaultState.update(() => newValue);
}
async setAutoCopyTotp(newValue: boolean): Promise<void> {
await this.autoCopyTotpState.update(() => newValue);
}
async setAutofillOnPageLoadCalloutIsDismissed(newValue: boolean): Promise<void> {
await this.autofillOnPageLoadCalloutIsDismissedState.update(() => newValue);
}
async setActivateAutofillOnPageLoadFromPolicy(newValue: boolean): Promise<void> {
await this.activateAutofillOnPageLoadFromPolicyState.update(() => newValue);
}
async setInlineMenuVisibility(newValue: InlineMenuVisibilitySetting): Promise<void> {
await this.inlineMenuVisibilityState.update(() => newValue);
}
/**
* If the ActivateAutofill policy is enabled, save a flag indicating if we need to
* enable Autofill on page load.
*/
handleActivateAutofillPolicy(policies$: Observable<Policy[]>): Observable<boolean[]> {
return policies$.pipe(
map((policies) => policies.find((p) => p.type == PolicyType.ActivateAutofill && p.enabled)),
filter((p) => p != null),
switchMap(async (_) => [
await firstValueFrom(this.activateAutofillOnPageLoadFromPolicy$),
await firstValueFrom(this.autofillOnPageLoad$),
]),
tap(([activated, autofillEnabled]) => {
if (activated === undefined) {
void this.setActivateAutofillOnPageLoadFromPolicy(!autofillEnabled);
}
}),
);
}
}

View File

@@ -71,8 +71,6 @@ export abstract class StateService<T extends Account = Account> {
setApiKeyClientSecret: (value: string, options?: StorageOptions) => Promise<void>; setApiKeyClientSecret: (value: string, options?: StorageOptions) => Promise<void>;
getAutoConfirmFingerPrints: (options?: StorageOptions) => Promise<boolean>; getAutoConfirmFingerPrints: (options?: StorageOptions) => Promise<boolean>;
setAutoConfirmFingerprints: (value: boolean, options?: StorageOptions) => Promise<void>; setAutoConfirmFingerprints: (value: boolean, options?: StorageOptions) => Promise<void>;
getAutoFillOnPageLoadDefault: (options?: StorageOptions) => Promise<boolean>;
setAutoFillOnPageLoadDefault: (value: boolean, options?: StorageOptions) => Promise<void>;
getBiometricAwaitingAcceptance: (options?: StorageOptions) => Promise<boolean>; getBiometricAwaitingAcceptance: (options?: StorageOptions) => Promise<boolean>;
setBiometricAwaitingAcceptance: (value: boolean, options?: StorageOptions) => Promise<void>; setBiometricAwaitingAcceptance: (value: boolean, options?: StorageOptions) => Promise<void>;
getBiometricFingerprintValidated: (options?: StorageOptions) => Promise<boolean>; getBiometricFingerprintValidated: (options?: StorageOptions) => Promise<boolean>;
@@ -243,8 +241,6 @@ export abstract class StateService<T extends Account = Account> {
setDisableAddLoginNotification: (value: boolean, options?: StorageOptions) => Promise<void>; setDisableAddLoginNotification: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableAutoBiometricsPrompt: (options?: StorageOptions) => Promise<boolean>; getDisableAutoBiometricsPrompt: (options?: StorageOptions) => Promise<boolean>;
setDisableAutoBiometricsPrompt: (value: boolean, options?: StorageOptions) => Promise<void>; setDisableAutoBiometricsPrompt: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableAutoTotpCopy: (options?: StorageOptions) => Promise<boolean>;
setDisableAutoTotpCopy: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableBadgeCounter: (options?: StorageOptions) => Promise<boolean>; getDisableBadgeCounter: (options?: StorageOptions) => Promise<boolean>;
setDisableBadgeCounter: (value: boolean, options?: StorageOptions) => Promise<void>; setDisableBadgeCounter: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableChangedPasswordNotification: (options?: StorageOptions) => Promise<boolean>; getDisableChangedPasswordNotification: (options?: StorageOptions) => Promise<boolean>;
@@ -264,8 +260,6 @@ export abstract class StateService<T extends Account = Account> {
setDisableFavicon: (value: boolean, options?: StorageOptions) => Promise<void>; setDisableFavicon: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableGa: (options?: StorageOptions) => Promise<boolean>; getDisableGa: (options?: StorageOptions) => Promise<boolean>;
setDisableGa: (value: boolean, options?: StorageOptions) => Promise<void>; setDisableGa: (value: boolean, options?: StorageOptions) => Promise<void>;
getDismissedAutofillCallout: (options?: StorageOptions) => Promise<boolean>;
setDismissedAutofillCallout: (value: boolean, options?: StorageOptions) => Promise<void>;
getDontShowCardsCurrentTab: (options?: StorageOptions) => Promise<boolean>; getDontShowCardsCurrentTab: (options?: StorageOptions) => Promise<boolean>;
setDontShowCardsCurrentTab: (value: boolean, options?: StorageOptions) => Promise<void>; setDontShowCardsCurrentTab: (value: boolean, options?: StorageOptions) => Promise<void>;
getDontShowIdentitiesCurrentTab: (options?: StorageOptions) => Promise<boolean>; getDontShowIdentitiesCurrentTab: (options?: StorageOptions) => Promise<boolean>;
@@ -294,10 +288,6 @@ export abstract class StateService<T extends Account = Account> {
setEmailVerified: (value: boolean, options?: StorageOptions) => Promise<void>; setEmailVerified: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableAlwaysOnTop: (options?: StorageOptions) => Promise<boolean>; getEnableAlwaysOnTop: (options?: StorageOptions) => Promise<boolean>;
setEnableAlwaysOnTop: (value: boolean, options?: StorageOptions) => Promise<void>; setEnableAlwaysOnTop: (value: boolean, options?: StorageOptions) => Promise<void>;
getAutoFillOverlayVisibility: (options?: StorageOptions) => Promise<number>;
setAutoFillOverlayVisibility: (value: number, options?: StorageOptions) => Promise<void>;
getEnableAutoFillOnPageLoad: (options?: StorageOptions) => Promise<boolean>;
setEnableAutoFillOnPageLoad: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableBrowserIntegration: (options?: StorageOptions) => Promise<boolean>; getEnableBrowserIntegration: (options?: StorageOptions) => Promise<boolean>;
setEnableBrowserIntegration: (value: boolean, options?: StorageOptions) => Promise<void>; setEnableBrowserIntegration: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableBrowserIntegrationFingerprint: (options?: StorageOptions) => Promise<boolean>; getEnableBrowserIntegrationFingerprint: (options?: StorageOptions) => Promise<boolean>;
@@ -486,13 +476,6 @@ export abstract class StateService<T extends Account = Account> {
getAvatarColor: (options?: StorageOptions) => Promise<string | null | undefined>; getAvatarColor: (options?: StorageOptions) => Promise<string | null | undefined>;
setAvatarColor: (value: string, options?: StorageOptions) => Promise<void>; setAvatarColor: (value: string, options?: StorageOptions) => Promise<void>;
getActivateAutoFillOnPageLoadFromPolicy: (
options?: StorageOptions,
) => Promise<boolean | undefined>;
setActivateAutoFillOnPageLoadFromPolicy: (
value: boolean,
options?: StorageOptions,
) => Promise<void>;
getSMOnboardingTasks: ( getSMOnboardingTasks: (
options?: StorageOptions, options?: StorageOptions,
) => Promise<Record<string, Record<string, boolean>>>; ) => Promise<Record<string, Record<string, boolean>>>;

View File

@@ -216,20 +216,16 @@ export class AccountProfile {
export class AccountSettings { export class AccountSettings {
autoConfirmFingerPrints?: boolean; autoConfirmFingerPrints?: boolean;
autoFillOnPageLoadDefault?: boolean;
biometricUnlock?: boolean; biometricUnlock?: boolean;
clearClipboard?: number; clearClipboard?: number;
collapsedGroupings?: string[]; collapsedGroupings?: string[];
defaultUriMatch?: UriMatchType; defaultUriMatch?: UriMatchType;
disableAutoBiometricsPrompt?: boolean; disableAutoBiometricsPrompt?: boolean;
disableAutoTotpCopy?: boolean;
disableBadgeCounter?: boolean; disableBadgeCounter?: boolean;
disableGa?: boolean; disableGa?: boolean;
dismissedAutoFillOnPageLoadCallout?: boolean;
dontShowCardsCurrentTab?: boolean; dontShowCardsCurrentTab?: boolean;
dontShowIdentitiesCurrentTab?: boolean; dontShowIdentitiesCurrentTab?: boolean;
enableAlwaysOnTop?: boolean; enableAlwaysOnTop?: boolean;
enableAutoFillOnPageLoad?: boolean;
enableBiometric?: boolean; enableBiometric?: boolean;
enableFullWidth?: boolean; enableFullWidth?: boolean;
equivalentDomains?: any; equivalentDomains?: any;
@@ -246,7 +242,6 @@ export class AccountSettings {
serverConfig?: ServerConfigData; serverConfig?: ServerConfigData;
approveLoginRequests?: boolean; approveLoginRequests?: boolean;
avatarColor?: string; avatarColor?: string;
activateAutoFillOnPageLoadFromPolicy?: boolean;
smOnboardingTasks?: Record<string, Record<string, boolean>>; smOnboardingTasks?: Record<string, Record<string, boolean>>;
trustDeviceChoiceForDecryption?: boolean; trustDeviceChoiceForDecryption?: boolean;
biometricPromptCancelled?: boolean; biometricPromptCancelled?: boolean;

View File

@@ -34,6 +34,5 @@ export class GlobalState {
disableAddLoginNotification?: boolean; disableAddLoginNotification?: boolean;
disableChangedPasswordNotification?: boolean; disableChangedPasswordNotification?: boolean;
disableContextMenuItem?: boolean; disableContextMenuItem?: boolean;
autoFillOverlayVisibility?: number;
deepLinkRedirectUrl?: string; deepLinkRedirectUrl?: string;
} }

View File

@@ -1,7 +1,6 @@
import { BehaviorSubject, concatMap } from "rxjs"; import { BehaviorSubject, concatMap } from "rxjs";
import { Jsonify, JsonValue } from "type-fest"; import { Jsonify, JsonValue } from "type-fest";
import { AutofillOverlayVisibility } from "../../../../../apps/browser/src/autofill/utils/autofill-overlay.enum";
import { OrganizationData } from "../../admin-console/models/data/organization.data"; import { OrganizationData } from "../../admin-console/models/data/organization.data";
import { PolicyData } from "../../admin-console/models/data/policy.data"; import { PolicyData } from "../../admin-console/models/data/policy.data";
import { ProviderData } from "../../admin-console/models/data/provider.data"; import { ProviderData } from "../../admin-console/models/data/provider.data";
@@ -374,24 +373,6 @@ export class StateService<
); );
} }
async getAutoFillOnPageLoadDefault(options?: StorageOptions): Promise<boolean> {
return (
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
?.settings?.autoFillOnPageLoadDefault ?? true
);
}
async setAutoFillOnPageLoadDefault(value: boolean, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
account.settings.autoFillOnPageLoadDefault = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getBiometricAwaitingAcceptance(options?: StorageOptions): Promise<boolean> { async getBiometricAwaitingAcceptance(options?: StorageOptions): Promise<boolean> {
return ( return (
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
@@ -1154,24 +1135,6 @@ export class StateService<
); );
} }
async getDisableAutoTotpCopy(options?: StorageOptions): Promise<boolean> {
return (
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
?.settings?.disableAutoTotpCopy ?? false
);
}
async setDisableAutoTotpCopy(value: boolean, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
account.settings.disableAutoTotpCopy = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getDisableBadgeCounter(options?: StorageOptions): Promise<boolean> { async getDisableBadgeCounter(options?: StorageOptions): Promise<boolean> {
return ( return (
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
@@ -1268,24 +1231,6 @@ export class StateService<
); );
} }
async getDismissedAutofillCallout(options?: StorageOptions): Promise<boolean> {
return (
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
?.settings?.dismissedAutoFillOnPageLoadCallout ?? false
);
}
async setDismissedAutofillCallout(value: boolean, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
account.settings.dismissedAutoFillOnPageLoadCallout = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getDontShowCardsCurrentTab(options?: StorageOptions): Promise<boolean> { async getDontShowCardsCurrentTab(options?: StorageOptions): Promise<boolean> {
return ( return (
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
@@ -1525,43 +1470,6 @@ export class StateService<
); );
} }
async getAutoFillOverlayVisibility(options?: StorageOptions): Promise<number> {
const locallyStoredOptions = await this.defaultOnDiskLocalOptions();
const reconciledOptions = this.reconcileOptions(options, locallyStoredOptions);
const globals = await this.getGlobals(reconciledOptions);
return globals?.autoFillOverlayVisibility ?? AutofillOverlayVisibility.Off;
}
async setAutoFillOverlayVisibility(value: number, options?: StorageOptions): Promise<void> {
const globals = await this.getGlobals(
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()),
);
globals.autoFillOverlayVisibility = value;
await this.saveGlobals(
globals,
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()),
);
}
async getEnableAutoFillOnPageLoad(options?: StorageOptions): Promise<boolean> {
return (
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
?.settings?.enableAutoFillOnPageLoad ?? false
);
}
async setEnableAutoFillOnPageLoad(value: boolean, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
account.settings.enableAutoFillOnPageLoad = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getEnableBrowserIntegration(options?: StorageOptions): Promise<boolean> { async getEnableBrowserIntegration(options?: StorageOptions): Promise<boolean> {
return ( return (
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
@@ -2612,26 +2520,6 @@ export class StateService<
); );
} }
async getActivateAutoFillOnPageLoadFromPolicy(options?: StorageOptions): Promise<boolean> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.settings?.activateAutoFillOnPageLoadFromPolicy;
}
async setActivateAutoFillOnPageLoadFromPolicy(
value: boolean,
options?: StorageOptions,
): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()),
);
account.settings.activateAutoFillOnPageLoadFromPolicy = value;
return await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()),
);
}
async getSMOnboardingTasks( async getSMOnboardingTasks(
options?: StorageOptions, options?: StorageOptions,
): Promise<Record<string, Record<string, boolean>>> { ): Promise<Record<string, Record<string, boolean>>> {

View File

@@ -49,3 +49,8 @@ export const SYNC_STATE = new StateDefinition("sync", "disk", { web: "memory" })
export const VAULT_SETTINGS_DISK = new StateDefinition("vaultSettings", "disk", { export const VAULT_SETTINGS_DISK = new StateDefinition("vaultSettings", "disk", {
web: "disk-local", web: "disk-local",
}); });
export const AUTOFILL_SETTINGS_DISK = new StateDefinition("autofillSettings", "disk");
export const AUTOFILL_SETTINGS_DISK_LOCAL = new StateDefinition("autofillSettingsLocal", "disk", {
web: "disk-local",
});

View File

@@ -74,14 +74,6 @@ export class SettingsService implements SettingsServiceAbstraction {
return this._disableFavicon.getValue(); return this._disableFavicon.getValue();
} }
async setAutoFillOverlayVisibility(value: number): Promise<void> {
return await this.stateService.setAutoFillOverlayVisibility(value);
}
async getAutoFillOverlayVisibility(): Promise<number> {
return await this.stateService.getAutoFillOverlayVisibility();
}
async clear(userId?: string): Promise<void> { async clear(userId?: string): Promise<void> {
if (userId == null || userId == (await this.stateService.getUserId())) { if (userId == null || userId == (await this.stateService.getUserId())) {
this._settings.next({}); this._settings.next({});

View File

@@ -13,6 +13,7 @@ import { MoveBiometricClientKeyHalfToStateProviders } from "./migrations/14-move
import { FolderMigrator } from "./migrations/15-move-folder-state-to-state-provider"; import { FolderMigrator } from "./migrations/15-move-folder-state-to-state-provider";
import { LastSyncMigrator } from "./migrations/16-move-last-sync-to-state-provider"; import { LastSyncMigrator } from "./migrations/16-move-last-sync-to-state-provider";
import { EnablePasskeysMigrator } from "./migrations/17-move-enable-passkeys-to-state-providers"; import { EnablePasskeysMigrator } from "./migrations/17-move-enable-passkeys-to-state-providers";
import { AutofillSettingsKeyMigrator } from "./migrations/18-move-autofill-settings-to-state-providers";
import { FixPremiumMigrator } from "./migrations/3-fix-premium"; import { FixPremiumMigrator } from "./migrations/3-fix-premium";
import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked"; import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked";
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys"; import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
@@ -23,7 +24,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting
import { MinVersionMigrator } from "./migrations/min-version"; import { MinVersionMigrator } from "./migrations/min-version";
export const MIN_VERSION = 2; export const MIN_VERSION = 2;
export const CURRENT_VERSION = 17; export const CURRENT_VERSION = 18;
export type MinVersion = typeof MIN_VERSION; export type MinVersion = typeof MIN_VERSION;
export async function migrate( export async function migrate(
@@ -56,7 +57,8 @@ export async function migrate(
.with(MoveBiometricClientKeyHalfToStateProviders, 13, 14) .with(MoveBiometricClientKeyHalfToStateProviders, 13, 14)
.with(FolderMigrator, 14, 15) .with(FolderMigrator, 14, 15)
.with(LastSyncMigrator, 15, 16) .with(LastSyncMigrator, 15, 16)
.with(EnablePasskeysMigrator, 16, CURRENT_VERSION) .with(EnablePasskeysMigrator, 16, 17)
.with(AutofillSettingsKeyMigrator, 17, CURRENT_VERSION)
.migrate(migrationHelper); .migrate(migrationHelper);
} }

View File

@@ -0,0 +1,223 @@
import { any, MockProxy } from "jest-mock-extended";
import { AutofillOverlayVisibility } from "../../../../../apps/browser/src/autofill/utils/autofill-overlay.enum";
import { StateDefinitionLike, MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import { AutofillSettingsKeyMigrator } from "./18-move-autofill-settings-to-state-providers";
function exampleJSON() {
return {
global: {
autoFillOverlayVisibility: AutofillOverlayVisibility.OnButtonClick,
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2", "user-3"],
"user-1": {
settings: {
autoFillOnPageLoadDefault: true,
enableAutoFillOnPageLoad: true,
dismissedAutoFillOnPageLoadCallout: true,
disableAutoTotpCopy: false,
activateAutoFillOnPageLoadFromPolicy: true,
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
},
"user-2": {
settings: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
};
}
function rollbackJSON() {
return {
global_autofillSettingsLocal_inlineMenuVisibility: AutofillOverlayVisibility.OnButtonClick,
"user_user-1_autofillSettings_autoCopyTotp": true,
"user_user-1_autofillSettings_autofillOnPageLoad": true,
"user_user-1_autofillSettings_autofillOnPageLoadCalloutIsDismissed": true,
"user_user-1_autofillSettings_autofillOnPageLoadDefault": true,
"user_user-1_autofillSettingsLocal_activateAutofillOnPageLoadFromPolicy": true,
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2", "user-3"],
"user-1": {
settings: {
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
},
"user-2": {
settings: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
};
}
const autofillSettingsStateDefinition: {
stateDefinition: StateDefinitionLike;
} = {
stateDefinition: {
name: "autofillSettings",
},
};
describe("ProviderKeysMigrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: AutofillSettingsKeyMigrator;
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(exampleJSON(), 17);
sut = new AutofillSettingsKeyMigrator(17, 18);
});
it("should remove autofill settings from all accounts", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).toHaveBeenCalledWith("global", {
otherStuff: "otherStuff1",
});
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
});
});
it("should set autofill setting values for each account", async () => {
await sut.migrate(helper);
expect(helper.setToGlobal).toHaveBeenCalledTimes(1);
expect(helper.setToGlobal).toHaveBeenCalledWith(
{
stateDefinition: {
name: "autofillSettingsLocal",
},
key: "inlineMenuVisibility",
},
1,
);
expect(helper.setToUser).toHaveBeenCalledTimes(5);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{ ...autofillSettingsStateDefinition, key: "autofillOnPageLoadDefault" },
true,
);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{ ...autofillSettingsStateDefinition, key: "autofillOnPageLoad" },
true,
);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{ ...autofillSettingsStateDefinition, key: "autofillOnPageLoadCalloutIsDismissed" },
true,
);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{ ...autofillSettingsStateDefinition, key: "autoCopyTotp" },
true,
);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{
stateDefinition: {
name: "autofillSettingsLocal",
},
key: "activateAutofillOnPageLoadFromPolicy",
},
true,
);
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 16);
sut = new AutofillSettingsKeyMigrator(17, 18);
});
it("should null out new values for each account", async () => {
await sut.rollback(helper);
expect(helper.setToGlobal).toHaveBeenCalledTimes(1);
expect(helper.setToGlobal).toHaveBeenCalledWith(
{
stateDefinition: {
name: "autofillSettingsLocal",
},
key: "inlineMenuVisibility",
},
null,
);
expect(helper.setToUser).toHaveBeenCalledTimes(5);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{ ...autofillSettingsStateDefinition, key: "autofillOnPageLoadDefault" },
null,
);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{ ...autofillSettingsStateDefinition, key: "autofillOnPageLoad" },
null,
);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{ ...autofillSettingsStateDefinition, key: "autofillOnPageLoadCalloutIsDismissed" },
null,
);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{ ...autofillSettingsStateDefinition, key: "autoCopyTotp" },
null,
);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{
stateDefinition: {
name: "autofillSettingsLocal",
},
key: "activateAutofillOnPageLoadFromPolicy",
},
null,
);
});
it("should add explicit value back to accounts", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).toHaveBeenCalledWith("global", {
autoFillOverlayVisibility: 1,
otherStuff: "otherStuff1",
});
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
otherStuff: "otherStuff2",
autoFillOnPageLoadDefault: true,
enableAutoFillOnPageLoad: true,
dismissedAutoFillOnPageLoadCallout: true,
disableAutoTotpCopy: false,
activateAutoFillOnPageLoadFromPolicy: true,
},
otherStuff: "otherStuff3",
});
});
it("should not try to restore values to missing accounts", async () => {
await sut.rollback(helper);
expect(helper.set).not.toHaveBeenCalledWith("user-3", any());
});
});
});

View File

@@ -0,0 +1,262 @@
import { InlineMenuVisibilitySetting } from "../../../../../apps/browser/src/autofill/utils/autofill-overlay.enum";
import { StateDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
type ExpectedAccountState = {
settings?: {
autoFillOnPageLoadDefault?: boolean;
enableAutoFillOnPageLoad?: boolean;
dismissedAutoFillOnPageLoadCallout?: boolean;
disableAutoTotpCopy?: boolean;
activateAutoFillOnPageLoadFromPolicy?: InlineMenuVisibilitySetting;
};
};
type ExpectedGlobalState = { autoFillOverlayVisibility?: InlineMenuVisibilitySetting };
const autofillSettingsStateDefinition: {
stateDefinition: StateDefinitionLike;
} = {
stateDefinition: {
name: "autofillSettings",
},
};
export class AutofillSettingsKeyMigrator extends Migrator<17, 18> {
async migrate(helper: MigrationHelper): Promise<void> {
// global state (e.g. "autoFillOverlayVisibility -> inlineMenuVisibility")
const globalState = await helper.get<ExpectedGlobalState>("global");
if (globalState?.autoFillOverlayVisibility != null) {
await helper.setToGlobal(
{
stateDefinition: {
name: "autofillSettingsLocal",
},
key: "inlineMenuVisibility",
},
globalState.autoFillOverlayVisibility,
);
// delete `autoFillOverlayVisibility` from state global
delete globalState.autoFillOverlayVisibility;
await helper.set<ExpectedGlobalState>("global", globalState);
}
// account state (e.g. account settings -> state provider framework keys)
const accounts = await helper.getAccounts<ExpectedAccountState>();
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
// migrate account state
async function migrateAccount(userId: string, account: ExpectedAccountState): Promise<void> {
let updateAccount = false;
const accountSettings = account?.settings;
if (accountSettings?.autoFillOnPageLoadDefault != null) {
await helper.setToUser(
userId,
{ ...autofillSettingsStateDefinition, key: "autofillOnPageLoadDefault" },
accountSettings.autoFillOnPageLoadDefault,
);
delete account.settings.autoFillOnPageLoadDefault;
updateAccount = true;
}
if (accountSettings?.enableAutoFillOnPageLoad != null) {
await helper.setToUser(
userId,
{ ...autofillSettingsStateDefinition, key: "autofillOnPageLoad" },
accountSettings?.enableAutoFillOnPageLoad,
);
delete account.settings.enableAutoFillOnPageLoad;
updateAccount = true;
}
if (accountSettings?.dismissedAutoFillOnPageLoadCallout != null) {
await helper.setToUser(
userId,
{ ...autofillSettingsStateDefinition, key: "autofillOnPageLoadCalloutIsDismissed" },
accountSettings?.dismissedAutoFillOnPageLoadCallout,
);
delete account.settings.dismissedAutoFillOnPageLoadCallout;
updateAccount = true;
}
if (accountSettings?.disableAutoTotpCopy != null) {
await helper.setToUser(
userId,
{ ...autofillSettingsStateDefinition, key: "autoCopyTotp" },
// invert the value to match the new naming convention
!accountSettings?.disableAutoTotpCopy,
);
delete account.settings.disableAutoTotpCopy;
updateAccount = true;
}
if (accountSettings?.activateAutoFillOnPageLoadFromPolicy != null) {
await helper.setToUser(
userId,
{
stateDefinition: {
name: "autofillSettingsLocal",
},
key: "activateAutofillOnPageLoadFromPolicy",
},
accountSettings?.activateAutoFillOnPageLoadFromPolicy,
);
delete account.settings.activateAutoFillOnPageLoadFromPolicy;
updateAccount = true;
}
if (updateAccount) {
// update the state account settings with the migrated values deleted
await helper.set(userId, account);
}
}
}
async rollback(helper: MigrationHelper): Promise<void> {
// global state (e.g. "inlineMenuVisibility -> autoFillOverlayVisibility")
const globalState = (await helper.get<ExpectedGlobalState>("global")) || {};
const inlineMenuVisibility: InlineMenuVisibilitySetting = await helper.getFromGlobal({
stateDefinition: {
name: "autofillSettingsLocal",
},
key: "inlineMenuVisibility",
});
if (inlineMenuVisibility) {
await helper.set<ExpectedGlobalState>("global", {
...globalState,
autoFillOverlayVisibility: inlineMenuVisibility,
});
// remove the global state provider framework key for `inlineMenuVisibility`
await helper.setToGlobal(
{
stateDefinition: {
name: "autofillSettingsLocal",
},
key: "inlineMenuVisibility",
},
null,
);
}
// account state (e.g. state provider framework keys -> account settings)
const accounts = await helper.getAccounts<ExpectedAccountState>();
await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]);
// rollback account state
async function rollbackAccount(userId: string, account: ExpectedAccountState): Promise<void> {
let updateAccount = false;
let settings = account?.settings || {};
const autoFillOnPageLoadDefault: boolean = await helper.getFromUser(userId, {
...autofillSettingsStateDefinition,
key: "autofillOnPageLoadDefault",
});
const enableAutoFillOnPageLoad: boolean = await helper.getFromUser(userId, {
...autofillSettingsStateDefinition,
key: "autofillOnPageLoad",
});
const dismissedAutoFillOnPageLoadCallout: boolean = await helper.getFromUser(userId, {
...autofillSettingsStateDefinition,
key: "autofillOnPageLoadCalloutIsDismissed",
});
const autoCopyTotp: boolean = await helper.getFromUser(userId, {
...autofillSettingsStateDefinition,
key: "autoCopyTotp",
});
const activateAutoFillOnPageLoadFromPolicy: InlineMenuVisibilitySetting =
await helper.getFromUser(userId, {
stateDefinition: {
name: "autofillSettingsLocal",
},
key: "activateAutofillOnPageLoadFromPolicy",
});
// update new settings and remove the account state provider framework keys for the rolled back values
if (autoFillOnPageLoadDefault != null) {
settings = { ...settings, autoFillOnPageLoadDefault };
await helper.setToUser(
userId,
{ ...autofillSettingsStateDefinition, key: "autofillOnPageLoadDefault" },
null,
);
updateAccount = true;
}
if (enableAutoFillOnPageLoad != null) {
settings = { ...settings, enableAutoFillOnPageLoad };
await helper.setToUser(
userId,
{ ...autofillSettingsStateDefinition, key: "autofillOnPageLoad" },
null,
);
updateAccount = true;
}
if (dismissedAutoFillOnPageLoadCallout != null) {
settings = { ...settings, dismissedAutoFillOnPageLoadCallout };
await helper.setToUser(
userId,
{ ...autofillSettingsStateDefinition, key: "autofillOnPageLoadCalloutIsDismissed" },
null,
);
updateAccount = true;
}
if (autoCopyTotp != null) {
// invert the value to match the new naming convention
settings = { ...settings, disableAutoTotpCopy: !autoCopyTotp };
await helper.setToUser(
userId,
{ ...autofillSettingsStateDefinition, key: "autoCopyTotp" },
null,
);
updateAccount = true;
}
if (activateAutoFillOnPageLoadFromPolicy != null) {
settings = { ...settings, activateAutoFillOnPageLoadFromPolicy };
await helper.setToUser(
userId,
{
stateDefinition: {
name: "autofillSettingsLocal",
},
key: "activateAutofillOnPageLoadFromPolicy",
},
null,
);
updateAccount = true;
}
if (updateAccount) {
// commit updated settings to state
await helper.set(userId, {
...account,
settings,
});
}
}
}
}

View File

@@ -5,6 +5,7 @@ import { makeStaticByteArray } from "../../../spec/utils";
import { ApiService } from "../../abstractions/api.service"; import { ApiService } from "../../abstractions/api.service";
import { SearchService } from "../../abstractions/search.service"; import { SearchService } from "../../abstractions/search.service";
import { SettingsService } from "../../abstractions/settings.service"; import { SettingsService } from "../../abstractions/settings.service";
import { AutofillSettingsService } from "../../autofill/services/autofill-settings.service";
import { ConfigServiceAbstraction } from "../../platform/abstractions/config/config.service.abstraction"; import { ConfigServiceAbstraction } from "../../platform/abstractions/config/config.service.abstraction";
import { CryptoService } from "../../platform/abstractions/crypto.service"; import { CryptoService } from "../../platform/abstractions/crypto.service";
import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service";
@@ -97,6 +98,7 @@ const cipherData: CipherData = {
describe("Cipher Service", () => { describe("Cipher Service", () => {
const cryptoService = mock<CryptoService>(); const cryptoService = mock<CryptoService>();
const stateService = mock<StateService>(); const stateService = mock<StateService>();
const autofillSettingsService = mock<AutofillSettingsService>();
const settingsService = mock<SettingsService>(); const settingsService = mock<SettingsService>();
const apiService = mock<ApiService>(); const apiService = mock<ApiService>();
const cipherFileUploadService = mock<CipherFileUploadService>(); const cipherFileUploadService = mock<CipherFileUploadService>();
@@ -121,6 +123,7 @@ describe("Cipher Service", () => {
i18nService, i18nService,
searchService, searchService,
stateService, stateService,
autofillSettingsService,
encryptService, encryptService,
cipherFileUploadService, cipherFileUploadService,
configService, configService,
@@ -266,6 +269,8 @@ describe("Cipher Service", () => {
Promise.resolve(new SymmetricCryptoKey(makeStaticByteArray(64)) as CipherKey), Promise.resolve(new SymmetricCryptoKey(makeStaticByteArray(64)) as CipherKey),
); );
cryptoService.encrypt.mockImplementation(encryptText); cryptoService.encrypt.mockImplementation(encryptText);
jest.spyOn(cipherService as any, "getAutofillOnPageLoadDefault").mockResolvedValue(true);
}); });
describe("login encryption", () => { describe("login encryption", () => {

View File

@@ -4,6 +4,7 @@ import { SemVer } from "semver";
import { ApiService } from "../../abstractions/api.service"; import { ApiService } from "../../abstractions/api.service";
import { SearchService } from "../../abstractions/search.service"; import { SearchService } from "../../abstractions/search.service";
import { SettingsService } from "../../abstractions/settings.service"; import { SettingsService } from "../../abstractions/settings.service";
import { AutofillSettingsServiceAbstraction } from "../../autofill/services/autofill-settings.service";
import { ErrorResponse } from "../../models/response/error.response"; import { ErrorResponse } from "../../models/response/error.response";
import { ListResponse } from "../../models/response/list.response"; import { ListResponse } from "../../models/response/list.response";
import { View } from "../../models/view/view"; import { View } from "../../models/view/view";
@@ -65,6 +66,7 @@ export class CipherService implements CipherServiceAbstraction {
private i18nService: I18nService, private i18nService: I18nService,
private searchService: SearchService, private searchService: SearchService,
private stateService: StateService, private stateService: StateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private encryptService: EncryptService, private encryptService: EncryptService,
private cipherFileUploadService: CipherFileUploadService, private cipherFileUploadService: CipherFileUploadService,
private configService: ConfigServiceAbstraction, private configService: ConfigServiceAbstraction,
@@ -1250,6 +1252,10 @@ export class CipherService implements CipherServiceAbstraction {
} }
} }
private async getAutofillOnPageLoadDefault() {
return await firstValueFrom(this.autofillSettingsService.autofillOnPageLoadDefault$);
}
private async getCipherForUrl( private async getCipherForUrl(
url: string, url: string,
lastUsed: boolean, lastUsed: boolean,
@@ -1265,7 +1271,8 @@ export class CipherService implements CipherServiceAbstraction {
} }
if (autofillOnPageLoad) { if (autofillOnPageLoad) {
const autofillOnPageLoadDefault = await this.stateService.getAutoFillOnPageLoadDefault(); const autofillOnPageLoadDefault = await this.getAutofillOnPageLoadDefault();
ciphers = ciphers.filter( ciphers = ciphers.filter(
(cipher) => (cipher) =>
cipher.login.autofillOnPageLoad || cipher.login.autofillOnPageLoad ||