Skip to content

Commit

Permalink
Merge branch 'main' into tools/pm-4440-replace-lastpass-sso-dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
ttalty committed Jan 18, 2024
2 parents 6994ac7 + 06028c3 commit f4ab317
Show file tree
Hide file tree
Showing 176 changed files with 1,745 additions and 766 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
<p align="center">
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/apps-combo-logo.png" alt="Bitwarden" />
<img src="https://raw.githubusercontent.com/bitwarden/brand/main/screenshots/apps-combo-logo.png" alt="Bitwarden" />
</p>
<p align="center">
<a href="https://github.com/bitwarden/clients/actions/workflows/build-browser.yml?query=branch:master" target="_blank">
<img src="https://github.com/bitwarden/clients/actions/workflows/build-browser.yml/badge.svg?branch=master" alt="Github Workflow browser build on master" />
<a href="https://github.com/bitwarden/clients/actions/workflows/build-browser.yml?query=branch:main" target="_blank">
<img src="https://github.com/bitwarden/clients/actions/workflows/build-browser.yml/badge.svg?branch=main" alt="GitHub Workflow browser build on main" />
</a>
<a href="https://github.com/bitwarden/clients/actions/workflows/build-cli.yml?query=branch:master" target="_blank">
<img src="https://github.com/bitwarden/clients/actions/workflows/build-cli.yml/badge.svg?branch=master" alt="Github Workflow CLI build on master" />
<a href="https://github.com/bitwarden/clients/actions/workflows/build-cli.yml?query=branch:main" target="_blank">
<img src="https://github.com/bitwarden/clients/actions/workflows/build-cli.yml/badge.svg?branch=main" alt="GitHub Workflow CLI build on main" />
</a>
<a href="https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml?query=branch:master" target="_blank">
<img src="https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml/badge.svg?branch=master" alt="Github Workflow desktop build on master" />
<a href="https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml?query=branch:main" target="_blank">
<img src="https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml/badge.svg?branch=main" alt="GitHub Workflow desktop build on main" />
</a>
<a href="https://github.com/bitwarden/clients/actions/workflows/build-web.yml?query=branch:master" target="_blank">
<img src="https://github.com/bitwarden/clients/actions/workflows/build-web.yml/badge.svg?branch=master" alt="Github Workflow web build on master" />
<a href="https://github.com/bitwarden/clients/actions/workflows/build-web.yml?query=branch:main" target="_blank">
<img src="https://github.com/bitwarden/clients/actions/workflows/build-web.yml/badge.svg?branch=main" alt="GitHub Workflow web build on main" />
</a>
<a href="https://gitter.im/bitwarden/Lobby" target="_blank">
<img src="https://badges.gitter.im/bitwarden/Lobby.svg" alt="gitter chat" />
Expand All @@ -39,6 +39,6 @@ Interested in contributing in a big way? Consider joining our team! We're hiring

