[PM-5617] Re-add setting to turn off browser autofill (#7592)
* [PM-5617] Re-Add Setting to Turn Off Default Browser Autofill * [PM-5617] Re-Add Setting to Turn Off Default Browser Autofill * [PM-5617] Removing the privacy optional permission from Firefox * [PM-5617] Adding jest tests to validate the behavior within BrowserApi * [PM-5617] Adjusting messaging based on feedback from design * [PM-5617] Adjusting messaging based on feedback from design * [PM-5617] Adjusting messaging based on feedback from design * [PM-5617] Removing unnecessary configService dependency
This commit is contained in:
parent
dbf836b573
commit
609296ad2b
|
@ -62,6 +62,9 @@ function distFirefox() {
|
|||
return dist("firefox", (manifest) => {
|
||||
delete manifest.storage;
|
||||
delete manifest.sandbox;
|
||||
manifest.optional_permissions = manifest.optional_permissions.filter(
|
||||
(permission) => permission !== "privacy",
|
||||
);
|
||||
return manifest;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2895,5 +2895,33 @@
|
|||
"commonImportFormats": {
|
||||
"message": "Common formats",
|
||||
"description": "Label indicating the most common import formats"
|
||||
},
|
||||
"overrideDefaultBrowserAutofillTitle": {
|
||||
"message": "Make Bitwarden your default password manager?",
|
||||
"description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior"
|
||||
},
|
||||
"overrideDefaultBrowserAutofillDescription": {
|
||||
"message": "Ignoring this option may cause conflicts between the Bitwarden auto-fill menu and your browser's.",
|
||||
"description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior"
|
||||
},
|
||||
"overrideDefaultBrowserAutofillPrivacyRequiredDescription": {
|
||||
"message": "This action will restart the Bitwarden extension. Ignoring this option may cause conflicts between the Bitwarden auto-fill menu and your browser's.",
|
||||
"description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior"
|
||||
},
|
||||
"overrideDefaultBrowserAutoFillSettings": {
|
||||
"message": "Make Bitwarden your default password manager",
|
||||
"description": "Label for the setting that allows overriding the default browser autofill settings"
|
||||
},
|
||||
"privacyPermissionAdditionNotGrantedTitle": {
|
||||
"message": "Unable to set Bitwarden as the default password manager",
|
||||
"description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings"
|
||||
},
|
||||
"privacyPermissionAdditionNotGrantedDescription": {
|
||||
"message": "You must grant browser privacy permissions to Bitwarden to set it as the default password manager.",
|
||||
"description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings"
|
||||
},
|
||||
"makeDefault": {
|
||||
"message": "Make default",
|
||||
"description": "Button text for the setting that allows overriding the default browser autofill settings"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,6 @@ import {
|
|||
settingsServiceFactory,
|
||||
SettingsServiceInitOptions,
|
||||
} from "../../../background/service-factories/settings-service.factory";
|
||||
import {
|
||||
configServiceFactory,
|
||||
ConfigServiceInitOptions,
|
||||
} from "../../../platform/background/service-factories/config-service.factory";
|
||||
import {
|
||||
CachedServices,
|
||||
factory,
|
||||
|
@ -47,8 +43,7 @@ export type AutoFillServiceInitOptions = AutoFillServiceOptions &
|
|||
EventCollectionServiceInitOptions &
|
||||
LogServiceInitOptions &
|
||||
SettingsServiceInitOptions &
|
||||
UserVerificationServiceInitOptions &
|
||||
ConfigServiceInitOptions;
|
||||
UserVerificationServiceInitOptions;
|
||||
|
||||
export function autofillServiceFactory(
|
||||
cache: { autofillService?: AbstractAutoFillService } & CachedServices,
|
||||
|
@ -67,7 +62,6 @@ export function autofillServiceFactory(
|
|||
await logServiceFactory(cache, opts),
|
||||
await settingsServiceFactory(cache, opts),
|
||||
await userVerificationServiceFactory(cache, opts),
|
||||
await configServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -41,21 +41,37 @@
|
|||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="box-footer" *ngIf="accountSwitcherEnabled">
|
||||
<div class="box-footer" *ngIf="accountSwitcherEnabled && canOverrideBrowserAutofillSetting">
|
||||
{{ "showAutoFillMenuOnFormFieldsDescAlt" | i18n }}
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{{ "turnOffBrowserBuiltInPasswordManagerSettings" | i18n }}
|
||||
<a
|
||||
[attr.href]="disablePasswordManagerLink"
|
||||
(click)="openDisablePasswordManagerLink($event)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ "turnOffBrowserBuiltInPasswordManagerSettingsLink" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content" *ngIf="canOverrideBrowserAutofillSetting">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="overrideBrowserAutofill" class="!tw-mr-0">{{
|
||||
"overrideDefaultBrowserAutoFillSettings" | i18n
|
||||
}}</label>
|
||||
<input
|
||||
id="overrideBrowserAutofill"
|
||||
type="checkbox"
|
||||
(change)="updateDefaultBrowserAutofillDisabled()"
|
||||
[(ngModel)]="defaultBrowserAutofillDisabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<span *ngIf="accountSwitcherEnabled">{{ "showAutoFillMenuOnFormFieldsDescAlt" | i18n }}</span>
|
||||
{{ "turnOffBrowserBuiltInPasswordManagerSettings" | i18n }}
|
||||
<a
|
||||
[attr.href]="disablePasswordManagerLink"
|
||||
(click)="openDisablePasswordManagerLink($event)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ "turnOffBrowserBuiltInPasswordManagerSettingsLink" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box tw-mt-4">
|
||||
<div class="box-content">
|
||||
|
|
|
@ -6,6 +6,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
|||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { UriMatchType } from "@bitwarden/common/vault/enums";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
import { enableAccountSwitching } from "../../../platform/flags";
|
||||
|
@ -17,6 +18,8 @@ import { AutofillOverlayVisibility } from "../../utils/autofill-overlay.enum";
|
|||
templateUrl: "autofill.component.html",
|
||||
})
|
||||
export class AutofillComponent implements OnInit {
|
||||
protected canOverrideBrowserAutofillSetting = false;
|
||||
protected defaultBrowserAutofillDisabled = false;
|
||||
protected autoFillOverlayVisibility: number;
|
||||
protected autoFillOverlayVisibilityOptions: any[];
|
||||
protected disablePasswordManagerLink: string;
|
||||
|
@ -35,6 +38,7 @@ export class AutofillComponent implements OnInit {
|
|||
private configService: ConfigServiceAbstraction,
|
||||
private settingsService: SettingsService,
|
||||
private autofillService: AutofillService,
|
||||
private dialogService: DialogService,
|
||||
) {
|
||||
this.autoFillOverlayVisibilityOptions = [
|
||||
{
|
||||
|
@ -68,6 +72,14 @@ export class AutofillComponent implements OnInit {
|
|||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.canOverrideBrowserAutofillSetting =
|
||||
this.platformUtilsService.isChrome() ||
|
||||
this.platformUtilsService.isEdge() ||
|
||||
this.platformUtilsService.isOpera() ||
|
||||
this.platformUtilsService.isVivaldi();
|
||||
|
||||
this.defaultBrowserAutofillDisabled = await this.browserAutofillSettingCurrentlyOverridden();
|
||||
|
||||
this.autoFillOverlayVisibility =
|
||||
(await this.settingsService.getAutoFillOverlayVisibility()) || AutofillOverlayVisibility.Off;
|
||||
|
||||
|
@ -87,6 +99,7 @@ export class AutofillComponent implements OnInit {
|
|||
await this.settingsService.getAutoFillOverlayVisibility();
|
||||
await this.settingsService.setAutoFillOverlayVisibility(this.autoFillOverlayVisibility);
|
||||
await this.handleUpdatingAutofillOverlayContentScripts(previousAutoFillOverlayVisibility);
|
||||
await this.requestPrivacyPermission();
|
||||
}
|
||||
|
||||
async updateAutoFillOnPageLoad() {
|
||||
|
@ -165,4 +178,73 @@ export class AutofillComponent implements OnInit {
|
|||
|
||||
await this.autofillService.reloadAutofillScripts();
|
||||
}
|
||||
|
||||
async requestPrivacyPermission() {
|
||||
if (
|
||||
this.autoFillOverlayVisibility === AutofillOverlayVisibility.Off ||
|
||||
!this.canOverrideBrowserAutofillSetting ||
|
||||
(await this.browserAutofillSettingCurrentlyOverridden())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const permissionGranted = await this.privacyPermissionGranted();
|
||||
const contentKey = permissionGranted
|
||||
? "overrideDefaultBrowserAutofillDescription"
|
||||
: "overrideDefaultBrowserAutofillPrivacyRequiredDescription";
|
||||
await this.dialogService.openSimpleDialog({
|
||||
title: { key: "overrideDefaultBrowserAutofillTitle" },
|
||||
content: { key: contentKey },
|
||||
acceptButtonText: { key: "makeDefault" },
|
||||
acceptAction: async () => await this.handleOverrideDialogAccept(),
|
||||
cancelButtonText: { key: "ignore" },
|
||||
type: "info",
|
||||
});
|
||||
}
|
||||
|
||||
async updateDefaultBrowserAutofillDisabled() {
|
||||
const privacyPermissionGranted = await this.privacyPermissionGranted();
|
||||
if (!this.defaultBrowserAutofillDisabled && !privacyPermissionGranted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!privacyPermissionGranted &&
|
||||
!(await BrowserApi.requestPermission({ permissions: ["privacy"] }))
|
||||
) {
|
||||
await this.dialogService.openSimpleDialog({
|
||||
title: { key: "privacyPermissionAdditionNotGrantedTitle" },
|
||||
content: { key: "privacyPermissionAdditionNotGrantedDescription" },
|
||||
acceptButtonText: { key: "ok" },
|
||||
cancelButtonText: null,
|
||||
type: "warning",
|
||||
});
|
||||
this.defaultBrowserAutofillDisabled = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
BrowserApi.updateDefaultBrowserAutofillSettings(!this.defaultBrowserAutofillDisabled);
|
||||
}
|
||||
|
||||
private handleOverrideDialogAccept = async () => {
|
||||
this.defaultBrowserAutofillDisabled = true;
|
||||
await this.updateDefaultBrowserAutofillDisabled();
|
||||
};
|
||||
|
||||
async browserAutofillSettingCurrentlyOverridden() {
|
||||
if (!this.canOverrideBrowserAutofillSetting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(await this.privacyPermissionGranted())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await BrowserApi.browserAutofillSettingsOverridden();
|
||||
}
|
||||
|
||||
async privacyPermissionGranted(): Promise<boolean> {
|
||||
return await BrowserApi.permissionsGranted(["privacy"]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import { mock, mockReset } from "jest-mock-extended";
|
|||
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/services/config/config.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
|
||||
import { SettingsService } from "@bitwarden/common/services/settings.service";
|
||||
import {
|
||||
|
@ -56,7 +55,6 @@ describe("AutofillService", () => {
|
|||
const logService = mock<LogService>();
|
||||
const settingsService = mock<SettingsService>();
|
||||
const userVerificationService = mock<UserVerificationService>();
|
||||
const configService = mock<ConfigService>();
|
||||
|
||||
beforeEach(() => {
|
||||
autofillService = new AutofillService(
|
||||
|
@ -67,7 +65,6 @@ describe("AutofillService", () => {
|
|||
logService,
|
||||
settingsService,
|
||||
userVerificationService,
|
||||
configService,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
|
|||
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||
|
@ -47,7 +46,6 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
private logService: LogService,
|
||||
private settingsService: SettingsService,
|
||||
private userVerificationService: UserVerificationService,
|
||||
private configService: ConfigServiceAbstraction,
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
|
|
@ -614,7 +614,6 @@ export default class MainBackground {
|
|||
this.logService,
|
||||
this.settingsService,
|
||||
this.userVerificationService,
|
||||
this.configService,
|
||||
);
|
||||
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
"webRequest",
|
||||
"webRequestBlocking"
|
||||
],
|
||||
"optional_permissions": ["nativeMessaging"],
|
||||
"optional_permissions": ["nativeMessaging", "privacy"],
|
||||
"content_security_policy": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'",
|
||||
"sandbox": {
|
||||
"pages": ["overlay/button.html", "overlay/list.html"],
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
"alarms",
|
||||
"scripting"
|
||||
],
|
||||
"optional_permissions": ["nativeMessaging"],
|
||||
"optional_permissions": ["nativeMessaging", "privacy"],
|
||||
"host_permissions": ["http://*/*", "https://*/*"],
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'",
|
||||
|
|
|
@ -136,4 +136,82 @@ describe("BrowserApi", () => {
|
|||
expect(result).toEqual(executeScriptResult);
|
||||
});
|
||||
});
|
||||
|
||||
describe("browserAutofillSettingsOverridden", () => {
|
||||
it("returns true if the browser autofill settings are overridden", async () => {
|
||||
const expectedDetails = {
|
||||
value: false,
|
||||
levelOfControl: "controlled_by_this_extension",
|
||||
} as chrome.types.ChromeSettingGetResultDetails;
|
||||
chrome.privacy.services.autofillAddressEnabled.get = jest.fn((details, callback) =>
|
||||
callback(expectedDetails),
|
||||
);
|
||||
chrome.privacy.services.autofillCreditCardEnabled.get = jest.fn((details, callback) =>
|
||||
callback(expectedDetails),
|
||||
);
|
||||
chrome.privacy.services.passwordSavingEnabled.get = jest.fn((details, callback) =>
|
||||
callback(expectedDetails),
|
||||
);
|
||||
|
||||
const result = await BrowserApi.browserAutofillSettingsOverridden();
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false if the browser autofill settings are not overridden", async () => {
|
||||
const expectedDetails = {
|
||||
value: true,
|
||||
levelOfControl: "controlled_by_this_extension",
|
||||
} as chrome.types.ChromeSettingGetResultDetails;
|
||||
chrome.privacy.services.autofillAddressEnabled.get = jest.fn((details, callback) =>
|
||||
callback(expectedDetails),
|
||||
);
|
||||
chrome.privacy.services.autofillCreditCardEnabled.get = jest.fn((details, callback) =>
|
||||
callback(expectedDetails),
|
||||
);
|
||||
chrome.privacy.services.passwordSavingEnabled.get = jest.fn((details, callback) =>
|
||||
callback(expectedDetails),
|
||||
);
|
||||
|
||||
const result = await BrowserApi.browserAutofillSettingsOverridden();
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false if the browser autofill settings are not controlled by the extension", async () => {
|
||||
const expectedDetails = {
|
||||
value: false,
|
||||
levelOfControl: "controlled_by_other_extensions",
|
||||
} as chrome.types.ChromeSettingGetResultDetails;
|
||||
chrome.privacy.services.autofillAddressEnabled.get = jest.fn((details, callback) =>
|
||||
callback(expectedDetails),
|
||||
);
|
||||
chrome.privacy.services.autofillCreditCardEnabled.get = jest.fn((details, callback) =>
|
||||
callback(expectedDetails),
|
||||
);
|
||||
chrome.privacy.services.passwordSavingEnabled.get = jest.fn((details, callback) =>
|
||||
callback(expectedDetails),
|
||||
);
|
||||
|
||||
const result = await BrowserApi.browserAutofillSettingsOverridden();
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateDefaultBrowserAutofillSettings", () => {
|
||||
it("updates the default browser autofill settings", async () => {
|
||||
await BrowserApi.updateDefaultBrowserAutofillSettings(false);
|
||||
|
||||
expect(chrome.privacy.services.autofillAddressEnabled.set).toHaveBeenCalledWith({
|
||||
value: false,
|
||||
});
|
||||
expect(chrome.privacy.services.autofillCreditCardEnabled.set).toHaveBeenCalledWith({
|
||||
value: false,
|
||||
});
|
||||
expect(chrome.privacy.services.passwordSavingEnabled.set).toHaveBeenCalledWith({
|
||||
value: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -445,4 +445,43 @@ export class BrowserApi {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies if the browser autofill settings are overridden by the extension.
|
||||
*/
|
||||
static async browserAutofillSettingsOverridden(): Promise<boolean> {
|
||||
const checkOverrideStatus = (details: chrome.types.ChromeSettingGetResultDetails) =>
|
||||
details.levelOfControl === "controlled_by_this_extension" && !details.value;
|
||||
|
||||
const autofillAddressOverridden: boolean = await new Promise((resolve) =>
|
||||
chrome.privacy.services.autofillAddressEnabled.get({}, (details) =>
|
||||
resolve(checkOverrideStatus(details)),
|
||||
),
|
||||
);
|
||||
|
||||
const autofillCreditCardOverridden: boolean = await new Promise((resolve) =>
|
||||
chrome.privacy.services.autofillCreditCardEnabled.get({}, (details) =>
|
||||
resolve(checkOverrideStatus(details)),
|
||||
),
|
||||
);
|
||||
|
||||
const passwordSavingOverridden: boolean = await new Promise((resolve) =>
|
||||
chrome.privacy.services.passwordSavingEnabled.get({}, (details) =>
|
||||
resolve(checkOverrideStatus(details)),
|
||||
),
|
||||
);
|
||||
|
||||
return autofillAddressOverridden && autofillCreditCardOverridden && passwordSavingOverridden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the browser autofill settings to the given value.
|
||||
*
|
||||
* @param value - Determines whether to enable or disable the autofill settings.
|
||||
*/
|
||||
static updateDefaultBrowserAutofillSettings(value: boolean) {
|
||||
chrome.privacy.services.autofillAddressEnabled.set({ value });
|
||||
chrome.privacy.services.autofillCreditCardEnabled.set({ value });
|
||||
chrome.privacy.services.passwordSavingEnabled.set({ value });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,23 @@ const port = {
|
|||
postMessage: jest.fn(),
|
||||
};
|
||||
|
||||
const privacy = {
|
||||
services: {
|
||||
autofillAddressEnabled: {
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
},
|
||||
autofillCreditCardEnabled: {
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
},
|
||||
passwordSavingEnabled: {
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// set chrome
|
||||
global.chrome = {
|
||||
i18n,
|
||||
|
@ -98,4 +115,5 @@ global.chrome = {
|
|||
scripting,
|
||||
windows,
|
||||
port,
|
||||
privacy,
|
||||
} as any;
|
||||
|
|
Loading…
Reference in New Issue