Skip to content

Commit

Permalink
[PM-13115] Allow users to disable extension content script injections…
Browse files Browse the repository at this point in the history
… by domain (#11826)

* add disabledInteractionsUris state to the domain settings service

* add routes and ui for user disabledInteractionsUris state management

* use disabled URIs service state as a preemptive conditon to injecting content scripts

* move disabled domains navigation button from account security settings to autofill settings

* update disabled domain terminology to blocked domain terminology

* update copy

* handle blocked domains initializing with null value

* add dismissable banner to the vault view when the active autofill tab is on the blocked domains list

* add autofill blocked domain indicators to autofill suggestions section header

* add BlockBrowserInjectionsByDomain feature flag and put feature behind it

* update router config to new style

* update tests and cleanup

* use full-width-notice slot for domain script injection blocked banner

* convert thrown error on content script injection block to a warning and early return

* simplify and enspeeden state resolution for blockedInteractionsUris

* refactor feature flag state fetching and update tests

* document domain settings service

* remove vault component presentational updates
  • Loading branch information
jprusik authored Jan 6, 2025
1 parent ddc8176 commit 15faf52
Show file tree
Hide file tree
Showing 23 changed files with 623 additions and 77 deletions.
15 changes: 15 additions & 0 deletions apps/browser/src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2324,6 +2324,9 @@
"message": "Domains",
"description": "A category title describing the concept of web domains"
},
"blockedDomains": {
"message": "Blocked domains"
},
"excludedDomains": {
"message": "Excluded domains"
},
Expand All @@ -2333,6 +2336,15 @@
"excludedDomainsDescAlt": {
"message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect."
},
"blockedDomainsDesc": {
"message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect."
},
"autofillBlockedNotice": {
"message": "Autofill is blocked for this website. Review or change this in settings."
},
"autofillBlockedTooltip": {
"message": "Autofill is blocked on this website. Review in settings."
},
"websiteItemLabel": {
"message": "Website $number$ (URI)",
"placeholders": {
Expand All @@ -2351,6 +2363,9 @@
}
}
},
"blockedDomainsSavedSuccess": {
"message": "Blocked domain changes saved"
},
"excludedDomainsSavedSuccess": {
"message": "Excluded domain changes saved"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mock, MockProxy, mockReset } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { BehaviorSubject, of } from "rxjs";

