1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 21:33:27 +00:00

feat(policies): PM-19311 Enforce URI Match Defaults organization policy (#16430)

* feat(policies): Add URI Match Default Policy enum

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* feat(policies): Add logic to read and set the default from policy data

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* In settings, set default, disable select and display hint

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Move applyUriMatchPolicy to writeValue function

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Remove code to disable individual options because we're disabling the entire select

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* WiP move resolved defaultUriMatch to Domain Settings Service

* Merge branch 'main' of github.com:bitwarden/clients into pm-19310-uri-match-policy

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Merge branch 'main' of github.com:bitwarden/clients into pm-19310-uri-match-policy

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Merge branch 'main' of github.com:bitwarden/clients into pm-19310-uri-match-policy

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Merge branch 'main' of github.com:bitwarden/clients into pm-19310-uri-match-policy

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Merge branch 'main' of github.com:bitwarden/clients into pm-19310-uri-match-policy

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Merge branch 'main' of github.com:bitwarden/clients into pm-19310-uri-match-policy

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Address local test failures related to null observables

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* add missing services

* Fix test to use new resolvedDefaultUriMatchStrategy$

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Move definition of defaultMatchDetection$ out of constructor

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Update cipher form story to use resolvedDefaultUriMatchStrategy

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Merge branch 'pm-19310-uri-match-policy' of github.com:bitwarden/clients into pm-19310-uri-match-policy

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Fix incomplete storybook mock in cipher form stories

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Add I18n key description

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Add comment regarding potential memory leak in domain settings service

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Add explicit check for null policy data

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Add explicit check for undefined policy data

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Merge branch 'pm-19310-uri-match-policy' of github.com:bitwarden/clients into pm-19310-uri-match-policy

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Add shareReplay to address potential memory leak

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Merge branch 'pm-19310-uri-match-policy' of github.com:bitwarden/clients into pm-19310-uri-match-policy

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Merge branch 'main' of github.com:bitwarden/clients into pm-19310-uri-match-policy

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Remove outdated comment

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

* Improve type safety/validation and null checks in DefaultDomainSettingsService

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>

---------

Signed-off-by: Ben Brooks <bbrooks@bitwarden.com>
Co-authored-by: Jonathan Prusik <jprusik@classynemesis.com>
This commit is contained in:
Ben Brooks
2025-10-17 07:58:17 -07:00
committed by GitHub
parent b8d55c4db1
commit 91a661a025
18 changed files with 139 additions and 20 deletions

View File

@@ -4459,7 +4459,7 @@
"message": "Common formats",
"description": "Label indicating the most common import formats"
},
"uriMatchDefaultStrategyHint": {
"uriMatchDefaultStrategyHint": {
"message": "URI match detection is how Bitwarden identifies autofill suggestions.",
"description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item."
},
@@ -5714,5 +5714,9 @@
},
"confirmKeyConnectorDomain": {
"message": "Confirm Key Connector domain"
},
"settingDisabledByPolicy": {
"message": "This setting is disabled by your organization's policy.",
"description": "This hint text is displayed when a user setting is disabled due to an organization policy."
}
}

View File

