mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +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",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
"uriMatchDefaultStrategyHint": {
|
"uriMatchDefaultStrategyHint": {
|
||||||
"message": "URI match detection is how Bitwarden identifies autofill suggestions.",
|
"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."
|
"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": {
|
"confirmKeyConnectorDomain": {
|
||||||
"message": "Confirm Key Connector domain"
|
"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 { mock, MockProxy, mockReset } from "jest-mock-extended";
|
||||||
import { BehaviorSubject, of } from "rxjs";
|
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 { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import {
|
import {
|
||||||
@@ -105,6 +106,7 @@ describe("OverlayBackground", () => {
|
|||||||
let platformUtilsService: MockProxy<BrowserPlatformUtilsService>;
|
let platformUtilsService: MockProxy<BrowserPlatformUtilsService>;
|
||||||
let enablePasskeysMock$: BehaviorSubject<boolean>;
|
let enablePasskeysMock$: BehaviorSubject<boolean>;
|
||||||
let vaultSettingsServiceMock: MockProxy<VaultSettingsService>;
|
let vaultSettingsServiceMock: MockProxy<VaultSettingsService>;
|
||||||
|
const policyService = mock<PolicyService>();
|
||||||
let fido2ActiveRequestManager: Fido2ActiveRequestManager;
|
let fido2ActiveRequestManager: Fido2ActiveRequestManager;
|
||||||
let selectedThemeMock$: BehaviorSubject<ThemeType>;
|
let selectedThemeMock$: BehaviorSubject<ThemeType>;
|
||||||
let inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;
|
let inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;
|
||||||
@@ -156,7 +158,11 @@ describe("OverlayBackground", () => {
|
|||||||
fakeStateProvider = new FakeStateProvider(accountService);
|
fakeStateProvider = new FakeStateProvider(accountService);
|
||||||
showFaviconsMock$ = new BehaviorSubject(true);
|
showFaviconsMock$ = new BehaviorSubject(true);
|
||||||
neverDomainsMock$ = new BehaviorSubject({});
|
neverDomainsMock$ = new BehaviorSubject({});
|
||||||
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
|
domainSettingsService = new DefaultDomainSettingsService(
|
||||||
|
fakeStateProvider,
|
||||||
|
policyService,
|
||||||
|
accountService,
|
||||||
|
);
|
||||||
domainSettingsService.showFavicons$ = showFaviconsMock$;
|
domainSettingsService.showFavicons$ = showFaviconsMock$;
|
||||||
domainSettingsService.neverDomains$ = neverDomainsMock$;
|
domainSettingsService.neverDomains$ = neverDomainsMock$;
|
||||||
logService = mock<LogService>();
|
logService = mock<LogService>();
|
||||||
|
|||||||
@@ -265,6 +265,9 @@
|
|||||||
[disabled]="option.disabled"
|
[disabled]="option.disabled"
|
||||||
></bit-option>
|
></bit-option>
|
||||||
</bit-select>
|
</bit-select>
|
||||||
|
<bit-hint *ngIf="isDefaultUriMatchDisabledByPolicy">
|
||||||
|
{{ "settingDisabledByPolicy" | i18n }}
|
||||||
|
</bit-hint>
|
||||||
<bit-hint *ngIf="getMatchHints() as hints">
|
<bit-hint *ngIf="getMatchHints() as hints">
|
||||||
{{ hints[0] | i18n }}
|
{{ hints[0] | i18n }}
|
||||||
<ng-container *ngIf="hints.length > 1">
|
<ng-container *ngIf="hints.length > 1">
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
||||||
import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component";
|
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 { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import {
|
import {
|
||||||
@@ -140,6 +141,8 @@ export class AutofillComponent implements OnInit {
|
|||||||
defaultUriMatch: new FormControl(),
|
defaultUriMatch: new FormControl(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
protected isDefaultUriMatchDisabledByPolicy = false;
|
||||||
|
|
||||||
advancedOptionWarningMap: Partial<Record<UriMatchStrategySetting, string>>;
|
advancedOptionWarningMap: Partial<Record<UriMatchStrategySetting, string>>;
|
||||||
enableAutofillOnPageLoad: boolean = false;
|
enableAutofillOnPageLoad: boolean = false;
|
||||||
enableInlineMenu: boolean = false;
|
enableInlineMenu: boolean = false;
|
||||||
@@ -174,6 +177,7 @@ export class AutofillComponent implements OnInit {
|
|||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private autofillBrowserSettingsService: AutofillBrowserSettingsService,
|
private autofillBrowserSettingsService: AutofillBrowserSettingsService,
|
||||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||||
|
private policyService: PolicyService,
|
||||||
) {
|
) {
|
||||||
this.autofillOnPageLoadOptions = [
|
this.autofillOnPageLoadOptions = [
|
||||||
{ name: this.i18nService.t("autoFillOnPageLoadYes"), value: true },
|
{ name: this.i18nService.t("autoFillOnPageLoadYes"), value: true },
|
||||||
@@ -302,7 +306,7 @@ export class AutofillComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const defaultUriMatch = await firstValueFrom(
|
const defaultUriMatch = await firstValueFrom(
|
||||||
this.domainSettingsService.defaultUriMatchStrategy$,
|
this.domainSettingsService.resolvedDefaultUriMatchStrategy$,
|
||||||
);
|
);
|
||||||
this.defaultUriMatch = defaultUriMatch == null ? UriMatchStrategy.Domain : defaultUriMatch;
|
this.defaultUriMatch = defaultUriMatch == null ? UriMatchStrategy.Domain : defaultUriMatch;
|
||||||
|
|
||||||
@@ -310,6 +314,8 @@ export class AutofillComponent implements OnInit {
|
|||||||
emitEvent: false,
|
emitEvent: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.applyUriMatchPolicy();
|
||||||
|
|
||||||
this.additionalOptionsForm.controls.enableContextMenuItem.valueChanges
|
this.additionalOptionsForm.controls.enableContextMenuItem.valueChanges
|
||||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||||
.subscribe((value) => {
|
.subscribe((value) => {
|
||||||
@@ -524,6 +530,20 @@ export class AutofillComponent implements OnInit {
|
|||||||
await this.updateDefaultBrowserAutofillDisabled();
|
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(
|
private async handleAdvancedMatch(
|
||||||
previous: UriMatchStrategySetting | null,
|
previous: UriMatchStrategySetting | null,
|
||||||
current: UriMatchStrategySetting | null,
|
current: UriMatchStrategySetting | null,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { mock, MockProxy, mockReset } from "jest-mock-extended";
|
import { mock, MockProxy, mockReset } from "jest-mock-extended";
|
||||||
import { BehaviorSubject, of, Subject } from "rxjs";
|
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 { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
|
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
|
||||||
@@ -86,6 +87,7 @@ describe("AutofillService", () => {
|
|||||||
const totpService = mock<TotpService>();
|
const totpService = mock<TotpService>();
|
||||||
const eventCollectionService = mock<EventCollectionService>();
|
const eventCollectionService = mock<EventCollectionService>();
|
||||||
const logService = mock<LogService>();
|
const logService = mock<LogService>();
|
||||||
|
const policyService = mock<PolicyService>();
|
||||||
const userVerificationService = mock<UserVerificationService>();
|
const userVerificationService = mock<UserVerificationService>();
|
||||||
const billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
const billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||||
const platformUtilsService = mock<PlatformUtilsService>();
|
const platformUtilsService = mock<PlatformUtilsService>();
|
||||||
@@ -138,7 +140,11 @@ describe("AutofillService", () => {
|
|||||||
userNotificationsSettings,
|
userNotificationsSettings,
|
||||||
messageListener,
|
messageListener,
|
||||||
);
|
);
|
||||||
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
|
domainSettingsService = new DefaultDomainSettingsService(
|
||||||
|
fakeStateProvider,
|
||||||
|
policyService,
|
||||||
|
accountService,
|
||||||
|
);
|
||||||
domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains);
|
domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains);
|
||||||
jest.spyOn(BrowserApi, "tabSendMessage");
|
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.
|
* Gets the default URI match strategy setting from the domain settings service.
|
||||||
*/
|
*/
|
||||||
async getDefaultUriMatchStrategy(): Promise<UriMatchStrategySetting> {
|
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.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);
|
this.themeStateService = new DefaultThemeStateService(this.globalStateProvider);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
import { of } from "rxjs";
|
import { of } from "rxjs";
|
||||||
|
|
||||||
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import {
|
import {
|
||||||
DomainSettingsService,
|
DomainSettingsService,
|
||||||
DefaultDomainSettingsService,
|
DefaultDomainSettingsService,
|
||||||
@@ -57,10 +58,15 @@ describe("ScriptInjectorService", () => {
|
|||||||
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
||||||
const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService);
|
const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService);
|
||||||
let domainSettingsService: DomainSettingsService;
|
let domainSettingsService: DomainSettingsService;
|
||||||
|
const policyService = mock<PolicyService>();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.spyOn(BrowserApi, "getTab").mockImplementation(async () => tabMock);
|
jest.spyOn(BrowserApi, "getTab").mockImplementation(async () => tabMock);
|
||||||
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
|
domainSettingsService = new DefaultDomainSettingsService(
|
||||||
|
fakeStateProvider,
|
||||||
|
policyService,
|
||||||
|
accountService,
|
||||||
|
);
|
||||||
domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains);
|
domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains);
|
||||||
domainSettingsService.blockedInteractionsUris$ = of({});
|
domainSettingsService.blockedInteractionsUris$ = of({});
|
||||||
scriptInjectorService = new BrowserScriptInjectorService(
|
scriptInjectorService = new BrowserScriptInjectorService(
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
safeProvider({
|
safeProvider({
|
||||||
provide: DomainSettingsService,
|
provide: DomainSettingsService,
|
||||||
useClass: DefaultDomainSettingsService,
|
useClass: DefaultDomainSettingsService,
|
||||||
deps: [StateProvider],
|
deps: [StateProvider, PolicyService, AccountService],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: AbstractStorageService,
|
provide: AbstractStorageService,
|
||||||
|
|||||||
@@ -562,7 +562,11 @@ export class ServiceContainer {
|
|||||||
this.authService,
|
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);
|
this.fileUploadService = new FileUploadService(this.logService, this.apiService);
|
||||||
|
|
||||||
|
|||||||
@@ -546,7 +546,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
safeProvider({
|
safeProvider({
|
||||||
provide: DomainSettingsService,
|
provide: DomainSettingsService,
|
||||||
useClass: DefaultDomainSettingsService,
|
useClass: DefaultDomainSettingsService,
|
||||||
deps: [StateProvider],
|
deps: [StateProvider, PolicyServiceAbstraction, AccountService],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: CipherServiceAbstraction,
|
provide: CipherServiceAbstraction,
|
||||||
|
|||||||
@@ -17,5 +17,6 @@ export enum PolicyType {
|
|||||||
FreeFamiliesSponsorshipPolicy = 13, // Disables free families plan for organization
|
FreeFamiliesSponsorshipPolicy = 13, // Disables free families plan for organization
|
||||||
RemoveUnlockWithPin = 14, // Do not allow members to unlock their account with a PIN.
|
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
|
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
|
AutotypeDefaultSetting = 17, // Sets the default autotype setting for desktop app
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import { mock } from "jest-mock-extended";
|
||||||
import { firstValueFrom, of } from "rxjs";
|
import { firstValueFrom, of } from "rxjs";
|
||||||
|
|
||||||
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
|
|
||||||
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";
|
||||||
@@ -10,6 +13,7 @@ describe("DefaultDomainSettingsService", () => {
|
|||||||
let domainSettingsService: DomainSettingsService;
|
let domainSettingsService: DomainSettingsService;
|
||||||
const mockUserId = Utils.newGuid() as UserId;
|
const mockUserId = Utils.newGuid() as UserId;
|
||||||
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
||||||
|
const policyService = mock<PolicyService>();
|
||||||
const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService);
|
const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService);
|
||||||
|
|
||||||
const mockEquivalentDomains = [
|
const mockEquivalentDomains = [
|
||||||
@@ -19,7 +23,11 @@ describe("DefaultDomainSettingsService", () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
|
domainSettingsService = new DefaultDomainSettingsService(
|
||||||
|
fakeStateProvider,
|
||||||
|
policyService,
|
||||||
|
accountService,
|
||||||
|
);
|
||||||
|
|
||||||
jest.spyOn(domainSettingsService, "getUrlEquivalentDomains");
|
jest.spyOn(domainSettingsService, "getUrlEquivalentDomains");
|
||||||
domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains);
|
domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains);
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
// 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 { 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 {
|
import {
|
||||||
NeverDomains,
|
NeverDomains,
|
||||||
@@ -87,6 +93,18 @@ export abstract class DomainSettingsService {
|
|||||||
defaultUriMatchStrategy$: Observable<UriMatchStrategySetting>;
|
defaultUriMatchStrategy$: Observable<UriMatchStrategySetting>;
|
||||||
setDefaultUriMatchStrategy: (newValue: UriMatchStrategySetting) => Promise<void>;
|
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
|
* 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>;
|
private defaultUriMatchStrategyState: ActiveUserState<UriMatchStrategySetting>;
|
||||||
readonly defaultUriMatchStrategy$: Observable<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.showFaviconsState = this.stateProvider.getGlobal(SHOW_FAVICONS);
|
||||||
this.showFavicons$ = this.showFaviconsState.state$.pipe(map((x) => x ?? true));
|
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(
|
this.defaultUriMatchStrategy$ = this.defaultUriMatchStrategyState.state$.pipe(
|
||||||
map((x) => x ?? UriMatchStrategy.Domain),
|
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> {
|
async setShowFavicons(newValue: boolean): Promise<void> {
|
||||||
|
|||||||
@@ -634,7 +634,9 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
const equivalentDomains = await firstValueFrom(
|
const equivalentDomains = await firstValueFrom(
|
||||||
this.domainSettingsService.getUrlEquivalentDomains(url),
|
this.domainSettingsService.getUrlEquivalentDomains(url),
|
||||||
);
|
);
|
||||||
defaultMatch ??= await firstValueFrom(this.domainSettingsService.defaultUriMatchStrategy$);
|
defaultMatch ??= await firstValueFrom(
|
||||||
|
this.domainSettingsService.resolvedDefaultUriMatchStrategy$,
|
||||||
|
);
|
||||||
|
|
||||||
const archiveFeatureEnabled = await this.configService.getFeatureFlag(
|
const archiveFeatureEnabled = await this.configService.getFeatureFlag(
|
||||||
FeatureFlag.PM19148_InnovationArchive,
|
FeatureFlag.PM19148_InnovationArchive,
|
||||||
|
|||||||
@@ -215,7 +215,9 @@ export default {
|
|||||||
{
|
{
|
||||||
provide: DomainSettingsService,
|
provide: DomainSettingsService,
|
||||||
useValue: {
|
useValue: {
|
||||||
defaultUriMatchStrategy$: new BehaviorSubject(UriMatchStrategy.StartsWith),
|
resolvedDefaultUriMatchStrategy$: new BehaviorSubject(UriMatchStrategy.StartsWith),
|
||||||
|
defaultUriMatchStrategy$: new BehaviorSubject(UriMatchStrategy.Domain),
|
||||||
|
defaultUriMatchStrategyPolicy$: new BehaviorSubject(null),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ describe("AutofillOptionsComponent", () => {
|
|||||||
liveAnnouncer = mock<LiveAnnouncer>();
|
liveAnnouncer = mock<LiveAnnouncer>();
|
||||||
platformUtilsService = mock<PlatformUtilsService>();
|
platformUtilsService = mock<PlatformUtilsService>();
|
||||||
domainSettingsService = mock<DomainSettingsService>();
|
domainSettingsService = mock<DomainSettingsService>();
|
||||||
domainSettingsService.defaultUriMatchStrategy$ = new BehaviorSubject(null);
|
domainSettingsService.resolvedDefaultUriMatchStrategy$ = new BehaviorSubject(null);
|
||||||
|
|
||||||
autofillSettingsService = mock<AutofillSettingsServiceAbstraction>();
|
autofillSettingsService = mock<AutofillSettingsServiceAbstraction>();
|
||||||
autofillSettingsService.autofillOnPageLoadDefault$ = new BehaviorSubject(false);
|
autofillSettingsService.autofillOnPageLoadDefault$ = new BehaviorSubject(false);
|
||||||
|
|||||||
@@ -76,10 +76,12 @@ export class AutofillOptionsComponent implements OnInit {
|
|||||||
return this.cipherFormContainer.config.mode === "partial-edit";
|
return this.cipherFormContainer.config.mode === "partial-edit";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected defaultMatchDetection$ = this.domainSettingsService.defaultUriMatchStrategy$.pipe(
|
protected defaultMatchDetection$ =
|
||||||
// The default match detection should only be shown when used on the browser
|
this.domainSettingsService.resolvedDefaultUriMatchStrategy$.pipe(
|
||||||
filter(() => this.platformUtilsService.getClientType() == ClientType.Browser),
|
// The default match detection should only be shown when used on the browser
|
||||||
);
|
filter(() => this.platformUtilsService.getClientType() == ClientType.Browser),
|
||||||
|
);
|
||||||
|
|
||||||
protected autofillOnPageLoadEnabled$ = this.autofillSettingsService.autofillOnPageLoad$;
|
protected autofillOnPageLoadEnabled$ = this.autofillSettingsService.autofillOnPageLoad$;
|
||||||
|
|
||||||
protected autofillOptions: { label: string; value: boolean | null }[] = [
|
protected autofillOptions: { label: string; value: boolean | null }[] = [
|
||||||
|
|||||||
Reference in New Issue
Block a user