Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/bitwarden/clients into ps/p…
Browse files Browse the repository at this point in the history
…m-5717/fix-calling-init-on-undefined-in-biometrics-service
  • Loading branch information
djsmith85 committed Jan 24, 2024
2 parents 9997306 + 7436f91 commit e0a2f15
Show file tree
Hide file tree
Showing 75 changed files with 2,033 additions and 369 deletions.
3 changes: 3 additions & 0 deletions apps/browser/gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
}
Expand Down
28 changes: 28 additions & 0 deletions apps/browser/src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -47,8 +43,7 @@ export type AutoFillServiceInitOptions = AutoFillServiceOptions &
EventCollectionServiceInitOptions &
LogServiceInitOptions &
SettingsServiceInitOptions &
UserVerificationServiceInitOptions &
ConfigServiceInitOptions;
UserVerificationServiceInitOptions;

export function autofillServiceFactory(
cache: { autofillService?: AbstractAutoFillService } & CachedServices,
Expand All @@ -67,7 +62,6 @@ export function autofillServiceFactory(
await logServiceFactory(cache, opts),
await settingsServiceFactory(cache, opts),
await userVerificationServiceFactory(cache, opts),
await configServiceFactory(cache, opts),
),
);
}
38 changes: 27 additions & 11 deletions apps/browser/src/autofill/popup/settings/autofill.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,37 @@ <h1 class="center">
</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">
Expand Down
82 changes: 82 additions & 0 deletions apps/browser/src/autofill/popup/settings/autofill.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
Expand All @@ -35,6 +38,7 @@ export class AutofillComponent implements OnInit {
private configService: ConfigServiceAbstraction,
private settingsService: SettingsService,
private autofillService: AutofillService,
private dialogService: DialogService,
) {
this.autoFillOverlayVisibilityOptions = [
{
Expand Down Expand Up @@ -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;

Expand All @@ -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() {
Expand Down Expand Up @@ -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"]);
}
}
5 changes: 5 additions & 0 deletions apps/browser/src/autofill/services/autofill-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ export class AutoFillConstants {
"checkbox",
...AutoFillConstants.ExcludedAutofillLoginTypes,
];

static readonly ExcludedOverlayTypes: string[] = [
"textarea",
...AutoFillConstants.ExcludedAutofillTypes,
];
}

export class CreditCardAutoFillConstants {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ describe("AutofillOverlayContentService", () => {
expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled();
});

it("ignores fields that are part of the ExcludedAutofillTypes", () => {
AutoFillConstants.ExcludedAutofillTypes.forEach((excludedType) => {
it("ignores fields that are part of the ExcludedOverlayTypes", () => {
AutoFillConstants.ExcludedOverlayTypes.forEach((excludedType) => {
autofillFieldData.type = excludedType;

autofillOverlayContentService.setupAutofillOverlayListenerOnField(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
private readonly findTabs = tabbable;
private readonly sendExtensionMessage = sendExtensionMessage;
private formFieldElements: Set<ElementWithOpId<FormFieldElement>> = new Set([]);
private ignoredFieldTypes: Set<string> = new Set(AutoFillConstants.ExcludedOverlayTypes);
private userFilledFields: Record<string, FillableFormFieldElement> = {};
private authStatus: AuthenticationStatus;
private focusableElements: FocusableElement[] = [];
Expand Down Expand Up @@ -715,12 +716,11 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
* @param autofillFieldData - Autofill field data captured from the form field element.
*/
private isIgnoredField(autofillFieldData: AutofillField): boolean {
const ignoredFieldTypes = new Set(AutoFillConstants.ExcludedAutofillTypes);
if (
autofillFieldData.readonly ||
autofillFieldData.disabled ||
!autofillFieldData.viewable ||
ignoredFieldTypes.has(autofillFieldData.type) ||
this.ignoredFieldTypes.has(autofillFieldData.type) ||
this.keywordsFoundInFieldData(autofillFieldData, ["search", "captcha"])
) {
return true;
Expand Down
3 changes: 0 additions & 3 deletions apps/browser/src/autofill/services/autofill.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand All @@ -67,7 +65,6 @@ describe("AutofillService", () => {
logService,
settingsService,
userVerificationService,
configService,
);
});

Expand Down
2 changes: 0 additions & 2 deletions apps/browser/src/autofill/services/autofill.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -47,7 +46,6 @@ export default class AutofillService implements AutofillServiceInterface {
private logService: LogService,
private settingsService: SettingsService,
private userVerificationService: UserVerificationService,
private configService: ConfigServiceAbstraction,
) {}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1967,19 +1967,42 @@ describe("CollectAutofillContentService", () => {
});

describe("getShadowRoot", () => {
beforeEach(() => {
// eslint-disable-next-line
// @ts-ignore
globalThis.chrome.dom = {
openOrClosedShadowRoot: jest.fn(),
};
});

it("returns null if the passed node is not an HTMLElement instance", () => {
const textNode = document.createTextNode("Hello, world!");
const shadowRoot = collectAutofillContentService["getShadowRoot"](textNode);

expect(shadowRoot).toEqual(null);
});

it("returns a value provided by Chrome's openOrClosedShadowRoot API", () => {
it("returns null if the passed node contains children elements", () => {
const element = document.createElement("div");
element.innerHTML = "<p>Hello, world!</p>";
const shadowRoot = collectAutofillContentService["getShadowRoot"](element);

// eslint-disable-next-line
// @ts-ignore
globalThis.chrome.dom = {
openOrClosedShadowRoot: jest.fn(),
};
expect(chrome.dom.openOrClosedShadowRoot).not.toBeCalled();
expect(shadowRoot).toEqual(null);
});

it("returns an open shadow root if the passed node has a shadowDOM element", () => {
const element = document.createElement("div");
element.attachShadow({ mode: "open" });

const shadowRoot = collectAutofillContentService["getShadowRoot"](element);

expect(shadowRoot).toBeInstanceOf(ShadowRoot);
});

it("returns a value provided by Chrome's openOrClosedShadowRoot API", () => {
const element = document.createElement("div");
collectAutofillContentService["getShadowRoot"](element);

Expand Down
Loading

0 comments on commit e0a2f15

Please sign in to comment.