From 3bebbcad6ecdb2ddb33d7d614298adc0f2cd8a22 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Wed, 5 Feb 2025 15:53:34 +0000 Subject: [PATCH 01/10] Remove policy with PIN in Web Vault --- .../organizations/policies/index.ts | 1 + .../organizations/policies/policies.module.ts | 3 +++ .../remove-unlock-with-pin.component.html | 4 ++++ .../remove-unlock-with-pin.component.ts | 18 ++++++++++++++++++ apps/web/src/app/app.component.ts | 2 ++ apps/web/src/locales/en/messages.json | 6 ++++++ .../admin-console/enums/policy-type.enum.ts | 1 + 7 files changed, 35 insertions(+) create mode 100644 apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.html create mode 100644 apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts diff --git a/apps/web/src/app/admin-console/organizations/policies/index.ts b/apps/web/src/app/admin-console/organizations/policies/index.ts index 1fb554d55a1..20137105993 100644 --- a/apps/web/src/app/admin-console/organizations/policies/index.ts +++ b/apps/web/src/app/admin-console/organizations/policies/index.ts @@ -10,3 +10,4 @@ export { SendOptionsPolicy } from "./send-options.component"; export { SingleOrgPolicy } from "./single-org.component"; export { TwoFactorAuthenticationPolicy } from "./two-factor-authentication.component"; export { PoliciesComponent } from "./policies.component"; +export { RemoveUnlockWithPinPolicy } from "./remove-unlock-with-pin.component"; diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.module.ts b/apps/web/src/app/admin-console/organizations/policies/policies.module.ts index c6b1fbe4bff..1b8ec5089f9 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.module.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.module.ts @@ -8,6 +8,7 @@ import { PasswordGeneratorPolicyComponent } from "./password-generator.component import { PersonalOwnershipPolicyComponent } from "./personal-ownership.component"; import { PoliciesComponent } from "./policies.component"; import { PolicyEditComponent } from "./policy-edit.component"; +import { RemoveUnlockWithPinPolicyComponent } from "./remove-unlock-with-pin.component"; import { RequireSsoPolicyComponent } from "./require-sso.component"; import { ResetPasswordPolicyComponent } from "./reset-password.component"; import { SendOptionsPolicyComponent } from "./send-options.component"; @@ -28,6 +29,7 @@ import { TwoFactorAuthenticationPolicyComponent } from "./two-factor-authenticat TwoFactorAuthenticationPolicyComponent, PoliciesComponent, PolicyEditComponent, + RemoveUnlockWithPinPolicyComponent, ], exports: [ DisableSendPolicyComponent, @@ -41,6 +43,7 @@ import { TwoFactorAuthenticationPolicyComponent } from "./two-factor-authenticat TwoFactorAuthenticationPolicyComponent, PoliciesComponent, PolicyEditComponent, + RemoveUnlockWithPinPolicyComponent, ], }) export class PoliciesModule {} diff --git a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.html b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.html new file mode 100644 index 00000000000..b0dd1b688bc --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.html @@ -0,0 +1,4 @@ + + + {{ "turnOn" | i18n }} + diff --git a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts new file mode 100644 index 00000000000..b737c803b5f --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts @@ -0,0 +1,18 @@ +import { Component } from "@angular/core"; + +import { PolicyType } from "@bitwarden/common/admin-console/enums"; + +import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; + +export class RemoveUnlockWithPinPolicy extends BasePolicy { + name = "removeUnlockWithPinPolicyTitle"; + description = "removeUnlockWithPinPolicyDesc"; + type = PolicyType.RemoveUnlockWithPin; + component = RemoveUnlockWithPinPolicyComponent; +} + +@Component({ + selector: "remove-unlock-with-pin", + templateUrl: "remove-unlock-with-pin.component.html", +}) +export class RemoveUnlockWithPinPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 2b87ffda536..fe92cdcd3af 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -45,6 +45,7 @@ import { SendOptionsPolicy, SingleOrgPolicy, TwoFactorAuthenticationPolicy, + RemoveUnlockWithPinPolicy, } from "./admin-console/organizations/policies"; const BroadcasterSubscriptionId = "AppComponent"; @@ -255,6 +256,7 @@ export class AppComponent implements OnDestroy, OnInit { this.policyListService.addPolicies([ new TwoFactorAuthenticationPolicy(), new MasterPasswordPolicy(), + new RemoveUnlockWithPinPolicy(), new ResetPasswordPolicy(), new PasswordGeneratorPolicy(), new SingleOrgPolicy(), diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index ef199163239..502964efe89 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -10385,5 +10385,11 @@ }, "assignedExceedsAvailable": { "message": "Assigned seats exceed available seats." + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." } } diff --git a/libs/common/src/admin-console/enums/policy-type.enum.ts b/libs/common/src/admin-console/enums/policy-type.enum.ts index 2ca9fbef8db..336b834ca56 100644 --- a/libs/common/src/admin-console/enums/policy-type.enum.ts +++ b/libs/common/src/admin-console/enums/policy-type.enum.ts @@ -13,4 +13,5 @@ export enum PolicyType { ActivateAutofill = 11, // Activates autofill with page load on the browser extension AutomaticAppLogIn = 12, // Enables automatic log in of apps from configured identity provider FreeFamiliesSponsorshipPolicy = 13, // Disables free families plan for organization + RemoveUnlockWithPin = 14, // Do not allow members to unlock their account with a PIN. } From 12605d28e390a9ae17fbdea877d556a050c04bfa Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Thu, 6 Feb 2025 10:00:02 +0000 Subject: [PATCH 02/10] Remove policy with PIN in Browser Extension --- .../auth/popup/settings/account-security.component.html | 1 + .../auth/popup/settings/account-security.component.ts | 9 +++++++++ .../src/admin-console/services/policy/policy.service.ts | 3 +++ 3 files changed, 13 insertions(+) diff --git a/apps/browser/src/auth/popup/settings/account-security.component.html b/apps/browser/src/auth/popup/settings/account-security.component.html index 8bc28c9754d..2dfbd824ba0 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.html +++ b/apps/browser/src/auth/popup/settings/account-security.component.html @@ -33,6 +33,7 @@

{{ "unlockMethods" | i18n }}

{{ "unlockWithPin" | i18n }} diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 5979afca4b8..9b639c55430 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -12,6 +12,8 @@ import { distinctUntilChanged, firstValueFrom, map, + Observable, + of, pairwise, startWith, Subject, @@ -108,6 +110,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { biometricUnavailabilityReason: string; showChangeMasterPass = true; showAutoPrompt = true; + pinEnabled$: Observable = of(true); form = this.formBuilder.group({ vaultTimeout: [null as VaultTimeout | null], @@ -193,6 +196,12 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { timeout = VaultTimeoutStringType.OnRestart; } + this.pinEnabled$ = this.policyService.get$(PolicyType.RemoveUnlockWithPin).pipe( + map((policy) => { + return policy == null || !policy.enabled; + }), + ); + const initialValues = { vaultTimeout: timeout, vaultTimeoutAction: await firstValueFrom( diff --git a/libs/common/src/admin-console/services/policy/policy.service.ts b/libs/common/src/admin-console/services/policy/policy.service.ts index 3378d2021ef..ed4c7970a78 100644 --- a/libs/common/src/admin-console/services/policy/policy.service.ts +++ b/libs/common/src/admin-console/services/policy/policy.service.ts @@ -247,6 +247,9 @@ export class PolicyService implements InternalPolicyServiceAbstraction { 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; } From bb3852914a9318d146b0c7faf99cb379f4cdeec1 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Thu, 6 Feb 2025 10:57:42 +0000 Subject: [PATCH 03/10] Remove policy with PIN in Desktop --- apps/desktop/src/app/accounts/settings.component.html | 2 +- apps/desktop/src/app/accounts/settings.component.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index 709506f576f..cd022b46c31 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -111,7 +111,7 @@

}} -
+
{{ diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index e45dceeaa78..36ca3275221 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1775,6 +1775,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, From 1dd9b3398da5c1c8644daf55e15cd6902a4d7b42 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Tue, 11 Feb 2025 18:00:22 +0000 Subject: [PATCH 05/10] unit tests coverage --- .../account-security.component.spec.ts | 199 ++++++++++++++ .../app/accounts/settings.component.spec.ts | 250 ++++++++++++++++++ .../desktop-autofill-settings.service.ts | 5 +- 3 files changed, 452 insertions(+), 2 deletions(-) create mode 100644 apps/browser/src/auth/popup/settings/account-security.component.spec.ts create mode 100644 apps/desktop/src/app/accounts/settings.component.spec.ts diff --git a/apps/browser/src/auth/popup/settings/account-security.component.spec.ts b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts new file mode 100644 index 00000000000..e68edd64e03 --- /dev/null +++ b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts @@ -0,0 +1,199 @@ +import { Component } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { mock } from "jest-mock-extended"; +import { firstValueFrom, of } from "rxjs"; + +import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; +import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { MessageSender } from "@bitwarden/common/platform/messaging"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; +import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; +import { DialogService, ToastService } from "@bitwarden/components"; +import { BiometricStateService, BiometricsService, KeyService } from "@bitwarden/key-management"; + +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popup-router-cache.service"; + +import { AccountSecurityComponent } from "./account-security.component"; + +@Component({ + standalone: true, + selector: "app-pop-out", + template: ` `, +}) +class MockPopOutComponent {} + +describe("AccountSecurityComponent", () => { + let component: AccountSecurityComponent; + let fixture: ComponentFixture; + + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + const vaultTimeoutSettingsService = mock(); + const biometricStateService = mock(); + const policyService = mock(); + const pinServiceAbstraction = mock(); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + providers: [ + { provide: AccountService, useValue: accountService }, + { provide: AccountSecurityComponent, useValue: mock() }, + { provide: BiometricsService, useValue: mock() }, + { provide: BiometricStateService, useValue: biometricStateService }, + { provide: DialogService, useValue: mock() }, + { provide: EnvironmentService, useValue: mock() }, + { provide: I18nService, useValue: mock() }, + { provide: MessageSender, useValue: mock() }, + { provide: KeyService, useValue: mock() }, + { provide: PinServiceAbstraction, useValue: pinServiceAbstraction }, + { provide: PlatformUtilsService, useValue: mock() }, + { provide: PolicyService, useValue: policyService }, + { provide: PopupRouterCacheService, useValue: mock() }, + { provide: StateService, useValue: mock() }, + { provide: ToastService, useValue: mock() }, + { provide: UserVerificationService, useValue: mock() }, + { provide: VaultTimeoutService, useValue: mock() }, + { provide: VaultTimeoutSettingsService, useValue: vaultTimeoutSettingsService }, + ], + }) + .overrideComponent(AccountSecurityComponent, { + remove: { + imports: [PopOutComponent], + }, + add: { + imports: [MockPopOutComponent], + }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(AccountSecurityComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + vaultTimeoutSettingsService.getVaultTimeoutByUserId$.mockReturnValue( + of(VaultTimeoutStringType.OnLocked), + ); + vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$.mockReturnValue( + of(VaultTimeoutAction.Lock), + ); + biometricStateService.promptAutomatically$ = of(false); + pinServiceAbstraction.isPinSet.mockResolvedValue(false); + }); + + it("pin enabled when RemoveUnlockWithPin policy is not set", async () => { + // @ts-strict-ignore + policyService.get$.mockReturnValue(of(null)); + + await component.ngOnInit(); + + await expect(firstValueFrom(component.pinEnabled$)).resolves.toBe(true); + }); + + it("pin enabled when RemoveUnlockWithPin policy is disabled", async () => { + const policy = new Policy(); + policy.type = PolicyType.RemoveUnlockWithPin; + policy.enabled = false; + + policyService.get$.mockReturnValue(of(policy)); + + await component.ngOnInit(); + + await expect(firstValueFrom(component.pinEnabled$)).resolves.toBe(true); + + fixture.detectChanges(); + + const pinInputElement = fixture.debugElement.query(By.css("#pin")); + expect(pinInputElement).not.toBeNull(); + expect(pinInputElement.name).toBe("input"); + }); + + it("pin disabled when RemoveUnlockWithPin policy is enabled", async () => { + const policy = new Policy(); + policy.type = PolicyType.RemoveUnlockWithPin; + policy.enabled = true; + + policyService.get$.mockReturnValue(of(policy)); + + await component.ngOnInit(); + + await expect(firstValueFrom(component.pinEnabled$)).resolves.toBe(false); + + fixture.detectChanges(); + + const pinInputElement = fixture.debugElement.query(By.css("#pin")); + expect(pinInputElement).toBeNull(); + }); + + it("pin visible when RemoveUnlockWithPin policy is not set", async () => { + // @ts-strict-ignore + policyService.get$.mockReturnValue(of(null)); + + await component.ngOnInit(); + fixture.detectChanges(); + + const pinInputElement = fixture.debugElement.query(By.css("#pin")); + expect(pinInputElement).not.toBeNull(); + expect(pinInputElement.name).toBe("input"); + }); + + it("pin visible when RemoveUnlockWithPin policy is disabled", async () => { + const policy = new Policy(); + policy.type = PolicyType.RemoveUnlockWithPin; + policy.enabled = false; + + policyService.get$.mockReturnValue(of(policy)); + + await component.ngOnInit(); + fixture.detectChanges(); + + const pinInputElement = fixture.debugElement.query(By.css("#pin")); + expect(pinInputElement).not.toBeNull(); + expect(pinInputElement.name).toBe("input"); + }); + + it("pin visible when RemoveUnlockWithPin policy is enabled and pin set", async () => { + const policy = new Policy(); + policy.type = PolicyType.RemoveUnlockWithPin; + policy.enabled = true; + + policyService.get$.mockReturnValue(of(policy)); + + pinServiceAbstraction.isPinSet.mockResolvedValue(true); + + await component.ngOnInit(); + fixture.detectChanges(); + + const pinInputElement = fixture.debugElement.query(By.css("#pin")); + expect(pinInputElement).not.toBeNull(); + expect(pinInputElement.name).toBe("input"); + }); + + it("pin not visible when RemoveUnlockWithPin policy is enabled", async () => { + const policy = new Policy(); + policy.type = PolicyType.RemoveUnlockWithPin; + policy.enabled = true; + + policyService.get$.mockReturnValue(of(policy)); + + await component.ngOnInit(); + fixture.detectChanges(); + + const pinInputElement = fixture.debugElement.query(By.css("#pin")); + expect(pinInputElement).toBeNull(); + }); +}); diff --git a/apps/desktop/src/app/accounts/settings.component.spec.ts b/apps/desktop/src/app/accounts/settings.component.spec.ts new file mode 100644 index 00000000000..c1cf834c634 --- /dev/null +++ b/apps/desktop/src/app/accounts/settings.component.spec.ts @@ -0,0 +1,250 @@ +import { NO_ERRORS_SCHEMA } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { mock } from "jest-mock-extended"; +import { firstValueFrom, of } from "rxjs"; + +import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; +import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { ThemeType } from "@bitwarden/common/platform/enums"; +import { MessageSender } from "@bitwarden/common/platform/messaging"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; +import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; +import { DialogService } from "@bitwarden/components"; +import { BiometricStateService, KeyService } from "@bitwarden/key-management"; + +import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service"; +import { DesktopBiometricsService } from "../../key-management/biometrics/desktop.biometrics.service"; +import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; +import { NativeMessagingManifestService } from "../services/native-messaging-manifest.service"; + +import { SettingsComponent } from "./settings.component"; + +describe("SettingsComponent", () => { + let component: SettingsComponent; + let fixture: ComponentFixture; + let originalIpc: any; + + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + const vaultTimeoutSettingsService = mock(); + const biometricStateService = mock(); + const policyService = mock(); + const i18nService = mock(); + const autofillSettingsServiceAbstraction = mock(); + const desktopSettingsService = mock(); + const domainSettingsService = mock(); + const desktopAutofillSettingsService = mock(); + const themeStateService = mock(); + const pinServiceAbstraction = mock(); + + beforeEach(async () => { + originalIpc = (global as any).ipc; + (global as any).ipc = { + auth: { + loginRequest: jest.fn(), + }, + platform: { + isDev: false, + isWindowsStore: false, + powermonitor: { + isLockMonitorAvailable: async () => false, + }, + }, + }; + + i18nService.supportedTranslationLocales = []; + + await TestBed.configureTestingModule({ + declarations: [SettingsComponent, I18nPipe], + providers: [ + { + provide: AutofillSettingsServiceAbstraction, + useValue: autofillSettingsServiceAbstraction, + }, + { provide: AccountService, useValue: accountService }, + { provide: BiometricStateService, useValue: biometricStateService }, + { provide: ConfigService, useValue: mock() }, + { + provide: DesktopAutofillSettingsService, + useValue: desktopAutofillSettingsService, + }, + { provide: DesktopBiometricsService, useValue: mock() }, + { provide: DesktopSettingsService, useValue: desktopSettingsService }, + { provide: DomainSettingsService, useValue: domainSettingsService }, + { provide: DialogService, useValue: mock() }, + { provide: I18nService, useValue: i18nService }, + { provide: LogService, useValue: mock() }, + { provide: MessageSender, useValue: mock() }, + { + provide: NativeMessagingManifestService, + useValue: mock(), + }, + { provide: KeyService, useValue: mock() }, + { provide: PinServiceAbstraction, useValue: pinServiceAbstraction }, + { provide: PlatformUtilsService, useValue: mock() }, + { provide: PolicyService, useValue: policyService }, + { provide: StateService, useValue: mock() }, + { provide: ThemeStateService, useValue: themeStateService }, + { provide: UserVerificationService, useValue: mock() }, + { provide: VaultTimeoutSettingsService, useValue: vaultTimeoutSettingsService }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(SettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + vaultTimeoutSettingsService.getVaultTimeoutByUserId$.mockReturnValue( + of(VaultTimeoutStringType.OnLocked), + ); + vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$.mockReturnValue( + of(VaultTimeoutAction.Lock), + ); + vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue(false); + biometricStateService.promptAutomatically$ = of(false); + biometricStateService.requirePasswordOnStart$ = of(false); + autofillSettingsServiceAbstraction.clearClipboardDelay$ = of(null); + desktopSettingsService.minimizeOnCopy$ = of(false); + desktopSettingsService.trayEnabled$ = of(false); + desktopSettingsService.minimizeToTray$ = of(false); + desktopSettingsService.closeToTray$ = of(false); + desktopSettingsService.startToTray$ = of(false); + desktopSettingsService.openAtLogin$ = of(false); + desktopSettingsService.alwaysShowDock$ = of(false); + desktopSettingsService.browserIntegrationEnabled$ = of(false); + desktopSettingsService.browserIntegrationFingerprintEnabled$ = of(false); + desktopSettingsService.hardwareAcceleration$ = of(false); + desktopSettingsService.sshAgentEnabled$ = of(false); + desktopSettingsService.preventScreenshots$ = of(false); + domainSettingsService.showFavicons$ = of(false); + desktopAutofillSettingsService.enableDuckDuckGoBrowserIntegration$ = of(false); + themeStateService.selectedTheme$ = of(ThemeType.System); + i18nService.userSetLocale$ = of("en"); + pinServiceAbstraction.isPinSet.mockResolvedValue(false); + }); + + afterEach(() => { + (global as any).ipc = originalIpc; + }); + + it("pin enabled when RemoveUnlockWithPin policy is not set", async () => { + // @ts-strict-ignore + policyService.get$.mockReturnValue(of(null)); + + await component.ngOnInit(); + + await expect(firstValueFrom(component.pinEnabled$)).resolves.toBe(true); + }); + + it("pin enabled when RemoveUnlockWithPin policy is disabled", async () => { + const policy = new Policy(); + policy.type = PolicyType.RemoveUnlockWithPin; + policy.enabled = false; + + policyService.get$.mockReturnValue(of(policy)); + + await component.ngOnInit(); + + await expect(firstValueFrom(component.pinEnabled$)).resolves.toBe(true); + + fixture.detectChanges(); + + const pinInputElement = fixture.debugElement.query(By.css("#pin")); + expect(pinInputElement).not.toBeNull(); + expect(pinInputElement.name).toBe("input"); + }); + + it("pin disabled when RemoveUnlockWithPin policy is enabled", async () => { + const policy = new Policy(); + policy.type = PolicyType.RemoveUnlockWithPin; + policy.enabled = true; + + policyService.get$.mockReturnValue(of(policy)); + + await component.ngOnInit(); + + await expect(firstValueFrom(component.pinEnabled$)).resolves.toBe(false); + + fixture.detectChanges(); + + const pinInputElement = fixture.debugElement.query(By.css("#pin")); + expect(pinInputElement).toBeNull(); + }); + + it("pin visible when RemoveUnlockWithPin policy is not set", async () => { + // @ts-strict-ignore + policyService.get$.mockReturnValue(of(null)); + + await component.ngOnInit(); + fixture.detectChanges(); + + const pinInputElement = fixture.debugElement.query(By.css("#pin")); + expect(pinInputElement).not.toBeNull(); + expect(pinInputElement.name).toBe("input"); + }); + + it("pin visible when RemoveUnlockWithPin policy is disabled", async () => { + const policy = new Policy(); + policy.type = PolicyType.RemoveUnlockWithPin; + policy.enabled = false; + + policyService.get$.mockReturnValue(of(policy)); + + await component.ngOnInit(); + fixture.detectChanges(); + + const pinInputElement = fixture.debugElement.query(By.css("#pin")); + expect(pinInputElement).not.toBeNull(); + expect(pinInputElement.name).toBe("input"); + }); + + it("pin visible when RemoveUnlockWithPin policy is enabled and pin set", async () => { + const policy = new Policy(); + policy.type = PolicyType.RemoveUnlockWithPin; + policy.enabled = true; + + policyService.get$.mockReturnValue(of(policy)); + + pinServiceAbstraction.isPinSet.mockResolvedValue(true); + + await component.ngOnInit(); + fixture.detectChanges(); + + const pinInputElement = fixture.debugElement.query(By.css("#pin")); + expect(pinInputElement).not.toBeNull(); + expect(pinInputElement.name).toBe("input"); + }); + + it("pin not visible when RemoveUnlockWithPin policy is enabled", async () => { + const policy = new Policy(); + policy.type = PolicyType.RemoveUnlockWithPin; + policy.enabled = true; + + policyService.get$.mockReturnValue(of(policy)); + + await component.ngOnInit(); + fixture.detectChanges(); + + const pinInputElement = fixture.debugElement.query(By.css("#pin")); + expect(pinInputElement).toBeNull(); + }); +}); diff --git a/apps/desktop/src/autofill/services/desktop-autofill-settings.service.ts b/apps/desktop/src/autofill/services/desktop-autofill-settings.service.ts index d60a08a9fe1..e7a7d24cdf2 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill-settings.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill-settings.service.ts @@ -18,8 +18,9 @@ export class DesktopAutofillSettingsService { private enableDuckDuckGoBrowserIntegrationState = this.stateProvider.getGlobal( ENABLE_DUCK_DUCK_GO_BROWSER_INTEGRATION, ); - readonly enableDuckDuckGoBrowserIntegration$ = - this.enableDuckDuckGoBrowserIntegrationState.state$.pipe(map((x) => x ?? false)); + enableDuckDuckGoBrowserIntegration$ = this.enableDuckDuckGoBrowserIntegrationState.state$.pipe( + map((x) => x ?? false), + ); constructor(private stateProvider: StateProvider) {} From 6cc68e41b6823382eb7e2ca8ced284e8db9cf199 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Tue, 11 Feb 2025 20:52:32 +0000 Subject: [PATCH 06/10] unit tests coverage --- .../app/accounts/settings.component.spec.ts | 112 ++++++++++++++---- .../enums/policy-type.enum.spec.ts | 7 ++ .../services/policy/policy.service.spec.ts | 25 +++- 3 files changed, 119 insertions(+), 25 deletions(-) create mode 100644 libs/common/src/admin-console/enums/policy-type.enum.spec.ts diff --git a/apps/desktop/src/app/accounts/settings.component.spec.ts b/apps/desktop/src/app/accounts/settings.component.spec.ts index c1cf834c634..3caad4d5af8 100644 --- a/apps/desktop/src/app/accounts/settings.component.spec.ts +++ b/apps/desktop/src/app/accounts/settings.component.spec.ts @@ -14,6 +14,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { DeviceType } from "@bitwarden/common/enums"; import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -28,7 +29,7 @@ import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/sp import { UserId } from "@bitwarden/common/types/guid"; import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { DialogService } from "@bitwarden/components"; -import { BiometricStateService, KeyService } from "@bitwarden/key-management"; +import { BiometricStateService, BiometricsStatus, KeyService } from "@bitwarden/key-management"; import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service"; import { DesktopBiometricsService } from "../../key-management/biometrics/desktop.biometrics.service"; @@ -54,6 +55,8 @@ describe("SettingsComponent", () => { const desktopAutofillSettingsService = mock(); const themeStateService = mock(); const pinServiceAbstraction = mock(); + const desktopBiometricsService = mock(); + const platformUtilsService = mock(); beforeEach(async () => { originalIpc = (global as any).ipc; @@ -86,7 +89,7 @@ describe("SettingsComponent", () => { provide: DesktopAutofillSettingsService, useValue: desktopAutofillSettingsService, }, - { provide: DesktopBiometricsService, useValue: mock() }, + { provide: DesktopBiometricsService, useValue: desktopBiometricsService }, { provide: DesktopSettingsService, useValue: desktopSettingsService }, { provide: DomainSettingsService, useValue: domainSettingsService }, { provide: DialogService, useValue: mock() }, @@ -99,7 +102,7 @@ describe("SettingsComponent", () => { }, { provide: KeyService, useValue: mock() }, { provide: PinServiceAbstraction, useValue: pinServiceAbstraction }, - { provide: PlatformUtilsService, useValue: mock() }, + { provide: PlatformUtilsService, useValue: platformUtilsService }, { provide: PolicyService, useValue: policyService }, { provide: StateService, useValue: mock() }, { provide: ThemeStateService, useValue: themeStateService }, @@ -159,35 +162,22 @@ describe("SettingsComponent", () => { const policy = new Policy(); policy.type = PolicyType.RemoveUnlockWithPin; policy.enabled = false; - policyService.get$.mockReturnValue(of(policy)); await component.ngOnInit(); await expect(firstValueFrom(component.pinEnabled$)).resolves.toBe(true); - - fixture.detectChanges(); - - const pinInputElement = fixture.debugElement.query(By.css("#pin")); - expect(pinInputElement).not.toBeNull(); - expect(pinInputElement.name).toBe("input"); }); it("pin disabled when RemoveUnlockWithPin policy is enabled", async () => { const policy = new Policy(); policy.type = PolicyType.RemoveUnlockWithPin; policy.enabled = true; - policyService.get$.mockReturnValue(of(policy)); await component.ngOnInit(); await expect(firstValueFrom(component.pinEnabled$)).resolves.toBe(false); - - fixture.detectChanges(); - - const pinInputElement = fixture.debugElement.query(By.css("#pin")); - expect(pinInputElement).toBeNull(); }); it("pin visible when RemoveUnlockWithPin policy is not set", async () => { @@ -200,13 +190,15 @@ describe("SettingsComponent", () => { const pinInputElement = fixture.debugElement.query(By.css("#pin")); expect(pinInputElement).not.toBeNull(); expect(pinInputElement.name).toBe("input"); + expect(pinInputElement.attributes).toMatchObject({ + type: "checkbox", + }); }); it("pin visible when RemoveUnlockWithPin policy is disabled", async () => { const policy = new Policy(); policy.type = PolicyType.RemoveUnlockWithPin; policy.enabled = false; - policyService.get$.mockReturnValue(of(policy)); await component.ngOnInit(); @@ -215,15 +207,16 @@ describe("SettingsComponent", () => { const pinInputElement = fixture.debugElement.query(By.css("#pin")); expect(pinInputElement).not.toBeNull(); expect(pinInputElement.name).toBe("input"); + expect(pinInputElement.attributes).toMatchObject({ + type: "checkbox", + }); }); it("pin visible when RemoveUnlockWithPin policy is enabled and pin set", async () => { const policy = new Policy(); policy.type = PolicyType.RemoveUnlockWithPin; policy.enabled = true; - policyService.get$.mockReturnValue(of(policy)); - pinServiceAbstraction.isPinSet.mockResolvedValue(true); await component.ngOnInit(); @@ -232,13 +225,15 @@ describe("SettingsComponent", () => { const pinInputElement = fixture.debugElement.query(By.css("#pin")); expect(pinInputElement).not.toBeNull(); expect(pinInputElement.name).toBe("input"); + expect(pinInputElement.attributes).toMatchObject({ + type: "checkbox", + }); }); it("pin not visible when RemoveUnlockWithPin policy is enabled", async () => { const policy = new Policy(); policy.type = PolicyType.RemoveUnlockWithPin; policy.enabled = true; - policyService.get$.mockReturnValue(of(policy)); await component.ngOnInit(); @@ -247,4 +242,81 @@ describe("SettingsComponent", () => { const pinInputElement = fixture.debugElement.query(By.css("#pin")); expect(pinInputElement).toBeNull(); }); + + describe("biometrics enabled", () => { + beforeEach(() => { + desktopBiometricsService.getBiometricsStatus.mockResolvedValue(BiometricsStatus.Available); + vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue(true); + }); + + it("require password or pin on app start message when RemoveUnlockWithPin policy is disabled and pin set and windows desktop", async () => { + const policy = new Policy(); + policy.type = PolicyType.RemoveUnlockWithPin; + policy.enabled = false; + policyService.get$.mockReturnValue(of(policy)); + platformUtilsService.getDevice.mockReturnValue(DeviceType.WindowsDesktop); + i18nService.t.mockImplementation((id: string) => { + if (id === "requirePasswordOnStart") { + return "Require password or pin on app start"; + } else if (id === "requirePasswordWithoutPinOnStart") { + return "Require password on app start"; + } + return ""; + }); + pinServiceAbstraction.isPinSet.mockResolvedValue(true); + + await component.ngOnInit(); + fixture.detectChanges(); + + const requirePasswordOnStartLabelElement = fixture.debugElement.query( + By.css("label[for='requirePasswordOnStart']"), + ); + expect(requirePasswordOnStartLabelElement).not.toBeNull(); + expect(requirePasswordOnStartLabelElement.children).toHaveLength(1); + expect(requirePasswordOnStartLabelElement.children[0].name).toBe("input"); + expect(requirePasswordOnStartLabelElement.children[0].attributes).toMatchObject({ + id: "requirePasswordOnStart", + type: "checkbox", + }); + const textNodes = requirePasswordOnStartLabelElement.childNodes + .filter((node) => node.nativeNode.nodeType === Node.TEXT_NODE) + .map((node) => node.nativeNode.wholeText?.trim()); + expect(textNodes).toContain("Require password or pin on app start"); + }); + + it("require password on app start message when RemoveUnlockWithPin policy is enabled and pin set and windows desktop", async () => { + const policy = new Policy(); + policy.type = PolicyType.RemoveUnlockWithPin; + policy.enabled = true; + policyService.get$.mockReturnValue(of(policy)); + platformUtilsService.getDevice.mockReturnValue(DeviceType.WindowsDesktop); + i18nService.t.mockImplementation((id: string) => { + if (id === "requirePasswordOnStart") { + return "Require password or pin on app start"; + } else if (id === "requirePasswordWithoutPinOnStart") { + return "Require password on app start"; + } + return ""; + }); + pinServiceAbstraction.isPinSet.mockResolvedValue(true); + + await component.ngOnInit(); + fixture.detectChanges(); + + const requirePasswordOnStartLabelElement = fixture.debugElement.query( + By.css("label[for='requirePasswordOnStart']"), + ); + expect(requirePasswordOnStartLabelElement).not.toBeNull(); + expect(requirePasswordOnStartLabelElement.children).toHaveLength(1); + expect(requirePasswordOnStartLabelElement.children[0].name).toBe("input"); + expect(requirePasswordOnStartLabelElement.children[0].attributes).toMatchObject({ + id: "requirePasswordOnStart", + type: "checkbox", + }); + const textNodes = requirePasswordOnStartLabelElement.childNodes + .filter((node) => node.nativeNode.nodeType === Node.TEXT_NODE) + .map((node) => node.nativeNode.wholeText?.trim()); + expect(textNodes).toContain("Require password on app start"); + }); + }); }); diff --git a/libs/common/src/admin-console/enums/policy-type.enum.spec.ts b/libs/common/src/admin-console/enums/policy-type.enum.spec.ts new file mode 100644 index 00000000000..51e1a75ad3c --- /dev/null +++ b/libs/common/src/admin-console/enums/policy-type.enum.spec.ts @@ -0,0 +1,7 @@ +import { PolicyType } from "@bitwarden/common/admin-console/enums/policy-type.enum"; + +describe("PolicyType", () => { + it("RemoveUnlockWithPin should be 14", () => { + expect(PolicyType.RemoveUnlockWithPin).toBe(14); + }); +}); diff --git a/libs/common/src/admin-console/services/policy/policy.service.spec.ts b/libs/common/src/admin-console/services/policy/policy.service.spec.ts index 12b57f1b4f7..48979f1e31e 100644 --- a/libs/common/src/admin-console/services/policy/policy.service.spec.ts +++ b/libs/common/src/admin-console/services/policy/policy.service.spec.ts @@ -220,19 +220,34 @@ describe("PolicyService", () => { arrayToRecord([ policyData("policy1", "org1", PolicyType.ActivateAutofill, true), policyData("policy2", "org1", PolicyType.DisablePersonalVaultExport, true), + policyData("policy3", "org1", PolicyType.RemoveUnlockWithPin, true), ]), ); - const result = await firstValueFrom( - policyService.get$(PolicyType.DisablePersonalVaultExport), - ); - - expect(result).toEqual({ + 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 () => { From a2a61dec4bfee6fa0ad60fc753298f1839949567 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Tue, 11 Feb 2025 22:06:47 +0000 Subject: [PATCH 07/10] unit tests coverage --- .../policies/base-policy.component.ts | 22 ++-- .../remove-unlock-with-pin.component.spec.ts | 110 ++++++++++++++++++ 2 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.spec.ts diff --git a/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts b/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts index 96d8d8b24a7..c73eeead02b 100644 --- a/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts @@ -35,24 +35,24 @@ export abstract class BasePolicyComponent implements OnInit { } } - loadData() { + buildRequest() { + const request = new PolicyRequest(); + request.enabled = this.enabled.value; + request.type = this.policy.type; + request.data = this.buildRequestData(); + + return Promise.resolve(request); + } + + private loadData() { this.data.patchValue(this.policyResponse.data ?? {}); } - buildRequestData() { + private buildRequestData() { if (this.data != null) { return this.data.value; } return null; } - - buildRequest() { - const request = new PolicyRequest(); - request.enabled = this.enabled.value; - request.type = this.policy.type; - request.data = this.buildRequestData(); - - return Promise.resolve(request); - } } diff --git a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.spec.ts b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.spec.ts new file mode 100644 index 00000000000..9058cd22f3e --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.spec.ts @@ -0,0 +1,110 @@ +import { CommonModule } from "@angular/common"; +import { NO_ERRORS_SCHEMA } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ReactiveFormsModule } from "@angular/forms"; +import { By } from "@angular/platform-browser"; +import { mock } from "jest-mock-extended"; + +import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { + RemoveUnlockWithPinPolicy, + RemoveUnlockWithPinPolicyComponent, +} from "./remove-unlock-with-pin.component"; + +describe("RemoveUnlockWithPinPolicy", () => { + const policy = new RemoveUnlockWithPinPolicy(); + + it("should have correct attributes", () => { + expect(policy.name).toEqual("removeUnlockWithPinPolicyTitle"); + expect(policy.description).toEqual("removeUnlockWithPinPolicyDesc"); + expect(policy.type).toEqual(PolicyType.RemoveUnlockWithPin); + expect(policy.component).toEqual(RemoveUnlockWithPinPolicyComponent); + }); +}); + +describe("RemoveUnlockWithPinPolicyComponent", () => { + let component: RemoveUnlockWithPinPolicyComponent; + let fixture: ComponentFixture; + const i18nService = mock(); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CommonModule, ReactiveFormsModule], + declarations: [RemoveUnlockWithPinPolicyComponent, I18nPipe], + providers: [ + { provide: I18nService, useValue: mock() }, + { provide: I18nService, useValue: i18nService }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(RemoveUnlockWithPinPolicyComponent); + component = fixture.componentInstance; + }); + + it("input selected on load when policy enabled", async () => { + component.policyResponse = new PolicyResponse({ + id: "policy1", + organizationId: "org1", + type: PolicyType.RemoveUnlockWithPin, + enabled: true, + }); + + component.ngOnInit(); + fixture.detectChanges(); + + expect(component.enabled.value).toBe(true); + const inputElement = fixture.debugElement.query(By.css("input")); + expect(inputElement).not.toBeNull(); + expect(inputElement.properties).toMatchObject({ + id: "enabled", + type: "checkbox", + checked: true, + }); + }); + + it("input not selected on load when policy disabled", async () => { + component.policyResponse = new PolicyResponse({ + id: "policy1", + organizationId: "org1", + type: PolicyType.RemoveUnlockWithPin, + enabled: false, + }); + + component.ngOnInit(); + fixture.detectChanges(); + + expect(component.enabled.value).toBe(false); + const inputElement = fixture.debugElement.query(By.css("input")); + expect(inputElement).not.toBeNull(); + expect(inputElement.properties).toMatchObject({ + id: "enabled", + type: "checkbox", + checked: false, + }); + }); + + it("turn on message label", async () => { + component.policyResponse = new PolicyResponse({ + id: "policy1", + organizationId: "org1", + type: PolicyType.RemoveUnlockWithPin, + enabled: false, + }); + i18nService.t.mockReturnValue("Turn on"); + + component.ngOnInit(); + fixture.detectChanges(); + + const bitLabelElement = fixture.debugElement.query(By.css("bit-label")); + expect(bitLabelElement).not.toBeNull(); + const textNodes = bitLabelElement.childNodes + .filter((node) => node.nativeNode.nodeType === Node.TEXT_NODE) + .map((node) => node.nativeNode.wholeText?.trim()); + expect(textNodes).toContain("Turn on"); + }); +}); From 0aa9d374c766acd234ee7f39a5545f125cfd3517 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Tue, 11 Feb 2025 22:28:13 +0000 Subject: [PATCH 08/10] private access method error --- .../organizations/policies/base-policy.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts b/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts index c73eeead02b..78fabb280be 100644 --- a/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts @@ -35,6 +35,10 @@ export abstract class BasePolicyComponent implements OnInit { } } + loadData() { + this.data.patchValue(this.policyResponse.data ?? {}); + } + buildRequest() { const request = new PolicyRequest(); request.enabled = this.enabled.value; @@ -44,10 +48,6 @@ export abstract class BasePolicyComponent implements OnInit { return Promise.resolve(request); } - private loadData() { - this.data.patchValue(this.policyResponse.data ?? {}); - } - private buildRequestData() { if (this.data != null) { return this.data.value; From a16ecaf0f7d7b0d1a8f0d9eb5e86f9b9fc5afcb7 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Tue, 11 Feb 2025 22:35:35 +0000 Subject: [PATCH 09/10] private access method error --- .../policies/base-policy.component.ts | 20 +++++++++---------- .../maximum-vault-timeout.component.ts | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts b/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts index 78fabb280be..9c6bb20f8f3 100644 --- a/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts @@ -35,11 +35,19 @@ export abstract class BasePolicyComponent implements OnInit { } } - loadData() { + protected loadData() { this.data.patchValue(this.policyResponse.data ?? {}); } - buildRequest() { + protected buildRequestData() { + if (this.data != null) { + return this.data.value; + } + + return null; + } + + protected buildRequest() { const request = new PolicyRequest(); request.enabled = this.enabled.value; request.type = this.policy.type; @@ -47,12 +55,4 @@ export abstract class BasePolicyComponent implements OnInit { return Promise.resolve(request); } - - private buildRequestData() { - if (this.data != null) { - return this.data.value; - } - - return null; - } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts index 0f5569bf7a8..39284f6bf08 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts @@ -43,7 +43,7 @@ export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent { ]; } - loadData() { + protected loadData() { const minutes = this.policyResponse.data?.minutes; const action = this.policyResponse.data?.action; @@ -54,7 +54,7 @@ export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent { }); } - buildRequestData() { + protected buildRequestData() { if (this.data.value.hours == null && this.data.value.minutes == null) { return null; } @@ -65,7 +65,7 @@ export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent { }; } - async buildRequest(): Promise { + protected async buildRequest(): Promise { const request = await super.buildRequest(); if (request.data?.minutes == null || request.data?.minutes <= 0) { throw new Error(this.i18nService.t("invalidMaximumVaultTimeout")); From d5f650dba4046e908f9cd5b23982b22cd85c1ea4 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Tue, 11 Feb 2025 22:42:42 +0000 Subject: [PATCH 10/10] private access method error --- .../policies/base-policy.component.ts | 18 +++++++++--------- .../maximum-vault-timeout.component.ts | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts b/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts index 9c6bb20f8f3..01c45264236 100644 --- a/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts @@ -35,6 +35,15 @@ export abstract class BasePolicyComponent implements OnInit { } } + buildRequest() { + const request = new PolicyRequest(); + request.enabled = this.enabled.value; + request.type = this.policy.type; + request.data = this.buildRequestData(); + + return Promise.resolve(request); + } + protected loadData() { this.data.patchValue(this.policyResponse.data ?? {}); } @@ -46,13 +55,4 @@ export abstract class BasePolicyComponent implements OnInit { return null; } - - protected buildRequest() { - const request = new PolicyRequest(); - request.enabled = this.enabled.value; - request.type = this.policy.type; - request.data = this.buildRequestData(); - - return Promise.resolve(request); - } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts index 39284f6bf08..a0ad1cd3010 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts @@ -65,7 +65,7 @@ export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent { }; } - protected async buildRequest(): Promise { + async buildRequest(): Promise { const request = await super.buildRequest(); if (request.data?.minutes == null || request.data?.minutes <= 0) { throw new Error(this.i18nService.t("invalidMaximumVaultTimeout"));