mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-25 12:15:18 +01:00
[EC-377] Transition Policy service into providing observables (#3259)
* Added abstractions for PolicyApiService and PolicyService * Added implementations for PolicyApiService and PolicyService * Updated all references to new PolicyApiService and PolicyService * Deleted old PolicyService abstraction and implementation * Fixed CLI import path for policy.service * Fixed main.background.ts policyApiService dependency for policyService * Ran prettier * Updated policy-api.service with the correct imports * [EC-377] Removed methods from StateService that read policies * [EC-377] Updated policy service getAll method to use observable collection * [EC-377] Added first unit tests for policy service * [EC-377] Added more unit tests for Policy Service * [EC-376] Sorted methods order in PolicyApiService * [EC-376] Removed unused clearCache method from PolicyService * [EC-376] Added upsert method to PolicyService * [EC-376] PolicyApiService putPolicy method now upserts data to PolicyService * [EC-377] Removed tests for deleted clearCache method * [EC-377] Added unit test for PolicyService.upsert * [EC-377] Updated references to state service observables * [EC-377] Removed getAll method from PolicyService and refactored components to use observable collection * [EC-377] Updated components to use concatMap instead of async subscribe * [EC-377] Removed getPolicyForOrganization from policyApiService * [EC-377] Updated policyAppliesToUser to return observable collection * [EC-377] Changed policyService.policyAppliesToUser to return observable * [EC-377] Fixed browser settings.component.ts getting vault timeout * Updated people.component.ts to get ResetPassword policy through a subscription * [EC-377] Changed passwordGenerationService.getOptions to return observable * [EC-377] Fixed CLI generate.command.ts getting enforcePasswordGeneratorPoliciesOnOptions * [EC-377] Fixed eslint errors on rxjs * [EC-377] Reverted changes on passwordGeneration.service and vaultTimeout.service * [EC-377] Removed eslint disable on web/vault/add-edit-component * [EC-377] Changed AccountData.policies to TemporaryDataEncryption * [EC-377] Updated import.component to be reactive to policyAppliesToUser$ * [EC-377] Updated importBlockedByPolicy$ * [EC-377] Fixed missing rename * [EC-377] Updated policyService.masterPasswordPolicyOptions to return observable * [EC-377] Fixed vaultTimeout imports from merge * [EC-377] Reverted call to passwordGenerationService.getOptions * [EC-377] Reverted call to enforcePasswordGeneratorPoliciesOnOptions * [EC-377] Removed unneeded ngOnDestroy * Apply suggestions from code review Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * [EC-377] Fixed login.component.ts and register.component.ts * [EC-377] Updated PolicyService to update vaultTimeout * [EC-377] Updated PolicyService dependencies * [EC-377] Renamed policyAppliesToUser to policyAppliesToActiveUser * [EC-377] VaultTimeoutSettings service now gets the vault timeout directly instead of using observables * [EC-377] Fixed unit tests by removing unneeded vaultTimeoutSettingsService * [EC-377] Set getDecryptedPolicies and setDecryptedPolicies as deprecated * [EC-377] Set PolicyService.getAll as deprecated and updated to use prototype.hasOwnProperty * [EC-565] Reverted unintended change to vaultTimeoutSettings that was causing a bug to not display the correct vault timeout * [EC-377] Removed unneeded destroy$ from preferences.component.ts * [EC-377] Fixed policy.service.ts import of OrganizationService Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> Co-authored-by: mimartin12 <77340197+mimartin12@users.noreply.github.com>
This commit is contained in:
parent
ea12ee2b10
commit
3a298bd989
@ -64,7 +64,7 @@ export default class CommandsBackground {
|
||||
}
|
||||
|
||||
private async generatePasswordToClipboard() {
|
||||
const options = (await this.passwordGenerationService.getOptions())[0];
|
||||
const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
|
||||
const password = await this.passwordGenerationService.generatePassword(options);
|
||||
this.platformUtilsService.copyToClipboard(password, { window: window });
|
||||
this.passwordGenerationService.addHistory(password);
|
||||
|
@ -66,7 +66,7 @@ export default class ContextMenusBackground {
|
||||
}
|
||||
|
||||
private async generatePasswordToClipboard() {
|
||||
const options = (await this.passwordGenerationService.getOptions())[0];
|
||||
const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
|
||||
const password = await this.passwordGenerationService.generatePassword(options);
|
||||
this.platformUtilsService.copyToClipboard(password, { window: window });
|
||||
this.passwordGenerationService.addHistory(password);
|
||||
|
@ -446,6 +446,8 @@ export default class NotificationBackground {
|
||||
}
|
||||
|
||||
private async allowPersonalOwnership(): Promise<boolean> {
|
||||
return !(await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership));
|
||||
return !(await firstValueFrom(
|
||||
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as program from "commander";
|
||||
import * as inquirer from "inquirer";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ExportFormat, ExportService } from "@bitwarden/common/abstractions/export.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
@ -15,7 +16,9 @@ export class ExportCommand {
|
||||
async run(options: program.OptionValues): Promise<Response> {
|
||||
if (
|
||||
options.organizationid == null &&
|
||||
(await this.policyService.policyAppliesToUser(PolicyType.DisablePersonalVaultExport))
|
||||
(await firstValueFrom(
|
||||
this.policyService.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport)
|
||||
))
|
||||
) {
|
||||
return Response.badRequest(
|
||||
"One or more organization policies prevents you from exporting your personal vault."
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Component, NgZone } from "@angular/core";
|
||||
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/components/login.component";
|
||||
@ -29,13 +30,14 @@ import { RouterService, StateService } from "../../core";
|
||||
selector: "app-login",
|
||||
templateUrl: "login.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class LoginComponent extends BaseLoginComponent {
|
||||
export class LoginComponent extends BaseLoginComponent implements OnInit, OnDestroy {
|
||||
showResetPasswordAutoEnrollWarning = false;
|
||||
enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions;
|
||||
policies: ListResponse<PolicyResponse>;
|
||||
showPasswordless = false;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
authService: AuthService,
|
||||
router: Router,
|
||||
@ -128,12 +130,21 @@ export class LoginComponent extends BaseLoginComponent {
|
||||
this.showResetPasswordAutoEnrollWarning =
|
||||
resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled;
|
||||
|
||||
this.enforcedPasswordPolicyOptions =
|
||||
await this.policyService.getMasterPasswordPolicyOptions(policyList);
|
||||
this.policyService
|
||||
.masterPasswordPolicyOptions$(policyList)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((enforcedPasswordPolicyOptions) => {
|
||||
this.enforcedPasswordPolicyOptions = enforcedPasswordPolicyOptions;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async goAfterLogIn() {
|
||||
const masterPassword = this.formGroup.value.masterPassword;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { UntypedFormBuilder } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component";
|
||||
@ -27,14 +28,14 @@ import { RouterService } from "../core";
|
||||
selector: "app-register",
|
||||
templateUrl: "register.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class RegisterComponent extends BaseRegisterComponent {
|
||||
export class RegisterComponent extends BaseRegisterComponent implements OnInit, OnDestroy {
|
||||
email = "";
|
||||
showCreateOrgMessage = false;
|
||||
layout = "";
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
|
||||
private policies: Policy[];
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
formValidationErrorService: FormValidationErrorsService,
|
||||
@ -130,11 +131,19 @@ export class RegisterComponent extends BaseRegisterComponent {
|
||||
}
|
||||
|
||||
if (this.policies != null) {
|
||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(
|
||||
this.policies
|
||||
);
|
||||
this.policyService
|
||||
.masterPasswordPolicyOptions$(this.policies)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((enforcedPasswordPolicyOptions) => {
|
||||
this.enforcedPolicyOptions = enforcedPasswordPolicyOptions;
|
||||
});
|
||||
}
|
||||
|
||||
await super.ngOnInit();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Substitute } from "@fluffy-spoon/substitute";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
|
||||
import { I18nPipe } from "@bitwarden/angular/pipes/i18n.pipe";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
@ -47,7 +47,7 @@ describe("TrialInitiationComponent", () => {
|
||||
};
|
||||
|
||||
policyServiceMock = {
|
||||
getMasterPasswordPolicyOptions: jest.fn(),
|
||||
masterPasswordPolicyOptions$: jest.fn(),
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
@ -145,14 +145,16 @@ describe("TrialInitiationComponent", () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
policyServiceMock.getMasterPasswordPolicyOptions.mockReturnValueOnce({
|
||||
minComplexity: 4,
|
||||
minLength: 10,
|
||||
requireLower: null,
|
||||
requireNumbers: null,
|
||||
requireSpecial: null,
|
||||
requireUpper: null,
|
||||
} as MasterPasswordPolicyOptions);
|
||||
policyServiceMock.masterPasswordPolicyOptions$.mockReturnValue(
|
||||
of({
|
||||
minComplexity: 4,
|
||||
minLength: 10,
|
||||
requireLower: null,
|
||||
requireNumbers: null,
|
||||
requireSpecial: null,
|
||||
requireUpper: null,
|
||||
} as MasterPasswordPolicyOptions)
|
||||
);
|
||||
|
||||
// Need to recreate component with new service mocks
|
||||
fixture = TestBed.createComponent(TrialInitiationComponent);
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { StepperSelectionEvent } from "@angular/cdk/stepper";
|
||||
import { TitleCasePipe } from "@angular/common";
|
||||
import { Component, OnInit, ViewChild } from "@angular/core";
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { UntypedFormBuilder, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs";
|
||||
import { first, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
@ -24,8 +24,7 @@ import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.co
|
||||
selector: "app-trial",
|
||||
templateUrl: "trial-initiation.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class TrialInitiationComponent implements OnInit {
|
||||
export class TrialInitiationComponent implements OnInit, OnDestroy {
|
||||
email = "";
|
||||
org = "";
|
||||
orgInfoSubLabel = "";
|
||||
@ -63,6 +62,8 @@ export class TrialInitiationComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
protected router: Router,
|
||||
@ -140,12 +141,20 @@ export class TrialInitiationComponent implements OnInit {
|
||||
}
|
||||
|
||||
if (this.policies != null) {
|
||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(
|
||||
this.policies
|
||||
);
|
||||
this.policyService
|
||||
.masterPasswordPolicyOptions$(this.policies)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((enforcedPasswordPolicyOptions) => {
|
||||
this.enforcedPolicyOptions = enforcedPasswordPolicyOptions;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
stepSelectionChange(event: StepperSelectionEvent) {
|
||||
// Set org info sub label
|
||||
if (event.selectedIndex === 1 && this.orgInfoFormGroup.controls.name.value === "") {
|
||||
|
@ -1,16 +1,32 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Injectable, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
import { DeviceType } from "@bitwarden/common/enums/deviceType";
|
||||
import { EventType } from "@bitwarden/common/enums/eventType";
|
||||
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
import { Policy } from "@bitwarden/common/models/domain/policy";
|
||||
import { EventResponse } from "@bitwarden/common/models/response/eventResponse";
|
||||
|
||||
@Injectable()
|
||||
export class EventService {
|
||||
export class EventService implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
private policies: Policy[];
|
||||
|
||||
constructor(private i18nService: I18nService, private policyService: PolicyService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.policyService.policies$.pipe(takeUntil(this.destroy$)).subscribe((policies) => {
|
||||
this.policies = policies;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
getDefaultDateFilters() {
|
||||
const d = new Date();
|
||||
const end = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59);
|
||||
@ -326,8 +342,7 @@ export class EventService {
|
||||
case EventType.Policy_Updated: {
|
||||
msg = this.i18nService.t("modifiedPolicyId", this.formatPolicyId(ev));
|
||||
|
||||
const policies = await this.policyService.getAll();
|
||||
const policy = policies.filter((p) => p.id === ev.policyId)[0];
|
||||
const policy = this.policies.filter((p) => p.id === ev.policyId)[0];
|
||||
let p1 = this.getShortId(ev.policyId);
|
||||
if (policy != null) {
|
||||
p1 = PolicyType[policy.type];
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { combineLatest, concatMap, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||
@ -12,7 +12,6 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
@ -43,10 +42,9 @@ import { UserGroupsComponent } from "./user-groups.component";
|
||||
selector: "app-org-people",
|
||||
templateUrl: "people.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class PeopleComponent
|
||||
extends BasePeopleComponent<OrganizationUserUserDetailsResponse>
|
||||
implements OnInit
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
@ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
|
||||
@ViewChild("groupsTemplate", { read: ViewContainerRef, static: true })
|
||||
@ -77,6 +75,8 @@ export class PeopleComponent
|
||||
orgResetPasswordPolicyEnabled = false;
|
||||
callingUserType: OrganizationUserType = null;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
apiService: ApiService,
|
||||
private route: ActivatedRoute,
|
||||
@ -84,10 +84,8 @@ export class PeopleComponent
|
||||
modalService: ModalService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
cryptoService: CryptoService,
|
||||
private router: Router,
|
||||
searchService: SearchService,
|
||||
validationService: ValidationService,
|
||||
private policyApiService: PolicyApiServiceAbstraction,
|
||||
private policyService: PolicyService,
|
||||
logService: LogService,
|
||||
searchPipe: SearchPipe,
|
||||
@ -113,53 +111,63 @@ export class PeopleComponent
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
const organization = await this.organizationService.get(this.organizationId);
|
||||
this.accessEvents = organization.useEvents;
|
||||
this.accessGroups = organization.useGroups;
|
||||
this.canResetPassword = organization.canManageUsersPassword;
|
||||
this.orgUseResetPassword = organization.useResetPassword;
|
||||
this.callingUserType = organization.type;
|
||||
this.orgHasKeys = organization.hasPublicAndPrivateKeys;
|
||||
combineLatest([this.route.params, this.route.queryParams, this.policyService.policies$])
|
||||
.pipe(
|
||||
concatMap(async ([params, qParams, policies]) => {
|
||||
this.organizationId = params.organizationId;
|
||||
const organization = await this.organizationService.get(this.organizationId);
|
||||
this.accessEvents = organization.useEvents;
|
||||
this.accessGroups = organization.useGroups;
|
||||
this.canResetPassword = organization.canManageUsersPassword;
|
||||
this.orgUseResetPassword = organization.useResetPassword;
|
||||
this.callingUserType = organization.type;
|
||||
this.orgHasKeys = organization.hasPublicAndPrivateKeys;
|
||||
|
||||
// Backfill pub/priv key if necessary
|
||||
if (this.canResetPassword && !this.orgHasKeys) {
|
||||
const orgShareKey = await this.cryptoService.getOrgKey(this.organizationId);
|
||||
const orgKeys = await this.cryptoService.makeKeyPair(orgShareKey);
|
||||
const request = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
|
||||
const response = await this.organizationApiService.updateKeys(this.organizationId, request);
|
||||
if (response != null) {
|
||||
this.orgHasKeys = response.publicKey != null && response.privateKey != null;
|
||||
await this.syncService.fullSync(true); // Replace oganizations with new data
|
||||
} else {
|
||||
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
|
||||
}
|
||||
}
|
||||
|
||||
await this.load();
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
this.searchText = qParams.search;
|
||||
if (qParams.viewEvents != null) {
|
||||
const user = this.users.filter((u) => u.id === qParams.viewEvents);
|
||||
if (user.length > 0 && user[0].status === OrganizationUserStatusType.Confirmed) {
|
||||
this.events(user[0]);
|
||||
// Backfill pub/priv key if necessary
|
||||
if (this.canResetPassword && !this.orgHasKeys) {
|
||||
const orgShareKey = await this.cryptoService.getOrgKey(this.organizationId);
|
||||
const orgKeys = await this.cryptoService.makeKeyPair(orgShareKey);
|
||||
const request = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
|
||||
const response = await this.organizationApiService.updateKeys(
|
||||
this.organizationId,
|
||||
request
|
||||
);
|
||||
if (response != null) {
|
||||
this.orgHasKeys = response.publicKey != null && response.privateKey != null;
|
||||
await this.syncService.fullSync(true); // Replace oganizations with new data
|
||||
} else {
|
||||
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const resetPasswordPolicy = policies
|
||||
.filter((policy) => policy.type === PolicyType.ResetPassword)
|
||||
.find((p) => p.organizationId === this.organizationId);
|
||||
this.orgResetPasswordPolicyEnabled = resetPasswordPolicy?.enabled;
|
||||
|
||||
await this.load();
|
||||
|
||||
this.searchText = qParams.search;
|
||||
if (qParams.viewEvents != null) {
|
||||
const user = this.users.filter((u) => u.id === qParams.viewEvents);
|
||||
if (user.length > 0 && user[0].status === OrganizationUserStatusType.Confirmed) {
|
||||
this.events(user[0]);
|
||||
}
|
||||
}
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async load() {
|
||||
const resetPasswordPolicy = await this.policyApiService.getPolicyForOrganization(
|
||||
PolicyType.ResetPassword,
|
||||
this.organizationId
|
||||
);
|
||||
this.orgResetPasswordPolicyEnabled = resetPasswordPolicy?.enabled;
|
||||
super.load();
|
||||
await super.load();
|
||||
}
|
||||
|
||||
getUsers(): Promise<ListResponse<OrganizationUserUserDetailsResponse>> {
|
||||
|
@ -1,4 +1,13 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from "@angular/core";
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from "@angular/core";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
import zxcvbn from "zxcvbn";
|
||||
|
||||
import { PasswordStrengthComponent } from "@bitwarden/angular/shared/components/password-strength/password-strength.component";
|
||||
@ -18,7 +27,7 @@ import { OrganizationUserResetPasswordRequest } from "@bitwarden/common/models/r
|
||||
selector: "app-reset-password",
|
||||
templateUrl: "reset-password.component.html",
|
||||
})
|
||||
export class ResetPasswordComponent implements OnInit {
|
||||
export class ResetPasswordComponent implements OnInit, OnDestroy {
|
||||
@Input() name: string;
|
||||
@Input() email: string;
|
||||
@Input() id: string;
|
||||
@ -32,6 +41,8 @@ export class ResetPasswordComponent implements OnInit {
|
||||
passwordStrengthResult: zxcvbn.ZXCVBNResult;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
@ -43,8 +54,18 @@ export class ResetPasswordComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
// Get Enforced Policy Options
|
||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
|
||||
this.policyService
|
||||
.masterPasswordPolicyOptions$()
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(
|
||||
(enforcedPasswordPolicyOptions) =>
|
||||
(this.enforcedPolicyOptions = enforcedPasswordPolicyOptions)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get loggedOutWarningName() {
|
||||
@ -52,7 +73,7 @@ export class ResetPasswordComponent implements OnInit {
|
||||
}
|
||||
|
||||
async generatePassword() {
|
||||
const options = (await this.passwordGenerationService.getOptions())[0];
|
||||
const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
|
||||
this.newPassword = await this.passwordGenerationService.generatePassword(options);
|
||||
this.passwordStrengthComponent.updatePasswordStrength(this.newPassword);
|
||||
}
|
||||
|
@ -47,7 +47,6 @@ export class OrganizationImportComponent extends ImportComponent {
|
||||
this.organizationId = params.organizationId;
|
||||
this.successNavigate = ["organizations", this.organizationId, "vault"];
|
||||
await super.ngOnInit();
|
||||
this.importBlockedByPolicy = false;
|
||||
});
|
||||
const organization = await this.organizationService.get(this.organizationId);
|
||||
this.organizationName = organization.name;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { takeUntil } from "rxjs";
|
||||
|
||||
import { ChangePasswordComponent } from "@bitwarden/angular/components/change-password.component";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
@ -21,7 +22,11 @@ import { PolicyResponse } from "@bitwarden/common/models/response/policyResponse
|
||||
selector: "emergency-access-takeover",
|
||||
templateUrl: "emergency-access-takeover.component.html",
|
||||
})
|
||||
export class EmergencyAccessTakeoverComponent extends ChangePasswordComponent implements OnInit {
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class EmergencyAccessTakeoverComponent
|
||||
extends ChangePasswordComponent
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
@Output() onDone = new EventEmitter();
|
||||
@Input() emergencyAccessId: string;
|
||||
@Input() name: string;
|
||||
@ -59,12 +64,19 @@ export class EmergencyAccessTakeoverComponent extends ChangePasswordComponent im
|
||||
const policies = response.data.map(
|
||||
(policyResponse: PolicyResponse) => new Policy(new PolicyData(policyResponse))
|
||||
);
|
||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(
|
||||
policies
|
||||
);
|
||||
|
||||
this.policyService
|
||||
.masterPasswordPolicyOptions$(policies)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((enforcedPolicyOptions) => (this.enforcedPolicyOptions = enforcedPolicyOptions));
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
ngOnDestroy(): void {
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (!(await this.strongPassword())) {
|
||||
return;
|
||||
|
@ -1,6 +1,15 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from "@angular/core";
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from "@angular/core";
|
||||
import { UntypedFormBuilder, Validators } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
@ -35,7 +44,7 @@ interface OnSuccessArgs {
|
||||
selector: "app-organization-plans",
|
||||
templateUrl: "organization-plans.component.html",
|
||||
})
|
||||
export class OrganizationPlansComponent implements OnInit {
|
||||
export class OrganizationPlansComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
|
||||
@ViewChild(TaxInfoComponent) taxComponent: TaxInfoComponent;
|
||||
|
||||
@ -73,6 +82,8 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
|
||||
plans: PlanResponse[];
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
@ -114,9 +125,21 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
this.formGroup.controls.billingEmail.addValidators(Validators.required);
|
||||
}
|
||||
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.SingleOrg)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((policyAppliesToActiveUser) => {
|
||||
this.singleOrgPolicyBlock = policyAppliesToActiveUser;
|
||||
});
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get createOrganization() {
|
||||
return this.organizationId == null;
|
||||
}
|
||||
@ -288,8 +311,6 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
}
|
||||
|
||||
async submit() {
|
||||
this.singleOrgPolicyBlock = await this.userHasBlockingSingleOrgPolicy();
|
||||
|
||||
if (this.singleOrgPolicyBlock) {
|
||||
return;
|
||||
}
|
||||
@ -353,10 +374,6 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private async userHasBlockingSingleOrgPolicy() {
|
||||
return this.policyService.policyAppliesToUser(PolicyType.SingleOrg);
|
||||
}
|
||||
|
||||
private async updateOrganization(orgId: string) {
|
||||
const request = new OrganizationUpgradeRequest();
|
||||
request.businessName = this.formGroup.controls.businessOwned.value
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Component, OnInit, Type, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { Component, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
@ -25,8 +26,7 @@ import { TwoFactorYubiKeyComponent } from "./two-factor-yubikey.component";
|
||||
selector: "app-two-factor-setup",
|
||||
templateUrl: "two-factor-setup.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class TwoFactorSetupComponent implements OnInit {
|
||||
export class TwoFactorSetupComponent implements OnInit, OnDestroy {
|
||||
@ViewChild("recoveryTemplate", { read: ViewContainerRef, static: true })
|
||||
recoveryModalRef: ViewContainerRef;
|
||||
@ViewChild("authenticatorTemplate", { read: ViewContainerRef, static: true })
|
||||
@ -49,6 +49,9 @@ export class TwoFactorSetupComponent implements OnInit {
|
||||
modal: ModalRef;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private twoFactorAuthPolicyAppliesToActiveUser: boolean;
|
||||
|
||||
constructor(
|
||||
protected apiService: ApiService,
|
||||
protected modalService: ModalService,
|
||||
@ -93,9 +96,22 @@ export class TwoFactorSetupComponent implements OnInit {
|
||||
}
|
||||
|
||||
this.providers.sort((a: any, b: any) => a.sort - b.sort);
|
||||
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.TwoFactorAuthentication)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((policyAppliesToActiveUser) => {
|
||||
this.twoFactorAuthPolicyAppliesToActiveUser = policyAppliesToActiveUser;
|
||||
});
|
||||
|
||||
await this.load();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.loading = true;
|
||||
const providerList = await this.getTwoFactorProviders();
|
||||
@ -203,9 +219,7 @@ export class TwoFactorSetupComponent implements OnInit {
|
||||
|
||||
private async evaluatePolicies() {
|
||||
if (this.organizationId == null && this.providers.filter((p) => p.enabled).length === 1) {
|
||||
this.showPolicyWarning = await this.policyService.policyAppliesToUser(
|
||||
PolicyType.TwoFactorAuthentication
|
||||
);
|
||||
this.showPolicyWarning = this.twoFactorAuthPolicyAppliesToActiveUser;
|
||||
} else {
|
||||
this.showPolicyWarning = false;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "importData" | i18n }}</h1>
|
||||
</div>
|
||||
<app-callout type="info" *ngIf="importBlockedByPolicy">
|
||||
<app-callout type="info" *ngIf="importBlockedByPolicy$ | async">
|
||||
{{ "personalOwnershipPolicyInEffectImports" | i18n }}
|
||||
</app-callout>
|
||||
<form #form (ngSubmit)="submit()" ngNativeValidate>
|
||||
@ -14,7 +14,7 @@
|
||||
name="Format"
|
||||
[(ngModel)]="format"
|
||||
class="form-control"
|
||||
[disabled]="importBlockedByPolicy"
|
||||
[disabled]="importBlockedByPolicy$ | async"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of featuredImportOptions" [ngValue]="o.id">{{ o.name }}</option>
|
||||
@ -296,7 +296,7 @@
|
||||
id="file"
|
||||
class="form-control-file"
|
||||
name="file"
|
||||
[disabled]="importBlockedByPolicy"
|
||||
[disabled]="importBlockedByPolicy$ | async"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -308,14 +308,14 @@
|
||||
class="form-control"
|
||||
name="FileContents"
|
||||
[(ngModel)]="fileContents"
|
||||
[disabled]="importBlockedByPolicy"
|
||||
[disabled]="importBlockedByPolicy$ | async"
|
||||
></textarea>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-submit"
|
||||
[disabled]="loading || importBlockedByPolicy"
|
||||
[ngClass]="{ manual: importBlockedByPolicy }"
|
||||
[disabled]="loading || importBlockedByPolicy$ | async"
|
||||
[ngClass]="{ manual: importBlockedByPolicy$ | async }"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "importData" | i18n }}</span>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import * as JSZip from "jszip";
|
||||
import { firstValueFrom, Subject } from "rxjs";
|
||||
import Swal, { SweetAlertIcon } from "sweetalert2";
|
||||
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
@ -19,18 +20,22 @@ import { FilePasswordPromptComponent } from "./file-password-prompt.component";
|
||||
selector: "app-import",
|
||||
templateUrl: "import.component.html",
|
||||
})
|
||||
export class ImportComponent implements OnInit {
|
||||
export class ImportComponent implements OnInit, OnDestroy {
|
||||
featuredImportOptions: ImportOption[];
|
||||
importOptions: ImportOption[];
|
||||
format: ImportType = null;
|
||||
fileContents: string;
|
||||
formPromise: Promise<ImportError>;
|
||||
loading = false;
|
||||
importBlockedByPolicy = false;
|
||||
importBlockedByPolicy$ = this.policyService.policyAppliesToActiveUser$(
|
||||
PolicyType.PersonalOwnership
|
||||
);
|
||||
|
||||
protected organizationId: string = null;
|
||||
protected successNavigate: any[] = ["vault"];
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected importService: ImportService,
|
||||
@ -43,14 +48,15 @@ export class ImportComponent implements OnInit {
|
||||
|
||||
async ngOnInit() {
|
||||
this.setImportOptions();
|
||||
}
|
||||
|
||||
this.importBlockedByPolicy = await this.policyService.policyAppliesToUser(
|
||||
PolicyType.PersonalOwnership
|
||||
);
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.importBlockedByPolicy) {
|
||||
if (await firstValueFrom(this.importBlockedByPolicy$)) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
|
||||
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/components/add-edit.component";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
@ -24,7 +24,7 @@ import { LoginUriView } from "@bitwarden/common/models/view/loginUriView";
|
||||
selector: "app-vault-add-edit",
|
||||
templateUrl: "add-edit.component.html",
|
||||
})
|
||||
export class AddEditComponent extends BaseAddEditComponent {
|
||||
export class AddEditComponent extends BaseAddEditComponent implements OnInit, OnDestroy {
|
||||
canAccessPremium: boolean;
|
||||
totpCode: string;
|
||||
totpCodeFormatted: string;
|
||||
@ -95,6 +95,10 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
toggleFavorite() {
|
||||
this.cipher.favorite = !this.cipher.favorite;
|
||||
}
|
||||
@ -133,7 +137,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
async generatePassword(): Promise<boolean> {
|
||||
const confirmed = await super.generatePassword();
|
||||
if (confirmed) {
|
||||
const options = (await this.passwordGenerationService.getOptions())[0];
|
||||
const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
|
||||
this.cipher.login.password = await this.passwordGenerationService.generatePassword(options);
|
||||
}
|
||||
return confirmed;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { Component, Input, OnDestroy, OnInit } from "@angular/core";
|
||||
import { map, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
@ -19,13 +20,15 @@ import { EnrollMasterPasswordReset } from "../../../organizations/users/enroll-m
|
||||
selector: "app-organization-options",
|
||||
templateUrl: "organization-options.component.html",
|
||||
})
|
||||
export class OrganizationOptionsComponent {
|
||||
export class OrganizationOptionsComponent implements OnInit, OnDestroy {
|
||||
actionPromise: Promise<void | boolean>;
|
||||
policies: Policy[];
|
||||
loaded = false;
|
||||
|
||||
@Input() organization: Organization;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
@ -38,12 +41,20 @@ export class OrganizationOptionsComponent {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.load();
|
||||
this.policyService.policies$
|
||||
.pipe(
|
||||
map((policies) => policies.filter((policy) => policy.type === PolicyType.ResetPassword)),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe((policies) => {
|
||||
this.policies = policies;
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.policies = await this.policyService.getAll(PolicyType.ResetPassword);
|
||||
this.loaded = true;
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
allowEnrollmentChanges(org: Organization): boolean {
|
||||
@ -83,7 +94,6 @@ export class OrganizationOptionsComponent {
|
||||
});
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast("success", null, "Unlinked SSO");
|
||||
await this.load();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
@ -105,7 +115,6 @@ export class OrganizationOptionsComponent {
|
||||
this.actionPromise = this.organizationApiService.leave(org.id);
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization"));
|
||||
await this.load();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { Observable, Subject, takeUntil, concatMap } from "rxjs";
|
||||
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
@ -33,7 +33,7 @@ import { LoginView } from "@bitwarden/common/models/view/loginView";
|
||||
import { SecureNoteView } from "@bitwarden/common/models/view/secureNoteView";
|
||||
|
||||
@Directive()
|
||||
export class AddEditComponent implements OnInit {
|
||||
export class AddEditComponent implements OnInit, OnDestroy {
|
||||
@Input() cloneMode = false;
|
||||
@Input() folderId: string = null;
|
||||
@Input() cipherId: string;
|
||||
@ -75,7 +75,9 @@ export class AddEditComponent implements OnInit {
|
||||
reprompt = false;
|
||||
canUseReprompt = true;
|
||||
|
||||
protected destroy$ = new Subject<void>();
|
||||
protected writeableCollections: CollectionView[];
|
||||
private personalOwnershipPolicyAppliesToActiveUser: boolean;
|
||||
private previousCipherId: string;
|
||||
|
||||
constructor(
|
||||
@ -152,14 +154,28 @@ export class AddEditComponent implements OnInit {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.init();
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
|
||||
.pipe(
|
||||
concatMap(async (policyAppliesToActiveUser) => {
|
||||
this.personalOwnershipPolicyAppliesToActiveUser = policyAppliesToActiveUser;
|
||||
await this.init();
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.ownershipOptions.length) {
|
||||
this.ownershipOptions = [];
|
||||
}
|
||||
if (await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)) {
|
||||
if (this.personalOwnershipPolicyAppliesToActiveUser) {
|
||||
this.allowPersonal = false;
|
||||
} else {
|
||||
const myEmail = await this.stateService.getEmail();
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
import { Directive, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
@ -15,7 +16,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCry
|
||||
import { PasswordColorText } from "../shared/components/password-strength/password-strength.component";
|
||||
|
||||
@Directive()
|
||||
export class ChangePasswordComponent implements OnInit {
|
||||
export class ChangePasswordComponent implements OnInit, OnDestroy {
|
||||
masterPassword: string;
|
||||
masterPasswordRetype: string;
|
||||
formPromise: Promise<any>;
|
||||
@ -28,6 +29,8 @@ export class ChangePasswordComponent implements OnInit {
|
||||
protected kdf: KdfType;
|
||||
protected kdfIterations: number;
|
||||
|
||||
protected destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected cryptoService: CryptoService,
|
||||
@ -40,7 +43,18 @@ export class ChangePasswordComponent implements OnInit {
|
||||
|
||||
async ngOnInit() {
|
||||
this.email = await this.stateService.getEmail();
|
||||
this.enforcedPolicyOptions ??= await this.policyService.getMasterPasswordPolicyOptions();
|
||||
this.policyService
|
||||
.masterPasswordPolicyOptions$()
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(
|
||||
(enforcedPasswordPolicyOptions) =>
|
||||
(this.enforcedPolicyOptions ??= enforcedPasswordPolicyOptions)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
|
@ -55,6 +55,13 @@ export class ExportComponent implements OnInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((policyAppliesToActiveUser) => {
|
||||
this.disabledByPolicy = policyAppliesToActiveUser;
|
||||
});
|
||||
|
||||
await this.checkExportDisabled();
|
||||
|
||||
merge(
|
||||
@ -71,9 +78,6 @@ export class ExportComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async checkExportDisabled() {
|
||||
this.disabledByPolicy = await this.policyService.policyAppliesToUser(
|
||||
PolicyType.DisablePersonalVaultExport
|
||||
);
|
||||
if (this.disabledByPolicy) {
|
||||
this.exportForm.disable();
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
@ -18,7 +19,7 @@ import { SendTextView } from "@bitwarden/common/models/view/sendTextView";
|
||||
import { SendView } from "@bitwarden/common/models/view/sendView";
|
||||
|
||||
@Directive()
|
||||
export class AddEditComponent implements OnInit {
|
||||
export class AddEditComponent implements OnInit, OnDestroy {
|
||||
@Input() sendId: string;
|
||||
@Input() type: SendType;
|
||||
|
||||
@ -45,6 +46,7 @@ export class AddEditComponent implements OnInit {
|
||||
showOptions = false;
|
||||
|
||||
private sendLinkBaseUrl: string;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
@ -80,9 +82,28 @@ export class AddEditComponent implements OnInit {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.DisableSend)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((policyAppliesToActiveUser) => {
|
||||
this.disableSend = policyAppliesToActiveUser;
|
||||
});
|
||||
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.SendOptions, (p) => p.data.disableHideEmail)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((policyAppliesToActiveUser) => {
|
||||
this.disableHideEmail = policyAppliesToActiveUser;
|
||||
});
|
||||
|
||||
await this.load();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get editMode(): boolean {
|
||||
return this.sendId != null;
|
||||
}
|
||||
@ -97,12 +118,6 @@ export class AddEditComponent implements OnInit {
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
|
||||
this.disableHideEmail = await this.policyService.policyAppliesToUser(
|
||||
PolicyType.SendOptions,
|
||||
(p) => p.data.disableHideEmail
|
||||
);
|
||||
|
||||
this.canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
this.emailVerified = await this.stateService.getEmailVerified();
|
||||
if (!this.canAccessPremium || !this.emailVerified) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Directive, NgZone, OnInit } from "@angular/core";
|
||||
import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
@ -12,7 +13,7 @@ import { SendType } from "@bitwarden/common/enums/sendType";
|
||||
import { SendView } from "@bitwarden/common/models/view/sendView";
|
||||
|
||||
@Directive()
|
||||
export class SendComponent implements OnInit {
|
||||
export class SendComponent implements OnInit, OnDestroy {
|
||||
disableSend = false;
|
||||
sendType = SendType;
|
||||
loaded = false;
|
||||
@ -36,6 +37,7 @@ export class SendComponent implements OnInit {
|
||||
onSuccessfulLoad: () => Promise<any>;
|
||||
|
||||
private searchTimeout: any;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
protected sendService: SendService,
|
||||
@ -49,7 +51,17 @@ export class SendComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.DisableSend)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((policyAppliesToActiveUser) => {
|
||||
this.disableSend = policyAppliesToActiveUser;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async load(filter: (send: SendView) => boolean = null) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Directive, Input, OnInit } from "@angular/core";
|
||||
import { Directive, Input, OnDestroy, OnInit } from "@angular/core";
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
@ -6,6 +6,7 @@ import {
|
||||
ValidationErrors,
|
||||
Validator,
|
||||
} from "@angular/forms";
|
||||
import { combineLatestWith, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
@ -13,7 +14,9 @@ import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
import { Policy } from "@bitwarden/common/models/domain/policy";
|
||||
|
||||
@Directive()
|
||||
export class VaultTimeoutInputComponent implements ControlValueAccessor, Validator, OnInit {
|
||||
export class VaultTimeoutInputComponent
|
||||
implements ControlValueAccessor, Validator, OnInit, OnDestroy
|
||||
{
|
||||
get showCustom() {
|
||||
return this.form.get("vaultTimeout").value === VaultTimeoutInputComponent.CUSTOM_VALUE;
|
||||
}
|
||||
@ -42,6 +45,7 @@ export class VaultTimeoutInputComponent implements ControlValueAccessor, Validat
|
||||
|
||||
private onChange: (vaultTimeout: number) => void;
|
||||
private validatorChange: () => void;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
@ -50,21 +54,19 @@ export class VaultTimeoutInputComponent implements ControlValueAccessor, Validat
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) {
|
||||
const vaultTimeoutPolicy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout);
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.MaximumVaultTimeout)
|
||||
.pipe(combineLatestWith(this.policyService.policies$), takeUntil(this.destroy$))
|
||||
.subscribe(([policyAppliesToActiveUser, policies]) => {
|
||||
if (policyAppliesToActiveUser) {
|
||||
const vaultTimeoutPolicy = policies.find(
|
||||
(policy) => policy.type === PolicyType.MaximumVaultTimeout && policy.enabled
|
||||
);
|
||||
|
||||
this.vaultTimeoutPolicy = vaultTimeoutPolicy[0];
|
||||
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60);
|
||||
this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60;
|
||||
|
||||
this.vaultTimeouts = this.vaultTimeouts.filter(
|
||||
(t) =>
|
||||
t.value <= this.vaultTimeoutPolicy.data.minutes &&
|
||||
(t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) &&
|
||||
t.value != null
|
||||
);
|
||||
this.validatorChange();
|
||||
}
|
||||
this.vaultTimeoutPolicy = vaultTimeoutPolicy;
|
||||
this.applyVaultTimeoutPolicy();
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line rxjs/no-async-subscribe
|
||||
this.form.valueChanges.subscribe(async (value) => {
|
||||
@ -87,6 +89,11 @@ export class VaultTimeoutInputComponent implements ControlValueAccessor, Validat
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.vaultTimeouts.push({
|
||||
name: this.i18nService.t("custom"),
|
||||
@ -154,4 +161,17 @@ export class VaultTimeoutInputComponent implements ControlValueAccessor, Validat
|
||||
private customTimeInMinutes() {
|
||||
return this.form.value.custom.hours * 60 + this.form.value.custom.minutes;
|
||||
}
|
||||
|
||||
private applyVaultTimeoutPolicy() {
|
||||
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60);
|
||||
this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60;
|
||||
|
||||
this.vaultTimeouts = this.vaultTimeouts.filter(
|
||||
(t) =>
|
||||
t.value <= this.vaultTimeoutPolicy.data.minutes &&
|
||||
(t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) &&
|
||||
t.value != null
|
||||
);
|
||||
this.validatorChange();
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,6 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
|
||||
}
|
||||
|
||||
async setupSubmitActions(): Promise<boolean> {
|
||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
|
||||
this.email = await this.stateService.getEmail();
|
||||
this.kdf = await this.stateService.getKdfType();
|
||||
this.kdfIterations = await this.stateService.getKdfIterations();
|
||||
|
@ -83,11 +83,15 @@ export class VaultFilterService {
|
||||
}
|
||||
|
||||
async checkForSingleOrganizationPolicy(): Promise<boolean> {
|
||||
return await this.policyService.policyAppliesToUser(PolicyType.SingleOrg);
|
||||
return await firstValueFrom(
|
||||
this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg)
|
||||
);
|
||||
}
|
||||
|
||||
async checkForPersonalOwnershipPolicy(): Promise<boolean> {
|
||||
return await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership);
|
||||
return await firstValueFrom(
|
||||
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
|
||||
);
|
||||
}
|
||||
|
||||
protected async getAllFoldersNested(folders: FolderView[]): Promise<TreeNode<FolderView>[]> {
|
||||
|
356
libs/common/spec/services/policy.service.spec.ts
Normal file
356
libs/common/spec/services/policy.service.spec.ts
Normal file
@ -0,0 +1,356 @@
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType";
|
||||
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
import { PermissionsApi } from "@bitwarden/common/models/api/permissionsApi";
|
||||
import { OrganizationData } from "@bitwarden/common/models/data/organizationData";
|
||||
import { PolicyData } from "@bitwarden/common/models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { Policy } from "@bitwarden/common/models/domain/policy";
|
||||
import { ResetPasswordPolicyOptions } from "@bitwarden/common/models/domain/resetPasswordPolicyOptions";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/listResponse";
|
||||
import { PolicyResponse } from "@bitwarden/common/models/response/policyResponse";
|
||||
import { ContainerService } from "@bitwarden/common/services/container.service";
|
||||
import { EncryptService } from "@bitwarden/common/services/encrypt.service";
|
||||
import { PolicyService } from "@bitwarden/common/services/policy/policy.service";
|
||||
import { StateService } from "@bitwarden/common/services/state.service";
|
||||
|
||||
describe("PolicyService", () => {
|
||||
let policyService: PolicyService;
|
||||
|
||||
let cryptoService: SubstituteOf<CryptoService>;
|
||||
let stateService: SubstituteOf<StateService>;
|
||||
let organizationService: SubstituteOf<OrganizationService>;
|
||||
let encryptService: SubstituteOf<EncryptService>;
|
||||
let activeAccount: BehaviorSubject<string>;
|
||||
let activeAccountUnlocked: BehaviorSubject<boolean>;
|
||||
|
||||
beforeEach(() => {
|
||||
stateService = Substitute.for();
|
||||
organizationService = Substitute.for();
|
||||
organizationService
|
||||
.getAll("user")
|
||||
.resolves([
|
||||
new Organization(
|
||||
organizationData(
|
||||
"test-organization",
|
||||
true,
|
||||
true,
|
||||
OrganizationUserStatusType.Accepted,
|
||||
false
|
||||
)
|
||||
),
|
||||
]);
|
||||
organizationService.getAll(undefined).resolves([]);
|
||||
organizationService.getAll(null).resolves([]);
|
||||
activeAccount = new BehaviorSubject("123");
|
||||
activeAccountUnlocked = new BehaviorSubject(true);
|
||||
stateService.getEncryptedPolicies().resolves({
|
||||
"1": policyData("1", "test-organization", PolicyType.MaximumVaultTimeout, true, {
|
||||
minutes: 14,
|
||||
}),
|
||||
});
|
||||
stateService.activeAccount$.returns(activeAccount);
|
||||
stateService.activeAccountUnlocked$.returns(activeAccountUnlocked);
|
||||
stateService.getUserId().resolves("user");
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService);
|
||||
|
||||
policyService = new PolicyService(stateService, organizationService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
activeAccount.complete();
|
||||
activeAccountUnlocked.complete();
|
||||
});
|
||||
|
||||
it("upsert", async () => {
|
||||
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 () => {
|
||||
await policyService.replace({
|
||||
"2": policyData("2", "test-organization", PolicyType.DisableSend, true),
|
||||
});
|
||||
|
||||
expect(await firstValueFrom(policyService.policies$)).toEqual([
|
||||
{
|
||||
id: "2",
|
||||
organizationId: "test-organization",
|
||||
type: PolicyType.DisableSend,
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("locking should clear", async () => {
|
||||
activeAccountUnlocked.next(false);
|
||||
// Sleep for 100ms to avoid timing issues
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
|
||||
expect((await firstValueFrom(policyService.policies$)).length).toBe(0);
|
||||
});
|
||||
|
||||
describe("clear", () => {
|
||||
it("null userId", async () => {
|
||||
await policyService.clear();
|
||||
|
||||
stateService.received(1).setEncryptedPolicies(Arg.any(), Arg.any());
|
||||
|
||||
expect((await firstValueFrom(policyService.policies$)).length).toBe(0);
|
||||
});
|
||||
|
||||
it("matching userId", async () => {
|
||||
await policyService.clear("user");
|
||||
|
||||
stateService.received(1).setEncryptedPolicies(Arg.any(), Arg.any());
|
||||
|
||||
expect((await firstValueFrom(policyService.policies$)).length).toBe(0);
|
||||
});
|
||||
|
||||
it("mismatching userId", async () => {
|
||||
await policyService.clear("12");
|
||||
|
||||
stateService.received(1).setEncryptedPolicies(Arg.any(), Arg.any());
|
||||
|
||||
expect((await firstValueFrom(policyService.policies$)).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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(null, null);
|
||||
|
||||
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("mapPoliciesFromToken", () => {
|
||||
it("null", async () => {
|
||||
const result = policyService.mapPoliciesFromToken(null);
|
||||
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
it("null data", async () => {
|
||||
const model = new ListResponse(null, PolicyResponse);
|
||||
model.data = null;
|
||||
const result = policyService.mapPoliciesFromToken(model);
|
||||
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
it("empty array", async () => {
|
||||
const model = new ListResponse(null, PolicyResponse);
|
||||
const result = policyService.mapPoliciesFromToken(model);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("success", async () => {
|
||||
const policyResponse: any = {
|
||||
Data: [
|
||||
{
|
||||
Id: "1",
|
||||
OrganizationId: "organization-1",
|
||||
Type: PolicyType.DisablePersonalVaultExport,
|
||||
Enabled: true,
|
||||
Data: { requireUpper: true },
|
||||
},
|
||||
{
|
||||
Id: "2",
|
||||
OrganizationId: "organization-2",
|
||||
Type: PolicyType.DisableSend,
|
||||
Enabled: false,
|
||||
Data: { minComplexity: 5, minLength: 20 },
|
||||
},
|
||||
],
|
||||
};
|
||||
const model = new ListResponse(policyResponse, PolicyResponse);
|
||||
const result = policyService.mapPoliciesFromToken(model);
|
||||
|
||||
expect(result).toEqual([
|
||||
new Policy(
|
||||
policyData("1", "organization-1", PolicyType.DisablePersonalVaultExport, true, {
|
||||
requireUpper: true,
|
||||
})
|
||||
),
|
||||
new Policy(
|
||||
policyData("2", "organization-2", PolicyType.DisableSend, false, {
|
||||
minComplexity: 5,
|
||||
minLength: 20,
|
||||
})
|
||||
),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policyAppliesToActiveUser", () => {
|
||||
it("MasterPassword does not apply", async () => {
|
||||
const result = await firstValueFrom(
|
||||
policyService.policyAppliesToActiveUser$(PolicyType.MasterPassword)
|
||||
);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it("MaximumVaultTimeout applies", async () => {
|
||||
const result = await firstValueFrom(
|
||||
policyService.policyAppliesToActiveUser$(PolicyType.MaximumVaultTimeout)
|
||||
);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it("DisablePersonalVaultExport does not apply", async () => {
|
||||
const result = await firstValueFrom(
|
||||
policyService.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport)
|
||||
);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
function policyData(
|
||||
id: string,
|
||||
organizationId: string,
|
||||
type: PolicyType,
|
||||
enabled: boolean,
|
||||
data?: any
|
||||
) {
|
||||
const policyData = new PolicyData({} as any);
|
||||
policyData.id = id;
|
||||
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
|
||||
) {
|
||||
const organizationData = new OrganizationData({} as any);
|
||||
organizationData.id = id;
|
||||
organizationData.enabled = enabled;
|
||||
organizationData.usePolicies = usePolicies;
|
||||
organizationData.status = status;
|
||||
organizationData.permissions = new PermissionsApi({ managePolicies: managePolicies } as any);
|
||||
return organizationData;
|
||||
}
|
||||
});
|
@ -1,6 +1,5 @@
|
||||
import { PolicyType } from "../../enums/policyType";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions";
|
||||
import { Policy } from "../../models/domain/policy";
|
||||
import { PolicyRequest } from "../../models/request/policyRequest";
|
||||
import { ListResponse } from "../../models/response/listResponse";
|
||||
import { PolicyResponse } from "../../models/response/policyResponse";
|
||||
@ -18,7 +17,6 @@ export class PolicyApiServiceAbstraction {
|
||||
organizationId: string,
|
||||
userId: string
|
||||
) => Promise<ListResponse<PolicyResponse>>;
|
||||
getPolicyForOrganization: (policyType: PolicyType, organizationId: string) => Promise<Policy>;
|
||||
getMasterPasswordPoliciesForInvitedUsers: (orgId: string) => Promise<MasterPasswordPolicyOptions>;
|
||||
putPolicy: (organizationId: string, type: PolicyType, request: PolicyRequest) => Promise<any>;
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { PolicyType } from "../../enums/policyType";
|
||||
import { PolicyData } from "../../models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions";
|
||||
@ -7,9 +9,17 @@ import { ListResponse } from "../../models/response/listResponse";
|
||||
import { PolicyResponse } from "../../models/response/policyResponse";
|
||||
|
||||
export abstract class PolicyService {
|
||||
getAll: (type?: PolicyType, userId?: string) => Promise<Policy[]>;
|
||||
policies$: Observable<Policy[]>;
|
||||
masterPasswordPolicyOptions$: (policies?: Policy[]) => Observable<MasterPasswordPolicyOptions>;
|
||||
policyAppliesToActiveUser$: (
|
||||
policyType: PolicyType,
|
||||
policyFilter?: (policy: Policy) => boolean
|
||||
) => Observable<boolean>;
|
||||
|
||||
getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise<MasterPasswordPolicyOptions>;
|
||||
/**
|
||||
* @deprecated Do not call this, use the policies$ observable collection
|
||||
*/
|
||||
getAll: (type?: PolicyType, userId?: string) => Promise<Policy[]>;
|
||||
evaluateMasterPassword: (
|
||||
passwordStrength: number,
|
||||
newPassword: string,
|
||||
@ -29,6 +39,6 @@ export abstract class PolicyService {
|
||||
|
||||
export abstract class InternalPolicyService extends PolicyService {
|
||||
upsert: (policy: PolicyData) => Promise<any>;
|
||||
replace: (policies: { [id: string]: PolicyData }) => Promise<any>;
|
||||
replace: (policies: { [id: string]: PolicyData }) => Promise<void>;
|
||||
clear: (userId?: string) => Promise<any>;
|
||||
}
|
||||
|
@ -103,7 +103,13 @@ export abstract class StateService<T extends Account = Account> {
|
||||
) => Promise<void>;
|
||||
getDecryptedPinProtected: (options?: StorageOptions) => Promise<EncString>;
|
||||
setDecryptedPinProtected: (value: EncString, options?: StorageOptions) => Promise<void>;
|
||||
/**
|
||||
* @deprecated Do not call this, use PolicyService
|
||||
*/
|
||||
getDecryptedPolicies: (options?: StorageOptions) => Promise<Policy[]>;
|
||||
/**
|
||||
* @deprecated Do not call this, use PolicyService
|
||||
*/
|
||||
setDecryptedPolicies: (value: Policy[], options?: StorageOptions) => Promise<void>;
|
||||
getDecryptedPrivateKey: (options?: StorageOptions) => Promise<ArrayBuffer>;
|
||||
setDecryptedPrivateKey: (value: ArrayBuffer, options?: StorageOptions) => Promise<void>;
|
||||
@ -214,7 +220,13 @@ export abstract class StateService<T extends Account = Account> {
|
||||
) => Promise<void>;
|
||||
getEncryptedPinProtected: (options?: StorageOptions) => Promise<string>;
|
||||
setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
/**
|
||||
* @deprecated Do not call this directly, use PolicyService
|
||||
*/
|
||||
getEncryptedPolicies: (options?: StorageOptions) => Promise<{ [id: string]: PolicyData }>;
|
||||
/**
|
||||
* @deprecated Do not call this directly, use PolicyService
|
||||
*/
|
||||
setEncryptedPolicies: (
|
||||
value: { [id: string]: PolicyData },
|
||||
options?: StorageOptions
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
import * as zxcvbn from "zxcvbn";
|
||||
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
@ -258,7 +259,11 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
|
||||
const policies: Policy[] =
|
||||
this.policyService == null
|
||||
? null
|
||||
: await this.policyService.getAll(PolicyType.PasswordGenerator);
|
||||
: await firstValueFrom(
|
||||
this.policyService.policies$.pipe(
|
||||
map((p) => p.filter((policy) => policy.type === PolicyType.PasswordGenerator))
|
||||
)
|
||||
);
|
||||
let enforcedOptions: PasswordGeneratorPolicyOptions = null;
|
||||
|
||||
if (policies == null || policies.length === 0) {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyApiServiceAbstraction } from "../../abstractions/policy/policy-api.service.abstraction";
|
||||
@ -6,7 +8,6 @@ import { StateService } from "../../abstractions/state.service";
|
||||
import { PolicyType } from "../../enums/policyType";
|
||||
import { PolicyData } from "../../models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions";
|
||||
import { Policy } from "../../models/domain/policy";
|
||||
import { PolicyRequest } from "../../models/request/policyRequest";
|
||||
import { ListResponse } from "../../models/response/listResponse";
|
||||
import { PolicyResponse } from "../../models/response/policyResponse";
|
||||
@ -79,30 +80,13 @@ export class PolicyApiService implements PolicyApiServiceAbstraction {
|
||||
return new ListResponse(r, PolicyResponse);
|
||||
}
|
||||
|
||||
async getPolicyForOrganization(policyType: PolicyType, organizationId: string): Promise<Policy> {
|
||||
const org = await this.organizationService.get(organizationId);
|
||||
if (org?.isProviderUser) {
|
||||
const orgPolicies = await this.getPolicies(organizationId);
|
||||
const policy = orgPolicies.data.find((p) => p.organizationId === organizationId);
|
||||
|
||||
if (policy == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Policy(new PolicyData(policy));
|
||||
}
|
||||
|
||||
const policies = await this.policyService.getAll(policyType);
|
||||
return policies.find((p) => p.organizationId === organizationId);
|
||||
}
|
||||
|
||||
async getMasterPasswordPoliciesForInvitedUsers(
|
||||
orgId: string
|
||||
): Promise<MasterPasswordPolicyOptions> {
|
||||
const userId = await this.stateService.getUserId();
|
||||
const response = await this.getPoliciesByInvitedUser(orgId, userId);
|
||||
const policies = await this.policyService.mapPoliciesFromToken(response);
|
||||
return this.policyService.getMasterPasswordPolicyOptions(policies);
|
||||
return await firstValueFrom(this.policyService.masterPasswordPolicyOptions$(policies));
|
||||
}
|
||||
|
||||
async putPolicy(organizationId: string, type: PolicyType, request: PolicyRequest): Promise<any> {
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { of, concatMap, BehaviorSubject, Observable, map } from "rxjs";
|
||||
|
||||
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
|
||||
import { InternalPolicyService as InternalPolicyServiceAbstraction } from "../../abstractions/policy/policy.service.abstraction";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType";
|
||||
import { OrganizationUserType } from "../../enums/organizationUserType";
|
||||
import { PolicyType } from "../../enums/policyType";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { PolicyData } from "../../models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions";
|
||||
import { Organization } from "../../models/domain/organization";
|
||||
@ -13,13 +16,37 @@ import { ListResponse } from "../../models/response/listResponse";
|
||||
import { PolicyResponse } from "../../models/response/policyResponse";
|
||||
|
||||
export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
policyCache: Policy[];
|
||||
private _policies: BehaviorSubject<Policy[]> = new BehaviorSubject([]);
|
||||
|
||||
policies$ = this._policies.asObservable();
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private organizationService: OrganizationService
|
||||
) {}
|
||||
) {
|
||||
this.stateService.activeAccountUnlocked$
|
||||
.pipe(
|
||||
concatMap(async (unlocked) => {
|
||||
if (Utils.global.bitwardenContainerService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!unlocked) {
|
||||
this._policies.next([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await this.stateService.getEncryptedPolicies();
|
||||
|
||||
await this.updateObservables(data);
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not call this, use the policies$ observable collection
|
||||
*/
|
||||
async getAll(type?: PolicyType, userId?: string): Promise<Policy[]> {
|
||||
let response: Policy[] = [];
|
||||
const decryptedPolicies = await this.stateService.getDecryptedPolicies({ userId: userId });
|
||||
@ -28,8 +55,7 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
} else {
|
||||
const diskPolicies = await this.stateService.getEncryptedPolicies({ userId: userId });
|
||||
for (const id in diskPolicies) {
|
||||
// eslint-disable-next-line
|
||||
if (diskPolicies.hasOwnProperty(id)) {
|
||||
if (Object.prototype.hasOwnProperty.call(diskPolicies, id)) {
|
||||
response.push(new Policy(diskPolicies[id]));
|
||||
}
|
||||
}
|
||||
@ -42,60 +68,72 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async getMasterPasswordPolicyOptions(policies?: Policy[]): Promise<MasterPasswordPolicyOptions> {
|
||||
let enforcedOptions: MasterPasswordPolicyOptions = null;
|
||||
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 (policies == null) {
|
||||
policies = await this.getAll(PolicyType.MasterPassword);
|
||||
} else {
|
||||
policies = policies.filter((p) => p.type === PolicyType.MasterPassword);
|
||||
}
|
||||
if (filteredPolicies == null || filteredPolicies.length === 0) {
|
||||
return enforcedOptions;
|
||||
}
|
||||
|
||||
if (policies == null || policies.length === 0) {
|
||||
return enforcedOptions;
|
||||
}
|
||||
filteredPolicies.forEach((currentPolicy) => {
|
||||
if (!currentPolicy.enabled || currentPolicy.data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
policies.forEach((currentPolicy) => {
|
||||
if (!currentPolicy.enabled || currentPolicy.data == null) {
|
||||
return;
|
||||
}
|
||||
if (enforcedOptions == null) {
|
||||
enforcedOptions = new MasterPasswordPolicyOptions();
|
||||
}
|
||||
|
||||
if (enforcedOptions == null) {
|
||||
enforcedOptions = new MasterPasswordPolicyOptions();
|
||||
}
|
||||
if (
|
||||
currentPolicy.data.minComplexity != null &&
|
||||
currentPolicy.data.minComplexity > enforcedOptions.minComplexity
|
||||
) {
|
||||
enforcedOptions.minComplexity = currentPolicy.data.minComplexity;
|
||||
}
|
||||
|
||||
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.minLength != null &&
|
||||
currentPolicy.data.minLength > enforcedOptions.minLength
|
||||
) {
|
||||
enforcedOptions.minLength = currentPolicy.data.minLength;
|
||||
}
|
||||
if (currentPolicy.data.requireUpper) {
|
||||
enforcedOptions.requireUpper = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireUpper) {
|
||||
enforcedOptions.requireUpper = true;
|
||||
}
|
||||
if (currentPolicy.data.requireLower) {
|
||||
enforcedOptions.requireLower = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireLower) {
|
||||
enforcedOptions.requireLower = true;
|
||||
}
|
||||
if (currentPolicy.data.requireNumbers) {
|
||||
enforcedOptions.requireNumbers = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireNumbers) {
|
||||
enforcedOptions.requireNumbers = true;
|
||||
}
|
||||
if (currentPolicy.data.requireSpecial) {
|
||||
enforcedOptions.requireSpecial = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (currentPolicy.data.requireSpecial) {
|
||||
enforcedOptions.requireSpecial = true;
|
||||
}
|
||||
});
|
||||
return enforcedOptions;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return enforcedOptions;
|
||||
policyAppliesToActiveUser$(
|
||||
policyType: PolicyType,
|
||||
policyFilter: (policy: Policy) => boolean = (p) => true
|
||||
) {
|
||||
return this.policies$.pipe(
|
||||
concatMap(async (policies) => {
|
||||
const userId = await this.stateService.getUserId();
|
||||
return await this.checkPoliciesThatApplyToUser(policies, policyType, policyFilter, userId);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
evaluateMasterPassword(
|
||||
@ -174,25 +212,8 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
userId?: string
|
||||
) {
|
||||
const policies = await this.getAll(policyType, userId);
|
||||
const organizations = await this.organizationService.getAll(userId);
|
||||
let filteredPolicies;
|
||||
|
||||
if (policyFilter != null) {
|
||||
filteredPolicies = policies.filter((p) => p.enabled && policyFilter(p));
|
||||
} else {
|
||||
filteredPolicies = policies.filter((p) => p.enabled);
|
||||
}
|
||||
|
||||
const policySet = new Set(filteredPolicies.map((p) => p.organizationId));
|
||||
|
||||
return organizations.some(
|
||||
(o) =>
|
||||
o.enabled &&
|
||||
o.status >= OrganizationUserStatusType.Accepted &&
|
||||
o.usePolicies &&
|
||||
!this.isExcemptFromPolicies(o, policyType) &&
|
||||
policySet.has(o.id)
|
||||
);
|
||||
return this.checkPoliciesThatApplyToUser(policies, policyType, policyFilter, userId);
|
||||
}
|
||||
|
||||
async upsert(policy: PolicyData): Promise<any> {
|
||||
@ -203,17 +224,19 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
|
||||
policies[policy.id] = policy;
|
||||
|
||||
await this.stateService.setDecryptedPolicies(null);
|
||||
await this.updateObservables(policies);
|
||||
await this.stateService.setEncryptedPolicies(policies);
|
||||
}
|
||||
|
||||
async replace(policies: { [id: string]: PolicyData }): Promise<any> {
|
||||
await this.stateService.setDecryptedPolicies(null);
|
||||
async replace(policies: { [id: string]: PolicyData }): Promise<void> {
|
||||
await this.updateObservables(policies);
|
||||
await this.stateService.setEncryptedPolicies(policies);
|
||||
}
|
||||
|
||||
async clear(userId?: string): Promise<any> {
|
||||
await this.stateService.setDecryptedPolicies(null, { userId: userId });
|
||||
async clear(userId?: string): Promise<void> {
|
||||
if (userId == null || userId == (await this.stateService.getUserId())) {
|
||||
this._policies.next([]);
|
||||
}
|
||||
await this.stateService.setEncryptedPolicies(null, { userId: userId });
|
||||
}
|
||||
|
||||
@ -224,4 +247,32 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
|
||||
return organization.isExemptFromPolicies;
|
||||
}
|
||||
|
||||
private async updateObservables(policiesMap: { [id: string]: PolicyData }) {
|
||||
const policies = Object.values(policiesMap || {}).map((f) => new Policy(f));
|
||||
|
||||
this._policies.next(policies);
|
||||
}
|
||||
|
||||
private async checkPoliciesThatApplyToUser(
|
||||
policies: Policy[],
|
||||
policyType: PolicyType,
|
||||
policyFilter: (policy: Policy) => boolean = (p) => true,
|
||||
userId?: string
|
||||
) {
|
||||
const organizations = await this.organizationService.getAll(userId);
|
||||
const filteredPolicies = policies.filter(
|
||||
(p) => p.type === policyType && p.enabled && policyFilter(p)
|
||||
);
|
||||
const policySet = new Set(filteredPolicies.map((p) => p.organizationId));
|
||||
|
||||
return organizations.some(
|
||||
(o) =>
|
||||
o.enabled &&
|
||||
o.status >= OrganizationUserStatusType.Accepted &&
|
||||
o.usePolicies &&
|
||||
policySet.has(o.id) &&
|
||||
!this.isExcemptFromPolicies(o, policyType)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import * as http from "http";
|
||||
import * as program from "commander";
|
||||
import * as inquirer from "inquirer";
|
||||
import Separator from "inquirer/lib/objects/separator";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
@ -372,7 +373,9 @@ export class LoginCommand {
|
||||
const masterPasswordHint = hint.input;
|
||||
|
||||
// Retrieve details for key generation
|
||||
const enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
|
||||
const enforcedPolicyOptions = await firstValueFrom(
|
||||
this.policyService.masterPasswordPolicyOptions$()
|
||||
);
|
||||
const kdf = await this.stateService.getKdfType();
|
||||
const kdfIterations = await this.stateService.getKdfIterations();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user