mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 09:13:33 +00:00
update tests and cleanup
This commit is contained in:
@@ -15,7 +15,6 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
|
|||||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
|
import { NeverDomains } from "@bitwarden/common/models/domain/domain-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 { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import {
|
import {
|
||||||
ButtonModule,
|
ButtonModule,
|
||||||
@@ -26,6 +25,7 @@ import {
|
|||||||
LinkModule,
|
LinkModule,
|
||||||
SectionComponent,
|
SectionComponent,
|
||||||
SectionHeaderComponent,
|
SectionHeaderComponent,
|
||||||
|
ToastService,
|
||||||
TypographyModule,
|
TypographyModule,
|
||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
|
|
||||||
@@ -59,7 +59,8 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class BlockedDomainsComponent implements AfterViewInit, OnDestroy {
|
export class BlockedDomainsComponent implements AfterViewInit, OnDestroy {
|
||||||
@ViewChildren("uriInput") uriInputElements: QueryList<ElementRef<HTMLInputElement>>;
|
@ViewChildren("uriInput") uriInputElements: QueryList<ElementRef<HTMLInputElement>> =
|
||||||
|
new QueryList();
|
||||||
|
|
||||||
dataIsPristine = true;
|
dataIsPristine = true;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
@@ -73,7 +74,7 @@ export class BlockedDomainsComponent implements AfterViewInit, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
private domainSettingsService: DomainSettingsService,
|
private domainSettingsService: DomainSettingsService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private toastService: ToastService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngAfterViewInit() {
|
async ngAfterViewInit() {
|
||||||
@@ -150,11 +151,11 @@ export class BlockedDomainsComponent implements AfterViewInit, OnDestroy {
|
|||||||
const validatedHost = Utils.getHostname(uri);
|
const validatedHost = Utils.getHostname(uri);
|
||||||
|
|
||||||
if (!validatedHost) {
|
if (!validatedHost) {
|
||||||
this.platformUtilsService.showToast(
|
this.toastService.showToast({
|
||||||
"error",
|
message: this.i18nService.t("excludedDomainsInvalidDomain", uri),
|
||||||
null,
|
title: "",
|
||||||
this.i18nService.t("excludedDomainsInvalidDomain", uri),
|
variant: "error",
|
||||||
);
|
});
|
||||||
|
|
||||||
// Don't reset via `handleStateUpdate` to allow existing input value correction
|
// Don't reset via `handleStateUpdate` to allow existing input value correction
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
@@ -176,7 +177,7 @@ export class BlockedDomainsComponent implements AfterViewInit, OnDestroy {
|
|||||||
if (stateIsUnchanged) {
|
if (stateIsUnchanged) {
|
||||||
// Reset UI state directly
|
// Reset UI state directly
|
||||||
const constructedNeverDomainsState = this.storedBlockedDomains.reduce(
|
const constructedNeverDomainsState = this.storedBlockedDomains.reduce(
|
||||||
(neverDomains, uri) => ({ ...neverDomains, [uri]: null }),
|
(neverDomains: NeverDomains, uri: string) => ({ ...neverDomains, [uri]: null }),
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
this.handleStateUpdate(constructedNeverDomainsState);
|
this.handleStateUpdate(constructedNeverDomainsState);
|
||||||
@@ -184,13 +185,17 @@ export class BlockedDomainsComponent implements AfterViewInit, OnDestroy {
|
|||||||
await this.domainSettingsService.setBlockedInteractionsUris(newBlockedDomainsSaveState);
|
await this.domainSettingsService.setBlockedInteractionsUris(newBlockedDomainsSaveState);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.platformUtilsService.showToast(
|
this.toastService.showToast({
|
||||||
"success",
|
message: this.i18nService.t("blockedDomainsSavedSuccess"),
|
||||||
null,
|
title: "",
|
||||||
this.i18nService.t("blockedDomainsSavedSuccess"),
|
variant: "success",
|
||||||
);
|
});
|
||||||
} catch {
|
} catch {
|
||||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
|
this.toastService.showToast({
|
||||||
|
message: this.i18nService.t("unexpectedError"),
|
||||||
|
title: "",
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
|
||||||
// Don't reset via `handleStateUpdate` to preserve input values
|
// Don't reset via `handleStateUpdate` to preserve input values
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import {
|
import {
|
||||||
QueryList,
|
QueryList,
|
||||||
@@ -17,7 +15,6 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
|
|||||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
|
import { NeverDomains } from "@bitwarden/common/models/domain/domain-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 { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import {
|
import {
|
||||||
ButtonModule,
|
ButtonModule,
|
||||||
@@ -28,6 +25,7 @@ import {
|
|||||||
LinkModule,
|
LinkModule,
|
||||||
SectionComponent,
|
SectionComponent,
|
||||||
SectionHeaderComponent,
|
SectionHeaderComponent,
|
||||||
|
ToastService,
|
||||||
TypographyModule,
|
TypographyModule,
|
||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
|
|
||||||
@@ -62,7 +60,8 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
|
export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
|
||||||
@ViewChildren("uriInput") uriInputElements: QueryList<ElementRef<HTMLInputElement>>;
|
@ViewChildren("uriInput") uriInputElements: QueryList<ElementRef<HTMLInputElement>> =
|
||||||
|
new QueryList();
|
||||||
|
|
||||||
accountSwitcherEnabled = false;
|
accountSwitcherEnabled = false;
|
||||||
dataIsPristine = true;
|
dataIsPristine = true;
|
||||||
@@ -77,7 +76,7 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
private domainSettingsService: DomainSettingsService,
|
private domainSettingsService: DomainSettingsService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private toastService: ToastService,
|
||||||
) {
|
) {
|
||||||
this.accountSwitcherEnabled = enableAccountSwitching();
|
this.accountSwitcherEnabled = enableAccountSwitching();
|
||||||
}
|
}
|
||||||
@@ -156,11 +155,11 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
|
|||||||
const validatedHost = Utils.getHostname(uri);
|
const validatedHost = Utils.getHostname(uri);
|
||||||
|
|
||||||
if (!validatedHost) {
|
if (!validatedHost) {
|
||||||
this.platformUtilsService.showToast(
|
this.toastService.showToast({
|
||||||
"error",
|
message: this.i18nService.t("excludedDomainsInvalidDomain", uri),
|
||||||
null,
|
title: "",
|
||||||
this.i18nService.t("excludedDomainsInvalidDomain", uri),
|
variant: "error",
|
||||||
);
|
});
|
||||||
|
|
||||||
// Don't reset via `handleStateUpdate` to allow existing input value correction
|
// Don't reset via `handleStateUpdate` to allow existing input value correction
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
@@ -182,7 +181,7 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
|
|||||||
if (stateIsUnchanged) {
|
if (stateIsUnchanged) {
|
||||||
// Reset UI state directly
|
// Reset UI state directly
|
||||||
const constructedNeverDomainsState = this.storedExcludedDomains.reduce(
|
const constructedNeverDomainsState = this.storedExcludedDomains.reduce(
|
||||||
(neverDomains, uri) => ({ ...neverDomains, [uri]: null }),
|
(neverDomains: NeverDomains, uri: string) => ({ ...neverDomains, [uri]: null }),
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
this.handleStateUpdate(constructedNeverDomainsState);
|
this.handleStateUpdate(constructedNeverDomainsState);
|
||||||
@@ -190,13 +189,17 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
|
|||||||
await this.domainSettingsService.setNeverDomains(newExcludedDomainsSaveState);
|
await this.domainSettingsService.setNeverDomains(newExcludedDomainsSaveState);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.platformUtilsService.showToast(
|
this.toastService.showToast({
|
||||||
"success",
|
message: this.i18nService.t("excludedDomainsSavedSuccess"),
|
||||||
null,
|
title: "",
|
||||||
this.i18nService.t("excludedDomainsSavedSuccess"),
|
variant: "success",
|
||||||
);
|
});
|
||||||
} catch {
|
} catch {
|
||||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
|
this.toastService.showToast({
|
||||||
|
message: this.i18nService.t("unexpectedError"),
|
||||||
|
title: "",
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
|
||||||
// Don't reset via `handleStateUpdate` to preserve input values
|
// Don't reset via `handleStateUpdate` to preserve input values
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ describe("AutofillService", () => {
|
|||||||
let messageListener: MockProxy<MessageListener>;
|
let messageListener: MockProxy<MessageListener>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configService = mock<ConfigService>();
|
||||||
|
configService.getFeatureFlag$.mockImplementation(() => of(false));
|
||||||
scriptInjectorService = new BrowserScriptInjectorService(
|
scriptInjectorService = new BrowserScriptInjectorService(
|
||||||
domainSettingsService,
|
domainSettingsService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
@@ -110,10 +112,10 @@ describe("AutofillService", () => {
|
|||||||
autofillSettingsService.inlineMenuVisibility$ = inlineMenuVisibilityMock$;
|
autofillSettingsService.inlineMenuVisibility$ = inlineMenuVisibilityMock$;
|
||||||
autofillSettingsService.showInlineMenuCards$ = showInlineMenuCardsMock$;
|
autofillSettingsService.showInlineMenuCards$ = showInlineMenuCardsMock$;
|
||||||
autofillSettingsService.showInlineMenuIdentities$ = showInlineMenuIdentitiesMock$;
|
autofillSettingsService.showInlineMenuIdentities$ = showInlineMenuIdentitiesMock$;
|
||||||
|
autofillSettingsService.autofillOnPageLoad$ = of(true);
|
||||||
activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked);
|
activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked);
|
||||||
authService = mock<AuthService>();
|
authService = mock<AuthService>();
|
||||||
authService.activeAccountStatus$ = activeAccountStatusMock$;
|
authService.activeAccountStatus$ = activeAccountStatusMock$;
|
||||||
configService = mock<ConfigService>();
|
|
||||||
messageListener = mock<MessageListener>();
|
messageListener = mock<MessageListener>();
|
||||||
enableChangedPasswordPromptMock$ = new BehaviorSubject(true);
|
enableChangedPasswordPromptMock$ = new BehaviorSubject(true);
|
||||||
enableAddedLoginPromptMock$ = new BehaviorSubject(true);
|
enableAddedLoginPromptMock$ = new BehaviorSubject(true);
|
||||||
@@ -389,6 +391,7 @@ describe("AutofillService", () => {
|
|||||||
);
|
);
|
||||||
tabMock = createChromeTabMock();
|
tabMock = createChromeTabMock();
|
||||||
sender = { tab: tabMock, frameId: 1 };
|
sender = { tab: tabMock, frameId: 1 };
|
||||||
|
jest.spyOn(BrowserApi, "getTab").mockImplementation(async () => tabMock);
|
||||||
jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation();
|
jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation();
|
||||||
jest
|
jest
|
||||||
.spyOn(autofillService, "getInlineMenuVisibility")
|
.spyOn(autofillService, "getInlineMenuVisibility")
|
||||||
|
|||||||
@@ -1,9 +1,22 @@
|
|||||||
import { mock } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
import { of } from "rxjs";
|
||||||
|
|
||||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
import {
|
||||||
|
DomainSettingsService,
|
||||||
|
DefaultDomainSettingsService,
|
||||||
|
} from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import {
|
||||||
|
FakeStateProvider,
|
||||||
|
FakeAccountService,
|
||||||
|
mockAccountServiceWith,
|
||||||
|
} from "@bitwarden/common/spec";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
|
import { createChromeTabMock } from "../../autofill/spec/autofill-mocks";
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
import { BrowserApi } from "../browser/browser-api";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -12,8 +25,19 @@ import {
|
|||||||
} from "./abstractions/script-injector.service";
|
} from "./abstractions/script-injector.service";
|
||||||
import { BrowserScriptInjectorService } from "./browser-script-injector.service";
|
import { BrowserScriptInjectorService } from "./browser-script-injector.service";
|
||||||
|
|
||||||
|
const mockEquivalentDomains = [
|
||||||
|
["example.com", "exampleapp.com", "example.co.uk", "ejemplo.es"],
|
||||||
|
["bitwarden.com", "bitwarden.co.uk", "sm-bitwarden.com"],
|
||||||
|
["example.co.uk", "exampleapp.co.uk"],
|
||||||
|
];
|
||||||
|
|
||||||
describe("ScriptInjectorService", () => {
|
describe("ScriptInjectorService", () => {
|
||||||
const tabId = 1;
|
const tabId = 1;
|
||||||
|
const tabMock = createChromeTabMock({ id: tabId });
|
||||||
|
const mockBlockedURI = new URL(tabMock.url);
|
||||||
|
jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation();
|
||||||
|
jest.spyOn(BrowserApi, "isManifestVersion");
|
||||||
|
|
||||||
const combinedManifestVersionFile = "content/autofill-init.js";
|
const combinedManifestVersionFile = "content/autofill-init.js";
|
||||||
const mv2SpecificFile = "content/autofill-init-mv2.js";
|
const mv2SpecificFile = "content/autofill-init-mv2.js";
|
||||||
const mv2Details = { file: mv2SpecificFile };
|
const mv2Details = { file: mv2SpecificFile };
|
||||||
@@ -23,14 +47,24 @@ describe("ScriptInjectorService", () => {
|
|||||||
runAt: "document_start",
|
runAt: "document_start",
|
||||||
};
|
};
|
||||||
const manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get");
|
const manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get");
|
||||||
|
|
||||||
let scriptInjectorService: BrowserScriptInjectorService;
|
let scriptInjectorService: BrowserScriptInjectorService;
|
||||||
jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation();
|
|
||||||
jest.spyOn(BrowserApi, "isManifestVersion");
|
|
||||||
const domainSettingsService = mock<DomainSettingsService>();
|
|
||||||
const platformUtilsService = mock<PlatformUtilsService>();
|
|
||||||
const logService = mock<LogService>();
|
const logService = mock<LogService>();
|
||||||
|
const platformUtilsService = mock<PlatformUtilsService>();
|
||||||
|
const mockUserId = Utils.newGuid() as UserId;
|
||||||
|
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
||||||
|
const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService);
|
||||||
|
let configService: MockProxy<ConfigService>;
|
||||||
|
let domainSettingsService: DomainSettingsService;
|
||||||
|
const expectedBlockedURIError = new Error("This URI of this tab is on the blocked domains list.");
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
jest.spyOn(BrowserApi, "getTab").mockImplementation(async () => tabMock);
|
||||||
|
configService = mock<ConfigService>();
|
||||||
|
configService.getFeatureFlag$.mockImplementation(() => of(false));
|
||||||
|
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService);
|
||||||
|
domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains);
|
||||||
|
domainSettingsService.blockedInteractionsUris$ = of(null);
|
||||||
scriptInjectorService = new BrowserScriptInjectorService(
|
scriptInjectorService = new BrowserScriptInjectorService(
|
||||||
domainSettingsService,
|
domainSettingsService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
@@ -77,6 +111,76 @@ describe("ScriptInjectorService", () => {
|
|||||||
{ world: "ISOLATED" },
|
{ world: "ISOLATED" },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("skips injecting the script in manifest v3 when the tab domain is a blocked domain", async () => {
|
||||||
|
domainSettingsService.blockedInteractionsUris$ = of({ [mockBlockedURI.host]: null });
|
||||||
|
manifestVersionSpy.mockReturnValue(3);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
scriptInjectorService.inject({
|
||||||
|
tabId,
|
||||||
|
injectDetails: {
|
||||||
|
file: combinedManifestVersionFile,
|
||||||
|
frame: 10,
|
||||||
|
...sharedInjectDetails,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(expectedBlockedURIError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips injecting the script in manifest v2 when the tab domain is a blocked domain", async () => {
|
||||||
|
domainSettingsService.blockedInteractionsUris$ = of({ [mockBlockedURI.host]: null });
|
||||||
|
manifestVersionSpy.mockReturnValue(2);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
scriptInjectorService.inject({
|
||||||
|
tabId,
|
||||||
|
injectDetails: {
|
||||||
|
file: combinedManifestVersionFile,
|
||||||
|
frame: "all_frames",
|
||||||
|
...sharedInjectDetails,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(expectedBlockedURIError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("injects the script in manifest v2 when given combined injection details", async () => {
|
||||||
|
manifestVersionSpy.mockReturnValue(2);
|
||||||
|
|
||||||
|
await scriptInjectorService.inject({
|
||||||
|
tabId,
|
||||||
|
injectDetails: {
|
||||||
|
file: combinedManifestVersionFile,
|
||||||
|
frame: "all_frames",
|
||||||
|
...sharedInjectDetails,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabId, {
|
||||||
|
...sharedInjectDetails,
|
||||||
|
allFrames: true,
|
||||||
|
file: combinedManifestVersionFile,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("injects the script in manifest v3 when given combined injection details", async () => {
|
||||||
|
manifestVersionSpy.mockReturnValue(3);
|
||||||
|
|
||||||
|
await scriptInjectorService.inject({
|
||||||
|
tabId,
|
||||||
|
injectDetails: {
|
||||||
|
file: combinedManifestVersionFile,
|
||||||
|
frame: 10,
|
||||||
|
...sharedInjectDetails,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(
|
||||||
|
tabId,
|
||||||
|
{ ...sharedInjectDetails, frameId: 10, file: combinedManifestVersionFile },
|
||||||
|
{ world: "ISOLATED" },
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("injection of mv2 specific details", () => {
|
describe("injection of mv2 specific details", () => {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Subject, takeUntil } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
|
|
||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
@@ -18,22 +17,12 @@ import {
|
|||||||
export class BrowserScriptInjectorService extends ScriptInjectorService {
|
export class BrowserScriptInjectorService extends ScriptInjectorService {
|
||||||
blockedDomains: Set<string> = null;
|
blockedDomains: Set<string> = null;
|
||||||
|
|
||||||
private destroy$ = new Subject<void>();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly domainSettingsService: DomainSettingsService,
|
private readonly domainSettingsService: DomainSettingsService,
|
||||||
private readonly platformUtilsService: PlatformUtilsService,
|
private readonly platformUtilsService: PlatformUtilsService,
|
||||||
private readonly logService: LogService,
|
private readonly logService: LogService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.domainSettingsService.blockedInteractionsUris$
|
|
||||||
.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe((neverDomains: NeverDomains) => {
|
|
||||||
if (neverDomains) {
|
|
||||||
this.blockedDomains = new Set(Object.keys(neverDomains));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,10 +38,20 @@ export class BrowserScriptInjectorService extends ScriptInjectorService {
|
|||||||
throw new Error("No file specified for script injection");
|
throw new Error("No file specified for script injection");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tab = tabId && (await BrowserApi.getTab(tabId));
|
||||||
|
const tabURL = tab?.url ? new URL(tab.url) : null;
|
||||||
|
|
||||||
// Check if the tab URI is on the disabled URIs list
|
// Check if the tab URI is on the disabled URIs list
|
||||||
const tab = await BrowserApi.getTab(tabId);
|
let injectionAllowedInTab = true;
|
||||||
const tabURL = tab.url ? new URL(tab.url) : null;
|
const blockedDomains = await firstValueFrom(
|
||||||
const injectionAllowedInTab = !(tabURL && this.blockedDomains?.has(tabURL.hostname));
|
this.domainSettingsService.blockedInteractionsUris$,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (blockedDomains && tabURL?.hostname) {
|
||||||
|
const blockedDomainsSet = new Set(Object.keys(blockedDomains));
|
||||||
|
|
||||||
|
injectionAllowedInTab = !(tabURL && blockedDomainsSet.has(tabURL.hostname));
|
||||||
|
}
|
||||||
|
|
||||||
if (!injectionAllowedInTab) {
|
if (!injectionAllowedInTab) {
|
||||||
throw new Error("This URI of this tab is on the blocked domains list.");
|
throw new Error("This URI of this tab is on the blocked domains list.");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom, of } from "rxjs";
|
||||||
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
|
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
@@ -45,8 +45,10 @@ describe("FilelessImporterBackground ", () => {
|
|||||||
const platformUtilsService = mock<PlatformUtilsService>();
|
const platformUtilsService = mock<PlatformUtilsService>();
|
||||||
const logService = mock<LogService>();
|
const logService = mock<LogService>();
|
||||||
let scriptInjectorService: BrowserScriptInjectorService;
|
let scriptInjectorService: BrowserScriptInjectorService;
|
||||||
|
let tabMock: chrome.tabs.Tab;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
domainSettingsService.blockedInteractionsUris$ = of(null);
|
||||||
scriptInjectorService = new BrowserScriptInjectorService(
|
scriptInjectorService = new BrowserScriptInjectorService(
|
||||||
domainSettingsService,
|
domainSettingsService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
@@ -81,12 +83,13 @@ describe("FilelessImporterBackground ", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
lpImporterPort = createPortSpyMock(FilelessImportPort.LpImporter);
|
lpImporterPort = createPortSpyMock(FilelessImportPort.LpImporter);
|
||||||
|
tabMock = lpImporterPort.sender.tab;
|
||||||
|
jest.spyOn(BrowserApi, "getTab").mockImplementation(async () => tabMock);
|
||||||
manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get");
|
manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get");
|
||||||
executeScriptInTabSpy = jest.spyOn(BrowserApi, "executeScriptInTab").mockResolvedValue(null);
|
executeScriptInTabSpy = jest.spyOn(BrowserApi, "executeScriptInTab").mockResolvedValue(null);
|
||||||
jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Unlocked);
|
jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Unlocked);
|
||||||
jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(true);
|
jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(true);
|
||||||
jest.spyOn(filelessImporterBackground as any, "removeIndividualVault");
|
jest.spyOn(filelessImporterBackground as any, "removeIndividualVault");
|
||||||
(firstValueFrom as jest.Mock).mockResolvedValue(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores the port connection if the port name is not present in the set of filelessImportNames", async () => {
|
it("ignores the port connection if the port name is not present in the set of filelessImportNames", async () => {
|
||||||
@@ -99,6 +102,7 @@ describe("FilelessImporterBackground ", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("posts a message to the port indicating that the fileless import feature is disabled if the user's auth status is not unlocked", async () => {
|
it("posts a message to the port indicating that the fileless import feature is disabled if the user's auth status is not unlocked", async () => {
|
||||||
|
(firstValueFrom as jest.Mock).mockResolvedValue(false);
|
||||||
jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Locked);
|
jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Locked);
|
||||||
|
|
||||||
triggerRuntimeOnConnectEvent(lpImporterPort);
|
triggerRuntimeOnConnectEvent(lpImporterPort);
|
||||||
@@ -135,6 +139,7 @@ describe("FilelessImporterBackground ", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("posts a message to the port indicating that the fileless import feature is enabled", async () => {
|
it("posts a message to the port indicating that the fileless import feature is enabled", async () => {
|
||||||
|
(firstValueFrom as jest.Mock).mockResolvedValue(false);
|
||||||
triggerRuntimeOnConnectEvent(lpImporterPort);
|
triggerRuntimeOnConnectEvent(lpImporterPort);
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
|
||||||
@@ -158,6 +163,7 @@ describe("FilelessImporterBackground ", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("triggers an injection of the `lp-suppress-import-download-script-append-mv2.js` script in manifest v2", async () => {
|
it("triggers an injection of the `lp-suppress-import-download-script-append-mv2.js` script in manifest v2", async () => {
|
||||||
|
(firstValueFrom as jest.Mock).mockResolvedValueOnce(false);
|
||||||
manifestVersionSpy.mockReturnValue(2);
|
manifestVersionSpy.mockReturnValue(2);
|
||||||
|
|
||||||
triggerRuntimeOnConnectEvent(lpImporterPort);
|
triggerRuntimeOnConnectEvent(lpImporterPort);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export class AutofillVaultListItemsComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* Indicators for the section.
|
* Indicators for the section.
|
||||||
*/
|
*/
|
||||||
@Input() sectionIndicators: string[];
|
@Input() sectionIndicators: string[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observable that determines whether the empty autofill tip should be shown.
|
* Observable that determines whether the empty autofill tip should be shown.
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
|
|||||||
/**
|
/**
|
||||||
* Indicators for the section.
|
* Indicators for the section.
|
||||||
*/
|
*/
|
||||||
@Input() sectionIndicators: string[];
|
@Input() sectionIndicators: string[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional description for the vault list item section. Will be shown below the title even when
|
* Optional description for the vault list item section. Will be shown below the title even when
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
|||||||
protected loading$ = this.vaultPopupItemsService.loading$;
|
protected loading$ = this.vaultPopupItemsService.loading$;
|
||||||
protected scriptInjectionIsBlocked = false;
|
protected scriptInjectionIsBlocked = false;
|
||||||
protected showScriptInjectionIsBlockedBanner = false;
|
protected showScriptInjectionIsBlockedBanner = false;
|
||||||
protected autofillTabHostname: string = null;
|
protected autofillTabHostname: string | null = null;
|
||||||
protected sectionIndicators: string[] = [];
|
protected sectionIndicators: string[] = [];
|
||||||
|
|
||||||
protected newItemItemValues$: Observable<NewItemInitialValues> =
|
protected newItemItemValues$: Observable<NewItemInitialValues> =
|
||||||
@@ -151,20 +151,24 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
|||||||
ngOnDestroy(): void {}
|
ngOnDestroy(): void {}
|
||||||
|
|
||||||
handleScriptInjectionIsBlockedBannerDismiss() {
|
handleScriptInjectionIsBlockedBannerDismiss() {
|
||||||
firstValueFrom(this.domainSettingsService.blockedInteractionsUris$)
|
if (!this.autofillTabHostname) {
|
||||||
.then((blockedURIs) => {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
void firstValueFrom(this.domainSettingsService.blockedInteractionsUris$).then(
|
||||||
|
(blockedURIs) => {
|
||||||
this.showScriptInjectionIsBlockedBanner = false;
|
this.showScriptInjectionIsBlockedBanner = false;
|
||||||
this.domainSettingsService
|
void this.domainSettingsService.setBlockedInteractionsUris({
|
||||||
.setBlockedInteractionsUris({
|
|
||||||
...blockedURIs,
|
...blockedURIs,
|
||||||
[this.autofillTabHostname]: { bannerIsDismissed: true },
|
[this.autofillTabHostname as string]: { bannerIsDismissed: true },
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
/* no-op */
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
/* no-op */
|
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(
|
||||||
|
"There was a problem dismissing the blocked interaction URI notification banner",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -483,7 +483,29 @@ export class ServiceContainer {
|
|||||||
|
|
||||||
this.containerService = new ContainerService(this.keyService, this.encryptService);
|
this.containerService = new ContainerService(this.keyService, this.encryptService);
|
||||||
|
|
||||||
this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider);
|
this.configApiService = new ConfigApiService(this.apiService, this.tokenService);
|
||||||
|
|
||||||
|
this.authService = new AuthService(
|
||||||
|
this.accountService,
|
||||||
|
this.messagingService,
|
||||||
|
this.keyService,
|
||||||
|
this.apiService,
|
||||||
|
this.stateService,
|
||||||
|
this.tokenService,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.configService = new DefaultConfigService(
|
||||||
|
this.configApiService,
|
||||||
|
this.environmentService,
|
||||||
|
this.logService,
|
||||||
|
this.stateProvider,
|
||||||
|
this.authService,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.domainSettingsService = new DefaultDomainSettingsService(
|
||||||
|
this.stateProvider,
|
||||||
|
this.configService,
|
||||||
|
);
|
||||||
|
|
||||||
this.fileUploadService = new FileUploadService(this.logService, this.apiService);
|
this.fileUploadService = new FileUploadService(this.logService, this.apiService);
|
||||||
|
|
||||||
@@ -579,25 +601,6 @@ export class ServiceContainer {
|
|||||||
|
|
||||||
this.taskSchedulerService = new DefaultTaskSchedulerService(this.logService);
|
this.taskSchedulerService = new DefaultTaskSchedulerService(this.logService);
|
||||||
|
|
||||||
this.authService = new AuthService(
|
|
||||||
this.accountService,
|
|
||||||
this.messagingService,
|
|
||||||
this.keyService,
|
|
||||||
this.apiService,
|
|
||||||
this.stateService,
|
|
||||||
this.tokenService,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.configApiService = new ConfigApiService(this.apiService, this.tokenService);
|
|
||||||
|
|
||||||
this.configService = new DefaultConfigService(
|
|
||||||
this.configApiService,
|
|
||||||
this.environmentService,
|
|
||||||
this.logService,
|
|
||||||
this.stateProvider,
|
|
||||||
this.authService,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
|
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
|
||||||
this.deviceTrustService = new DeviceTrustService(
|
this.deviceTrustService = new DeviceTrustService(
|
||||||
this.keyGenerationService,
|
this.keyGenerationService,
|
||||||
|
|||||||
@@ -463,6 +463,11 @@ const safeProviders: SafeProvider[] = [
|
|||||||
useClass: CipherFileUploadService,
|
useClass: CipherFileUploadService,
|
||||||
deps: [ApiServiceAbstraction, FileUploadServiceAbstraction],
|
deps: [ApiServiceAbstraction, FileUploadServiceAbstraction],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: DomainSettingsService,
|
||||||
|
useClass: DefaultDomainSettingsService,
|
||||||
|
deps: [StateProvider, ConfigService],
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: CipherServiceAbstraction,
|
provide: CipherServiceAbstraction,
|
||||||
useFactory: (
|
useFactory: (
|
||||||
@@ -1243,11 +1248,6 @@ const safeProviders: SafeProvider[] = [
|
|||||||
useClass: BadgeSettingsService,
|
useClass: BadgeSettingsService,
|
||||||
deps: [StateProvider],
|
deps: [StateProvider],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
|
||||||
provide: DomainSettingsService,
|
|
||||||
useClass: DefaultDomainSettingsService,
|
|
||||||
deps: [StateProvider, ConfigService],
|
|
||||||
}),
|
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: BiometricStateService,
|
provide: BiometricStateService,
|
||||||
useClass: DefaultBiometricStateService,
|
useClass: DefaultBiometricStateService,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import { MockProxy, mock } from "jest-mock-extended";
|
||||||
import { firstValueFrom, of } from "rxjs";
|
import { firstValueFrom, of } from "rxjs";
|
||||||
|
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
|
|
||||||
import { FakeStateProvider, FakeAccountService, mockAccountServiceWith } from "../../../spec";
|
import { FakeStateProvider, FakeAccountService, mockAccountServiceWith } from "../../../spec";
|
||||||
import { Utils } from "../../platform/misc/utils";
|
import { Utils } from "../../platform/misc/utils";
|
||||||
import { UserId } from "../../types/guid";
|
import { UserId } from "../../types/guid";
|
||||||
@@ -8,8 +11,10 @@ import { DefaultDomainSettingsService, DomainSettingsService } from "./domain-se
|
|||||||
|
|
||||||
describe("DefaultDomainSettingsService", () => {
|
describe("DefaultDomainSettingsService", () => {
|
||||||
let domainSettingsService: DomainSettingsService;
|
let domainSettingsService: DomainSettingsService;
|
||||||
|
const configServiceMock = mock<ConfigService>();
|
||||||
const mockUserId = Utils.newGuid() as UserId;
|
const mockUserId = Utils.newGuid() as UserId;
|
||||||
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
||||||
|
let mockConfigService: MockProxy<ConfigService>;
|
||||||
const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService);
|
const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService);
|
||||||
|
|
||||||
const mockEquivalentDomains = [
|
const mockEquivalentDomains = [
|
||||||
@@ -19,10 +24,12 @@ describe("DefaultDomainSettingsService", () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
|
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, mockConfigService);
|
||||||
|
jest.spyOn(configServiceMock, "getFeatureFlag$").mockReturnValue(of(false));
|
||||||
|
|
||||||
jest.spyOn(domainSettingsService, "getUrlEquivalentDomains");
|
jest.spyOn(domainSettingsService, "getUrlEquivalentDomains");
|
||||||
domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains);
|
domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains);
|
||||||
|
domainSettingsService.blockedInteractionsUris$ = of(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getUrlEquivalentDomains", () => {
|
describe("getUrlEquivalentDomains", () => {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import { combineLatest, map, Observable } from "rxjs";
|
import { combineLatest, map, Observable } from "rxjs";
|
||||||
|
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NeverDomains,
|
NeverDomains,
|
||||||
@@ -11,6 +10,7 @@ import {
|
|||||||
UriMatchStrategySetting,
|
UriMatchStrategySetting,
|
||||||
UriMatchStrategy,
|
UriMatchStrategy,
|
||||||
} from "../../models/domain/domain-service";
|
} from "../../models/domain/domain-service";
|
||||||
|
import { ConfigService } from "../../platform/abstractions/config/config.service";
|
||||||
import { Utils } from "../../platform/misc/utils";
|
import { Utils } from "../../platform/misc/utils";
|
||||||
import {
|
import {
|
||||||
DOMAIN_SETTINGS_DISK,
|
DOMAIN_SETTINGS_DISK,
|
||||||
@@ -99,7 +99,7 @@ export class DefaultDomainSettingsService implements DomainSettingsService {
|
|||||||
|
|
||||||
this.blockedInteractionsUris$ = combineLatest([
|
this.blockedInteractionsUris$ = combineLatest([
|
||||||
this.blockedInteractionsUrisState.state$,
|
this.blockedInteractionsUrisState.state$,
|
||||||
this.configService.getFeatureFlag$(FeatureFlag.BlockBrowserInjectionsByDomain),
|
this.configService?.getFeatureFlag$(FeatureFlag.BlockBrowserInjectionsByDomain),
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([blockedUris, blockBrowserInjectionsByDomainEnabled]) => {
|
map(([blockedUris, blockBrowserInjectionsByDomainEnabled]) => {
|
||||||
if (!blockBrowserInjectionsByDomainEnabled) {
|
if (!blockBrowserInjectionsByDomainEnabled) {
|
||||||
|
|||||||
Reference in New Issue
Block a user