1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +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

@@ -17,5 +17,6 @@ export enum PolicyType {
FreeFamiliesSponsorshipPolicy = 13, // Disables free families plan for organization
RemoveUnlockWithPin = 14, // Do not allow members to unlock their account with a PIN.
RestrictedItemTypes = 15, // Restricts item types that can be created within an organization
UriMatchDefaults = 16, // Sets the default URI matching strategy for all users within an organization
AutotypeDefaultSetting = 17, // Sets the default autotype setting for desktop app
}

View File

@@ -1,5 +1,8 @@
import { mock } from "jest-mock-extended";
import { firstValueFrom, of } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { FakeStateProvider, FakeAccountService, mockAccountServiceWith } from "../../../spec";
import { Utils } from "../../platform/misc/utils";
import { UserId } from "../../types/guid";
@@ -10,6 +13,7 @@ describe("DefaultDomainSettingsService", () => {
let domainSettingsService: DomainSettingsService;
const mockUserId = Utils.newGuid() as UserId;
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
const policyService = mock<PolicyService>();
const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService);
const mockEquivalentDomains = [
@@ -19,7 +23,11 @@ describe("DefaultDomainSettingsService", () => {
];
beforeEach(() => {
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
domainSettingsService = new DefaultDomainSettingsService(
fakeStateProvider,
policyService,
accountService,
);
jest.spyOn(domainSettingsService, "getUrlEquivalentDomains");
domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains);

View File

@@ -1,6 +1,12 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { map, Observable } from "rxjs";
import { combineLatest, map, Observable, switchMap, shareReplay } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums/policy-type.enum";
import { getFirstPolicy } from "@bitwarden/common/admin-console/services/policy/default-policy.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import {
NeverDomains,
@@ -87,6 +93,18 @@ export abstract class DomainSettingsService {
defaultUriMatchStrategy$: Observable<UriMatchStrategySetting>;
setDefaultUriMatchStrategy: (newValue: UriMatchStrategySetting) => Promise<void>;
/**
* Org policy value for default for URI-matching
* strategies. Can be overridden by cipher-specific settings.
*/
defaultUriMatchStrategyPolicy$: Observable<UriMatchStrategySetting>;
/**
* Resolved (concerning user setting, org policy, etc) default for URI-matching
* strategies. Can be overridden by cipher-specific settings.
*/
resolvedDefaultUriMatchStrategy$: Observable<UriMatchStrategySetting>;
/**
* Helper function for the common resolution of a given URL against equivalent domains
*/
@@ -109,7 +127,15 @@ export class DefaultDomainSettingsService implements DomainSettingsService {
private defaultUriMatchStrategyState: ActiveUserState<UriMatchStrategySetting>;
readonly defaultUriMatchStrategy$: Observable<UriMatchStrategySetting>;
constructor(private stateProvider: StateProvider) {
readonly defaultUriMatchStrategyPolicy$: Observable<UriMatchStrategySetting>;
readonly resolvedDefaultUriMatchStrategy$: Observable<UriMatchStrategySetting>;
constructor(
private stateProvider: StateProvider,
private policyService: PolicyService,
private accountService: AccountService,
) {
this.showFaviconsState = this.stateProvider.getGlobal(SHOW_FAVICONS);
this.showFavicons$ = this.showFaviconsState.state$.pipe(map((x) => x ?? true));
@@ -129,6 +155,31 @@ export class DefaultDomainSettingsService implements DomainSettingsService {
this.defaultUriMatchStrategy$ = this.defaultUriMatchStrategyState.state$.pipe(
map((x) => x ?? UriMatchStrategy.Domain),
);
this.defaultUriMatchStrategyPolicy$ = this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.policiesByType$(PolicyType.UriMatchDefaults, userId),
),
getFirstPolicy,
map((policy) => {
if (!policy?.enabled || policy?.data == null) {
return null;
}
const data = policy.data?.defaultUriMatchStrategy;
// Validate that data is a valid UriMatchStrategy value
return Object.values(UriMatchStrategy).includes(data) ? data : null;
}),
shareReplay({ bufferSize: 1, refCount: true }),
);
this.resolvedDefaultUriMatchStrategy$ = combineLatest([
this.defaultUriMatchStrategy$,
this.defaultUriMatchStrategyPolicy$,
]).pipe(
map(([userSettingValue, policySettingValue]) => policySettingValue || userSettingValue),
shareReplay({ bufferSize: 1, refCount: true }),
);
}
async setShowFavicons(newValue: boolean): Promise<void> {

View File

@@ -634,7 +634,9 @@ export class CipherService implements CipherServiceAbstraction {
const equivalentDomains = await firstValueFrom(
this.domainSettingsService.getUrlEquivalentDomains(url),
);
defaultMatch ??= await firstValueFrom(this.domainSettingsService.defaultUriMatchStrategy$);
defaultMatch ??= await firstValueFrom(
this.domainSettingsService.resolvedDefaultUriMatchStrategy$,
);
const archiveFeatureEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM19148_InnovationArchive,