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:
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -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$);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -364,7 +364,7 @@ const safeProviders: SafeProvider[] = [
|
||||
safeProvider({
|
||||
provide: DomainSettingsService,
|
||||
useClass: DefaultDomainSettingsService,
|
||||
deps: [StateProvider],
|
||||
deps: [StateProvider, PolicyService, AccountService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: AbstractStorageService,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user