import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
Expand All @@ -14,6 +14,7 @@ import {
} from "@bitwarden/common/autofill/services/domain-settings.service";
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import {
EnvironmentService,
Region,
Expand Down Expand Up @@ -93,6 +94,7 @@ describe("OverlayBackground", () => {
let logService: MockProxy<LogService>;
let cipherService: MockProxy<CipherService>;
let autofillService: MockProxy<AutofillService>;
let configService: MockProxy<ConfigService>;
let activeAccountStatusMock$: BehaviorSubject<AuthenticationStatus>;
let authService: MockProxy<AuthService>;
let environmentMock$: BehaviorSubject<CloudEnvironment>;
Expand Down Expand Up @@ -149,11 +151,13 @@ describe("OverlayBackground", () => {
}

beforeEach(() => {
configService = mock<ConfigService>();
configService.getFeatureFlag$.mockImplementation(() => of(true));
accountService = mockAccountServiceWith(mockUserId);
fakeStateProvider = new FakeStateProvider(accountService);
showFaviconsMock$ = new BehaviorSubject(true);
neverDomainsMock$ = new BehaviorSubject({});
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService);
domainSettingsService.showFavicons$ = showFaviconsMock$;
domainSettingsService.neverDomains$ = neverDomainsMock$;
logService = mock<LogService>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
DefaultDomainSettingsService,
DomainSettingsService,
} from "@bitwarden/common/autofill/services/domain-settings.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import {
EnvironmentService,
Region,
Expand Down Expand Up @@ -61,6 +62,7 @@ describe("OverlayBackground", () => {
let overlayBackground: LegacyOverlayBackground;
const cipherService = mock<CipherService>();
const autofillService = mock<AutofillService>();
let configService: MockProxy<ConfigService>;
let activeAccountStatusMock$: BehaviorSubject<AuthenticationStatus>;
let authService: MockProxy<AuthService>;

Expand Down Expand Up @@ -92,7 +94,9 @@ describe("OverlayBackground", () => {
};

beforeEach(() => {
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
configService = mock<ConfigService>();
configService.getFeatureFlag$.mockImplementation(() => of(true));
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService);
activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked);
authService = mock<AuthService>();
authService.activeAccountStatus$ = activeAccountStatusMock$;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,4 +255,16 @@ <h2 class="box-header">{{ "additionalOptions" | i18n }}</h2>
{{ "showIdentitiesCurrentTabDesc" | i18n }}
</div>
</div>
<div class="box list" *ngIf="blockBrowserInjectionsByDomainEnabled">
<div class="box-content single-line">
<button
type="button"
class="box-content-row box-content-row-flex text-default"
routerLink="/blocked-domains"
>
<div class="row-main">{{ "blockedDomains" | i18n }}</div>
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
</button>
</div>
</div>
</main>
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class AutofillV1Component implements OnInit {
protected autoFillOverlayVisibilityOptions: any[];
protected disablePasswordManagerLink: string;
protected inlineMenuPositioningImprovementsEnabled: boolean = false;
protected blockBrowserInjectionsByDomainEnabled: boolean = false;
protected showInlineMenuIdentities: boolean = true;
protected showInlineMenuCards: boolean = true;
inlineMenuIsEnabled: boolean = false;
Expand Down Expand Up @@ -120,6 +121,10 @@ export class AutofillV1Component implements OnInit {
FeatureFlag.InlineMenuPositioningImprovements,
);

this.blockBrowserInjectionsByDomainEnabled = await this.configService.getFeatureFlag(
FeatureFlag.BlockBrowserInjectionsByDomain,
);

this.inlineMenuIsEnabled = this.isInlineMenuEnabled();

this.showInlineMenuIdentities =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,5 +282,11 @@ <h2 bitTypography="h6">{{ "additionalOptions" | i18n }}</h2>
</bit-form-field>
</bit-card>
</bit-section>
<bit-section *ngIf="blockBrowserInjectionsByDomainEnabled">
<bit-item>
<a bit-item-content routerLink="/blocked-domains">{{ "blockedDomains" | i18n }}</a>
<i slot="end" class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
</bit-item>
</bit-section>
</div>
</popup-page>
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import {

import { BrowserApi } from "../../../platform/browser/browser-api";
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component";
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";

Expand All @@ -67,7 +66,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
JslibModule,
LinkModule,
PopOutComponent,
PopupFooterComponent,
PopupHeaderComponent,
PopupPageComponent,
RouterModule,
Expand All @@ -87,6 +85,7 @@ export class AutofillComponent implements OnInit {
protected inlineMenuVisibility: InlineMenuVisibilitySetting =
AutofillOverlayVisibility.OnFieldFocus;
protected inlineMenuPositioningImprovementsEnabled: boolean = false;
protected blockBrowserInjectionsByDomainEnabled: boolean = false;
protected browserClientVendor: BrowserClientVendor = BrowserClientVendors.Unknown;
protected disablePasswordManagerURI: DisablePasswordManagerUri =
DisablePasswordManagerUris.Unknown;
Expand Down Expand Up @@ -164,6 +163,10 @@ export class AutofillComponent implements OnInit {
FeatureFlag.InlineMenuPositioningImprovements,
);

this.blockBrowserInjectionsByDomainEnabled = await this.configService.getFeatureFlag(
FeatureFlag.BlockBrowserInjectionsByDomain,
);

this.showInlineMenuIdentities =
this.inlineMenuPositioningImprovementsEnabled &&
(await firstValueFrom(this.autofillSettingsService.showInlineMenuIdentities$));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<popup-page>
<popup-header slot="header" pageTitle="{{ 'blockedDomains' | i18n }}" showBackButton>
<ng-container slot="end">
<app-pop-out></app-pop-out>
</ng-container>
</popup-header>

<div class="tw-bg-background-alt">
<p>{{ "blockedDomainsDesc" | i18n }}</p>
<bit-section *ngIf="!isLoading">
<bit-section-header>
<h2 bitTypography="h6">{{ "domainsTitle" | i18n }}</h2>
<span bitTypography="body2" slot="end">{{ blockedDomainsState?.length || 0 }}</span>
</bit-section-header>

<ng-container *ngIf="blockedDomainsState">
<bit-item
*ngFor="let domain of blockedDomainsState; let i = index; trackBy: trackByFunction"
>
<bit-item-content>
<bit-label *ngIf="i >= fieldsEditThreshold">{{
"websiteItemLabel" | i18n: i + 1
}}</bit-label>
<input
*ngIf="i >= fieldsEditThreshold"
#uriInput
appInputVerbatim
bitInput
id="excludedDomain{{ i }}"
inputmode="url"
name="excludedDomain{{ i }}"
type="text"
(change)="fieldChange()"
[(ngModel)]="blockedDomainsState[i]"
/>
<div id="excludedDomain{{ i }}" *ngIf="i < fieldsEditThreshold">{{ domain }}</div>
</bit-item-content>
<button
*ngIf="i < fieldsEditThreshold"
appA11yTitle="{{ 'remove' | i18n }}"
bitIconButton="bwi-minus-circle"
buttonType="danger"
size="small"
slot="end"
type="button"
(click)="removeDomain(i)"
></button>
</bit-item>
</ng-container>
<button bitLink class="tw-pt-2" type="button" linkType="primary" (click)="addNewDomain()">
<i class="bwi bwi-plus-circle bwi-fw" aria-hidden="true"></i> {{ "addDomain" | i18n }}
</button>
</bit-section>
</div>
<popup-footer slot="footer">
<button
bitButton
buttonType="primary"
type="submit"
[disabled]="dataIsPristine"
(click)="saveChanges()"
>
{{ "save" | i18n }}
</button>
</popup-footer>
</popup-page>
Loading

0 comments on commit 15faf52

Please sign in to comment.