1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 13:53:34 +00:00

[PM-18566] Wire up vNextPolicyService for Clients (#13678)

* wire up vNext impl

* wire up vNextPolicyService for browser

* wire up vNextPolicyService for desktop

* wire up vNextPolicyService for cli

* fix test

* fix missed caller

* cleanup

* fix missing property assignment

* fix QA bug for PM-19205

* fix QA bug for PM-19206

* fix QA bug for pm-19228

* cleanup
This commit is contained in:
Brandon Treston
2025-03-25 11:30:47 -04:00
committed by GitHub
parent a9fd16968f
commit 0fd01ed7ee
84 changed files with 723 additions and 1246 deletions

View File

@@ -99,7 +99,7 @@ describe("AccountSecurityComponent", () => {
it("pin enabled when RemoveUnlockWithPin policy is not set", async () => { it("pin enabled when RemoveUnlockWithPin policy is not set", async () => {
// @ts-strict-ignore // @ts-strict-ignore
policyService.get$.mockReturnValue(of(null)); policyService.policiesByType$.mockReturnValue(of([null]));
await component.ngOnInit(); await component.ngOnInit();
@@ -111,7 +111,7 @@ describe("AccountSecurityComponent", () => {
policy.type = PolicyType.RemoveUnlockWithPin; policy.type = PolicyType.RemoveUnlockWithPin;
policy.enabled = false; policy.enabled = false;
policyService.get$.mockReturnValue(of(policy)); policyService.policiesByType$.mockReturnValue(of([policy]));
await component.ngOnInit(); await component.ngOnInit();
@@ -129,7 +129,7 @@ describe("AccountSecurityComponent", () => {
policy.type = PolicyType.RemoveUnlockWithPin; policy.type = PolicyType.RemoveUnlockWithPin;
policy.enabled = true; policy.enabled = true;
policyService.get$.mockReturnValue(of(policy)); policyService.policiesByType$.mockReturnValue(of([policy]));
await component.ngOnInit(); await component.ngOnInit();
@@ -143,7 +143,7 @@ describe("AccountSecurityComponent", () => {
it("pin visible when RemoveUnlockWithPin policy is not set", async () => { it("pin visible when RemoveUnlockWithPin policy is not set", async () => {
// @ts-strict-ignore // @ts-strict-ignore
policyService.get$.mockReturnValue(of(null)); policyService.policiesByType$.mockReturnValue(of([null]));
await component.ngOnInit(); await component.ngOnInit();
fixture.detectChanges(); fixture.detectChanges();
@@ -158,7 +158,7 @@ describe("AccountSecurityComponent", () => {
policy.type = PolicyType.RemoveUnlockWithPin; policy.type = PolicyType.RemoveUnlockWithPin;
policy.enabled = false; policy.enabled = false;
policyService.get$.mockReturnValue(of(policy)); policyService.policiesByType$.mockReturnValue(of([policy]));
await component.ngOnInit(); await component.ngOnInit();
fixture.detectChanges(); fixture.detectChanges();
@@ -173,7 +173,7 @@ describe("AccountSecurityComponent", () => {
policy.type = PolicyType.RemoveUnlockWithPin; policy.type = PolicyType.RemoveUnlockWithPin;
policy.enabled = true; policy.enabled = true;
policyService.get$.mockReturnValue(of(policy)); policyService.policiesByType$.mockReturnValue(of([policy]));
pinServiceAbstraction.isPinSet.mockResolvedValue(true); pinServiceAbstraction.isPinSet.mockResolvedValue(true);
@@ -190,7 +190,7 @@ describe("AccountSecurityComponent", () => {
policy.type = PolicyType.RemoveUnlockWithPin; policy.type = PolicyType.RemoveUnlockWithPin;
policy.enabled = true; policy.enabled = true;
policyService.get$.mockReturnValue(of(policy)); policyService.policiesByType$.mockReturnValue(of([policy]));
await component.ngOnInit(); await component.ngOnInit();
fixture.detectChanges(); fixture.detectChanges();

View File

@@ -27,8 +27,10 @@ import { FingerprintDialogComponent, VaultTimeoutInputComponent } from "@bitward
import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { getFirstPolicy } from "@bitwarden/common/admin-console/services/policy/default-policy.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { DeviceType } from "@bitwarden/common/enums"; import { DeviceType } from "@bitwarden/common/enums";
import { import {
VaultTimeout, VaultTimeout,
@@ -152,8 +154,14 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
const hasMasterPassword = await this.userVerificationService.hasMasterPassword(); const hasMasterPassword = await this.userVerificationService.hasMasterPassword();
this.showMasterPasswordOnClientRestartOption = hasMasterPassword; this.showMasterPasswordOnClientRestartOption = hasMasterPassword;
const maximumVaultTimeoutPolicy = this.policyService.get$(PolicyType.MaximumVaultTimeout); const maximumVaultTimeoutPolicy = this.accountService.activeAccount$.pipe(
if ((await firstValueFrom(this.policyService.get$(PolicyType.MaximumVaultTimeout))) != null) { getUserId,
switchMap((userId) =>
this.policyService.policiesByType$(PolicyType.MaximumVaultTimeout, userId),
),
getFirstPolicy,
);
if ((await firstValueFrom(maximumVaultTimeoutPolicy)) != null) {
this.hasVaultTimeoutPolicy = true; this.hasVaultTimeoutPolicy = true;
} }
@@ -195,7 +203,12 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
timeout = VaultTimeoutStringType.OnRestart; timeout = VaultTimeoutStringType.OnRestart;
} }
this.pinEnabled$ = this.policyService.get$(PolicyType.RemoveUnlockWithPin).pipe( this.pinEnabled$ = this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.policiesByType$(PolicyType.RemoveUnlockWithPin, userId),
),
getFirstPolicy,
map((policy) => { map((policy) => {
return policy == null || !policy.enabled; return policy == null || !policy.enabled;
}), }),

View File

@@ -8,6 +8,9 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { BrowserApi } from "../../platform/browser/browser-api"; import { BrowserApi } from "../../platform/browser/browser-api";
import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service"; import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service";
@@ -35,10 +38,12 @@ describe("AutoSubmitLoginBackground", () => {
let configService: MockProxy<ConfigService>; let configService: MockProxy<ConfigService>;
let platformUtilsService: MockProxy<PlatformUtilsService>; let platformUtilsService: MockProxy<PlatformUtilsService>;
let policyDetails: MockProxy<Policy>; let policyDetails: MockProxy<Policy>;
let automaticAppLogInPolicy$: BehaviorSubject<Policy>; let automaticAppLogInPolicy$: BehaviorSubject<Policy[]>;
let policyAppliesToActiveUser$: BehaviorSubject<boolean>; let policyAppliesToUser$: BehaviorSubject<boolean>;
let policyService: MockProxy<PolicyService>; let policyService: MockProxy<PolicyService>;
let autoSubmitLoginBackground: AutoSubmitLoginBackground; let autoSubmitLoginBackground: AutoSubmitLoginBackground;
let accountService: FakeAccountService;
const mockUserId = Utils.newGuid() as UserId;
const validIpdUrl1 = "https://example.com"; const validIpdUrl1 = "https://example.com";
const validIpdUrl2 = "https://subdomain.example3.com"; const validIpdUrl2 = "https://subdomain.example3.com";
const validAutoSubmitHost = "some-valid-url.com"; const validAutoSubmitHost = "some-valid-url.com";
@@ -61,12 +66,13 @@ describe("AutoSubmitLoginBackground", () => {
idpHost: `${validIpdUrl1} , https://example2.com/some/sub-route ,${validIpdUrl2}, [invalidValue] ,,`, idpHost: `${validIpdUrl1} , https://example2.com/some/sub-route ,${validIpdUrl2}, [invalidValue] ,,`,
}, },
}); });
automaticAppLogInPolicy$ = new BehaviorSubject<Policy>(policyDetails); automaticAppLogInPolicy$ = new BehaviorSubject<Policy[]>([policyDetails]);
policyAppliesToActiveUser$ = new BehaviorSubject<boolean>(true); policyAppliesToUser$ = new BehaviorSubject<boolean>(true);
policyService = mock<PolicyService>({ policyService = mock<PolicyService>({
get$: jest.fn().mockReturnValue(automaticAppLogInPolicy$), policiesByType$: jest.fn().mockReturnValue(automaticAppLogInPolicy$),
policyAppliesToActiveUser$: jest.fn().mockReturnValue(policyAppliesToActiveUser$), policyAppliesToUser$: jest.fn().mockReturnValue(policyAppliesToUser$),
}); });
accountService = mockAccountServiceWith(mockUserId);
autoSubmitLoginBackground = new AutoSubmitLoginBackground( autoSubmitLoginBackground = new AutoSubmitLoginBackground(
logService, logService,
autofillService, autofillService,
@@ -75,6 +81,7 @@ describe("AutoSubmitLoginBackground", () => {
configService, configService,
platformUtilsService, platformUtilsService,
policyService, policyService,
accountService,
); );
}); });
@@ -84,7 +91,7 @@ describe("AutoSubmitLoginBackground", () => {
describe("when the AutoSubmitLoginBackground feature is disabled", () => { describe("when the AutoSubmitLoginBackground feature is disabled", () => {
it("destroys all event listeners when the AutomaticAppLogIn policy is not enabled", async () => { it("destroys all event listeners when the AutomaticAppLogIn policy is not enabled", async () => {
automaticAppLogInPolicy$.next(mock<Policy>({ ...policyDetails, enabled: false })); automaticAppLogInPolicy$.next([mock<Policy>({ ...policyDetails, enabled: false })]);
await autoSubmitLoginBackground.init(); await autoSubmitLoginBackground.init();
@@ -92,7 +99,7 @@ describe("AutoSubmitLoginBackground", () => {
}); });
it("destroys all event listeners when the AutomaticAppLogIn policy does not apply to the current user", async () => { it("destroys all event listeners when the AutomaticAppLogIn policy does not apply to the current user", async () => {
policyAppliesToActiveUser$.next(false); policyAppliesToUser$.next(false);
await autoSubmitLoginBackground.init(); await autoSubmitLoginBackground.init();
@@ -100,7 +107,7 @@ describe("AutoSubmitLoginBackground", () => {
}); });
it("destroys all event listeners when the idpHost is not specified in the AutomaticAppLogIn policy", async () => { it("destroys all event listeners when the idpHost is not specified in the AutomaticAppLogIn policy", async () => {
automaticAppLogInPolicy$.next(mock<Policy>({ ...policyDetails, data: { idpHost: "" } })); automaticAppLogInPolicy$.next([mock<Policy>({ ...policyDetails, data: { idpHost: "" } })]);
await autoSubmitLoginBackground.init(); await autoSubmitLoginBackground.init();
@@ -264,6 +271,7 @@ describe("AutoSubmitLoginBackground", () => {
configService, configService,
platformUtilsService, platformUtilsService,
policyService, policyService,
accountService,
); );
jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(tab); jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(tab);
}); });

View File

@@ -1,12 +1,15 @@
// 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 { firstValueFrom } from "rxjs"; import { firstValueFrom, switchMap } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { getFirstPolicy } from "@bitwarden/common/admin-console/services/policy/default-policy.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
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 { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -42,6 +45,7 @@ export class AutoSubmitLoginBackground implements AutoSubmitLoginBackgroundAbstr
private configService: ConfigService, private configService: ConfigService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private policyService: PolicyService, private policyService: PolicyService,
private accountService: AccountService,
) { ) {
this.isSafariBrowser = this.platformUtilsService.isSafari(); this.isSafariBrowser = this.platformUtilsService.isSafari();
} }
@@ -56,8 +60,14 @@ export class AutoSubmitLoginBackground implements AutoSubmitLoginBackgroundAbstr
FeatureFlag.IdpAutoSubmitLogin, FeatureFlag.IdpAutoSubmitLogin,
); );
if (featureFlagEnabled) { if (featureFlagEnabled) {
this.policyService this.accountService.activeAccount$
.get$(PolicyType.AutomaticAppLogIn) .pipe(
getUserId,
switchMap((userId) =>
this.policyService.policiesByType$(PolicyType.AutomaticAppLogIn, userId),
),
getFirstPolicy,
)
.subscribe(this.handleAutoSubmitLoginPolicySubscription.bind(this)); .subscribe(this.handleAutoSubmitLoginPolicySubscription.bind(this));
} }
} }
@@ -86,7 +96,12 @@ export class AutoSubmitLoginBackground implements AutoSubmitLoginBackgroundAbstr
*/ */
private applyPolicyToActiveUser = async (policy: Policy) => { private applyPolicyToActiveUser = async (policy: Policy) => {
const policyAppliesToUser = await firstValueFrom( const policyAppliesToUser = await firstValueFrom(
this.policyService.policyAppliesToActiveUser$(PolicyType.AutomaticAppLogIn), this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.AutomaticAppLogIn, userId),
),
),
); );
if (!policyAppliesToUser) { if (!policyAppliesToUser) {

View File

@@ -2,7 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom } from "rxjs"; import { BehaviorSubject, firstValueFrom } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { DefaultPolicyService } from "@bitwarden/common/admin-console/services/policy/default-policy.service";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service";
@@ -51,7 +51,7 @@ describe("NotificationBackground", () => {
const cipherService = mock<CipherService>(); const cipherService = mock<CipherService>();
let activeAccountStatusMock$: BehaviorSubject<AuthenticationStatus>; let activeAccountStatusMock$: BehaviorSubject<AuthenticationStatus>;
let authService: MockProxy<AuthService>; let authService: MockProxy<AuthService>;
const policyService = mock<PolicyService>(); const policyService = mock<DefaultPolicyService>();
const folderService = mock<FolderService>(); const folderService = mock<FolderService>();
const userNotificationSettingsService = mock<UserNotificationSettingsService>(); const userNotificationSettingsService = mock<UserNotificationSettingsService>();
const domainSettingsService = mock<DomainSettingsService>(); const domainSettingsService = mock<DomainSettingsService>();

View File

@@ -1,6 +1,6 @@
// 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 { firstValueFrom } from "rxjs"; import { firstValueFrom, switchMap } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
@@ -8,7 +8,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
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 { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { getOptionalUserId, getUserId } from "@bitwarden/common/auth/services/account.service";
import { import {
ExtensionCommand, ExtensionCommand,
ExtensionCommandType, ExtensionCommandType,
@@ -743,7 +743,12 @@ export default class NotificationBackground {
private async removeIndividualVault(): Promise<boolean> { private async removeIndividualVault(): Promise<boolean> {
return await firstValueFrom( return await firstValueFrom(
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId),
),
),
); );
} }

View File

@@ -26,8 +26,8 @@ import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abs
import { InternalPolicyService as InternalPolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { InternalPolicyService as InternalPolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { DefaultOrganizationService } from "@bitwarden/common/admin-console/services/organization/default-organization.service"; import { DefaultOrganizationService } from "@bitwarden/common/admin-console/services/organization/default-organization.service";
import { DefaultPolicyService } from "@bitwarden/common/admin-console/services/policy/default-policy.service";
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
@@ -685,7 +685,7 @@ export default class MainBackground {
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
this.organizationService = new DefaultOrganizationService(this.stateProvider); this.organizationService = new DefaultOrganizationService(this.stateProvider);
this.policyService = new PolicyService(this.stateProvider, this.organizationService); this.policyService = new DefaultPolicyService(this.stateProvider, this.organizationService);
this.vaultTimeoutSettingsService = new DefaultVaultTimeoutSettingsService( this.vaultTimeoutSettingsService = new DefaultVaultTimeoutSettingsService(
this.accountService, this.accountService,
@@ -728,9 +728,14 @@ export default class MainBackground {
this.autofillSettingsService = new AutofillSettingsService( this.autofillSettingsService = new AutofillSettingsService(
this.stateProvider, this.stateProvider,
this.policyService, this.policyService,
this.accountService,
); );
this.badgeSettingsService = new BadgeSettingsService(this.stateProvider); this.badgeSettingsService = new BadgeSettingsService(this.stateProvider);
this.policyApiService = new PolicyApiService(this.policyService, this.apiService); this.policyApiService = new PolicyApiService(
this.policyService,
this.apiService,
this.accountService,
);
this.keyConnectorService = new KeyConnectorService( this.keyConnectorService = new KeyConnectorService(
this.accountService, this.accountService,
this.masterPasswordService, this.masterPasswordService,
@@ -1202,6 +1207,7 @@ export default class MainBackground {
this.configService, this.configService,
this.platformUtilsService, this.platformUtilsService,
this.policyService, this.policyService,
this.accountService,
); );
const contextMenuClickedHandler = new ContextMenuClickedHandler( const contextMenuClickedHandler = new ContextMenuClickedHandler(

View File

@@ -51,7 +51,7 @@ describe("FamiliesPolicyService", () => {
organizationService.organizations$.mockReturnValue(of(organizations)); organizationService.organizations$.mockReturnValue(of(organizations));
const policies = [{ organizationId: "org1", enabled: true }] as Policy[]; const policies = [{ organizationId: "org1", enabled: true }] as Policy[];
policyService.getAll$.mockReturnValue(of(policies)); policyService.policiesByType$.mockReturnValue(of(policies));
const result = await firstValueFrom(service.isFreeFamilyPolicyEnabled$()); const result = await firstValueFrom(service.isFreeFamilyPolicyEnabled$());
expect(result).toBe(true); expect(result).toBe(true);
@@ -64,7 +64,7 @@ describe("FamiliesPolicyService", () => {
organizationService.organizations$.mockReturnValue(of(organizations)); organizationService.organizations$.mockReturnValue(of(organizations));
const policies = [{ organizationId: "org1", enabled: false }] as Policy[]; const policies = [{ organizationId: "org1", enabled: false }] as Policy[];
policyService.getAll$.mockReturnValue(of(policies)); policyService.policiesByType$.mockReturnValue(of(policies));
const result = await firstValueFrom(service.isFreeFamilyPolicyEnabled$()); const result = await firstValueFrom(service.isFreeFamilyPolicyEnabled$());
expect(result).toBe(false); expect(result).toBe(false);

View File

@@ -47,7 +47,7 @@ export class FamiliesPolicyService {
map((organizations) => organizations.find((org) => org.canManageSponsorships)?.id), map((organizations) => organizations.find((org) => org.canManageSponsorships)?.id),
switchMap((enterpriseOrgId) => switchMap((enterpriseOrgId) =>
this.policyService this.policyService
.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy, userId) .policiesByType$(PolicyType.FreeFamiliesSponsorshipPolicy, userId)
.pipe( .pipe(
map( map(
(policies) => (policies) =>

View File

@@ -482,7 +482,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({ safeProvider({
provide: AutofillSettingsServiceAbstraction, provide: AutofillSettingsServiceAbstraction,
useClass: AutofillSettingsService, useClass: AutofillSettingsService,
deps: [StateProvider, PolicyService], deps: [StateProvider, PolicyService, AccountService],
}), }),
safeProvider({ safeProvider({
provide: UserNotificationSettingsServiceAbstraction, provide: UserNotificationSettingsServiceAbstraction,

View File

@@ -64,7 +64,7 @@ describe("SendV2Component", () => {
}); });
policyService = mock<PolicyService>(); policyService = mock<PolicyService>();
policyService.policyAppliesToActiveUser$.mockReturnValue(of(true)); // Return `true` by default policyService.policyAppliesToUser$.mockReturnValue(of(true)); // Return `true` by default
sendListFiltersService = new SendListFiltersService(mock(), new FormBuilder()); sendListFiltersService = new SendListFiltersService(mock(), new FormBuilder());

View File

@@ -2,11 +2,13 @@ import { CommonModule } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { RouterLink } from "@angular/router"; import { RouterLink } from "@angular/router";
import { combineLatest } from "rxjs"; import { combineLatest, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { ButtonModule, CalloutModule, Icons, NoItemsModule } from "@bitwarden/components"; import { ButtonModule, CalloutModule, Icons, NoItemsModule } from "@bitwarden/components";
import { import {
@@ -66,6 +68,7 @@ export class SendV2Component implements OnInit, OnDestroy {
protected sendItemsService: SendItemsService, protected sendItemsService: SendItemsService,
protected sendListFiltersService: SendListFiltersService, protected sendListFiltersService: SendListFiltersService,
private policyService: PolicyService, private policyService: PolicyService,
private accountService: AccountService,
) { ) {
combineLatest([ combineLatest([
this.sendItemsService.emptyList$, this.sendItemsService.emptyList$,
@@ -93,9 +96,14 @@ export class SendV2Component implements OnInit, OnDestroy {
this.listState = null; this.listState = null;
}); });
this.policyService this.accountService.activeAccount$
.policyAppliesToActiveUser$(PolicyType.DisableSend) .pipe(
.pipe(takeUntilDestroyed()) getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.DisableSend, userId),
),
takeUntilDestroyed(),
)
.subscribe((sendsDisabled) => { .subscribe((sendsDisabled) => {
this.sendsDisabled = sendsDisabled; this.sendsDisabled = sendsDisabled;
}); });

View File

@@ -36,7 +36,7 @@ describe("VaultPopupListFiltersService", () => {
let folderViews$ = new BehaviorSubject([]); let folderViews$ = new BehaviorSubject([]);
const cipherViews$ = new BehaviorSubject({}); const cipherViews$ = new BehaviorSubject({});
let decryptedCollections$ = new BehaviorSubject<CollectionView[]>([]); let decryptedCollections$ = new BehaviorSubject<CollectionView[]>([]);
const policyAppliesToActiveUser$ = new BehaviorSubject<boolean>(false); const policyAppliesToUser$ = new BehaviorSubject<boolean>(false);
let viewCacheService: { let viewCacheService: {
signal: jest.Mock; signal: jest.Mock;
mockSignal: WritableSignal<CachedFilterState>; mockSignal: WritableSignal<CachedFilterState>;
@@ -65,7 +65,7 @@ describe("VaultPopupListFiltersService", () => {
} as I18nService; } as I18nService;
const policyService = { const policyService = {
policyAppliesToActiveUser$: jest.fn(() => policyAppliesToActiveUser$), policyAppliesToUser$: jest.fn(() => policyAppliesToUser$),
}; };
const state$ = new BehaviorSubject<boolean>(false); const state$ = new BehaviorSubject<boolean>(false);
@@ -75,8 +75,8 @@ describe("VaultPopupListFiltersService", () => {
_memberOrganizations$ = new BehaviorSubject<Organization[]>([]); // Fresh instance per test _memberOrganizations$ = new BehaviorSubject<Organization[]>([]); // Fresh instance per test
folderViews$ = new BehaviorSubject([]); // Fresh instance per test folderViews$ = new BehaviorSubject([]); // Fresh instance per test
decryptedCollections$ = new BehaviorSubject<CollectionView[]>([]); // Fresh instance per test decryptedCollections$ = new BehaviorSubject<CollectionView[]>([]); // Fresh instance per test
policyAppliesToActiveUser$.next(false); policyAppliesToUser$.next(false);
policyService.policyAppliesToActiveUser$.mockClear(); policyService.policyAppliesToUser$.mockClear();
const accountService = mockAccountServiceWith("userId" as UserId); const accountService = mockAccountServiceWith("userId" as UserId);
const mockCachedSignal = createMockSignal<CachedFilterState>({}); const mockCachedSignal = createMockSignal<CachedFilterState>({});
@@ -196,14 +196,15 @@ describe("VaultPopupListFiltersService", () => {
}); });
describe("PersonalOwnership policy", () => { describe("PersonalOwnership policy", () => {
it('calls policyAppliesToActiveUser$ with "PersonalOwnership"', () => { it('calls policyAppliesToUser$ with "PersonalOwnership"', () => {
expect(policyService.policyAppliesToActiveUser$).toHaveBeenCalledWith( expect(policyService.policyAppliesToUser$).toHaveBeenCalledWith(
PolicyType.PersonalOwnership, PolicyType.PersonalOwnership,
"userId",
); );
}); });
it("returns an empty array when the policy applies and there is a single organization", (done) => { it("returns an empty array when the policy applies and there is a single organization", (done) => {
policyAppliesToActiveUser$.next(true); policyAppliesToUser$.next(true);
_memberOrganizations$.next([ _memberOrganizations$.next([
{ name: "bobby's org", id: "1234-3323-23223" }, { name: "bobby's org", id: "1234-3323-23223" },
] as Organization[]); ] as Organization[]);
@@ -215,7 +216,7 @@ describe("VaultPopupListFiltersService", () => {
}); });
it('adds "myVault" when the policy does not apply and there are multiple organizations', (done) => { it('adds "myVault" when the policy does not apply and there are multiple organizations', (done) => {
policyAppliesToActiveUser$.next(false); policyAppliesToUser$.next(false);
const orgs = [ const orgs = [
{ name: "bobby's org", id: "1234-3323-23223" }, { name: "bobby's org", id: "1234-3323-23223" },
{ name: "alice's org", id: "2223-4343-99888" }, { name: "alice's org", id: "2223-4343-99888" },
@@ -234,7 +235,7 @@ describe("VaultPopupListFiltersService", () => {
}); });
it('does not add "myVault" the policy applies and there are multiple organizations', (done) => { it('does not add "myVault" the policy applies and there are multiple organizations', (done) => {
policyAppliesToActiveUser$.next(true); policyAppliesToUser$.next(true);
const orgs = [ const orgs = [
{ name: "bobby's org", id: "1234-3323-23223" }, { name: "bobby's org", id: "1234-3323-23223" },
{ name: "alice's org", id: "2223-3242-99888" }, { name: "alice's org", id: "2223-3242-99888" },
@@ -679,7 +680,7 @@ function createSeededVaultPopupListFiltersService(
} as any; } as any;
const policyServiceMock = { const policyServiceMock = {
policyAppliesToActiveUser$: jest.fn(() => new BehaviorSubject(false)), policyAppliesToUser$: jest.fn(() => new BehaviorSubject(false)),
} as any; } as any;
const stateProviderMock = { const stateProviderMock = {

View File

@@ -8,7 +8,6 @@ import {
filter, filter,
map, map,
Observable, Observable,
of,
shareReplay, shareReplay,
startWith, startWith,
switchMap, switchMap,
@@ -23,6 +22,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ProductTierType } from "@bitwarden/common/billing/enums"; import { ProductTierType } from "@bitwarden/common/billing/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
@@ -288,68 +288,70 @@ export class VaultPopupListFiltersService {
/** /**
* Organization array structured to be directly passed to `ChipSelectComponent` * Organization array structured to be directly passed to `ChipSelectComponent`
*/ */
organizations$: Observable<ChipSelectOption<Organization>[]> = combineLatest([
organizations$: Observable<ChipSelectOption<Organization>[]> =
this.accountService.activeAccount$.pipe( this.accountService.activeAccount$.pipe(
switchMap((account) => getUserId,
account === null ? of([]) : this.organizationService.memberOrganizations$(account.id), switchMap((userId) =>
combineLatest([
this.organizationService.memberOrganizations$(userId),
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId),
]),
), ),
), map(([orgs, personalOwnershipApplies]): [Organization[], boolean] => [
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), orgs.sort(Utils.getSortFunction(this.i18nService, "name")),
]).pipe( personalOwnershipApplies,
map(([orgs, personalOwnershipApplies]): [Organization[], boolean] => [ ]),
orgs.sort(Utils.getSortFunction(this.i18nService, "name")), map(([orgs, personalOwnershipApplies]) => {
personalOwnershipApplies, // When there are no organizations return an empty array,
]), // resulting in the org filter being hidden
map(([orgs, personalOwnershipApplies]) => { if (!orgs.length) {
// When there are no organizations return an empty array, return [];
// resulting in the org filter being hidden }
if (!orgs.length) {
return [];
}
// When there is only one organization and personal ownership policy applies, // When there is only one organization and personal ownership policy applies,
// return an empty array, resulting in the org filter being hidden // return an empty array, resulting in the org filter being hidden
if (orgs.length === 1 && personalOwnershipApplies) { if (orgs.length === 1 && personalOwnershipApplies) {
return []; return [];
} }
const myVaultOrg: ChipSelectOption<Organization>[] = []; const myVaultOrg: ChipSelectOption<Organization>[] = [];
// Only add "My vault" if personal ownership policy does not apply // Only add "My vault" if personal ownership policy does not apply
if (!personalOwnershipApplies) { if (!personalOwnershipApplies) {
myVaultOrg.push({ myVaultOrg.push({
value: { id: MY_VAULT_ID } as Organization, value: { id: MY_VAULT_ID } as Organization,
label: this.i18nService.t("myVault"), label: this.i18nService.t("myVault"),
icon: "bwi-user", icon: "bwi-user",
}); });
} }
return [ return [
...myVaultOrg, ...myVaultOrg,
...orgs.map((org) => { ...orgs.map((org) => {
let icon = "bwi-business"; let icon = "bwi-business";
if (!org.enabled) { if (!org.enabled) {
// Show a warning icon if the organization is deactivated // Show a warning icon if the organization is deactivated
icon = "bwi-exclamation-triangle tw-text-danger"; icon = "bwi-exclamation-triangle tw-text-danger";
} else if ( } else if (
org.productTierType === ProductTierType.Families || org.productTierType === ProductTierType.Families ||
org.productTierType === ProductTierType.Free org.productTierType === ProductTierType.Free
) { ) {
// Show a family icon if the organization is a family or free org // Show a family icon if the organization is a family or free org
icon = "bwi-family"; icon = "bwi-family";
} }
return { return {
value: org, value: org,
label: org.name, label: org.name,
icon, icon,
}; };
}), }),
]; ];
}), }),
shareReplay({ refCount: true, bufferSize: 1 }), shareReplay({ refCount: true, bufferSize: 1 }),
); );
/** /**
* Folder array structured to be directly passed to `ChipSelectComponent` * Folder array structured to be directly passed to `ChipSelectComponent`

View File

@@ -5,7 +5,7 @@ import * as http from "http";
import { OptionValues } from "commander"; import { OptionValues } from "commander";
import * as inquirer from "inquirer"; import * as inquirer from "inquirer";
import Separator from "inquirer/lib/objects/separator"; import Separator from "inquirer/lib/objects/separator";
import { firstValueFrom, map } from "rxjs"; import { firstValueFrom, map, switchMap } from "rxjs";
import { import {
LoginStrategyServiceAbstraction, LoginStrategyServiceAbstraction,
@@ -29,6 +29,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request"; import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ClientType } from "@bitwarden/common/enums"; import { ClientType } from "@bitwarden/common/enums";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
@@ -555,7 +556,10 @@ export class LoginCommand {
); );
const enforcedPolicyOptions = await firstValueFrom( const enforcedPolicyOptions = await firstValueFrom(
this.policyService.masterPasswordPolicyOptions$(), this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)),
),
); );
// Verify master password meets policy requirements // Verify master password meets policy requirements

View File

@@ -28,8 +28,8 @@ import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abs
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
import { DefaultOrganizationService } from "@bitwarden/common/admin-console/services/organization/default-organization.service"; import { DefaultOrganizationService } from "@bitwarden/common/admin-console/services/organization/default-organization.service";
import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service"; import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service";
import { DefaultPolicyService } from "@bitwarden/common/admin-console/services/policy/default-policy.service";
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { ProviderApiService } from "@bitwarden/common/admin-console/services/provider/provider-api.service"; import { ProviderApiService } from "@bitwarden/common/admin-console/services/provider/provider-api.service";
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -237,7 +237,7 @@ export class ServiceContainer {
cryptoFunctionService: NodeCryptoFunctionService; cryptoFunctionService: NodeCryptoFunctionService;
encryptService: EncryptServiceImplementation; encryptService: EncryptServiceImplementation;
authService: AuthService; authService: AuthService;
policyService: PolicyService; policyService: DefaultPolicyService;
policyApiService: PolicyApiServiceAbstraction; policyApiService: PolicyApiServiceAbstraction;
logService: ConsoleLogService; logService: ConsoleLogService;
sendService: SendService; sendService: SendService;
@@ -469,7 +469,7 @@ export class ServiceContainer {
this.ssoUrlService = new SsoUrlService(); this.ssoUrlService = new SsoUrlService();
this.organizationService = new DefaultOrganizationService(this.stateProvider); this.organizationService = new DefaultOrganizationService(this.stateProvider);
this.policyService = new PolicyService(this.stateProvider, this.organizationService); this.policyService = new DefaultPolicyService(this.stateProvider, this.organizationService);
this.vaultTimeoutSettingsService = new DefaultVaultTimeoutSettingsService( this.vaultTimeoutSettingsService = new DefaultVaultTimeoutSettingsService(
this.accountService, this.accountService,
@@ -560,7 +560,11 @@ export class ServiceContainer {
this.providerService = new ProviderService(this.stateProvider); this.providerService = new ProviderService(this.stateProvider);
this.policyApiService = new PolicyApiService(this.policyService, this.apiService); this.policyApiService = new PolicyApiService(
this.policyService,
this.apiService,
this.accountService,
);
this.keyConnectorService = new KeyConnectorService( this.keyConnectorService = new KeyConnectorService(
this.accountService, this.accountService,
@@ -672,6 +676,7 @@ export class ServiceContainer {
this.autofillSettingsService = new AutofillSettingsService( this.autofillSettingsService = new AutofillSettingsService(
this.stateProvider, this.stateProvider,
this.policyService, this.policyService,
this.accountService,
); );
this.cipherService = new CipherService( this.cipherService = new CipherService(

View File

@@ -2,10 +2,13 @@
// @ts-strict-ignore // @ts-strict-ignore
import { OptionValues } from "commander"; import { OptionValues } from "commander";
import * as inquirer from "inquirer"; import * as inquirer from "inquirer";
import { firstValueFrom, switchMap } from "rxjs";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { EventType } from "@bitwarden/common/enums"; import { EventType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@@ -26,14 +29,19 @@ export class ExportCommand {
private exportService: VaultExportServiceAbstraction, private exportService: VaultExportServiceAbstraction,
private policyService: PolicyService, private policyService: PolicyService,
private eventCollectionService: EventCollectionService, private eventCollectionService: EventCollectionService,
private accountService: AccountService,
private configService: ConfigService, private configService: ConfigService,
) {} ) {}
async run(options: OptionValues): Promise<Response> { async run(options: OptionValues): Promise<Response> {
if ( const policyApplies$ = this.accountService.activeAccount$.pipe(
options.organizationid == null && getUserId,
(await this.policyService.policyAppliesToUser(PolicyType.DisablePersonalVaultExport)) switchMap((userId) =>
) { this.policyService.policyAppliesToUser$(PolicyType.DisablePersonalVaultExport, userId),
),
);
if (options.organizationid == null && (await firstValueFrom(policyApplies$))) {
return Response.badRequest( return Response.badRequest(
"One or more organization policies prevents you from exporting your personal vault.", "One or more organization policies prevents you from exporting your personal vault.",
); );

View File

@@ -501,6 +501,7 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.exportService, this.serviceContainer.exportService,
this.serviceContainer.policyService, this.serviceContainer.policyService,
this.serviceContainer.eventCollectionService, this.serviceContainer.eventCollectionService,
this.serviceContainer.accountService,
this.serviceContainer.configService, this.serviceContainer.configService,
); );
const response = await command.run(options); const response = await command.run(options);

View File

@@ -153,7 +153,7 @@ describe("SettingsComponent", () => {
it("pin enabled when RemoveUnlockWithPin policy is not set", async () => { it("pin enabled when RemoveUnlockWithPin policy is not set", async () => {
// @ts-strict-ignore // @ts-strict-ignore
policyService.get$.mockReturnValue(of(null)); policyService.policiesByType$.mockReturnValue(of([null]));
await component.ngOnInit(); await component.ngOnInit();
@@ -164,7 +164,7 @@ describe("SettingsComponent", () => {
const policy = new Policy(); const policy = new Policy();
policy.type = PolicyType.RemoveUnlockWithPin; policy.type = PolicyType.RemoveUnlockWithPin;
policy.enabled = false; policy.enabled = false;
policyService.get$.mockReturnValue(of(policy)); policyService.policiesByType$.mockReturnValue(of([policy]));
await component.ngOnInit(); await component.ngOnInit();
@@ -175,7 +175,7 @@ describe("SettingsComponent", () => {
const policy = new Policy(); const policy = new Policy();
policy.type = PolicyType.RemoveUnlockWithPin; policy.type = PolicyType.RemoveUnlockWithPin;
policy.enabled = true; policy.enabled = true;
policyService.get$.mockReturnValue(of(policy)); policyService.policiesByType$.mockReturnValue(of([policy]));
await component.ngOnInit(); await component.ngOnInit();
@@ -184,7 +184,7 @@ describe("SettingsComponent", () => {
it("pin visible when RemoveUnlockWithPin policy is not set", async () => { it("pin visible when RemoveUnlockWithPin policy is not set", async () => {
// @ts-strict-ignore // @ts-strict-ignore
policyService.get$.mockReturnValue(of(null)); policyService.policiesByType$.mockReturnValue(of([null]));
await component.ngOnInit(); await component.ngOnInit();
fixture.detectChanges(); fixture.detectChanges();
@@ -201,7 +201,7 @@ describe("SettingsComponent", () => {
const policy = new Policy(); const policy = new Policy();
policy.type = PolicyType.RemoveUnlockWithPin; policy.type = PolicyType.RemoveUnlockWithPin;
policy.enabled = false; policy.enabled = false;
policyService.get$.mockReturnValue(of(policy)); policyService.policiesByType$.mockReturnValue(of([policy]));
await component.ngOnInit(); await component.ngOnInit();
fixture.detectChanges(); fixture.detectChanges();
@@ -218,7 +218,7 @@ describe("SettingsComponent", () => {
const policy = new Policy(); const policy = new Policy();
policy.type = PolicyType.RemoveUnlockWithPin; policy.type = PolicyType.RemoveUnlockWithPin;
policy.enabled = true; policy.enabled = true;
policyService.get$.mockReturnValue(of(policy)); policyService.policiesByType$.mockReturnValue(of([policy]));
pinServiceAbstraction.isPinSet.mockResolvedValue(true); pinServiceAbstraction.isPinSet.mockResolvedValue(true);
await component.ngOnInit(); await component.ngOnInit();
@@ -236,7 +236,7 @@ describe("SettingsComponent", () => {
const policy = new Policy(); const policy = new Policy();
policy.type = PolicyType.RemoveUnlockWithPin; policy.type = PolicyType.RemoveUnlockWithPin;
policy.enabled = true; policy.enabled = true;
policyService.get$.mockReturnValue(of(policy)); policyService.policiesByType$.mockReturnValue(of([policy]));
await component.ngOnInit(); await component.ngOnInit();
fixture.detectChanges(); fixture.detectChanges();
@@ -255,7 +255,7 @@ describe("SettingsComponent", () => {
const policy = new Policy(); const policy = new Policy();
policy.type = PolicyType.RemoveUnlockWithPin; policy.type = PolicyType.RemoveUnlockWithPin;
policy.enabled = false; policy.enabled = false;
policyService.get$.mockReturnValue(of(policy)); policyService.policiesByType$.mockReturnValue(of([policy]));
platformUtilsService.getDevice.mockReturnValue(DeviceType.WindowsDesktop); platformUtilsService.getDevice.mockReturnValue(DeviceType.WindowsDesktop);
i18nService.t.mockImplementation((id: string) => { i18nService.t.mockImplementation((id: string) => {
if (id === "requirePasswordOnStart") { if (id === "requirePasswordOnStart") {
@@ -290,7 +290,7 @@ describe("SettingsComponent", () => {
const policy = new Policy(); const policy = new Policy();
policy.type = PolicyType.RemoveUnlockWithPin; policy.type = PolicyType.RemoveUnlockWithPin;
policy.enabled = true; policy.enabled = true;
policyService.get$.mockReturnValue(of(policy)); policyService.policiesByType$.mockReturnValue(of([policy]));
platformUtilsService.getDevice.mockReturnValue(DeviceType.WindowsDesktop); platformUtilsService.getDevice.mockReturnValue(DeviceType.WindowsDesktop);
i18nService.t.mockImplementation((id: string) => { i18nService.t.mockImplementation((id: string) => {
if (id === "requirePasswordOnStart") { if (id === "requirePasswordOnStart") {

View File

@@ -17,8 +17,10 @@ import {
import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { getFirstPolicy } from "@bitwarden/common/admin-console/services/policy/default-policy.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { DeviceType } from "@bitwarden/common/enums"; import { DeviceType } from "@bitwarden/common/enums";
@@ -235,7 +237,12 @@ export class SettingsComponent implements OnInit, OnDestroy {
); );
// Load timeout policy // Load timeout policy
this.vaultTimeoutPolicyCallout = this.policyService.get$(PolicyType.MaximumVaultTimeout).pipe( this.vaultTimeoutPolicyCallout = this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.policiesByType$(PolicyType.MaximumVaultTimeout, userId),
),
getFirstPolicy,
filter((policy) => policy != null), filter((policy) => policy != null),
map((policy) => { map((policy) => {
let timeout; let timeout;
@@ -259,7 +266,12 @@ export class SettingsComponent implements OnInit, OnDestroy {
// Load initial values // Load initial values
this.userHasPinSet = await this.pinService.isPinSet(activeAccount.id); this.userHasPinSet = await this.pinService.isPinSet(activeAccount.id);
this.pinEnabled$ = this.policyService.get$(PolicyType.RemoveUnlockWithPin).pipe( this.pinEnabled$ = this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.policiesByType$(PolicyType.RemoveUnlockWithPin, userId),
),
getFirstPolicy,
map((policy) => { map((policy) => {
return policy == null || !policy.enabled; return policy == null || !policy.enabled;
}), }),

View File

@@ -1,6 +1,6 @@
// 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 { firstValueFrom } from "rxjs"; import { firstValueFrom, switchMap } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
@@ -144,10 +144,14 @@ export class EncryptedMessageHandlerService {
const credentialCreatePayload = payload as CredentialCreatePayload; const credentialCreatePayload = payload as CredentialCreatePayload;
if ( const policyApplies$ = this.accountService.activeAccount$.pipe(
credentialCreatePayload.name == null || getUserId,
(await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)) switchMap((userId) =>
) { this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId),
),
);
if (credentialCreatePayload.name == null || (await firstValueFrom(policyApplies$))) {
return { status: "failure" }; return { status: "failure" };
} }

View File

@@ -5,6 +5,7 @@ import { firstValueFrom, Subject } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -48,6 +49,7 @@ export class VaultFilterComponent
protected billingApiService: BillingApiServiceAbstraction, protected billingApiService: BillingApiServiceAbstraction,
protected dialogService: DialogService, protected dialogService: DialogService,
protected configService: ConfigService, protected configService: ConfigService,
protected accountService: AccountService,
) { ) {
super( super(
vaultFilterService, vaultFilterService,
@@ -58,6 +60,7 @@ export class VaultFilterComponent
billingApiService, billingApiService,
dialogService, dialogService,
configService, configService,
accountService,
); );
} }

View File

@@ -118,7 +118,10 @@ export class OrganizationLayoutComponent implements OnInit {
), ),
); );
this.hideNewOrgButton$ = this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg); this.hideNewOrgButton$ = this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.policyService.policyAppliesToUser$(PolicyType.SingleOrg, userId)),
);
const provider$ = this.organization$.pipe( const provider$ = this.organization$.pipe(
switchMap((organization) => this.providerService.get$(organization.providerId)), switchMap((organization) => this.providerService.get$(organization.providerId)),

View File

@@ -3,11 +3,13 @@
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { Component, Inject, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { Component, Inject, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms"; import { FormBuilder, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs"; import { Subject, switchMap, takeUntil } from "rxjs";
import { PasswordStrengthV2Component } from "@bitwarden/angular/tools/password-strength/password-strength-v2.component"; import { PasswordStrengthV2Component } from "@bitwarden/angular/tools/password-strength/password-strength-v2.component";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -81,12 +83,16 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
private toastService: ToastService, private toastService: ToastService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private dialogRef: DialogRef<ResetPasswordDialogResult>, private dialogRef: DialogRef<ResetPasswordDialogResult>,
private accountService: AccountService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
this.policyService this.accountService.activeAccount$
.masterPasswordPolicyOptions$() .pipe(
.pipe(takeUntil(this.destroy$)) getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)),
takeUntil(this.destroy$),
)
.subscribe( .subscribe(
(enforcedPasswordPolicyOptions) => (enforcedPasswordPolicyOptions) =>
(this.enforcedPolicyOptions = enforcedPasswordPolicyOptions), (this.enforcedPolicyOptions = enforcedPasswordPolicyOptions),

View File

@@ -43,6 +43,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { isNotSelfUpgradable, ProductTierType } from "@bitwarden/common/billing/enums"; import { isNotSelfUpgradable, ProductTierType } from "@bitwarden/common/billing/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
@@ -168,15 +169,18 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
this.canUseSecretsManager$ = organization$.pipe(map((org) => org.useSecretsManager)); this.canUseSecretsManager$ = organization$.pipe(map((org) => org.useSecretsManager));
const policies$ = organization$.pipe( const policies$ = combineLatest([
switchMap((organization) => { this.accountService.activeAccount$.pipe(getUserId),
organization$,
]).pipe(
switchMap(([userId, organization]) => {
if (organization.isProviderUser) { if (organization.isProviderUser) {
return from(this.policyApiService.getPolicies(organization.id)).pipe( return from(this.policyApiService.getPolicies(organization.id)).pipe(
map((response) => Policy.fromListResponse(response)), map((response) => Policy.fromListResponse(response)),
); );
} }
return this.policyService.policies$; return this.policyService.policies$(userId);
}), }),
); );

View File

@@ -8,11 +8,15 @@ import { InternalPolicyService } from "@bitwarden/common/admin-console/abstracti
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { ResetPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/reset-password-policy-options"; import { ResetPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/reset-password-policy-options";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
// FIXME: remove `src` and fix import // FIXME: remove `src` and fix import
@@ -38,6 +42,8 @@ describe("WebLoginComponentService", () => {
let passwordGenerationService: MockProxy<PasswordGenerationServiceAbstraction>; let passwordGenerationService: MockProxy<PasswordGenerationServiceAbstraction>;
let platformUtilsService: MockProxy<PlatformUtilsService>; let platformUtilsService: MockProxy<PlatformUtilsService>;
let ssoLoginService: MockProxy<SsoLoginServiceAbstraction>; let ssoLoginService: MockProxy<SsoLoginServiceAbstraction>;
const mockUserId = Utils.newGuid() as UserId;
let accountService: FakeAccountService;
beforeEach(() => { beforeEach(() => {
acceptOrganizationInviteService = mock<AcceptOrganizationInviteService>(); acceptOrganizationInviteService = mock<AcceptOrganizationInviteService>();
@@ -50,6 +56,7 @@ describe("WebLoginComponentService", () => {
passwordGenerationService = mock<PasswordGenerationServiceAbstraction>(); passwordGenerationService = mock<PasswordGenerationServiceAbstraction>();
platformUtilsService = mock<PlatformUtilsService>(); platformUtilsService = mock<PlatformUtilsService>();
ssoLoginService = mock<SsoLoginServiceAbstraction>(); ssoLoginService = mock<SsoLoginServiceAbstraction>();
accountService = mockAccountServiceWith(mockUserId);
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [ providers: [
@@ -65,6 +72,7 @@ describe("WebLoginComponentService", () => {
{ provide: PasswordGenerationServiceAbstraction, useValue: passwordGenerationService }, { provide: PasswordGenerationServiceAbstraction, useValue: passwordGenerationService },
{ provide: PlatformUtilsService, useValue: platformUtilsService }, { provide: PlatformUtilsService, useValue: platformUtilsService },
{ provide: SsoLoginServiceAbstraction, useValue: ssoLoginService }, { provide: SsoLoginServiceAbstraction, useValue: ssoLoginService },
{ provide: AccountService, useValue: accountService },
], ],
}); });
service = TestBed.inject(WebLoginComponentService); service = TestBed.inject(WebLoginComponentService);

View File

@@ -2,7 +2,7 @@
// @ts-strict-ignore // @ts-strict-ignore
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs"; import { firstValueFrom, switchMap } from "rxjs";
import { import {
DefaultLoginComponentService, DefaultLoginComponentService,
@@ -12,7 +12,9 @@ import {
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -39,6 +41,7 @@ export class WebLoginComponentService
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
ssoLoginService: SsoLoginServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction,
private router: Router, private router: Router,
private accountService: AccountService,
) { ) {
super( super(
cryptoFunctionService, cryptoFunctionService,
@@ -93,7 +96,10 @@ export class WebLoginComponentService
resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled; resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled;
const enforcedPasswordPolicyOptions = await firstValueFrom( const enforcedPasswordPolicyOptions = await firstValueFrom(
this.policyService.masterPasswordPolicyOptions$(policies), this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)),
),
); );
return { return {

View File

@@ -10,9 +10,12 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { CsprngArray } from "@bitwarden/common/types/csprng"; import { CsprngArray } from "@bitwarden/common/types/csprng";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management"; import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management";
@@ -30,6 +33,8 @@ describe("WebRegistrationFinishService", () => {
let policyApiService: MockProxy<PolicyApiServiceAbstraction>; let policyApiService: MockProxy<PolicyApiServiceAbstraction>;
let logService: MockProxy<LogService>; let logService: MockProxy<LogService>;
let policyService: MockProxy<PolicyService>; let policyService: MockProxy<PolicyService>;
const mockUserId = Utils.newGuid() as UserId;
let accountService: FakeAccountService;
beforeEach(() => { beforeEach(() => {
keyService = mock<KeyService>(); keyService = mock<KeyService>();
@@ -38,6 +43,7 @@ describe("WebRegistrationFinishService", () => {
policyApiService = mock<PolicyApiServiceAbstraction>(); policyApiService = mock<PolicyApiServiceAbstraction>();
logService = mock<LogService>(); logService = mock<LogService>();
policyService = mock<PolicyService>(); policyService = mock<PolicyService>();
accountService = mockAccountServiceWith(mockUserId);
service = new WebRegistrationFinishService( service = new WebRegistrationFinishService(
keyService, keyService,
@@ -46,6 +52,7 @@ describe("WebRegistrationFinishService", () => {
policyApiService, policyApiService,
logService, logService,
policyService, policyService,
accountService,
); );
}); });

View File

@@ -12,6 +12,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { RegisterFinishRequest } from "@bitwarden/common/auth/models/request/registration/register-finish.request"; import { RegisterFinishRequest } from "@bitwarden/common/auth/models/request/registration/register-finish.request";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
@@ -30,6 +31,7 @@ export class WebRegistrationFinishService
private policyApiService: PolicyApiServiceAbstraction, private policyApiService: PolicyApiServiceAbstraction,
private logService: LogService, private logService: LogService,
private policyService: PolicyService, private policyService: PolicyService,
private accountService: AccountService,
) { ) {
super(keyService, accountApiService); super(keyService, accountApiService);
} }
@@ -68,7 +70,7 @@ export class WebRegistrationFinishService
} }
const masterPasswordPolicyOpts: MasterPasswordPolicyOptions = await firstValueFrom( const masterPasswordPolicyOpts: MasterPasswordPolicyOptions = await firstValueFrom(
this.policyService.masterPasswordPolicyOptions$(policies), this.policyService.masterPasswordPolicyOptions$(null, policies),
); );
return masterPasswordPolicyOpts; return masterPasswordPolicyOpts;

View File

@@ -3,11 +3,12 @@
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, OnDestroy, OnInit, Inject, Input } from "@angular/core"; import { Component, OnDestroy, OnInit, Inject, Input } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms"; import { FormBuilder, Validators } from "@angular/forms";
import { takeUntil } from "rxjs"; import { switchMap, takeUntil } from "rxjs";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component"; import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -79,9 +80,12 @@ export class EmergencyAccessTakeoverComponent
const policies = await this.emergencyAccessService.getGrantorPolicies( const policies = await this.emergencyAccessService.getGrantorPolicies(
this.params.emergencyAccessId, this.params.emergencyAccessId,
); );
this.policyService this.accountService.activeAccount$
.masterPasswordPolicyOptions$(policies) .pipe(
.pipe(takeUntil(this.destroy$)) getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId, policies)),
takeUntil(this.destroy$),
)
.subscribe((enforcedPolicyOptions) => (this.enforcedPolicyOptions = enforcedPolicyOptions)); .subscribe((enforcedPolicyOptions) => (this.enforcedPolicyOptions = enforcedPolicyOptions));
} }

View File

@@ -25,6 +25,7 @@ import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two
import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response";
import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response";
import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service"; import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service";
import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
@@ -109,13 +110,17 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
this.providers.sort((a: any, b: any) => a.sort - b.sort); this.providers.sort((a: any, b: any) => a.sort - b.sort);
this.policyService this.accountService.activeAccount$
.policyAppliesToActiveUser$(PolicyType.TwoFactorAuthentication) .pipe(
.pipe(takeUntil(this.destroy$)) getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.TwoFactorAuthentication, userId),
),
takeUntil(this.destroy$),
)
.subscribe((policyAppliesToActiveUser) => { .subscribe((policyAppliesToActiveUser) => {
this.twoFactorAuthPolicyAppliesToActiveUser = policyAppliesToActiveUser; this.twoFactorAuthPolicyAppliesToActiveUser = policyAppliesToActiveUser;
}); });
await this.load(); await this.load();
} }

View File

@@ -1,10 +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 { Component, HostBinding, OnDestroy, OnInit } from "@angular/core"; import { Component, HostBinding, OnDestroy, OnInit } from "@angular/core";
import { Subject, takeUntil } from "rxjs"; import { Subject, switchMap, takeUntil } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
import { WebauthnLoginAdminService } from "../../core"; import { WebauthnLoginAdminService } from "../../core";
@@ -35,6 +37,7 @@ export class WebauthnLoginSettingsComponent implements OnInit, OnDestroy {
private webauthnService: WebauthnLoginAdminService, private webauthnService: WebauthnLoginAdminService,
private dialogService: DialogService, private dialogService: DialogService,
private policyService: PolicyService, private policyService: PolicyService,
private accountService: AccountService,
) {} ) {}
@HostBinding("attr.aria-busy") @HostBinding("attr.aria-busy")
@@ -57,9 +60,14 @@ export class WebauthnLoginSettingsComponent implements OnInit, OnDestroy {
requireSsoPolicyEnabled = false; requireSsoPolicyEnabled = false;
ngOnInit(): void { ngOnInit(): void {
this.policyService this.accountService.activeAccount$
.policyAppliesToActiveUser$(PolicyType.RequireSso) .pipe(
.pipe(takeUntil(this.destroy$)) getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.RequireSso, userId),
),
takeUntil(this.destroy$),
)
.subscribe((enabled) => { .subscribe((enabled) => {
this.requireSsoPolicyEnabled = enabled; this.requireSsoPolicyEnabled = enabled;
}); });

View File

@@ -13,7 +13,7 @@ import {
} from "@angular/core"; } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms"; import { FormBuilder, Validators } from "@angular/forms";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { Subject, firstValueFrom, map, takeUntil } from "rxjs"; import { Subject, firstValueFrom, map, switchMap, takeUntil } from "rxjs";
import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -28,6 +28,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request"; import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { import {
BillingApiServiceAbstraction, BillingApiServiceAbstraction,
BillingInformation, BillingInformation,
@@ -265,9 +266,14 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
} }
this.upgradeFlowPrefillForm(); this.upgradeFlowPrefillForm();
this.policyService this.accountService.activeAccount$
.policyAppliesToActiveUser$(PolicyType.SingleOrg) .pipe(
.pipe(takeUntil(this.destroy$)) getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.SingleOrg, userId),
),
takeUntil(this.destroy$),
)
.subscribe((policyAppliesToActiveUser) => { .subscribe((policyAppliesToActiveUser) => {
this.singleOrgPolicyAppliesToActiveUser = policyAppliesToActiveUser; this.singleOrgPolicyAppliesToActiveUser = policyAppliesToActiveUser;
}); });

View File

@@ -12,7 +12,7 @@ import {
import { FormBuilder, Validators } from "@angular/forms"; import { FormBuilder, Validators } from "@angular/forms";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { Subject, firstValueFrom, takeUntil } from "rxjs"; import { Subject, firstValueFrom, takeUntil } from "rxjs";
import { debounceTime, map } from "rxjs/operators"; import { debounceTime, map, switchMap } from "rxjs/operators";
import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -31,6 +31,7 @@ import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/mode
import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-organization-create.request"; import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-organization-create.request";
import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response"; import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction";
import { PaymentMethodType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; import { PaymentMethodType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
@@ -240,9 +241,14 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
this.formGroup.controls.billingEmail.addValidators(Validators.required); this.formGroup.controls.billingEmail.addValidators(Validators.required);
} }
this.policyService this.accountService.activeAccount$
.policyAppliesToActiveUser$(PolicyType.SingleOrg) .pipe(
.pipe(takeUntil(this.destroy$)) getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.SingleOrg, userId),
),
takeUntil(this.destroy$),
)
.subscribe((policyAppliesToActiveUser) => { .subscribe((policyAppliesToActiveUser) => {
this.singleOrgPolicyAppliesToActiveUser = policyAppliesToActiveUser; this.singleOrgPolicyAppliesToActiveUser = policyAppliesToActiveUser;
}); });

View File

@@ -96,7 +96,7 @@ export class FreeFamiliesPolicyService {
return this.accountService.activeAccount$.pipe( return this.accountService.activeAccount$.pipe(
getUserId, getUserId,
switchMap((userId) => switchMap((userId) =>
this.policyService.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy, userId), this.policyService.policiesByType$(PolicyType.FreeFamiliesSponsorshipPolicy, userId),
), ),
map((policies) => ({ map((policies) => ({
isFreeFamilyPolicyEnabled: policies.some( isFreeFamilyPolicyEnabled: policies.some(

View File

@@ -89,7 +89,7 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
this.availableSponsorshipOrgs$ = combineLatest([ this.availableSponsorshipOrgs$ = combineLatest([
this.organizationService.organizations$(userId), this.organizationService.organizations$(userId),
this.policyService.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy, userId), this.policyService.policiesByType$(PolicyType.FreeFamiliesSponsorshipPolicy, userId),
]).pipe( ]).pipe(
map(([organizations, policies]) => map(([organizations, policies]) =>
organizations organizations

View File

@@ -10,7 +10,6 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { 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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { DialogService, ToastService } from "@bitwarden/components"; import { DialogService, ToastService } from "@bitwarden/components";
@@ -36,7 +35,6 @@ export class SponsoringOrgRowComponent implements OnInit {
private logService: LogService, private logService: LogService,
private dialogService: DialogService, private dialogService: DialogService,
private toastService: ToastService, private toastService: ToastService,
private configService: ConfigService,
private policyService: PolicyService, private policyService: PolicyService,
private accountService: AccountService, private accountService: AccountService,
) {} ) {}
@@ -54,7 +52,7 @@ export class SponsoringOrgRowComponent implements OnInit {
this.isFreeFamilyPolicyEnabled$ = this.accountService.activeAccount$.pipe( this.isFreeFamilyPolicyEnabled$ = this.accountService.activeAccount$.pipe(
getUserId, getUserId,
switchMap((userId) => switchMap((userId) =>
this.policyService.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy, userId), this.policyService.policiesByType$(PolicyType.FreeFamiliesSponsorshipPolicy, userId),
), ),
map( map(
(policies) => (policies) =>

View File

@@ -4,7 +4,7 @@ import { StepperSelectionEvent } from "@angular/cdk/stepper";
import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms"; import { FormBuilder, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom, Subject, takeUntil } from "rxjs"; import { firstValueFrom, Subject, switchMap, takeUntil } from "rxjs";
import { PasswordInputResult, RegistrationFinishService } from "@bitwarden/auth/angular"; import { PasswordInputResult, RegistrationFinishService } from "@bitwarden/auth/angular";
import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common";
@@ -12,6 +12,8 @@ import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abs
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { import {
OrganizationBillingServiceAbstraction as OrganizationBillingService, OrganizationBillingServiceAbstraction as OrganizationBillingService,
OrganizationInformation, OrganizationInformation,
@@ -106,6 +108,7 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy {
private validationService: ValidationService, private validationService: ValidationService,
private loginStrategyService: LoginStrategyServiceAbstraction, private loginStrategyService: LoginStrategyServiceAbstraction,
private configService: ConfigService, private configService: ConfigService,
private accountService: AccountService,
) {} ) {}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
@@ -173,9 +176,12 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy {
} }
if (policies !== null) { if (policies !== null) {
this.policyService this.accountService.activeAccount$
.masterPasswordPolicyOptions$(policies) .pipe(
.pipe(takeUntil(this.destroy$)) getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId, policies)),
takeUntil(this.destroy$),
)
.subscribe((enforcedPasswordPolicyOptions) => { .subscribe((enforcedPasswordPolicyOptions) => {
this.enforcedPolicyOptions = enforcedPasswordPolicyOptions; this.enforcedPolicyOptions = enforcedPasswordPolicyOptions;
}); });

View File

@@ -256,6 +256,7 @@ const safeProviders: SafeProvider[] = [
PolicyApiServiceAbstraction, PolicyApiServiceAbstraction,
LogService, LogService,
PolicyService, PolicyService,
AccountService,
], ],
}), }),
safeProvider({ safeProvider({
@@ -311,6 +312,7 @@ const safeProviders: SafeProvider[] = [
PlatformUtilsService, PlatformUtilsService,
SsoLoginServiceAbstraction, SsoLoginServiceAbstraction,
Router, Router,
AccountService,
], ],
}), }),
safeProvider({ safeProvider({

View File

@@ -1,10 +1,13 @@
// 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 { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { switchMap } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { DeviceType, EventType } from "@bitwarden/common/enums"; import { DeviceType, EventType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { EventResponse } from "@bitwarden/common/models/response/event.response"; import { EventResponse } from "@bitwarden/common/models/response/event.response";
@@ -19,10 +22,16 @@ export class EventService {
private i18nService: I18nService, private i18nService: I18nService,
policyService: PolicyService, policyService: PolicyService,
private configService: ConfigService, private configService: ConfigService,
private accountService: AccountService,
) { ) {
policyService.policies$.subscribe((policies) => { accountService.activeAccount$
this.policies = policies; .pipe(
}); getUserId,
switchMap((userId) => policyService.policies$(userId)),
)
.subscribe((policies) => {
this.policies = policies;
});
} }
getDefaultDateFilters() { getDefaultDateFilters() {

View File

@@ -2,11 +2,23 @@
// @ts-strict-ignore // @ts-strict-ignore
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms"; import { FormBuilder } from "@angular/forms";
import { concatMap, filter, firstValueFrom, map, Observable, Subject, takeUntil, tap } from "rxjs"; import {
concatMap,
filter,
firstValueFrom,
map,
Observable,
Subject,
switchMap,
takeUntil,
tap,
} from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { getFirstPolicy } from "@bitwarden/common/admin-console/services/policy/default-policy.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { import {
VaultTimeout, VaultTimeout,
@@ -100,7 +112,12 @@ export class PreferencesComponent implements OnInit, OnDestroy {
this.availableVaultTimeoutActions$ = this.availableVaultTimeoutActions$ =
this.vaultTimeoutSettingsService.availableVaultTimeoutActions$(); this.vaultTimeoutSettingsService.availableVaultTimeoutActions$();
this.vaultTimeoutPolicyCallout = this.policyService.get$(PolicyType.MaximumVaultTimeout).pipe( this.vaultTimeoutPolicyCallout = this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.policiesByType$(PolicyType.MaximumVaultTimeout, userId),
),
getFirstPolicy,
filter((policy) => policy != null), filter((policy) => policy != null),
map((policy) => { map((policy) => {
let timeout; let timeout;

View File

@@ -75,8 +75,10 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
) {} ) {}
async ngOnInit() { async ngOnInit() {
const resetPasswordPolicies$ = this.policyService.policies$.pipe( const resetPasswordPolicies$ = this.accountService.activeAccount$.pipe(
map((policies) => policies.filter((policy) => policy.type === PolicyType.ResetPassword)), getUserId,
switchMap((userId) => this.policyService.policies$(userId)),
map((policies) => policies.filter((p) => p.type == PolicyType.ResetPassword)),
); );
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));

View File

@@ -6,6 +6,9 @@ import { firstValueFrom, merge, Subject, switchMap, takeUntil } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
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 { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -101,6 +104,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
protected billingApiService: BillingApiServiceAbstraction, protected billingApiService: BillingApiServiceAbstraction,
protected dialogService: DialogService, protected dialogService: DialogService,
protected configService: ConfigService, protected configService: ConfigService,
protected accountService: AccountService,
) {} ) {}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
@@ -110,10 +114,18 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
this.isLoaded = true; this.isLoaded = true;
// Without refactoring the entire component, we need to manually update the organization filter whenever the policies update // Without refactoring the entire component, we need to manually update the organization filter whenever the policies update
merge( this.accountService.activeAccount$
this.policyService.get$(PolicyType.SingleOrg), .pipe(
this.policyService.get$(PolicyType.PersonalOwnership), getUserId,
) switchMap((userId) =>
merge(
this.policyService.policiesByType$(PolicyType.SingleOrg, userId).pipe(getFirstPolicy),
this.policyService
.policiesByType$(PolicyType.PersonalOwnership, userId)
.pipe(getFirstPolicy),
),
),
)
.pipe( .pipe(
switchMap(() => this.addOrganizationFilter()), switchMap(() => this.addOrganizationFilter()),
takeUntil(this.destroy$), takeUntil(this.destroy$),
@@ -190,9 +202,22 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
} }
protected async addOrganizationFilter(): Promise<VaultFilterSection> { protected async addOrganizationFilter(): Promise<VaultFilterSection> {
const singleOrgPolicy = await this.policyService.policyAppliesToUser(PolicyType.SingleOrg); const singleOrgPolicy = await firstValueFrom(
const personalVaultPolicy = await this.policyService.policyAppliesToUser( this.accountService.activeAccount$.pipe(
PolicyType.PersonalOwnership, getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.SingleOrg, userId),
),
),
);
const personalVaultPolicy = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId),
),
),
); );
const addAction = !singleOrgPolicy const addAction = !singleOrgPolicy

View File

@@ -65,11 +65,11 @@ describe("vault filter service", () => {
organizationService.memberOrganizations$.mockReturnValue(organizations); organizationService.memberOrganizations$.mockReturnValue(organizations);
folderService.folderViews$.mockReturnValue(folderViews); folderService.folderViews$.mockReturnValue(folderViews);
collectionService.decryptedCollections$ = collectionViews; collectionService.decryptedCollections$ = collectionViews;
policyService.policyAppliesToActiveUser$ policyService.policyAppliesToUser$
.calledWith(PolicyType.PersonalOwnership) .calledWith(PolicyType.PersonalOwnership, mockUserId)
.mockReturnValue(personalOwnershipPolicy); .mockReturnValue(personalOwnershipPolicy);
policyService.policyAppliesToActiveUser$ policyService.policyAppliesToUser$
.calledWith(PolicyType.SingleOrg) .calledWith(PolicyType.SingleOrg, mockUserId)
.mockReturnValue(singleOrgPolicy); .mockReturnValue(singleOrgPolicy);
cipherService.cipherViews$.mockReturnValue(cipherViews); cipherService.cipherViews$.mockReturnValue(cipherViews);

View File

@@ -55,8 +55,14 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
organizationTree$: Observable<TreeNode<OrganizationFilter>> = combineLatest([ organizationTree$: Observable<TreeNode<OrganizationFilter>> = combineLatest([
this.memberOrganizations$, this.memberOrganizations$,
this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg), this.activeUserId$.pipe(
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), switchMap((userId) => this.policyService.policyAppliesToUser$(PolicyType.SingleOrg, userId)),
),
this.activeUserId$.pipe(
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId),
),
),
]).pipe( ]).pipe(
switchMap(([orgs, singleOrgPolicy, personalOwnershipPolicy]) => switchMap(([orgs, singleOrgPolicy, personalOwnershipPolicy]) =>
this.buildOrganizationTree(orgs, singleOrgPolicy, personalOwnershipPolicy), this.buildOrganizationTree(orgs, singleOrgPolicy, personalOwnershipPolicy),

View File

@@ -142,13 +142,13 @@ describe("VaultOnboardingComponent", () => {
}); });
describe("individualVaultPolicyCheck", () => { describe("individualVaultPolicyCheck", () => {
it("should set isIndividualPolicyVault to true", async () => { it("should set isIndividualPolicyVault to true", () => {
individualVaultPolicyCheckSpy.mockRestore(); individualVaultPolicyCheckSpy.mockRestore();
const spy = jest const spy = jest
.spyOn((component as any).policyService, "policyAppliesToActiveUser$") .spyOn((component as any).policyService, "policyAppliesToUser$")
.mockReturnValue(of(true)); .mockReturnValue(of(true));
await component.individualVaultPolicyCheck(); component.individualVaultPolicyCheck();
fixture.detectChanges(); fixture.detectChanges();
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
}); });

View File

@@ -11,7 +11,7 @@ import {
SimpleChanges, SimpleChanges,
OnChanges, OnChanges,
} from "@angular/core"; } from "@angular/core";
import { Subject, takeUntil, Observable, firstValueFrom, fromEvent } from "rxjs"; import { Subject, takeUntil, Observable, firstValueFrom, fromEvent, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -20,7 +20,6 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { 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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { UserId } from "@bitwarden/common/types/guid"; import { UserId } from "@bitwarden/common/types/guid";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
@@ -67,7 +66,6 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy {
protected policyService: PolicyService, protected policyService: PolicyService,
private apiService: ApiService, private apiService: ApiService,
private vaultOnboardingService: VaultOnboardingServiceAbstraction, private vaultOnboardingService: VaultOnboardingServiceAbstraction,
private configService: ConfigService,
private accountService: AccountService, private accountService: AccountService,
) {} ) {}
@@ -165,9 +163,14 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy {
} }
individualVaultPolicyCheck() { individualVaultPolicyCheck() {
this.policyService this.accountService.activeAccount$
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership) .pipe(
.pipe(takeUntil(this.destroy$)) getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId),
),
takeUntil(this.destroy$),
)
.subscribe((data) => { .subscribe((data) => {
this.isIndividualPolicyVault = data; this.isIndividualPolicyVault = data;
}); });

View File

@@ -38,7 +38,7 @@ describe("AdminConsoleCipherFormConfigService", () => {
status: OrganizationUserStatusType.Confirmed, status: OrganizationUserStatusType.Confirmed,
userId: "UserId", userId: "UserId",
}; };
const policyAppliesToActiveUser$ = new BehaviorSubject<boolean>(true); const policyAppliesToUser$ = new BehaviorSubject<boolean>(true);
const collection = { const collection = {
id: "12345-5555", id: "12345-5555",
organizationId: "234534-34334", organizationId: "234534-34334",
@@ -75,7 +75,7 @@ describe("AdminConsoleCipherFormConfigService", () => {
}, },
{ {
provide: PolicyService, provide: PolicyService,
useValue: { policyAppliesToActiveUser$: () => policyAppliesToActiveUser$ }, useValue: { policyAppliesToUser$: () => policyAppliesToUser$ },
}, },
{ {
provide: RoutedVaultFilterService, provide: RoutedVaultFilterService,
@@ -129,13 +129,13 @@ describe("AdminConsoleCipherFormConfigService", () => {
}); });
it("sets `allowPersonalOwnership`", async () => { it("sets `allowPersonalOwnership`", async () => {
policyAppliesToActiveUser$.next(true); policyAppliesToUser$.next(true);
let result = await adminConsoleConfigService.buildConfig("clone", cipherId); let result = await adminConsoleConfigService.buildConfig("clone", cipherId);
expect(result.allowPersonalOwnership).toBe(false); expect(result.allowPersonalOwnership).toBe(false);
policyAppliesToActiveUser$.next(false); policyAppliesToUser$.next(false);
result = await adminConsoleConfigService.buildConfig("clone", cipherId); result = await adminConsoleConfigService.buildConfig("clone", cipherId);
@@ -143,7 +143,7 @@ describe("AdminConsoleCipherFormConfigService", () => {
}); });
it("disables personal ownership when not cloning", async () => { it("disables personal ownership when not cloning", async () => {
policyAppliesToActiveUser$.next(false); policyAppliesToUser$.next(false);
let result = await adminConsoleConfigService.buildConfig("add", cipherId); let result = await adminConsoleConfigService.buildConfig("add", cipherId);

View File

@@ -10,6 +10,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums"; import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
@@ -30,9 +31,13 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
private apiService: ApiService = inject(ApiService); private apiService: ApiService = inject(ApiService);
private accountService: AccountService = inject(AccountService); private accountService: AccountService = inject(AccountService);
private allowPersonalOwnership$ = this.policyService private allowPersonalOwnership$ = this.accountService.activeAccount$.pipe(
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership) getUserId,
.pipe(map((p) => !p)); switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId),
),
map((p) => !p),
);
private organizationId$ = this.routedVaultFilterService.filter$.pipe( private organizationId$ = this.routedVaultFilterService.filter$.pipe(
map((filter) => filter.organizationId), map((filter) => filter.organizationId),

View File

@@ -8,6 +8,7 @@ import {
map, map,
Observable, Observable,
Subject, Subject,
switchMap,
take, take,
takeUntil, takeUntil,
withLatestFrom, withLatestFrom,
@@ -18,6 +19,8 @@ import { OrgDomainServiceAbstraction } from "@bitwarden/common/admin-console/abs
import { OrganizationDomainResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain.response"; import { OrganizationDomainResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain.response";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { HttpStatusCode } from "@bitwarden/common/enums"; import { HttpStatusCode } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
@@ -55,6 +58,7 @@ export class DomainVerificationComponent implements OnInit, OnDestroy {
private toastService: ToastService, private toastService: ToastService,
private configService: ConfigService, private configService: ConfigService,
private policyService: PolicyService, private policyService: PolicyService,
private accountService: AccountService,
) { ) {
this.accountDeprovisioningEnabled$ = this.configService.getFeatureFlag$( this.accountDeprovisioningEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.AccountDeprovisioning, FeatureFlag.AccountDeprovisioning,
@@ -83,7 +87,9 @@ export class DomainVerificationComponent implements OnInit, OnDestroy {
if (await this.configService.getFeatureFlag(FeatureFlag.AccountDeprovisioning)) { if (await this.configService.getFeatureFlag(FeatureFlag.AccountDeprovisioning)) {
const singleOrgPolicy = await firstValueFrom( const singleOrgPolicy = await firstValueFrom(
this.policyService.policies$.pipe( this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.policyService.policies$(userId)),
map((policies) => map((policies) =>
policies.find( policies.find(
(p) => p.type === PolicyType.SingleOrg && p.organizationId === this.organizationId, (p) => p.type === PolicyType.SingleOrg && p.organizationId === this.organizationId,

View File

@@ -1,11 +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 { Directive, OnDestroy, OnInit } from "@angular/core"; import { Directive, OnDestroy, OnInit } from "@angular/core";
import { Subject, firstValueFrom, map, takeUntil } from "rxjs"; import { Subject, firstValueFrom, map, switchMap, takeUntil } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
@@ -52,9 +53,12 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
this.email = await firstValueFrom( this.email = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.email)), this.accountService.activeAccount$.pipe(map((a) => a?.email)),
); );
this.policyService this.accountService.activeAccount$
.masterPasswordPolicyOptions$() .pipe(
.pipe(takeUntil(this.destroy$)) getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)),
takeUntil(this.destroy$),
)
.subscribe( .subscribe(
(enforcedPasswordPolicyOptions) => (enforcedPasswordPolicyOptions) =>
(this.enforcedPolicyOptions ??= enforcedPasswordPolicyOptions), (this.enforcedPolicyOptions ??= enforcedPasswordPolicyOptions),

View File

@@ -75,12 +75,13 @@ import { OrganizationApiService } from "@bitwarden/common/admin-console/services
import { OrgDomainApiService } from "@bitwarden/common/admin-console/services/organization-domain/org-domain-api.service"; import { OrgDomainApiService } from "@bitwarden/common/admin-console/services/organization-domain/org-domain-api.service";
import { OrgDomainService } from "@bitwarden/common/admin-console/services/organization-domain/org-domain.service"; import { OrgDomainService } from "@bitwarden/common/admin-console/services/organization-domain/org-domain.service";
import { DefaultOrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/services/organization-management-preferences/default-organization-management-preferences.service"; import { DefaultOrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/services/organization-management-preferences/default-organization-management-preferences.service";
import { DefaultPolicyService } from "@bitwarden/common/admin-console/services/policy/default-policy.service";
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { ProviderApiService } from "@bitwarden/common/admin-console/services/provider/provider-api.service"; import { ProviderApiService } from "@bitwarden/common/admin-console/services/provider/provider-api.service";
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service"; import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service";
import { import {
AccountService,
AccountService as AccountServiceAbstraction, AccountService as AccountServiceAbstraction,
InternalAccountService, InternalAccountService,
} from "@bitwarden/common/auth/abstractions/account.service"; } from "@bitwarden/common/auth/abstractions/account.service";
@@ -947,7 +948,7 @@ const safeProviders: SafeProvider[] = [
}), }),
safeProvider({ safeProvider({
provide: InternalPolicyService, provide: InternalPolicyService,
useClass: PolicyService, useClass: DefaultPolicyService,
deps: [StateProvider, OrganizationServiceAbstraction], deps: [StateProvider, OrganizationServiceAbstraction],
}), }),
safeProvider({ safeProvider({
@@ -957,7 +958,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({ safeProvider({
provide: PolicyApiServiceAbstraction, provide: PolicyApiServiceAbstraction,
useClass: PolicyApiService, useClass: PolicyApiService,
deps: [InternalPolicyService, ApiServiceAbstraction], deps: [InternalPolicyService, ApiServiceAbstraction, AccountService],
}), }),
safeProvider({ safeProvider({
provide: InternalMasterPasswordServiceAbstraction, provide: InternalMasterPasswordServiceAbstraction,
@@ -1259,7 +1260,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({ safeProvider({
provide: AutofillSettingsServiceAbstraction, provide: AutofillSettingsServiceAbstraction,
useClass: AutofillSettingsService, useClass: AutofillSettingsService,
deps: [StateProvider, PolicyServiceAbstraction], deps: [StateProvider, PolicyServiceAbstraction, AccountService],
}), }),
safeProvider({ safeProvider({
provide: BadgeSettingsServiceAbstraction, provide: BadgeSettingsServiceAbstraction,

View File

@@ -155,9 +155,14 @@ export class AddEditComponent implements OnInit, OnDestroy {
} }
async ngOnInit() { async ngOnInit() {
this.policyService this.accountService.activeAccount$
.policyAppliesToActiveUser$(PolicyType.DisableSend) .pipe(
.pipe(takeUntil(this.destroy$)) getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.DisableSend, userId),
),
takeUntil(this.destroy$),
)
.subscribe((policyAppliesToActiveUser) => { .subscribe((policyAppliesToActiveUser) => {
this.disableSend = policyAppliesToActiveUser; this.disableSend = policyAppliesToActiveUser;
if (this.disableSend) { if (this.disableSend) {
@@ -168,7 +173,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.accountService.activeAccount$ this.accountService.activeAccount$
.pipe( .pipe(
getUserId, getUserId,
switchMap((userId) => this.policyService.getAll$(PolicyType.SendOptions, userId)), switchMap((userId) => this.policyService.policiesByType$(PolicyType.SendOptions, userId)),
map((policies) => policies?.some((p) => p.data.disableHideEmail)), map((policies) => policies?.some((p) => p.data.disableHideEmail)),
takeUntil(this.destroy$), takeUntil(this.destroy$),
) )

View File

@@ -9,6 +9,7 @@ import {
from, from,
switchMap, switchMap,
takeUntil, takeUntil,
combineLatest,
} from "rxjs"; } from "rxjs";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
@@ -85,18 +86,23 @@ export class SendComponent implements OnInit, OnDestroy {
) {} ) {}
async ngOnInit() { async ngOnInit() {
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.accountService.activeAccount$
.pipe(
this.policyService getUserId,
.policyAppliesToActiveUser$(PolicyType.DisableSend) switchMap((userId) =>
.pipe(takeUntil(this.destroy$)) this.policyService.policyAppliesToUser$(PolicyType.DisableSend, userId),
.subscribe((policyAppliesToActiveUser) => { ),
this.disableSend = policyAppliesToActiveUser; takeUntil(this.destroy$),
)
.subscribe((policyAppliesToUser) => {
this.disableSend = policyAppliesToUser;
}); });
this._searchText$ combineLatest([this._searchText$, this.accountService.activeAccount$.pipe(getUserId)])
.pipe( .pipe(
switchMap((searchText) => from(this.searchService.isSearchable(userId, searchText))), switchMap(([searchText, userId]) =>
from(this.searchService.isSearchable(userId, searchText)),
),
takeUntil(this.destroy$), takeUntil(this.destroy$),
) )
.subscribe((isSearchable) => { .subscribe((isSearchable) => {

View File

@@ -2,7 +2,7 @@
// @ts-strict-ignore // @ts-strict-ignore
import { DatePipe } from "@angular/common"; import { DatePipe } from "@angular/common";
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { concatMap, firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; import { concatMap, firstValueFrom, map, Observable, Subject, switchMap, takeUntil } from "rxjs";
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service";
@@ -193,9 +193,12 @@ export class AddEditComponent implements OnInit, OnDestroy {
} }
async ngOnInit() { async ngOnInit() {
this.policyService this.accountService.activeAccount$
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
.pipe( .pipe(
getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId),
),
concatMap(async (policyAppliesToActiveUser) => { concatMap(async (policyAppliesToActiveUser) => {
this.personalOwnershipPolicyAppliesToActiveUser = policyAppliesToActiveUser; this.personalOwnershipPolicyAppliesToActiveUser = policyAppliesToActiveUser;
await this.init(); await this.init();

View File

@@ -112,13 +112,23 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
async checkForSingleOrganizationPolicy(): Promise<boolean> { async checkForSingleOrganizationPolicy(): Promise<boolean> {
return await firstValueFrom( return await firstValueFrom(
this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg), this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.SingleOrg, userId),
),
),
); );
} }
async checkForPersonalOwnershipPolicy(): Promise<boolean> { async checkForPersonalOwnershipPolicy(): Promise<boolean> {
return await firstValueFrom( return await firstValueFrom(
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId),
),
),
); );
} }

View File

@@ -2,25 +2,32 @@ import { ComponentFixture, TestBed } from "@angular/core/testing";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { import {
VaultTimeoutSettingsService, VaultTimeoutSettingsService,
VaultTimeoutStringType, VaultTimeoutStringType,
} from "@bitwarden/common/key-management/vault-timeout"; } from "@bitwarden/common/key-management/vault-timeout";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { VaultTimeoutInputComponent } from "./vault-timeout-input.component"; import { VaultTimeoutInputComponent } from "./vault-timeout-input.component";
describe("VaultTimeoutInputComponent", () => { describe("VaultTimeoutInputComponent", () => {
let component: VaultTimeoutInputComponent; let component: VaultTimeoutInputComponent;
let fixture: ComponentFixture<VaultTimeoutInputComponent>; let fixture: ComponentFixture<VaultTimeoutInputComponent>;
const get$ = jest.fn().mockReturnValue(new BehaviorSubject({})); const policiesByType$ = jest.fn().mockReturnValue(new BehaviorSubject({}));
const availableVaultTimeoutActions$ = jest.fn().mockReturnValue(new BehaviorSubject([])); const availableVaultTimeoutActions$ = jest.fn().mockReturnValue(new BehaviorSubject([]));
const mockUserId = Utils.newGuid() as UserId;
const accountService = mockAccountServiceWith(mockUserId);
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [VaultTimeoutInputComponent], imports: [VaultTimeoutInputComponent],
providers: [ providers: [
{ provide: PolicyService, useValue: { get$ } }, { provide: PolicyService, useValue: { policiesByType$ } },
{ provide: AccountService, useValue: accountService },
{ provide: VaultTimeoutSettingsService, useValue: { availableVaultTimeoutActions$ } }, { provide: VaultTimeoutSettingsService, useValue: { availableVaultTimeoutActions$ } },
{ provide: I18nService, useValue: { t: (key: string) => key } }, { provide: I18nService, useValue: { t: (key: string) => key } },
], ],

View File

@@ -14,12 +14,15 @@ import {
ValidationErrors, ValidationErrors,
Validator, Validator,
} from "@angular/forms"; } from "@angular/forms";
import { filter, map, Observable, Subject, takeUntil } from "rxjs"; import { filter, map, Observable, Subject, switchMap, takeUntil } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
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 {
VaultTimeout, VaultTimeout,
VaultTimeoutAction, VaultTimeoutAction,
@@ -123,12 +126,17 @@ export class VaultTimeoutInputComponent
private policyService: PolicyService, private policyService: PolicyService,
private vaultTimeoutSettingsService: VaultTimeoutSettingsService, private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
private i18nService: I18nService, private i18nService: I18nService,
private accountService: AccountService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
this.policyService this.accountService.activeAccount$
.get$(PolicyType.MaximumVaultTimeout)
.pipe( .pipe(
getUserId,
switchMap((userId) =>
this.policyService.policiesByType$(PolicyType.MaximumVaultTimeout, userId),
),
getFirstPolicy,
filter((policy) => policy != null), filter((policy) => policy != null),
takeUntil(this.destroy$), takeUntil(this.destroy$),
) )
@@ -136,7 +144,6 @@ export class VaultTimeoutInputComponent
this.vaultTimeoutPolicy = policy; this.vaultTimeoutPolicy = policy;
this.applyVaultTimeoutPolicy(); this.applyVaultTimeoutPolicy();
}); });
this.form.valueChanges this.form.valueChanges
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe((value: VaultTimeoutFormValue) => { .subscribe((value: VaultTimeoutFormValue) => {

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { ListResponse } from "../../../models/response/list.response"; import { ListResponse } from "../../../models/response/list.response";
import { PolicyType } from "../../enums"; import { PolicyType } from "../../enums";
import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options";
@@ -7,19 +5,23 @@ import { Policy } from "../../models/domain/policy";
import { PolicyRequest } from "../../models/request/policy.request"; import { PolicyRequest } from "../../models/request/policy.request";
import { PolicyResponse } from "../../models/response/policy.response"; import { PolicyResponse } from "../../models/response/policy.response";
export class PolicyApiServiceAbstraction { export abstract class PolicyApiServiceAbstraction {
getPolicy: (organizationId: string, type: PolicyType) => Promise<PolicyResponse>; abstract getPolicy: (organizationId: string, type: PolicyType) => Promise<PolicyResponse>;
getPolicies: (organizationId: string) => Promise<ListResponse<PolicyResponse>>; abstract getPolicies: (organizationId: string) => Promise<ListResponse<PolicyResponse>>;
getPoliciesByToken: ( abstract getPoliciesByToken: (
organizationId: string, organizationId: string,
token: string, token: string,
email: string, email: string,
organizationUserId: string, organizationUserId: string,
) => Promise<Policy[] | undefined>; ) => Promise<Policy[] | undefined>;
getMasterPasswordPolicyOptsForOrgUser: ( abstract getMasterPasswordPolicyOptsForOrgUser: (
orgId: string, orgId: string,
) => Promise<MasterPasswordPolicyOptions | null>; ) => Promise<MasterPasswordPolicyOptions | null>;
putPolicy: (organizationId: string, type: PolicyType, request: PolicyRequest) => Promise<any>; abstract putPolicy: (
organizationId: string,
type: PolicyType,
request: PolicyRequest,
) => Promise<any>;
} }

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { UserId } from "../../../types/guid"; import { UserId } from "../../../types/guid";
@@ -11,43 +9,27 @@ import { ResetPasswordPolicyOptions } from "../../models/domain/reset-password-p
export abstract class PolicyService { export abstract class PolicyService {
/** /**
* All policies for the active user from sync data. * All policies for the provided user from sync data.
* May include policies that are disabled or otherwise do not apply to the user. Be careful using this! * May include policies that are disabled or otherwise do not apply to the user. Be careful using this!
* Consider using {@link get$} or {@link getAll$} instead, which will only return policies that should be enforced against the user. * Consider {@link policiesByType$} instead, which will only return policies that should be enforced against the user.
*/ */
policies$: Observable<Policy[]>; abstract policies$: (userId: UserId) => Observable<Policy[]>;
/** /**
* @returns the first {@link Policy} found that applies to the active user. * @returns all {@link Policy} objects of a given type that apply to the specified user.
* A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner). * A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner).
* @param policyType the {@link PolicyType} to search for * @param policyType the {@link PolicyType} to search for
* @see {@link getAll$} if you need all policies of a given type * @param userId the {@link UserId} to search against
*/ */
get$: (policyType: PolicyType) => Observable<Policy>; abstract policiesByType$: (policyType: PolicyType, userId: UserId) => Observable<Policy[]>;
/** /**
* @returns all {@link Policy} objects of a given type that apply to the specified user (or the active user if not specified). * @returns true if a policy of the specified type applies to the specified user, otherwise false.
* A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner). * A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner).
* @param policyType the {@link PolicyType} to search for * This does not take into account the policy's configuration - if that is important, use {@link policiesByType$} to get the
*/
getAll$: (policyType: PolicyType, userId: UserId) => Observable<Policy[]>;
/**
* All {@link Policy} objects for the specified user (from sync data).
* May include policies that are disabled or otherwise do not apply to the user.
* Consider using {@link getAll$} instead, which will only return policies that should be enforced against the user.
*/
getAll: (policyType: PolicyType) => Promise<Policy[]>;
/**
* @returns true if a policy of the specified type applies to the active user, otherwise false.
* A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner).
* This does not take into account the policy's configuration - if that is important, use {@link getAll$} to get the
* {@link Policy} objects and then filter by Policy.data. * {@link Policy} objects and then filter by Policy.data.
*/ */
policyAppliesToActiveUser$: (policyType: PolicyType) => Observable<boolean>; abstract policyAppliesToUser$: (policyType: PolicyType, userId: UserId) => Observable<boolean>;
policyAppliesToUser: (policyType: PolicyType) => Promise<boolean>;
// Policy specific interfaces // Policy specific interfaces
@@ -56,28 +38,31 @@ export abstract class PolicyService {
* @returns a set of options which represent the minimum Master Password settings that the user must * @returns a set of options which represent the minimum Master Password settings that the user must
* comply with in order to comply with **all** Master Password policies. * comply with in order to comply with **all** Master Password policies.
*/ */
masterPasswordPolicyOptions$: (policies?: Policy[]) => Observable<MasterPasswordPolicyOptions>; abstract masterPasswordPolicyOptions$: (
userId: UserId,
policies?: Policy[],
) => Observable<MasterPasswordPolicyOptions | undefined>;
/** /**
* Evaluates whether a proposed Master Password complies with all Master Password policies that apply to the user. * Evaluates whether a proposed Master Password complies with all Master Password policies that apply to the user.
*/ */
evaluateMasterPassword: ( abstract evaluateMasterPassword: (
passwordStrength: number, passwordStrength: number,
newPassword: string, newPassword: string,
enforcedPolicyOptions?: MasterPasswordPolicyOptions, enforcedPolicyOptions?: MasterPasswordPolicyOptions,
) => boolean; ) => boolean;
/** /**
* @returns Reset Password policy options for the specified organization and a boolean indicating whether the policy * @returns {@link ResetPasswordPolicyOptions} for the specified organization and a boolean indicating whether the policy
* is enabled * is enabled
*/ */
getResetPasswordPolicyOptions: ( abstract getResetPasswordPolicyOptions: (
policies: Policy[], policies: Policy[],
orgId: string, orgId: string,
) => [ResetPasswordPolicyOptions, boolean]; ) => [ResetPasswordPolicyOptions, boolean];
} }
export abstract class InternalPolicyService extends PolicyService { export abstract class InternalPolicyService extends PolicyService {
upsert: (policy: PolicyData) => Promise<void>; abstract upsert: (policy: PolicyData, userId: UserId) => Promise<void>;
replace: (policies: { [id: string]: PolicyData }, userId: UserId) => Promise<void>; abstract replace: (policies: { [id: string]: PolicyData }, userId: UserId) => Promise<void>;
} }

View File

@@ -1,68 +0,0 @@
import { Observable } from "rxjs";
import { UserId } from "../../../types/guid";
import { PolicyType } from "../../enums";
import { PolicyData } from "../../models/data/policy.data";
import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options";
import { Policy } from "../../models/domain/policy";
import { ResetPasswordPolicyOptions } from "../../models/domain/reset-password-policy-options";
export abstract class vNextPolicyService {
/**
* All policies for the provided user from sync data.
* May include policies that are disabled or otherwise do not apply to the user. Be careful using this!
* Consider {@link policiesByType$} instead, which will only return policies that should be enforced against the user.
*/
abstract policies$: (userId: UserId) => Observable<Policy[]>;
/**
* @returns all {@link Policy} objects of a given type that apply to the specified user.
* A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner).
* @param policyType the {@link PolicyType} to search for
* @param userId the {@link UserId} to search against
*/
abstract policiesByType$: (policyType: PolicyType, userId: UserId) => Observable<Policy[]>;
/**
* @returns true if a policy of the specified type applies to the specified user, otherwise false.
* A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner).
* This does not take into account the policy's configuration - if that is important, use {@link policiesByType$} to get the
* {@link Policy} objects and then filter by Policy.data.
*/
abstract policyAppliesToUser$: (policyType: PolicyType, userId: UserId) => Observable<boolean>;
// Policy specific interfaces
/**
* Combines all Master Password policies that apply to the user.
* @returns a set of options which represent the minimum Master Password settings that the user must
* comply with in order to comply with **all** Master Password policies.
*/
abstract masterPasswordPolicyOptions$: (
userId: UserId,
policies?: Policy[],
) => Observable<MasterPasswordPolicyOptions | undefined>;
/**
* Evaluates whether a proposed Master Password complies with all Master Password policies that apply to the user.
*/
abstract evaluateMasterPassword: (
passwordStrength: number,
newPassword: string,
enforcedPolicyOptions?: MasterPasswordPolicyOptions,
) => boolean;
/**
* @returns {@link ResetPasswordPolicyOptions} for the specified organization and a boolean indicating whether the policy
* is enabled
*/
abstract getResetPasswordPolicyOptions: (
policies: Policy[],
orgId: string,
) => [ResetPasswordPolicyOptions, boolean];
}
export abstract class vNextInternalPolicyService extends vNextPolicyService {
abstract upsert: (policy: PolicyData, userId: UserId) => Promise<void>;
abstract replace: (policies: { [id: string]: PolicyData }, userId: UserId) => Promise<void>;
}

View File

@@ -15,11 +15,11 @@ import { MasterPasswordPolicyOptions } from "../../../admin-console/models/domai
import { Organization } from "../../../admin-console/models/domain/organization"; import { Organization } from "../../../admin-console/models/domain/organization";
import { Policy } from "../../../admin-console/models/domain/policy"; import { Policy } from "../../../admin-console/models/domain/policy";
import { ResetPasswordPolicyOptions } from "../../../admin-console/models/domain/reset-password-policy-options"; import { ResetPasswordPolicyOptions } from "../../../admin-console/models/domain/reset-password-policy-options";
import { POLICIES } from "../../../admin-console/services/policy/policy.service";
import { PolicyId, UserId } from "../../../types/guid"; import { PolicyId, UserId } from "../../../types/guid";
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
import { DefaultvNextPolicyService, getFirstPolicy } from "./default-vnext-policy.service"; import { DefaultPolicyService, getFirstPolicy } from "./default-policy.service";
import { POLICIES } from "./policy-state";
describe("PolicyService", () => { describe("PolicyService", () => {
const userId = "userId" as UserId; const userId = "userId" as UserId;
@@ -27,7 +27,7 @@ describe("PolicyService", () => {
let organizationService: MockProxy<OrganizationService>; let organizationService: MockProxy<OrganizationService>;
let singleUserState: FakeSingleUserState<Record<PolicyId, PolicyData>>; let singleUserState: FakeSingleUserState<Record<PolicyId, PolicyData>>;
let policyService: DefaultvNextPolicyService; let policyService: DefaultPolicyService;
beforeEach(() => { beforeEach(() => {
const accountService = mockAccountServiceWith(userId); const accountService = mockAccountServiceWith(userId);
@@ -59,7 +59,7 @@ describe("PolicyService", () => {
organizationService.organizations$.calledWith(userId).mockReturnValue(organizations$); organizationService.organizations$.calledWith(userId).mockReturnValue(organizations$);
policyService = new DefaultvNextPolicyService(stateProvider, organizationService); policyService = new DefaultPolicyService(stateProvider, organizationService);
}); });
it("upsert", async () => { it("upsert", async () => {

View File

@@ -3,7 +3,7 @@ import { combineLatest, map, Observable, of } from "rxjs";
import { StateProvider } from "../../../platform/state"; import { StateProvider } from "../../../platform/state";
import { UserId } from "../../../types/guid"; import { UserId } from "../../../types/guid";
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
import { vNextPolicyService } from "../../abstractions/policy/vnext-policy.service"; import { PolicyService } from "../../abstractions/policy/policy.service.abstraction";
import { OrganizationUserStatusType, PolicyType } from "../../enums"; import { OrganizationUserStatusType, PolicyType } from "../../enums";
import { PolicyData } from "../../models/data/policy.data"; import { PolicyData } from "../../models/data/policy.data";
import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options";
@@ -11,7 +11,7 @@ import { Organization } from "../../models/domain/organization";
import { Policy } from "../../models/domain/policy"; import { Policy } from "../../models/domain/policy";
import { ResetPasswordPolicyOptions } from "../../models/domain/reset-password-policy-options"; import { ResetPasswordPolicyOptions } from "../../models/domain/reset-password-policy-options";
import { POLICIES } from "./vnext-policy-state"; import { POLICIES } from "./policy-state";
export function policyRecordToArray(policiesMap: { [id: string]: PolicyData }) { export function policyRecordToArray(policiesMap: { [id: string]: PolicyData }) {
return Object.values(policiesMap || {}).map((f) => new Policy(f)); return Object.values(policiesMap || {}).map((f) => new Policy(f));
@@ -21,7 +21,7 @@ export const getFirstPolicy = map<Policy[], Policy | undefined>((policies) => {
return policies.at(0) ?? undefined; return policies.at(0) ?? undefined;
}); });
export class DefaultvNextPolicyService implements vNextPolicyService { export class DefaultPolicyService implements PolicyService {
constructor( constructor(
private stateProvider: StateProvider, private stateProvider: StateProvider,
private organizationService: OrganizationService, private organizationService: OrganizationService,
@@ -89,7 +89,7 @@ export class DefaultvNextPolicyService implements vNextPolicyService {
const policies$ = policies ? of(policies) : this.policies$(userId); const policies$ = policies ? of(policies) : this.policies$(userId);
return policies$.pipe( return policies$.pipe(
map((obsPolicies) => { map((obsPolicies) => {
const enforcedOptions: MasterPasswordPolicyOptions = new MasterPasswordPolicyOptions(); let enforcedOptions: MasterPasswordPolicyOptions | undefined = undefined;
const filteredPolicies = const filteredPolicies =
obsPolicies.filter((p) => p.type === PolicyType.MasterPassword) ?? []; obsPolicies.filter((p) => p.type === PolicyType.MasterPassword) ?? [];
@@ -102,6 +102,10 @@ export class DefaultvNextPolicyService implements vNextPolicyService {
return; return;
} }
if (!enforcedOptions) {
enforcedOptions = new MasterPasswordPolicyOptions();
}
if ( if (
currentPolicy.data.minComplexity != null && currentPolicy.data.minComplexity != null &&
currentPolicy.data.minComplexity > enforcedOptions.minComplexity currentPolicy.data.minComplexity > enforcedOptions.minComplexity

View File

@@ -1,6 +1,8 @@
import { firstValueFrom } from "rxjs"; import { firstValueFrom, map, switchMap } from "rxjs";
import { ApiService } from "../../../abstractions/api.service"; import { ApiService } from "../../../abstractions/api.service";
import { AccountService } from "../../../auth/abstractions/account.service";
import { getUserId } from "../../../auth/services/account.service";
import { HttpStatusCode } from "../../../enums"; import { HttpStatusCode } from "../../../enums";
import { ErrorResponse } from "../../../models/response/error.response"; import { ErrorResponse } from "../../../models/response/error.response";
import { ListResponse } from "../../../models/response/list.response"; import { ListResponse } from "../../../models/response/list.response";
@@ -18,6 +20,7 @@ export class PolicyApiService implements PolicyApiServiceAbstraction {
constructor( constructor(
private policyService: InternalPolicyService, private policyService: InternalPolicyService,
private apiService: ApiService, private apiService: ApiService,
private accountService: AccountService,
) {} ) {}
async getPolicy(organizationId: string, type: PolicyType): Promise<PolicyResponse> { async getPolicy(organizationId: string, type: PolicyType): Promise<PolicyResponse> {
@@ -93,8 +96,14 @@ export class PolicyApiService implements PolicyApiServiceAbstraction {
return null; return null;
} }
return await firstValueFrom( return firstValueFrom(
this.policyService.masterPasswordPolicyOptions$([masterPasswordPolicy]), this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.masterPasswordPolicyOptions$(userId, [masterPasswordPolicy]),
),
map((policy) => policy ?? null),
),
); );
} catch (error) { } catch (error) {
// If policy not found, return null // If policy not found, return null
@@ -114,8 +123,9 @@ export class PolicyApiService implements PolicyApiServiceAbstraction {
true, true,
true, true,
); );
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const response = new PolicyResponse(r); const response = new PolicyResponse(r);
const data = new PolicyData(response); const data = new PolicyData(response);
await this.policyService.upsert(data); await this.policyService.upsert(data, userId);
} }
} }

View File

@@ -1,556 +0,0 @@
import { mock, MockProxy } from "jest-mock-extended";
import { firstValueFrom, of } from "rxjs";
import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec";
import { FakeActiveUserState, FakeSingleUserState } from "../../../../spec/fake-state";
import {
OrganizationUserStatusType,
OrganizationUserType,
PolicyType,
} from "../../../admin-console/enums";
import { PermissionsApi } from "../../../admin-console/models/api/permissions.api";
import { OrganizationData } from "../../../admin-console/models/data/organization.data";
import { PolicyData } from "../../../admin-console/models/data/policy.data";
import { MasterPasswordPolicyOptions } from "../../../admin-console/models/domain/master-password-policy-options";
import { Organization } from "../../../admin-console/models/domain/organization";
import { Policy } from "../../../admin-console/models/domain/policy";
import { ResetPasswordPolicyOptions } from "../../../admin-console/models/domain/reset-password-policy-options";
import { POLICIES, PolicyService } from "../../../admin-console/services/policy/policy.service";
import { PolicyId, UserId } from "../../../types/guid";
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
describe("PolicyService", () => {
const userId = "userId" as UserId;
let stateProvider: FakeStateProvider;
let organizationService: MockProxy<OrganizationService>;
let activeUserState: FakeActiveUserState<Record<PolicyId, PolicyData>>;
let singleUserState: FakeSingleUserState<Record<PolicyId, PolicyData>>;
let policyService: PolicyService;
beforeEach(() => {
const accountService = mockAccountServiceWith(userId);
stateProvider = new FakeStateProvider(accountService);
organizationService = mock<OrganizationService>();
activeUserState = stateProvider.activeUser.getFake(POLICIES);
singleUserState = stateProvider.singleUser.getFake(activeUserState.userId, POLICIES);
const organizations$ = of([
// User
organization("org1", true, true, OrganizationUserStatusType.Confirmed, false),
// Owner
organization(
"org2",
true,
true,
OrganizationUserStatusType.Confirmed,
false,
OrganizationUserType.Owner,
),
// Does not use policies
organization("org3", true, false, OrganizationUserStatusType.Confirmed, false),
// Another User
organization("org4", true, true, OrganizationUserStatusType.Confirmed, false),
// Another User
organization("org5", true, true, OrganizationUserStatusType.Confirmed, false),
// Can manage policies
organization("org6", true, true, OrganizationUserStatusType.Confirmed, true),
]);
organizationService.organizations$.mockReturnValue(organizations$);
policyService = new PolicyService(stateProvider, organizationService);
});
it("upsert", async () => {
activeUserState.nextState(
arrayToRecord([
policyData("1", "test-organization", PolicyType.MaximumVaultTimeout, true, { minutes: 14 }),
]),
);
await policyService.upsert(policyData("99", "test-organization", PolicyType.DisableSend, true));
expect(await firstValueFrom(policyService.policies$)).toEqual([
{
id: "1",
organizationId: "test-organization",
type: PolicyType.MaximumVaultTimeout,
enabled: true,
data: { minutes: 14 },
},
{
id: "99",
organizationId: "test-organization",
type: PolicyType.DisableSend,
enabled: true,
},
]);
});
it("replace", async () => {
activeUserState.nextState(
arrayToRecord([
policyData("1", "test-organization", PolicyType.MaximumVaultTimeout, true, { minutes: 14 }),
]),
);
await policyService.replace(
{
"2": policyData("2", "test-organization", PolicyType.DisableSend, true),
},
userId,
);
expect(await firstValueFrom(policyService.policies$)).toEqual([
{
id: "2",
organizationId: "test-organization",
type: PolicyType.DisableSend,
enabled: true,
},
]);
});
describe("masterPasswordPolicyOptions", () => {
it("returns default policy options", async () => {
const data: any = {
minComplexity: 5,
minLength: 20,
requireUpper: true,
};
const model = [
new Policy(policyData("1", "test-organization-3", PolicyType.MasterPassword, true, data)),
];
const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(model));
expect(result).toEqual({
minComplexity: 5,
minLength: 20,
requireLower: false,
requireNumbers: false,
requireSpecial: false,
requireUpper: true,
enforceOnLogin: false,
});
});
it("returns null", async () => {
const data: any = {};
const model = [
new Policy(
policyData("3", "test-organization-3", PolicyType.DisablePersonalVaultExport, true, data),
),
new Policy(
policyData("4", "test-organization-3", PolicyType.MaximumVaultTimeout, true, data),
),
];
const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(model));
expect(result).toEqual(null);
});
it("returns specified policy options", async () => {
const data: any = {
minLength: 14,
};
const model = [
new Policy(
policyData("3", "test-organization-3", PolicyType.DisablePersonalVaultExport, true, data),
),
new Policy(policyData("4", "test-organization-3", PolicyType.MasterPassword, true, data)),
];
const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(model));
expect(result).toEqual({
minComplexity: 0,
minLength: 14,
requireLower: false,
requireNumbers: false,
requireSpecial: false,
requireUpper: false,
enforceOnLogin: false,
});
});
});
describe("evaluateMasterPassword", () => {
it("false", async () => {
const enforcedPolicyOptions = new MasterPasswordPolicyOptions();
enforcedPolicyOptions.minLength = 14;
const result = policyService.evaluateMasterPassword(10, "password", enforcedPolicyOptions);
expect(result).toEqual(false);
});
it("true", async () => {
const enforcedPolicyOptions = new MasterPasswordPolicyOptions();
const result = policyService.evaluateMasterPassword(0, "password", enforcedPolicyOptions);
expect(result).toEqual(true);
});
});
describe("getResetPasswordPolicyOptions", () => {
it("default", async () => {
const result = policyService.getResetPasswordPolicyOptions([], "");
expect(result).toEqual([new ResetPasswordPolicyOptions(), false]);
});
it("returns autoEnrollEnabled true", async () => {
const data: any = {
autoEnrollEnabled: true,
};
const policies = [
new Policy(policyData("5", "test-organization-3", PolicyType.ResetPassword, true, data)),
];
const result = policyService.getResetPasswordPolicyOptions(policies, "test-organization-3");
expect(result).toEqual([{ autoEnrollEnabled: true }, true]);
});
});
describe("get$", () => {
it("returns the specified PolicyType", async () => {
activeUserState.nextState(
arrayToRecord([
policyData("policy1", "org1", PolicyType.ActivateAutofill, true),
policyData("policy2", "org1", PolicyType.DisablePersonalVaultExport, true),
policyData("policy3", "org1", PolicyType.RemoveUnlockWithPin, true),
]),
);
await expect(
firstValueFrom(policyService.get$(PolicyType.ActivateAutofill)),
).resolves.toMatchObject({
id: "policy1",
organizationId: "org1",
type: PolicyType.ActivateAutofill,
enabled: true,
});
await expect(
firstValueFrom(policyService.get$(PolicyType.DisablePersonalVaultExport)),
).resolves.toMatchObject({
id: "policy2",
organizationId: "org1",
type: PolicyType.DisablePersonalVaultExport,
enabled: true,
});
await expect(
firstValueFrom(policyService.get$(PolicyType.RemoveUnlockWithPin)),
).resolves.toMatchObject({
id: "policy3",
organizationId: "org1",
type: PolicyType.RemoveUnlockWithPin,
enabled: true,
});
});
it("does not return disabled policies", async () => {
activeUserState.nextState(
arrayToRecord([
policyData("policy1", "org1", PolicyType.ActivateAutofill, true),
policyData("policy2", "org1", PolicyType.DisablePersonalVaultExport, false),
]),
);
const result = await firstValueFrom(
policyService.get$(PolicyType.DisablePersonalVaultExport),
);
expect(result).toBeNull();
});
it("does not return policies that do not apply to the user because the user's role is exempt", async () => {
activeUserState.nextState(
arrayToRecord([
policyData("policy1", "org1", PolicyType.ActivateAutofill, true),
policyData("policy2", "org2", PolicyType.DisablePersonalVaultExport, false),
]),
);
const result = await firstValueFrom(
policyService.get$(PolicyType.DisablePersonalVaultExport),
);
expect(result).toBeNull();
});
it.each([
["owners", "org2"],
["administrators", "org6"],
])("returns the password generator policy for %s", async (_, organization) => {
activeUserState.nextState(
arrayToRecord([
policyData("policy1", "org1", PolicyType.ActivateAutofill, false),
policyData("policy2", organization, PolicyType.PasswordGenerator, true),
]),
);
const result = await firstValueFrom(policyService.get$(PolicyType.PasswordGenerator));
expect(result).toBeTruthy();
});
it("does not return policies for organizations that do not use policies", async () => {
activeUserState.nextState(
arrayToRecord([
policyData("policy1", "org3", PolicyType.ActivateAutofill, true),
policyData("policy2", "org2", PolicyType.DisablePersonalVaultExport, true),
]),
);
const result = await firstValueFrom(policyService.get$(PolicyType.ActivateAutofill));
expect(result).toBeNull();
});
});
describe("getAll$", () => {
it("returns the specified PolicyTypes", async () => {
singleUserState.nextState(
arrayToRecord([
policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true),
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, true),
policyData("policy4", "org1", PolicyType.DisablePersonalVaultExport, true),
]),
);
const result = await firstValueFrom(
policyService.getAll$(PolicyType.DisablePersonalVaultExport, activeUserState.userId),
);
expect(result).toEqual([
{
id: "policy1",
organizationId: "org4",
type: PolicyType.DisablePersonalVaultExport,
enabled: true,
},
{
id: "policy3",
organizationId: "org5",
type: PolicyType.DisablePersonalVaultExport,
enabled: true,
},
{
id: "policy4",
organizationId: "org1",
type: PolicyType.DisablePersonalVaultExport,
enabled: true,
},
]);
});
it("does not return disabled policies", async () => {
singleUserState.nextState(
arrayToRecord([
policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true),
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, false), // disabled
policyData("policy4", "org1", PolicyType.DisablePersonalVaultExport, true),
]),
);
const result = await firstValueFrom(
policyService.getAll$(PolicyType.DisablePersonalVaultExport, activeUserState.userId),
);
expect(result).toEqual([
{
id: "policy1",
organizationId: "org4",
type: PolicyType.DisablePersonalVaultExport,
enabled: true,
},
{
id: "policy4",
organizationId: "org1",
type: PolicyType.DisablePersonalVaultExport,
enabled: true,
},
]);
});
it("does not return policies that do not apply to the user because the user's role is exempt", async () => {
singleUserState.nextState(
arrayToRecord([
policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true),
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, true),
policyData("policy4", "org2", PolicyType.DisablePersonalVaultExport, true), // owner
]),
);
const result = await firstValueFrom(
policyService.getAll$(PolicyType.DisablePersonalVaultExport, activeUserState.userId),
);
expect(result).toEqual([
{
id: "policy1",
organizationId: "org4",
type: PolicyType.DisablePersonalVaultExport,
enabled: true,
},
{
id: "policy3",
organizationId: "org5",
type: PolicyType.DisablePersonalVaultExport,
enabled: true,
},
]);
});
it("does not return policies for organizations that do not use policies", async () => {
singleUserState.nextState(
arrayToRecord([
policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true),
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
policyData("policy3", "org3", PolicyType.DisablePersonalVaultExport, true), // does not use policies
policyData("policy4", "org1", PolicyType.DisablePersonalVaultExport, true),
]),
);
const result = await firstValueFrom(
policyService.getAll$(PolicyType.DisablePersonalVaultExport, activeUserState.userId),
);
expect(result).toEqual([
{
id: "policy1",
organizationId: "org4",
type: PolicyType.DisablePersonalVaultExport,
enabled: true,
},
{
id: "policy4",
organizationId: "org1",
type: PolicyType.DisablePersonalVaultExport,
enabled: true,
},
]);
});
});
describe("policyAppliesToActiveUser$", () => {
it("returns true when the policyType applies to the user", async () => {
activeUserState.nextState(
arrayToRecord([
policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true),
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, true),
policyData("policy4", "org1", PolicyType.DisablePersonalVaultExport, true),
]),
);
const result = await firstValueFrom(
policyService.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport),
);
expect(result).toBe(true);
});
it("returns false when policyType is disabled", async () => {
activeUserState.nextState(
arrayToRecord([
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, false), // disabled
]),
);
const result = await firstValueFrom(
policyService.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport),
);
expect(result).toBe(false);
});
it("returns false when the policyType does not apply to the user because the user's role is exempt", async () => {
activeUserState.nextState(
arrayToRecord([
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
policyData("policy4", "org2", PolicyType.DisablePersonalVaultExport, true), // owner
]),
);
const result = await firstValueFrom(
policyService.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport),
);
expect(result).toBe(false);
});
it("returns false for organizations that do not use policies", async () => {
activeUserState.nextState(
arrayToRecord([
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
policyData("policy3", "org3", PolicyType.DisablePersonalVaultExport, true), // does not use policies
]),
);
const result = await firstValueFrom(
policyService.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport),
);
expect(result).toBe(false);
});
});
function policyData(
id: string,
organizationId: string,
type: PolicyType,
enabled: boolean,
data?: any,
) {
const policyData = new PolicyData({} as any);
policyData.id = id as PolicyId;
policyData.organizationId = organizationId;
policyData.type = type;
policyData.enabled = enabled;
policyData.data = data;
return policyData;
}
function organizationData(
id: string,
enabled: boolean,
usePolicies: boolean,
status: OrganizationUserStatusType,
managePolicies: boolean,
type: OrganizationUserType = OrganizationUserType.User,
) {
const organizationData = new OrganizationData({} as any, {} as any);
organizationData.id = id;
organizationData.enabled = enabled;
organizationData.usePolicies = usePolicies;
organizationData.status = status;
organizationData.permissions = new PermissionsApi({ managePolicies: managePolicies } as any);
organizationData.type = type;
return organizationData;
}
function organization(
id: string,
enabled: boolean,
usePolicies: boolean,
status: OrganizationUserStatusType,
managePolicies: boolean,
type: OrganizationUserType = OrganizationUserType.User,
) {
return new Organization(
organizationData(id, enabled, usePolicies, status, managePolicies, type),
);
}
function arrayToRecord(input: PolicyData[]): Record<PolicyId, PolicyData> {
return Object.fromEntries(input.map((i) => [i.id, i]));
}
});

View File

@@ -1,257 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { combineLatest, firstValueFrom, map, Observable, of, switchMap } from "rxjs";
import { UserKeyDefinition, POLICIES_DISK, StateProvider } from "../../../platform/state";
import { PolicyId, UserId } from "../../../types/guid";
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
import { InternalPolicyService as InternalPolicyServiceAbstraction } from "../../abstractions/policy/policy.service.abstraction";
import { OrganizationUserStatusType, PolicyType } from "../../enums";
import { PolicyData } from "../../models/data/policy.data";
import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options";
import { Organization } from "../../models/domain/organization";
import { Policy } from "../../models/domain/policy";
import { ResetPasswordPolicyOptions } from "../../models/domain/reset-password-policy-options";
const policyRecordToArray = (policiesMap: { [id: string]: PolicyData }) =>
Object.values(policiesMap || {}).map((f) => new Policy(f));
export const POLICIES = UserKeyDefinition.record<PolicyData, PolicyId>(POLICIES_DISK, "policies", {
deserializer: (policyData) => policyData,
clearOn: ["logout"],
});
export class PolicyService implements InternalPolicyServiceAbstraction {
private activeUserPolicyState = this.stateProvider.getActive(POLICIES);
private activeUserPolicies$ = this.activeUserPolicyState.state$.pipe(
map((policyData) => policyRecordToArray(policyData)),
);
policies$ = this.activeUserPolicies$;
constructor(
private stateProvider: StateProvider,
private organizationService: OrganizationService,
) {}
get$(policyType: PolicyType): Observable<Policy> {
const filteredPolicies$ = this.activeUserPolicies$.pipe(
map((policies) => policies.filter((p) => p.type === policyType)),
);
const organizations$ = this.stateProvider.activeUserId$.pipe(
switchMap((userId) => this.organizationService.organizations$(userId)),
);
return combineLatest([filteredPolicies$, organizations$]).pipe(
map(
([policies, organizations]) =>
this.enforcedPolicyFilter(policies, organizations)?.at(0) ?? null,
),
);
}
getAll$(policyType: PolicyType, userId: UserId) {
const filteredPolicies$ = this.stateProvider.getUserState$(POLICIES, userId).pipe(
map((policyData) => policyRecordToArray(policyData)),
map((policies) => policies.filter((p) => p.type === policyType)),
);
return combineLatest([filteredPolicies$, this.organizationService.organizations$(userId)]).pipe(
map(([policies, organizations]) => this.enforcedPolicyFilter(policies, organizations)),
);
}
async getAll(policyType: PolicyType) {
return await firstValueFrom(
this.policies$.pipe(map((policies) => policies.filter((p) => p.type === policyType))),
);
}
policyAppliesToActiveUser$(policyType: PolicyType) {
return this.get$(policyType).pipe(map((policy) => policy != null));
}
async policyAppliesToUser(policyType: PolicyType) {
return await firstValueFrom(this.policyAppliesToActiveUser$(policyType));
}
private enforcedPolicyFilter(policies: Policy[], organizations: Organization[]) {
const orgDict = Object.fromEntries(organizations.map((o) => [o.id, o]));
return policies.filter((policy) => {
const organization = orgDict[policy.organizationId];
// This shouldn't happen, i.e. the user should only have policies for orgs they are a member of
// But if it does, err on the side of enforcing the policy
if (organization == null) {
return true;
}
return (
policy.enabled &&
organization.status >= OrganizationUserStatusType.Accepted &&
organization.usePolicies &&
!this.isExemptFromPolicy(policy.type, organization)
);
});
}
masterPasswordPolicyOptions$(policies?: Policy[]): Observable<MasterPasswordPolicyOptions> {
const observable = policies ? of(policies) : this.policies$;
return observable.pipe(
map((obsPolicies) => {
let enforcedOptions: MasterPasswordPolicyOptions = null;
const filteredPolicies = obsPolicies.filter((p) => p.type === PolicyType.MasterPassword);
if (filteredPolicies == null || filteredPolicies.length === 0) {
return enforcedOptions;
}
filteredPolicies.forEach((currentPolicy) => {
if (!currentPolicy.enabled || currentPolicy.data == null) {
return;
}
if (enforcedOptions == null) {
enforcedOptions = new MasterPasswordPolicyOptions();
}
if (
currentPolicy.data.minComplexity != null &&
currentPolicy.data.minComplexity > enforcedOptions.minComplexity
) {
enforcedOptions.minComplexity = currentPolicy.data.minComplexity;
}
if (
currentPolicy.data.minLength != null &&
currentPolicy.data.minLength > enforcedOptions.minLength
) {
enforcedOptions.minLength = currentPolicy.data.minLength;
}
if (currentPolicy.data.requireUpper) {
enforcedOptions.requireUpper = true;
}
if (currentPolicy.data.requireLower) {
enforcedOptions.requireLower = true;
}
if (currentPolicy.data.requireNumbers) {
enforcedOptions.requireNumbers = true;
}
if (currentPolicy.data.requireSpecial) {
enforcedOptions.requireSpecial = true;
}
if (currentPolicy.data.enforceOnLogin) {
enforcedOptions.enforceOnLogin = true;
}
});
return enforcedOptions;
}),
);
}
evaluateMasterPassword(
passwordStrength: number,
newPassword: string,
enforcedPolicyOptions: MasterPasswordPolicyOptions,
): boolean {
if (enforcedPolicyOptions == null) {
return true;
}
if (
enforcedPolicyOptions.minComplexity > 0 &&
enforcedPolicyOptions.minComplexity > passwordStrength
) {
return false;
}
if (
enforcedPolicyOptions.minLength > 0 &&
enforcedPolicyOptions.minLength > newPassword.length
) {
return false;
}
if (enforcedPolicyOptions.requireUpper && newPassword.toLocaleLowerCase() === newPassword) {
return false;
}
if (enforcedPolicyOptions.requireLower && newPassword.toLocaleUpperCase() === newPassword) {
return false;
}
if (enforcedPolicyOptions.requireNumbers && !/[0-9]/.test(newPassword)) {
return false;
}
// eslint-disable-next-line
if (enforcedPolicyOptions.requireSpecial && !/[!@#$%\^&*]/g.test(newPassword)) {
return false;
}
return true;
}
getResetPasswordPolicyOptions(
policies: Policy[],
orgId: string,
): [ResetPasswordPolicyOptions, boolean] {
const resetPasswordPolicyOptions = new ResetPasswordPolicyOptions();
if (policies == null || orgId == null) {
return [resetPasswordPolicyOptions, false];
}
const policy = policies.find(
(p) => p.organizationId === orgId && p.type === PolicyType.ResetPassword && p.enabled,
);
resetPasswordPolicyOptions.autoEnrollEnabled = policy?.data?.autoEnrollEnabled ?? false;
return [resetPasswordPolicyOptions, policy?.enabled ?? false];
}
async upsert(policy: PolicyData): Promise<void> {
await this.activeUserPolicyState.update((policies) => {
policies ??= {};
policies[policy.id] = policy;
return policies;
});
}
async replace(policies: { [id: string]: PolicyData }, userId: UserId): Promise<void> {
await this.stateProvider.setUserState(POLICIES, policies, userId);
}
/**
* Determines whether an orgUser is exempt from a specific policy because of their role
* Generally orgUsers who can manage policies are exempt from them, but some policies are stricter
*/
private isExemptFromPolicy(policyType: PolicyType, organization: Organization) {
switch (policyType) {
case PolicyType.MaximumVaultTimeout:
// Max Vault Timeout applies to everyone except owners
return organization.isOwner;
case PolicyType.PasswordGenerator:
// password generation policy applies to everyone
return false;
case PolicyType.PersonalOwnership:
// individual vault policy applies to everyone except admins and owners
return organization.isAdmin;
case PolicyType.FreeFamiliesSponsorshipPolicy:
// free Bitwarden families policy applies to everyone
return false;
case PolicyType.RemoveUnlockWithPin:
// free Remove Unlock with PIN policy applies to everyone
return false;
default:
return organization.canManagePolicies;
}
}
}

View File

@@ -1,9 +1,11 @@
// 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 { map, Observable, switchMap } from "rxjs";
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "../../admin-console/enums"; import { PolicyType } from "../../admin-console/enums";
import { AccountService } from "../../auth/abstractions/account.service";
import { getUserId } from "../../auth/services/account.service";
import { import {
AUTOFILL_SETTINGS_DISK, AUTOFILL_SETTINGS_DISK,
AUTOFILL_SETTINGS_DISK_LOCAL, AUTOFILL_SETTINGS_DISK_LOCAL,
@@ -152,6 +154,7 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
constructor( constructor(
private stateProvider: StateProvider, private stateProvider: StateProvider,
private policyService: PolicyService, private policyService: PolicyService,
private accountService: AccountService,
) { ) {
this.autofillOnPageLoadState = this.stateProvider.getActive(AUTOFILL_ON_PAGE_LOAD); this.autofillOnPageLoadState = this.stateProvider.getActive(AUTOFILL_ON_PAGE_LOAD);
this.autofillOnPageLoad$ = this.autofillOnPageLoadState.state$.pipe(map((x) => x ?? false)); this.autofillOnPageLoad$ = this.autofillOnPageLoadState.state$.pipe(map((x) => x ?? false));
@@ -169,8 +172,11 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
this.autofillOnPageLoadCalloutIsDismissed$ = this.autofillOnPageLoadCalloutIsDismissed$ =
this.autofillOnPageLoadCalloutIsDismissedState.state$.pipe(map((x) => x ?? false)); this.autofillOnPageLoadCalloutIsDismissedState.state$.pipe(map((x) => x ?? false));
this.activateAutofillOnPageLoadFromPolicy$ = this.policyService.policyAppliesToActiveUser$( this.activateAutofillOnPageLoadFromPolicy$ = this.accountService.activeAccount$.pipe(
PolicyType.ActivateAutofill, getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.ActivateAutofill, userId),
),
); );
this.autofillOnPageLoadPolicyToastHasDisplayedState = this.stateProvider.getActive( this.autofillOnPageLoadPolicyToastHasDisplayedState = this.stateProvider.getActive(

View File

@@ -175,7 +175,7 @@ describe("VaultTimeoutSettingsService", () => {
"returns $expected when policy is $policy, and user preference is $userPreference", "returns $expected when policy is $policy, and user preference is $userPreference",
async ({ policy, userPreference, expected }) => { async ({ policy, userPreference, expected }) => {
userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true })); userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true }));
policyService.getAll$.mockReturnValue( policyService.policiesByType$.mockReturnValue(
of(policy === null ? [] : ([{ data: { action: policy } }] as unknown as Policy[])), of(policy === null ? [] : ([{ data: { action: policy } }] as unknown as Policy[])),
); );
@@ -213,7 +213,7 @@ describe("VaultTimeoutSettingsService", () => {
userDecryptionOptionsSubject.next( userDecryptionOptionsSubject.next(
new UserDecryptionOptions({ hasMasterPassword: false }), new UserDecryptionOptions({ hasMasterPassword: false }),
); );
policyService.getAll$.mockReturnValue( policyService.policiesByType$.mockReturnValue(
of(policy === null ? [] : ([{ data: { action: policy } }] as unknown as Policy[])), of(policy === null ? [] : ([{ data: { action: policy } }] as unknown as Policy[])),
); );
@@ -257,7 +257,7 @@ describe("VaultTimeoutSettingsService", () => {
"when policy is %s, and vault timeout is %s, returns %s", "when policy is %s, and vault timeout is %s, returns %s",
async (policy, vaultTimeout, expected) => { async (policy, vaultTimeout, expected) => {
userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true })); userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true }));
policyService.getAll$.mockReturnValue( policyService.policiesByType$.mockReturnValue(
of(policy === null ? [] : ([{ data: { minutes: policy } }] as unknown as Policy[])), of(policy === null ? [] : ([{ data: { minutes: policy } }] as unknown as Policy[])),
); );

View File

@@ -9,7 +9,6 @@ import {
distinctUntilChanged, distinctUntilChanged,
firstValueFrom, firstValueFrom,
from, from,
map,
shareReplay, shareReplay,
switchMap, switchMap,
tap, tap,
@@ -24,6 +23,7 @@ import { BiometricStateService, KeyService } from "@bitwarden/key-management";
import { PolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "../../../admin-console/enums"; import { PolicyType } from "../../../admin-console/enums";
import { Policy } from "../../../admin-console/models/domain/policy"; import { Policy } from "../../../admin-console/models/domain/policy";
import { getFirstPolicy } from "../../../admin-console/services/policy/default-policy.service";
import { AccountService } from "../../../auth/abstractions/account.service"; import { AccountService } from "../../../auth/abstractions/account.service";
import { TokenService } from "../../../auth/abstractions/token.service"; import { TokenService } from "../../../auth/abstractions/token.service";
import { LogService } from "../../../platform/abstractions/log.service"; import { LogService } from "../../../platform/abstractions/log.service";
@@ -266,8 +266,8 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
} }
return this.policyService return this.policyService
.getAll$(PolicyType.MaximumVaultTimeout, userId) .policiesByType$(PolicyType.MaximumVaultTimeout, userId)
.pipe(map((policies) => policies[0] ?? null)); .pipe(getFirstPolicy);
} }
private async getAvailableVaultTimeoutActions(userId?: string): Promise<VaultTimeoutAction[]> { private async getAvailableVaultTimeoutActions(userId?: string): Promise<VaultTimeoutAction[]> {

View File

@@ -329,7 +329,12 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit {
private async handlePolicies() { private async handlePolicies() {
combineLatest([ combineLatest([
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId),
),
),
this.organizations$, this.organizations$,
]) ])
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))

View File

@@ -22,6 +22,7 @@ import { Account, AccountService } from "@bitwarden/common/auth/abstractions/acc
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { import {
MasterPasswordVerification, MasterPasswordVerification,
MasterPasswordVerificationResponse, MasterPasswordVerificationResponse,
@@ -584,7 +585,10 @@ export class LockComponent implements OnInit, OnDestroy {
// If we do not have any saved policies, attempt to load them from the service // If we do not have any saved policies, attempt to load them from the service
if (this.enforcedMasterPasswordOptions == undefined) { if (this.enforcedMasterPasswordOptions == undefined) {
this.enforcedMasterPasswordOptions = await firstValueFrom( this.enforcedMasterPasswordOptions = await firstValueFrom(
this.policyService.masterPasswordPolicyOptions$(), this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)),
),
); );
} }

View File

@@ -14,7 +14,6 @@ import {
import { ReactiveFormsModule, UntypedFormBuilder, Validators } from "@angular/forms"; import { ReactiveFormsModule, UntypedFormBuilder, Validators } from "@angular/forms";
import { import {
combineLatest, combineLatest,
firstValueFrom,
map, map,
merge, merge,
Observable, Observable,
@@ -212,12 +211,18 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
this.formDisabled.emit(c === "DISABLED"); this.formDisabled.emit(c === "DISABLED");
}); });
// policies this.disablePersonalVaultExportPolicy$ = this.accountService.activeAccount$.pipe(
this.disablePersonalVaultExportPolicy$ = this.policyService.policyAppliesToActiveUser$( getUserId,
PolicyType.DisablePersonalVaultExport, switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.DisablePersonalVaultExport, userId),
),
); );
this.disablePersonalOwnershipPolicy$ = this.policyService.policyAppliesToActiveUser$(
PolicyType.PersonalOwnership, this.disablePersonalOwnershipPolicy$ = this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId),
),
); );
merge( merge(
@@ -227,8 +232,6 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
.pipe(startWith(0), takeUntil(this.destroy$)) .pipe(startWith(0), takeUntil(this.destroy$))
.subscribe(() => this.adjustValidators()); .subscribe(() => this.adjustValidators());
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
// Wire up the password generation for the password-protected export // Wire up the password generation for the password-protected export
const account$ = this.accountService.activeAccount$.pipe( const account$ = this.accountService.activeAccount$.pipe(
pin({ pin({
@@ -251,9 +254,14 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
}); });
if (this.organizationId) { if (this.organizationId) {
this.organizations$ = this.organizationService this.organizations$ = this.accountService.activeAccount$.pipe(
.memberOrganizations$(userId) getUserId,
.pipe(map((orgs) => orgs.filter((org) => org.id == this.organizationId))); switchMap((userId) =>
this.organizationService
.memberOrganizations$(userId)
.pipe(map((orgs) => orgs.filter((org) => org.id == this.organizationId))),
),
);
this.exportForm.controls.vaultSelector.patchValue(this.organizationId); this.exportForm.controls.vaultSelector.patchValue(this.organizationId);
this.exportForm.controls.vaultSelector.disable(); this.exportForm.controls.vaultSelector.disable();
@@ -263,7 +271,10 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
this.organizations$ = combineLatest({ this.organizations$ = combineLatest({
collections: this.collectionService.decryptedCollections$, collections: this.collectionService.decryptedCollections$,
memberOrganizations: this.organizationService.memberOrganizations$(userId), memberOrganizations: this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.organizationService.memberOrganizations$(userId)),
),
}).pipe( }).pipe(
map(({ collections, memberOrganizations }) => { map(({ collections, memberOrganizations }) => {
const managedCollectionsOrgIds = new Set( const managedCollectionsOrgIds = new Set(

View File

@@ -207,7 +207,7 @@ const providers = {
describe("CredentialGeneratorService", () => { describe("CredentialGeneratorService", () => {
beforeEach(async () => { beforeEach(async () => {
await accountService.switchAccount(SomeUser); await accountService.switchAccount(SomeUser);
policyService.getAll$.mockImplementation(() => new BehaviorSubject([]).asObservable()); policyService.policiesByType$.mockImplementation(() => new BehaviorSubject([]).asObservable());
i18nService.t.mockImplementation((key: string) => key); i18nService.t.mockImplementation((key: string) => key);
apiService.fetch.mockImplementation(() => Promise.resolve(mock<Response>())); apiService.fetch.mockImplementation(() => Promise.resolve(mock<Response>()));
jest.clearAllMocks(); jest.clearAllMocks();
@@ -567,7 +567,7 @@ describe("CredentialGeneratorService", () => {
// awareness; they exercise the logic without being comprehensive // awareness; they exercise the logic without being comprehensive
it("enforces the active user's policy", async () => { it("enforces the active user's policy", async () => {
const policy$ = new BehaviorSubject([passwordOverridePolicy]); const policy$ = new BehaviorSubject([passwordOverridePolicy]);
policyService.getAll$.mockReturnValue(policy$); policyService.policiesByType$.mockReturnValue(policy$);
const generator = new CredentialGeneratorService( const generator = new CredentialGeneratorService(
randomizer, randomizer,
policyService, policyService,
@@ -578,15 +578,22 @@ describe("CredentialGeneratorService", () => {
const result = await firstValueFrom(generator.algorithms$(["password"], { account$ })); const result = await firstValueFrom(generator.algorithms$(["password"], { account$ }));
expect(policyService.getAll$).toHaveBeenCalledWith(PolicyType.PasswordGenerator, SomeUser); expect(policyService.policiesByType$).toHaveBeenCalledWith(
PolicyType.PasswordGenerator,
SomeUser,
);
expect(result.some((a) => a.id === Generators.password.id)).toBeTruthy(); expect(result.some((a) => a.id === Generators.password.id)).toBeTruthy();
expect(result.some((a) => a.id === Generators.passphrase.id)).toBeFalsy(); expect(result.some((a) => a.id === Generators.passphrase.id)).toBeFalsy();
}); });
it("follows changes to the active user", async () => { it("follows changes to the active user", async () => {
const account$ = new BehaviorSubject(accounts[SomeUser]); const account$ = new BehaviorSubject(accounts[SomeUser]);
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passwordOverridePolicy])); policyService.policiesByType$.mockReturnValueOnce(
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passphraseOverridePolicy])); new BehaviorSubject([passwordOverridePolicy]),
);
policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passphraseOverridePolicy]),
);
const generator = new CredentialGeneratorService( const generator = new CredentialGeneratorService(
randomizer, randomizer,
policyService, policyService,
@@ -603,7 +610,7 @@ describe("CredentialGeneratorService", () => {
const [someResult, anotherResult] = results; const [someResult, anotherResult] = results;
expect(policyService.getAll$).toHaveBeenNthCalledWith( expect(policyService.policiesByType$).toHaveBeenNthCalledWith(
1, 1,
PolicyType.PasswordGenerator, PolicyType.PasswordGenerator,
SomeUser, SomeUser,
@@ -611,7 +618,7 @@ describe("CredentialGeneratorService", () => {
expect(someResult.some((a: any) => a.id === Generators.password.id)).toBeTruthy(); expect(someResult.some((a: any) => a.id === Generators.password.id)).toBeTruthy();
expect(someResult.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy(); expect(someResult.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy();
expect(policyService.getAll$).toHaveBeenNthCalledWith( expect(policyService.policiesByType$).toHaveBeenNthCalledWith(
2, 2,
PolicyType.PasswordGenerator, PolicyType.PasswordGenerator,
AnotherUser, AnotherUser,
@@ -621,7 +628,9 @@ describe("CredentialGeneratorService", () => {
}); });
it("reads an arbitrary user's settings", async () => { it("reads an arbitrary user's settings", async () => {
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passwordOverridePolicy])); policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passwordOverridePolicy]),
);
const generator = new CredentialGeneratorService( const generator = new CredentialGeneratorService(
randomizer, randomizer,
policyService, policyService,
@@ -633,14 +642,21 @@ describe("CredentialGeneratorService", () => {
const result = await firstValueFrom(generator.algorithms$("password", { account$ })); const result = await firstValueFrom(generator.algorithms$("password", { account$ }));
expect(policyService.getAll$).toHaveBeenCalledWith(PolicyType.PasswordGenerator, AnotherUser); expect(policyService.policiesByType$).toHaveBeenCalledWith(
PolicyType.PasswordGenerator,
AnotherUser,
);
expect(result.some((a: any) => a.id === Generators.password.id)).toBeTruthy(); expect(result.some((a: any) => a.id === Generators.password.id)).toBeTruthy();
expect(result.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy(); expect(result.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy();
}); });
it("follows changes to the arbitrary user", async () => { it("follows changes to the arbitrary user", async () => {
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passwordOverridePolicy])); policyService.policiesByType$.mockReturnValueOnce(
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passphraseOverridePolicy])); new BehaviorSubject([passwordOverridePolicy]),
);
policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passphraseOverridePolicy]),
);
const generator = new CredentialGeneratorService( const generator = new CredentialGeneratorService(
randomizer, randomizer,
policyService, policyService,
@@ -658,17 +674,25 @@ describe("CredentialGeneratorService", () => {
sub.unsubscribe(); sub.unsubscribe();
const [someResult, anotherResult] = results; const [someResult, anotherResult] = results;
expect(policyService.getAll$).toHaveBeenCalledWith(PolicyType.PasswordGenerator, SomeUser); expect(policyService.policiesByType$).toHaveBeenCalledWith(
PolicyType.PasswordGenerator,
SomeUser,
);
expect(someResult.some((a: any) => a.id === Generators.password.id)).toBeTruthy(); expect(someResult.some((a: any) => a.id === Generators.password.id)).toBeTruthy();
expect(someResult.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy(); expect(someResult.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy();
expect(policyService.getAll$).toHaveBeenCalledWith(PolicyType.PasswordGenerator, AnotherUser); expect(policyService.policiesByType$).toHaveBeenCalledWith(
PolicyType.PasswordGenerator,
AnotherUser,
);
expect(anotherResult.some((a: any) => a.id === Generators.passphrase.id)).toBeTruthy(); expect(anotherResult.some((a: any) => a.id === Generators.passphrase.id)).toBeTruthy();
expect(anotherResult.some((a: any) => a.id === Generators.password.id)).toBeFalsy(); expect(anotherResult.some((a: any) => a.id === Generators.password.id)).toBeFalsy();
}); });
it("errors when the arbitrary user's stream errors", async () => { it("errors when the arbitrary user's stream errors", async () => {
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passwordOverridePolicy])); policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passwordOverridePolicy]),
);
const generator = new CredentialGeneratorService( const generator = new CredentialGeneratorService(
randomizer, randomizer,
policyService, policyService,
@@ -692,7 +716,9 @@ describe("CredentialGeneratorService", () => {
}); });
it("completes when the arbitrary user's stream completes", async () => { it("completes when the arbitrary user's stream completes", async () => {
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passwordOverridePolicy])); policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passwordOverridePolicy]),
);
const generator = new CredentialGeneratorService( const generator = new CredentialGeneratorService(
randomizer, randomizer,
policyService, policyService,
@@ -716,7 +742,9 @@ describe("CredentialGeneratorService", () => {
}); });
it("ignores repeated arbitrary user emissions", async () => { it("ignores repeated arbitrary user emissions", async () => {
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passwordOverridePolicy])); policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passwordOverridePolicy]),
);
const generator = new CredentialGeneratorService( const generator = new CredentialGeneratorService(
randomizer, randomizer,
policyService, policyService,
@@ -780,7 +808,7 @@ describe("CredentialGeneratorService", () => {
const settings = { foo: "value" }; const settings = { foo: "value" };
await stateProvider.setUserState(SettingsKey, settings, SomeUser); await stateProvider.setUserState(SettingsKey, settings, SomeUser);
const policy$ = new BehaviorSubject([somePolicy]); const policy$ = new BehaviorSubject([somePolicy]);
policyService.getAll$.mockReturnValue(policy$); policyService.policiesByType$.mockReturnValue(policy$);
const generator = new CredentialGeneratorService( const generator = new CredentialGeneratorService(
randomizer, randomizer,
policyService, policyService,
@@ -908,7 +936,7 @@ describe("CredentialGeneratorService", () => {
); );
const account$ = new BehaviorSubject(accounts[SomeUser]).asObservable(); const account$ = new BehaviorSubject(accounts[SomeUser]).asObservable();
const policy$ = new BehaviorSubject([somePolicy]); const policy$ = new BehaviorSubject([somePolicy]);
policyService.getAll$.mockReturnValue(policy$); policyService.policiesByType$.mockReturnValue(policy$);
const result = await firstValueFrom(generator.policy$(SomeConfiguration, { account$ })); const result = await firstValueFrom(generator.policy$(SomeConfiguration, { account$ }));
@@ -926,7 +954,7 @@ describe("CredentialGeneratorService", () => {
const account = new BehaviorSubject(accounts[SomeUser]); const account = new BehaviorSubject(accounts[SomeUser]);
const account$ = account.asObservable(); const account$ = account.asObservable();
const somePolicySubject = new BehaviorSubject([somePolicy]); const somePolicySubject = new BehaviorSubject([somePolicy]);
policyService.getAll$.mockReturnValueOnce(somePolicySubject.asObservable()); policyService.policiesByType$.mockReturnValueOnce(somePolicySubject.asObservable());
const emissions: GeneratorConstraints<SomeSettings>[] = []; const emissions: GeneratorConstraints<SomeSettings>[] = [];
const sub = generator const sub = generator
.policy$(SomeConfiguration, { account$ }) .policy$(SomeConfiguration, { account$ })
@@ -954,7 +982,9 @@ describe("CredentialGeneratorService", () => {
const account$ = account.asObservable(); const account$ = account.asObservable();
const somePolicy$ = new BehaviorSubject([somePolicy]).asObservable(); const somePolicy$ = new BehaviorSubject([somePolicy]).asObservable();
const anotherPolicy$ = new BehaviorSubject([]).asObservable(); const anotherPolicy$ = new BehaviorSubject([]).asObservable();
policyService.getAll$.mockReturnValueOnce(somePolicy$).mockReturnValueOnce(anotherPolicy$); policyService.policiesByType$
.mockReturnValueOnce(somePolicy$)
.mockReturnValueOnce(anotherPolicy$);
const emissions: GeneratorConstraints<SomeSettings>[] = []; const emissions: GeneratorConstraints<SomeSettings>[] = [];
const sub = generator const sub = generator
.policy$(SomeConfiguration, { account$ }) .policy$(SomeConfiguration, { account$ })

View File

@@ -114,11 +114,13 @@ export class CredentialGeneratorService {
const algorithms$ = dependencies.account$.pipe( const algorithms$ = dependencies.account$.pipe(
distinctUntilChanged(), distinctUntilChanged(),
switchMap((account) => { switchMap((account) => {
const policies$ = this.policyService.getAll$(PolicyType.PasswordGenerator, account.id).pipe( const policies$ = this.policyService
map((p) => new Set(availableAlgorithms(p))), .policiesByType$(PolicyType.PasswordGenerator, account.id)
// complete policy emissions otherwise `switchMap` holds `algorithms$` open indefinitely .pipe(
takeUntil(anyComplete(dependencies.account$)), map((p) => new Set(availableAlgorithms(p))),
); // complete policy emissions otherwise `switchMap` holds `algorithms$` open indefinitely
takeUntil(anyComplete(dependencies.account$)),
);
return policies$; return policies$;
}), }),
map((available) => { map((available) => {
@@ -280,7 +282,7 @@ export class CredentialGeneratorService {
switchMap(({ userId, email }) => { switchMap(({ userId, email }) => {
// complete policy emissions otherwise `switchMap` holds `policies$` open indefinitely // complete policy emissions otherwise `switchMap` holds `policies$` open indefinitely
const policies$ = this.policyService const policies$ = this.policyService
.getAll$(configuration.policy.type, userId) .policiesByType$(configuration.policy.type, userId)
.pipe( .pipe(
mapPolicyToConstraints(configuration.policy, email), mapPolicyToConstraints(configuration.policy, email),
takeUntil(anyComplete(dependencies.account$)), takeUntil(anyComplete(dependencies.account$)),

View File

@@ -19,7 +19,7 @@ function mockPolicyService(config?: { state?: BehaviorSubject<Policy[]> }) {
const service = mock<PolicyService>(); const service = mock<PolicyService>();
const stateValue = config?.state ?? new BehaviorSubject<Policy[]>([null]); const stateValue = config?.state ?? new BehaviorSubject<Policy[]>([null]);
service.getAll$.mockReturnValue(stateValue); service.policiesByType$.mockReturnValue(stateValue);
return service; return service;
} }
@@ -103,7 +103,7 @@ describe("Password generator service", () => {
await firstValueFrom(service.evaluator$(SomeUser)); await firstValueFrom(service.evaluator$(SomeUser));
expect(policy.getAll$).toHaveBeenCalledWith(PolicyType.PasswordGenerator, SomeUser); expect(policy.policiesByType$).toHaveBeenCalledWith(PolicyType.PasswordGenerator, SomeUser);
}); });
it("should map the policy using the generation strategy", async () => { it("should map the policy using the generation strategy", async () => {
@@ -150,7 +150,7 @@ describe("Password generator service", () => {
await firstValueFrom(service.evaluator$(SomeUser)); await firstValueFrom(service.evaluator$(SomeUser));
await firstValueFrom(service.evaluator$(SomeUser)); await firstValueFrom(service.evaluator$(SomeUser));
expect(policy.getAll$).toHaveBeenCalledTimes(1); expect(policy.policiesByType$).toHaveBeenCalledTimes(1);
}); });
it("should cache the password generator policy for each user", async () => { it("should cache the password generator policy for each user", async () => {
@@ -161,8 +161,16 @@ describe("Password generator service", () => {
await firstValueFrom(service.evaluator$(SomeUser)); await firstValueFrom(service.evaluator$(SomeUser));
await firstValueFrom(service.evaluator$(AnotherUser)); await firstValueFrom(service.evaluator$(AnotherUser));
expect(policy.getAll$).toHaveBeenNthCalledWith(1, PolicyType.PasswordGenerator, SomeUser); expect(policy.policiesByType$).toHaveBeenNthCalledWith(
expect(policy.getAll$).toHaveBeenNthCalledWith(2, PolicyType.PasswordGenerator, AnotherUser); 1,
PolicyType.PasswordGenerator,
SomeUser,
);
expect(policy.policiesByType$).toHaveBeenNthCalledWith(
2,
PolicyType.PasswordGenerator,
AnotherUser,
);
}); });
}); });

View File

@@ -51,7 +51,7 @@ export class DefaultGeneratorService<Options, Policy> implements GeneratorServic
} }
private createEvaluator(userId: UserId) { private createEvaluator(userId: UserId) {
const evaluator$ = this.policy.getAll$(this.strategy.policy, userId).pipe( const evaluator$ = this.policy.policiesByType$(this.strategy.policy, userId).pipe(
// create the evaluator from the policies // create the evaluator from the policies
this.strategy.toEvaluator(), this.strategy.toEvaluator(),
); );

View File

@@ -155,7 +155,7 @@ const NoPolicyProfile: CoreProfileMetadata<SomeSettings> = {
describe("GeneratorProfileProvider", () => { describe("GeneratorProfileProvider", () => {
beforeEach(async () => { beforeEach(async () => {
policyService.getAll$.mockImplementation(() => new BehaviorSubject([]).asObservable()); policyService.policiesByType$.mockImplementation(() => new BehaviorSubject([]).asObservable());
const encryptor$ = new BehaviorSubject({ userId: SomeUser, encryptor }); const encryptor$ = new BehaviorSubject({ userId: SomeUser, encryptor });
encryptorProvider.userEncryptor$.mockReturnValue(encryptor$); encryptorProvider.userEncryptor$.mockReturnValue(encryptor$);
jest.clearAllMocks(); jest.clearAllMocks();
@@ -211,7 +211,7 @@ describe("GeneratorProfileProvider", () => {
const profileProvider = new GeneratorProfileProvider(dependencyProvider, policyService); const profileProvider = new GeneratorProfileProvider(dependencyProvider, policyService);
const account$ = new BehaviorSubject(accounts[SomeUser]).asObservable(); const account$ = new BehaviorSubject(accounts[SomeUser]).asObservable();
const policy$ = new BehaviorSubject([somePolicy]); const policy$ = new BehaviorSubject([somePolicy]);
policyService.getAll$.mockReturnValue(policy$); policyService.policiesByType$.mockReturnValue(policy$);
const result = await firstValueFrom(profileProvider.constraints$(SomeProfile, { account$ })); const result = await firstValueFrom(profileProvider.constraints$(SomeProfile, { account$ }));
@@ -223,7 +223,7 @@ describe("GeneratorProfileProvider", () => {
const account$ = new BehaviorSubject(accounts[SomeUser]).asObservable(); const account$ = new BehaviorSubject(accounts[SomeUser]).asObservable();
const expectedPolicy = [somePolicy]; const expectedPolicy = [somePolicy];
const policy$ = new BehaviorSubject(expectedPolicy); const policy$ = new BehaviorSubject(expectedPolicy);
policyService.getAll$.mockReturnValue(policy$); policyService.policiesByType$.mockReturnValue(policy$);
await firstValueFrom(profileProvider.constraints$(SomeProfile, { account$ })); await firstValueFrom(profileProvider.constraints$(SomeProfile, { account$ }));
@@ -284,7 +284,7 @@ describe("GeneratorProfileProvider", () => {
const account = new BehaviorSubject(accounts[SomeUser]); const account = new BehaviorSubject(accounts[SomeUser]);
const account$ = account.asObservable(); const account$ = account.asObservable();
const somePolicySubject = new BehaviorSubject([somePolicy]); const somePolicySubject = new BehaviorSubject([somePolicy]);
policyService.getAll$.mockReturnValueOnce(somePolicySubject.asObservable()); policyService.policiesByType$.mockReturnValueOnce(somePolicySubject.asObservable());
const emissions: GeneratorConstraints<SomeSettings>[] = []; const emissions: GeneratorConstraints<SomeSettings>[] = [];
const sub = profileProvider const sub = profileProvider
.constraints$(SomeProfile, { account$ }) .constraints$(SomeProfile, { account$ })

View File

@@ -86,7 +86,7 @@ export class GeneratorProfileProvider {
); );
const policies$ = profile.constraints.type const policies$ = profile.constraints.type
? this.policyService.getAll$(profile.constraints.type, account.id) ? this.policyService.policiesByType$(profile.constraints.type, account.id)
: of([]); : of([]);
const context: ProfileContext<Settings> = { const context: ProfileContext<Settings> = {

View File

@@ -47,7 +47,7 @@ describe("DefaultGeneratorNavigationService", () => {
describe("evaluator$", () => { describe("evaluator$", () => {
it("emits a GeneratorNavigationEvaluator", async () => { it("emits a GeneratorNavigationEvaluator", async () => {
const policyService = mock<PolicyService>({ const policyService = mock<PolicyService>({
getAll$() { policiesByType$() {
return of([]); return of([]);
}, },
}); });
@@ -62,7 +62,7 @@ describe("DefaultGeneratorNavigationService", () => {
describe("enforcePolicy", () => { describe("enforcePolicy", () => {
it("applies policy", async () => { it("applies policy", async () => {
const policyService = mock<PolicyService>({ const policyService = mock<PolicyService>({
getAll$(_type: PolicyType, _user: UserId) { policiesByType$(_type: PolicyType, _user: UserId) {
return of([ return of([
new Policy({ new Policy({
id: "" as any, id: "" as any,

View File

@@ -41,7 +41,7 @@ export class DefaultGeneratorNavigationService implements GeneratorNavigationSer
* @param userId: Identifies the user making the request * @param userId: Identifies the user making the request
*/ */
evaluator$(userId: UserId) { evaluator$(userId: UserId) {
const evaluator$ = this.policy.getAll$(PolicyType.PasswordGenerator, userId).pipe( const evaluator$ = this.policy.policiesByType$(PolicyType.PasswordGenerator, userId).pipe(
reduceCollection(preferPassword, DisabledGeneratorNavigationPolicy), reduceCollection(preferPassword, DisabledGeneratorNavigationPolicy),
distinctIfShallowMatch(), distinctIfShallowMatch(),
map((policy) => new GeneratorNavigationEvaluator(policy)), map((policy) => new GeneratorNavigationEvaluator(policy)),

View File

@@ -99,7 +99,7 @@ export class SendOptionsComponent implements OnInit {
this.accountService.activeAccount$ this.accountService.activeAccount$
.pipe( .pipe(
getUserId, getUserId,
switchMap((userId) => this.policyService.getAll$(PolicyType.SendOptions, userId)), switchMap((userId) => this.policyService.policiesByType$(PolicyType.SendOptions, userId)),
map((policies) => policies?.some((p) => p.data.disableHideEmail)), map((policies) => policies?.some((p) => p.data.disableHideEmail)),
takeUntilDestroyed(), takeUntilDestroyed(),
) )

View File

@@ -1,10 +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 { inject, Injectable } from "@angular/core"; import { inject, Injectable } from "@angular/core";
import { combineLatest, firstValueFrom, map } from "rxjs"; import { combineLatest, firstValueFrom, map, switchMap } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { SendId } from "@bitwarden/common/types/guid"; import { SendId } from "@bitwarden/common/types/guid";
@@ -22,6 +24,7 @@ import {
export class DefaultSendFormConfigService implements SendFormConfigService { export class DefaultSendFormConfigService implements SendFormConfigService {
private policyService: PolicyService = inject(PolicyService); private policyService: PolicyService = inject(PolicyService);
private sendService: SendService = inject(SendService); private sendService: SendService = inject(SendService);
private accountService: AccountService = inject(AccountService);
async buildConfig( async buildConfig(
mode: SendFormMode, mode: SendFormMode,
@@ -40,9 +43,11 @@ export class DefaultSendFormConfigService implements SendFormConfigService {
}; };
} }
private areSendsEnabled$ = this.policyService private areSendsEnabled$ = this.accountService.activeAccount$.pipe(
.policyAppliesToActiveUser$(PolicyType.DisableSend) getUserId,
.pipe(map((p) => !p)); switchMap((userId) => this.policyService.policyAppliesToUser$(PolicyType.DisableSend, userId)),
map((p) => !p),
);
private getSend(id?: SendId) { private getSend(id?: SendId) {
if (id == null) { if (id == null) {

View File

@@ -89,9 +89,13 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService {
); );
} }
private allowPersonalOwnership$ = this.policyService private allowPersonalOwnership$ = this.accountService.activeAccount$.pipe(
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership) getUserId,
.pipe(map((p) => !p)); switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId),
),
map((p) => !p),
);
private getCipher(userId: UserId, id?: CipherId): Promise<Cipher | null> { private getCipher(userId: UserId, id?: CipherId): Promise<Cipher | null> {
if (id == null) { if (id == null) {