# Contribute

Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution.
Code contributions are welcome! Please commit any pull requests against the `main` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution.

Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
3 changes: 3 additions & 0 deletions apps/browser/src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1493,6 +1493,9 @@
"invalidPin": {
"message": "Invalid PIN code."
},
"tooManyInvalidPinEntryAttemptsLoggingOut": {
"message": "Too many invalid PIN entry attempts. Logging out."
},
"unlockWithBiometrics": {
"message": "Unlock with biometrics"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { PinCryptoServiceAbstraction, PinCryptoService } from "@bitwarden/auth/common";

import {
VaultTimeoutSettingsServiceInitOptions,
vaultTimeoutSettingsServiceFactory,
} from "../../../background/service-factories/vault-timeout-settings-service.factory";
import {
CryptoServiceInitOptions,
cryptoServiceFactory,
} from "../../../platform/background/service-factories/crypto-service.factory";
import {
FactoryOptions,
CachedServices,
factory,
} from "../../../platform/background/service-factories/factory-options";
import {
LogServiceInitOptions,
logServiceFactory,
} from "../../../platform/background/service-factories/log-service.factory";
import {
StateServiceInitOptions,
stateServiceFactory,
} from "../../../platform/background/service-factories/state-service.factory";

type PinCryptoServiceFactoryOptions = FactoryOptions;

export type PinCryptoServiceInitOptions = PinCryptoServiceFactoryOptions &
StateServiceInitOptions &
CryptoServiceInitOptions &
VaultTimeoutSettingsServiceInitOptions &
LogServiceInitOptions;

export function pinCryptoServiceFactory(
cache: { pinCryptoService?: PinCryptoServiceAbstraction } & CachedServices,
opts: PinCryptoServiceInitOptions,
): Promise<PinCryptoServiceAbstraction> {
return factory(
cache,
"pinCryptoService",
opts,
async () =>
new PinCryptoService(
await stateServiceFactory(cache, opts),
await cryptoServiceFactory(cache, opts),
await vaultTimeoutSettingsServiceFactory(cache, opts),
await logServiceFactory(cache, opts),
),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ import {
I18nServiceInitOptions,
i18nServiceFactory,
} from "../../../platform/background/service-factories/i18n-service.factory";
import {
LogServiceInitOptions,
logServiceFactory,
} from "../../../platform/background/service-factories/log-service.factory";
import {
StateServiceInitOptions,
stateServiceFactory,
} from "../../../platform/background/service-factories/state-service.factory";

import { PinCryptoServiceInitOptions, pinCryptoServiceFactory } from "./pin-crypto-service.factory";
import {
UserVerificationApiServiceInitOptions,
userVerificationApiServiceFactory,
Expand All @@ -30,7 +35,9 @@ export type UserVerificationServiceInitOptions = UserVerificationServiceFactoryO
StateServiceInitOptions &
CryptoServiceInitOptions &
I18nServiceInitOptions &
UserVerificationApiServiceInitOptions;
UserVerificationApiServiceInitOptions &
PinCryptoServiceInitOptions &
LogServiceInitOptions;

export function userVerificationServiceFactory(
cache: { userVerificationService?: AbstractUserVerificationService } & CachedServices,
Expand All @@ -46,6 +53,8 @@ export function userVerificationServiceFactory(
await cryptoServiceFactory(cache, opts),
await i18nServiceFactory(cache, opts),
await userVerificationApiServiceFactory(cache, opts),
await pinCryptoServiceFactory(cache, opts),
await logServiceFactory(cache, opts),
),
);
}
3 changes: 3 additions & 0 deletions apps/browser/src/auth/popup/lock.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Component, NgZone } from "@angular/core";
import { Router } from "@angular/router";

import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
import { PinCryptoServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
Expand Down Expand Up @@ -56,6 +57,7 @@ export class LockComponent extends BaseLockComponent {
dialogService: DialogService,
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
userVerificationService: UserVerificationService,
pinCryptoService: PinCryptoServiceAbstraction,
private routerService: BrowserRouterService,
) {
super(
Expand All @@ -77,6 +79,7 @@ export class LockComponent extends BaseLockComponent {
dialogService,
deviceTrustCryptoService,
userVerificationService,
pinCryptoService,
);
this.successRoute = "/tabs/current";
this.isInitialLockScreen = (window as any).previousPopupUrl == null;
Expand Down
13 changes: 13 additions & 0 deletions apps/browser/src/autofill/jest/autofill-mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,24 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";

import { OverlayCipherData } from "../background/abstractions/overlay.background";
import AutofillField from "../models/autofill-field";
import AutofillForm from "../models/autofill-form";
import AutofillPageDetails from "../models/autofill-page-details";
import AutofillScript, { FillScript } from "../models/autofill-script";
import { InitAutofillOverlayButtonMessage } from "../overlay/abstractions/autofill-overlay-button";
import { InitAutofillOverlayListMessage } from "../overlay/abstractions/autofill-overlay-list";
import { GenerateFillScriptOptions, PageDetail } from "../services/abstractions/autofill.service";

function createAutofillFormMock(customFields = {}): AutofillForm {
return {
opid: "default-form-opid",
htmlID: "default-htmlID",
htmlAction: "default-htmlAction",
htmlMethod: "default-htmlMethod",
htmlName: "default-htmlName",
...customFields,
};
}

function createAutofillFieldMock(customFields = {}): AutofillField {
return {
opid: "default-input-field-opid",
Expand Down Expand Up @@ -258,6 +270,7 @@ function createPortSpyMock(name: string) {
}

export {
createAutofillFormMock,
createAutofillFieldMock,
createPageDetailMock,
createAutofillPageDetailsMock,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
import { UriMatchType } from "@bitwarden/common/vault/enums";

import { BrowserApi } from "../../../platform/browser/browser-api";
import { flagEnabled } from "../../../platform/flags";
import { enableAccountSwitching } from "../../../platform/flags";
import { AutofillService } from "../../services/abstractions/autofill.service";
import { AutofillOverlayVisibility } from "../../utils/autofill-overlay.enum";

Expand Down Expand Up @@ -65,7 +65,7 @@ export class AutofillComponent implements OnInit {
{ name: i18nService.t("never"), value: UriMatchType.Never },
];

this.accountSwitcherEnabled = flagEnabled("accountSwitching");
this.accountSwitcherEnabled = enableAccountSwitching();
this.disablePasswordManagerLink = this.getDisablePasswordManagerLink();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { mock } from "jest-mock-extended";

import { createAutofillFieldMock, createAutofillFormMock } from "../jest/autofill-mocks";
import AutofillField from "../models/autofill-field";
import AutofillForm from "../models/autofill-form";
import {
Expand Down Expand Up @@ -2079,6 +2080,42 @@ describe("CollectAutofillContentService", () => {
);
});

it("removes cached autofill elements that are nested within a removed node", () => {
const form = document.createElement("form") as ElementWithOpId<HTMLFormElement>;
const usernameInput = document.createElement("input") as ElementWithOpId<FormFieldElement>;
usernameInput.setAttribute("type", "text");
usernameInput.setAttribute("name", "username");
form.appendChild(usernameInput);
document.body.appendChild(form);
const removedNodes = document.querySelectorAll("form");
const autofillForm: AutofillForm = createAutofillFormMock({});
const autofillField: AutofillField = createAutofillFieldMock({});
collectAutofillContentService["autofillFormElements"] = new Map([[form, autofillForm]]);
collectAutofillContentService["autofillFieldElements"] = new Map([
[usernameInput, autofillField],
]);
collectAutofillContentService["domRecentlyMutated"] = false;
collectAutofillContentService["noFieldsFound"] = true;
collectAutofillContentService["currentLocationHref"] = window.location.href;

collectAutofillContentService["handleMutationObserverMutation"]([
{
type: "childList",
addedNodes: null,
attributeName: null,
attributeNamespace: null,
nextSibling: null,
oldValue: null,
previousSibling: null,
removedNodes: removedNodes,
target: document.body,
},
]);

expect(collectAutofillContentService["autofillFormElements"].size).toEqual(0);
expect(collectAutofillContentService["autofillFieldElements"].size).toEqual(0);
});

it("will handle updating the autofill element if any attribute mutations are encountered", () => {
const mutationRecord: MutationRecord = {
type: "attributes",
Expand Down Expand Up @@ -2389,6 +2426,12 @@ describe("CollectAutofillContentService", () => {
};
const updatedAttributes = ["action", "name", "id", "method"];

beforeEach(() => {
collectAutofillContentService["autofillFormElements"] = new Map([
[formElement, autofillForm],
]);
});

updatedAttributes.forEach((attribute) => {
it(`will update the ${attribute} value for the form element`, () => {
jest.spyOn(collectAutofillContentService["autofillFormElements"], "set");
Expand Down Expand Up @@ -2454,6 +2497,12 @@ describe("CollectAutofillContentService", () => {
"data-stripe",
];

beforeEach(() => {
collectAutofillContentService["autofillFieldElements"] = new Map([
[fieldElement, autofillField],
]);
});

updatedAttributes.forEach((attribute) => {
it(`will update the ${attribute} value for the field element`, async () => {
jest.spyOn(collectAutofillContentService["autofillFieldElements"], "set");
Expand All @@ -2471,26 +2520,6 @@ describe("CollectAutofillContentService", () => {
});
});

it("will check the dom element's visibility if the `style` or `class` attribute has updated ", async () => {
jest.spyOn(
collectAutofillContentService["domElementVisibilityService"],
"isFormFieldViewable",
);
const attributes = ["class", "style"];

for (const attribute of attributes) {
await collectAutofillContentService["updateAutofillFieldElementData"](
attribute,
fieldElement,
autofillField,
);

expect(
collectAutofillContentService["domElementVisibilityService"].isFormFieldViewable,
).toBeCalledWith(fieldElement);
}
});

it("will not update an attribute value if it is not present in the updateActions object", async () => {
jest.spyOn(collectAutofillContentService["autofillFieldElements"], "set");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1029,19 +1029,14 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
continue;
}

if (node instanceof HTMLFormElement || this.isNodeFormFieldElement(node)) {
isElementMutated = true;
mutatedElements.push(node);
continue;
}

const childNodes = this.queryAllTreeWalkerNodes(
const autofillElementNodes = this.queryAllTreeWalkerNodes(
node,
(node: Node) => node instanceof HTMLFormElement || this.isNodeFormFieldElement(node),
) as HTMLElement[];
if (childNodes.length) {

if (autofillElementNodes.length) {
isElementMutated = true;
mutatedElements.push(...childNodes);
mutatedElements.push(...autofillElementNodes);
}
}

Expand Down Expand Up @@ -1182,7 +1177,9 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
}

updateActions[attributeName]();
this.autofillFormElements.set(element, dataTarget);
if (this.autofillFormElements.has(element)) {
this.autofillFormElements.set(element, dataTarget);
}
}

/**
Expand Down Expand Up @@ -1233,15 +1230,9 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte

updateActions[attributeName]();

const visibilityAttributesSet = new Set(["class", "style"]);
if (
visibilityAttributesSet.has(attributeName) &&
!dataTarget.htmlClass?.includes("com-bitwarden-browser-animated-fill")
) {
dataTarget.viewable = await this.domElementVisibilityService.isFormFieldViewable(element);
if (this.autofillFieldElements.has(element)) {
this.autofillFieldElements.set(element, dataTarget);
}

this.autofillFieldElements.set(element, dataTarget);
}

/**
Expand Down
Loading

0 comments on commit f4ab317

Please sign in to comment.