@@ -1,6 +1,7 @@
import { mock, MockProxy, mockReset } from "jest-mock-extended";
import { BehaviorSubject, of } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import {
@@ -105,6 +106,7 @@ describe("OverlayBackground", () => {
let platformUtilsService: MockProxy<BrowserPlatformUtilsService>;
let enablePasskeysMock$: BehaviorSubject<boolean>;
let vaultSettingsServiceMock: MockProxy<VaultSettingsService>;
const policyService = mock<PolicyService>();
let fido2ActiveRequestManager: Fido2ActiveRequestManager;
let selectedThemeMock$: BehaviorSubject<ThemeType>;
let inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;
@@ -156,7 +158,11 @@ describe("OverlayBackground", () => {
fakeStateProvider = new FakeStateProvider(accountService);
showFaviconsMock$ = new BehaviorSubject(true);
neverDomainsMock$ = new BehaviorSubject({});
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
domainSettingsService = new DefaultDomainSettingsService(
fakeStateProvider,
policyService,
accountService,
);
domainSettingsService.showFavicons$ = showFaviconsMock$;
domainSettingsService.neverDomains$ = neverDomainsMock$;
logService = mock<LogService>();

View File

@@ -265,6 +265,9 @@
[disabled]="option.disabled"
></bit-option>
</bit-select>
<bit-hint *ngIf="isDefaultUriMatchDisabledByPolicy">
{{ "settingDisabledByPolicy" | i18n }}
</bit-hint>
<bit-hint *ngIf="getMatchHints() as hints">
{{ hints[0] | i18n }}
<ng-container *ngIf="hints.length > 1">

View File

@@ -26,6 +26,7 @@ import {
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import {
@@ -140,6 +141,8 @@ export class AutofillComponent implements OnInit {
defaultUriMatch: new FormControl(),
});
protected isDefaultUriMatchDisabledByPolicy = false;
advancedOptionWarningMap: Partial<Record<UriMatchStrategySetting, string>>;
enableAutofillOnPageLoad: boolean = false;
enableInlineMenu: boolean = false;
@@ -174,6 +177,7 @@ export class AutofillComponent implements OnInit {
private accountService: AccountService,
private autofillBrowserSettingsService: AutofillBrowserSettingsService,
private restrictedItemTypesService: RestrictedItemTypesService,
private policyService: PolicyService,
) {
this.autofillOnPageLoadOptions = [
{ name: this.i18nService.t("autoFillOnPageLoadYes"), value: true },
@@ -302,7 +306,7 @@ export class AutofillComponent implements OnInit {
});
const defaultUriMatch = await firstValueFrom(
this.domainSettingsService.defaultUriMatchStrategy$,
this.domainSettingsService.resolvedDefaultUriMatchStrategy$,
);
this.defaultUriMatch = defaultUriMatch == null ? UriMatchStrategy.Domain : defaultUriMatch;
@@ -310,6 +314,8 @@ export class AutofillComponent implements OnInit {
emitEvent: false,
});
this.applyUriMatchPolicy();
this.additionalOptionsForm.controls.enableContextMenuItem.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
@@ -524,6 +530,20 @@ export class AutofillComponent implements OnInit {
await this.updateDefaultBrowserAutofillDisabled();
};
private applyUriMatchPolicy() {
this.domainSettingsService.defaultUriMatchStrategyPolicy$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
if (value !== null) {
this.isDefaultUriMatchDisabledByPolicy = true;
this.additionalOptionsForm.controls.defaultUriMatch.disable({ emitEvent: false });
} else {
this.isDefaultUriMatchDisabledByPolicy = false;
this.additionalOptionsForm.controls.defaultUriMatch.enable({ emitEvent: false });
}
});
}
private async handleAdvancedMatch(
previous: UriMatchStrategySetting | null,
current: UriMatchStrategySetting | null,

View File

@@ -1,6 +1,7 @@
import { mock, MockProxy, mockReset } from "jest-mock-extended";
import { BehaviorSubject, of, Subject } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
@@ -86,6 +87,7 @@ describe("AutofillService", () => {
const totpService = mock<TotpService>();
const eventCollectionService = mock<EventCollectionService>();
const logService = mock<LogService>();
const policyService = mock<PolicyService>();
const userVerificationService = mock<UserVerificationService>();
const billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
const platformUtilsService = mock<PlatformUtilsService>();
@@ -138,7 +140,11 @@ describe("AutofillService", () => {
userNotificationsSettings,
messageListener,
);
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
domainSettingsService = new DefaultDomainSettingsService(
fakeStateProvider,
policyService,
accountService,
);
domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains);
jest.spyOn(BrowserApi, "tabSendMessage");
});

View File

@@ -400,7 +400,7 @@ export default class AutofillService implements AutofillServiceInterface {
* Gets the default URI match strategy setting from the domain settings service.
*/
async getDefaultUriMatchStrategy(): Promise<UriMatchStrategySetting> {
return await firstValueFrom(this.domainSettingsService.defaultUriMatchStrategy$);
return await firstValueFrom(this.domainSettingsService.resolvedDefaultUriMatchStrategy$);
}
/**

View File

@@ -923,7 +923,11 @@ export default class MainBackground {
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider);
this.domainSettingsService = new DefaultDomainSettingsService(
this.stateProvider,
this.policyService,
this.accountService,
);
this.themeStateService = new DefaultThemeStateService(this.globalStateProvider);

View File

@@ -1,6 +1,7 @@
import { mock } from "jest-mock-extended";
import { of } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import {
DomainSettingsService,
DefaultDomainSettingsService,
@@ -57,10 +58,15 @@ describe("ScriptInjectorService", () => {
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService);
let domainSettingsService: DomainSettingsService;
const policyService = mock<PolicyService>();
beforeEach(() => {
jest.spyOn(BrowserApi, "getTab").mockImplementation(async () => tabMock);
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
domainSettingsService = new DefaultDomainSettingsService(
fakeStateProvider,
policyService,
accountService,
);
domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains);
domainSettingsService.blockedInteractionsUris$ = of({});
scriptInjectorService = new BrowserScriptInjectorService(

View File

@@ -364,7 +364,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: DomainSettingsService,
useClass: DefaultDomainSettingsService,
deps: [StateProvider],
deps: [StateProvider, PolicyService, AccountService],
}),
safeProvider({
provide: AbstractStorageService,

View File

@@ -562,7 +562,11 @@ export class ServiceContainer {
this.authService,
);
this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider);
this.domainSettingsService = new DefaultDomainSettingsService(
this.stateProvider,
this.policyService,
this.accountService,
);
this.fileUploadService = new FileUploadService(this.logService, this.apiService);