mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
[PM-18566] Wire up vNextPolicyService for Clients (#13678)
* wire up vNext impl * wire up vNextPolicyService for browser * wire up vNextPolicyService for desktop * wire up vNextPolicyService for cli * fix test * fix missed caller * cleanup * fix missing property assignment * fix QA bug for PM-19205 * fix QA bug for PM-19206 * fix QA bug for pm-19228 * cleanup
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { ListResponse } from "../../../models/response/list.response";
|
||||
import { PolicyType } from "../../enums";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options";
|
||||
@@ -7,19 +5,23 @@ import { Policy } from "../../models/domain/policy";
|
||||
import { PolicyRequest } from "../../models/request/policy.request";
|
||||
import { PolicyResponse } from "../../models/response/policy.response";
|
||||
|
||||
export class PolicyApiServiceAbstraction {
|
||||
getPolicy: (organizationId: string, type: PolicyType) => Promise<PolicyResponse>;
|
||||
getPolicies: (organizationId: string) => Promise<ListResponse<PolicyResponse>>;
|
||||
export abstract class PolicyApiServiceAbstraction {
|
||||
abstract getPolicy: (organizationId: string, type: PolicyType) => Promise<PolicyResponse>;
|
||||
abstract getPolicies: (organizationId: string) => Promise<ListResponse<PolicyResponse>>;
|
||||
|
||||
getPoliciesByToken: (
|
||||
abstract getPoliciesByToken: (
|
||||
organizationId: string,
|
||||
token: string,
|
||||
email: string,
|
||||
organizationUserId: string,
|
||||
) => Promise<Policy[] | undefined>;
|
||||
|
||||
getMasterPasswordPolicyOptsForOrgUser: (
|
||||
abstract getMasterPasswordPolicyOptsForOrgUser: (
|
||||
orgId: string,
|
||||
) => Promise<MasterPasswordPolicyOptions | null>;
|
||||
putPolicy: (organizationId: string, type: PolicyType, request: PolicyRequest) => Promise<any>;
|
||||
abstract putPolicy: (
|
||||
organizationId: string,
|
||||
type: PolicyType,
|
||||
request: PolicyRequest,
|
||||
) => Promise<any>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { UserId } from "../../../types/guid";
|
||||
@@ -11,43 +9,27 @@ import { ResetPasswordPolicyOptions } from "../../models/domain/reset-password-p
|
||||
|
||||
export abstract class PolicyService {
|
||||
/**
|
||||
* All policies for the active user from sync data.
|
||||
* All policies for the provided user from sync data.
|
||||
* May include policies that are disabled or otherwise do not apply to the user. Be careful using this!
|
||||
* Consider using {@link get$} or {@link getAll$} instead, which will only return policies that should be enforced against the user.
|
||||
* Consider {@link policiesByType$} instead, which will only return policies that should be enforced against the user.
|
||||
*/
|
||||
policies$: Observable<Policy[]>;
|
||||
abstract policies$: (userId: UserId) => Observable<Policy[]>;
|
||||
|
||||
/**
|
||||
* @returns the first {@link Policy} found that applies to the active user.
|
||||
* @returns all {@link Policy} objects of a given type that apply to the specified user.
|
||||
* A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner).
|
||||
* @param policyType the {@link PolicyType} to search for
|
||||
* @see {@link getAll$} if you need all policies of a given type
|
||||
* @param userId the {@link UserId} to search against
|
||||
*/
|
||||
get$: (policyType: PolicyType) => Observable<Policy>;
|
||||
abstract policiesByType$: (policyType: PolicyType, userId: UserId) => Observable<Policy[]>;
|
||||
|
||||
/**
|
||||
* @returns all {@link Policy} objects of a given type that apply to the specified user (or the active user if not specified).
|
||||
* @returns true if a policy of the specified type applies to the specified user, otherwise false.
|
||||
* A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner).
|
||||
* @param policyType the {@link PolicyType} to search for
|
||||
*/
|
||||
getAll$: (policyType: PolicyType, userId: UserId) => Observable<Policy[]>;
|
||||
|
||||
/**
|
||||
* All {@link Policy} objects for the specified user (from sync data).
|
||||
* May include policies that are disabled or otherwise do not apply to the user.
|
||||
* Consider using {@link getAll$} instead, which will only return policies that should be enforced against the user.
|
||||
*/
|
||||
getAll: (policyType: PolicyType) => Promise<Policy[]>;
|
||||
|
||||
/**
|
||||
* @returns true if a policy of the specified type applies to the active user, otherwise false.
|
||||
* A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner).
|
||||
* This does not take into account the policy's configuration - if that is important, use {@link getAll$} to get the
|
||||
* This does not take into account the policy's configuration - if that is important, use {@link policiesByType$} to get the
|
||||
* {@link Policy} objects and then filter by Policy.data.
|
||||
*/
|
||||
policyAppliesToActiveUser$: (policyType: PolicyType) => Observable<boolean>;
|
||||
|
||||
policyAppliesToUser: (policyType: PolicyType) => Promise<boolean>;
|
||||
abstract policyAppliesToUser$: (policyType: PolicyType, userId: UserId) => Observable<boolean>;
|
||||
|
||||
// Policy specific interfaces
|
||||
|
||||
@@ -56,28 +38,31 @@ export abstract class PolicyService {
|
||||
* @returns a set of options which represent the minimum Master Password settings that the user must
|
||||
* comply with in order to comply with **all** Master Password policies.
|
||||
*/
|
||||
masterPasswordPolicyOptions$: (policies?: Policy[]) => Observable<MasterPasswordPolicyOptions>;
|
||||
abstract masterPasswordPolicyOptions$: (
|
||||
userId: UserId,
|
||||
policies?: Policy[],
|
||||
) => Observable<MasterPasswordPolicyOptions | undefined>;
|
||||
|
||||
/**
|
||||
* Evaluates whether a proposed Master Password complies with all Master Password policies that apply to the user.
|
||||
*/
|
||||
evaluateMasterPassword: (
|
||||
abstract evaluateMasterPassword: (
|
||||
passwordStrength: number,
|
||||
newPassword: string,
|
||||
enforcedPolicyOptions?: MasterPasswordPolicyOptions,
|
||||
) => boolean;
|
||||
|
||||
/**
|
||||
* @returns Reset Password policy options for the specified organization and a boolean indicating whether the policy
|
||||
* @returns {@link ResetPasswordPolicyOptions} for the specified organization and a boolean indicating whether the policy
|
||||
* is enabled
|
||||
*/
|
||||
getResetPasswordPolicyOptions: (
|
||||
abstract getResetPasswordPolicyOptions: (
|
||||
policies: Policy[],
|
||||
orgId: string,
|
||||
) => [ResetPasswordPolicyOptions, boolean];
|
||||
}
|
||||
|
||||
export abstract class InternalPolicyService extends PolicyService {
|
||||
upsert: (policy: PolicyData) => Promise<void>;
|
||||
replace: (policies: { [id: string]: PolicyData }, userId: UserId) => Promise<void>;
|
||||
abstract upsert: (policy: PolicyData, userId: UserId) => Promise<void>;
|
||||
abstract replace: (policies: { [id: string]: PolicyData }, userId: UserId) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { PolicyType } from "../../enums";
|
||||
import { PolicyData } from "../../models/data/policy.data";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options";
|
||||
import { Policy } from "../../models/domain/policy";
|
||||
import { ResetPasswordPolicyOptions } from "../../models/domain/reset-password-policy-options";
|
||||
|
||||
export abstract class vNextPolicyService {
|
||||
/**
|
||||
* All policies for the provided user from sync data.
|
||||
* May include policies that are disabled or otherwise do not apply to the user. Be careful using this!
|
||||
* Consider {@link policiesByType$} instead, which will only return policies that should be enforced against the user.
|
||||
*/
|
||||
abstract policies$: (userId: UserId) => Observable<Policy[]>;
|
||||
|
||||
/**
|
||||
* @returns all {@link Policy} objects of a given type that apply to the specified user.
|
||||
* A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner).
|
||||
* @param policyType the {@link PolicyType} to search for
|
||||
* @param userId the {@link UserId} to search against
|
||||
*/
|
||||
abstract policiesByType$: (policyType: PolicyType, userId: UserId) => Observable<Policy[]>;
|
||||
|
||||
/**
|
||||
* @returns true if a policy of the specified type applies to the specified user, otherwise false.
|
||||
* A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner).
|
||||
* This does not take into account the policy's configuration - if that is important, use {@link policiesByType$} to get the
|
||||
* {@link Policy} objects and then filter by Policy.data.
|
||||
*/
|
||||
abstract policyAppliesToUser$: (policyType: PolicyType, userId: UserId) => Observable<boolean>;
|
||||
|
||||
// Policy specific interfaces
|
||||
|
||||
/**
|
||||
* Combines all Master Password policies that apply to the user.
|
||||
* @returns a set of options which represent the minimum Master Password settings that the user must
|
||||
* comply with in order to comply with **all** Master Password policies.
|
||||
*/
|
||||
abstract masterPasswordPolicyOptions$: (
|
||||
userId: UserId,
|
||||
policies?: Policy[],
|
||||
) => Observable<MasterPasswordPolicyOptions | undefined>;
|
||||
|
||||
/**
|
||||
* Evaluates whether a proposed Master Password complies with all Master Password policies that apply to the user.
|
||||
*/
|
||||
abstract evaluateMasterPassword: (
|
||||
passwordStrength: number,
|
||||
newPassword: string,
|
||||
enforcedPolicyOptions?: MasterPasswordPolicyOptions,
|
||||
) => boolean;
|
||||
|
||||
/**
|
||||
* @returns {@link ResetPasswordPolicyOptions} for the specified organization and a boolean indicating whether the policy
|
||||
* is enabled
|
||||
*/
|
||||
abstract getResetPasswordPolicyOptions: (
|
||||
policies: Policy[],
|
||||
orgId: string,
|
||||
) => [ResetPasswordPolicyOptions, boolean];
|
||||
}
|
||||
|
||||
export abstract class vNextInternalPolicyService extends vNextPolicyService {
|
||||
abstract upsert: (policy: PolicyData, userId: UserId) => Promise<void>;
|
||||
abstract replace: (policies: { [id: string]: PolicyData }, userId: UserId) => Promise<void>;
|
||||
}
|
||||
@@ -15,11 +15,11 @@ import { MasterPasswordPolicyOptions } from "../../../admin-console/models/domai
|
||||
import { Organization } from "../../../admin-console/models/domain/organization";
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
import { ResetPasswordPolicyOptions } from "../../../admin-console/models/domain/reset-password-policy-options";
|
||||
import { POLICIES } from "../../../admin-console/services/policy/policy.service";
|
||||
import { PolicyId, UserId } from "../../../types/guid";
|
||||
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
|
||||
|
||||
import { DefaultvNextPolicyService, getFirstPolicy } from "./default-vnext-policy.service";
|
||||
import { DefaultPolicyService, getFirstPolicy } from "./default-policy.service";
|
||||
import { POLICIES } from "./policy-state";
|
||||
|
||||
describe("PolicyService", () => {
|
||||
const userId = "userId" as UserId;
|
||||
@@ -27,7 +27,7 @@ describe("PolicyService", () => {
|
||||
let organizationService: MockProxy<OrganizationService>;
|
||||
let singleUserState: FakeSingleUserState<Record<PolicyId, PolicyData>>;
|
||||
|
||||
let policyService: DefaultvNextPolicyService;
|
||||
let policyService: DefaultPolicyService;
|
||||
|
||||
beforeEach(() => {
|
||||
const accountService = mockAccountServiceWith(userId);
|
||||
@@ -59,7 +59,7 @@ describe("PolicyService", () => {
|
||||
|
||||
organizationService.organizations$.calledWith(userId).mockReturnValue(organizations$);
|
||||
|
||||
policyService = new DefaultvNextPolicyService(stateProvider, organizationService);
|
||||
policyService = new DefaultPolicyService(stateProvider, organizationService);
|
||||
});
|
||||
|
||||
it("upsert", async () => {
|
||||
@@ -3,7 +3,7 @@ import { combineLatest, map, Observable, of } from "rxjs";
|
||||
import { StateProvider } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
|
||||
import { vNextPolicyService } from "../../abstractions/policy/vnext-policy.service";
|
||||
import { PolicyService } from "../../abstractions/policy/policy.service.abstraction";
|
||||
import { OrganizationUserStatusType, PolicyType } from "../../enums";
|
||||
import { PolicyData } from "../../models/data/policy.data";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options";
|
||||
@@ -11,7 +11,7 @@ import { Organization } from "../../models/domain/organization";
|
||||
import { Policy } from "../../models/domain/policy";
|
||||
import { ResetPasswordPolicyOptions } from "../../models/domain/reset-password-policy-options";
|
||||
|
||||
import { POLICIES } from "./vnext-policy-state";
|
||||
import { POLICIES } from "./policy-state";
|
||||
|
||||
export function policyRecordToArray(policiesMap: { [id: string]: PolicyData }) {
|
||||
return Object.values(policiesMap || {}).map((f) => new Policy(f));
|
||||
@@ -21,7 +21,7 @@ export const getFirstPolicy = map<Policy[], Policy | undefined>((policies) => {
|
||||
return policies.at(0) ?? undefined;
|
||||
});
|
||||
|
||||
export class DefaultvNextPolicyService implements vNextPolicyService {
|
||||
export class DefaultPolicyService implements PolicyService {
|
||||
constructor(
|
||||
private stateProvider: StateProvider,
|
||||
private organizationService: OrganizationService,
|
||||
@@ -89,7 +89,7 @@ export class DefaultvNextPolicyService implements vNextPolicyService {
|
||||
const policies$ = policies ? of(policies) : this.policies$(userId);
|
||||
return policies$.pipe(
|
||||
map((obsPolicies) => {
|
||||
const enforcedOptions: MasterPasswordPolicyOptions = new MasterPasswordPolicyOptions();
|
||||
let enforcedOptions: MasterPasswordPolicyOptions | undefined = undefined;
|
||||
const filteredPolicies =
|
||||
obsPolicies.filter((p) => p.type === PolicyType.MasterPassword) ?? [];
|
||||
|
||||
@@ -102,6 +102,10 @@ export class DefaultvNextPolicyService implements vNextPolicyService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!enforcedOptions) {
|
||||
enforcedOptions = new MasterPasswordPolicyOptions();
|
||||
}
|
||||
|
||||
if (
|
||||
currentPolicy.data.minComplexity != null &&
|
||||
currentPolicy.data.minComplexity > enforcedOptions.minComplexity
|
||||
@@ -1,6 +1,8 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { firstValueFrom, map, switchMap } from "rxjs";
|
||||
|
||||
import { ApiService } from "../../../abstractions/api.service";
|
||||
import { AccountService } from "../../../auth/abstractions/account.service";
|
||||
import { getUserId } from "../../../auth/services/account.service";
|
||||
import { HttpStatusCode } from "../../../enums";
|
||||
import { ErrorResponse } from "../../../models/response/error.response";
|
||||
import { ListResponse } from "../../../models/response/list.response";
|
||||
@@ -18,6 +20,7 @@ export class PolicyApiService implements PolicyApiServiceAbstraction {
|
||||
constructor(
|
||||
private policyService: InternalPolicyService,
|
||||
private apiService: ApiService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
async getPolicy(organizationId: string, type: PolicyType): Promise<PolicyResponse> {
|
||||
@@ -93,8 +96,14 @@ export class PolicyApiService implements PolicyApiServiceAbstraction {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await firstValueFrom(
|
||||
this.policyService.masterPasswordPolicyOptions$([masterPasswordPolicy]),
|
||||
return firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) =>
|
||||
this.policyService.masterPasswordPolicyOptions$(userId, [masterPasswordPolicy]),
|
||||
),
|
||||
map((policy) => policy ?? null),
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
// If policy not found, return null
|
||||
@@ -114,8 +123,9 @@ export class PolicyApiService implements PolicyApiServiceAbstraction {
|
||||
true,
|
||||
true,
|
||||
);
|
||||
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const response = new PolicyResponse(r);
|
||||
const data = new PolicyData(response);
|
||||
await this.policyService.upsert(data);
|
||||
await this.policyService.upsert(data, userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,556 +0,0 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { firstValueFrom, of } from "rxjs";
|
||||
|
||||
import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec";
|
||||
import { FakeActiveUserState, FakeSingleUserState } from "../../../../spec/fake-state";
|
||||
import {
|
||||
OrganizationUserStatusType,
|
||||
OrganizationUserType,
|
||||
PolicyType,
|
||||
} from "../../../admin-console/enums";
|
||||
import { PermissionsApi } from "../../../admin-console/models/api/permissions.api";
|
||||
import { OrganizationData } from "../../../admin-console/models/data/organization.data";
|
||||
import { PolicyData } from "../../../admin-console/models/data/policy.data";
|
||||
import { MasterPasswordPolicyOptions } from "../../../admin-console/models/domain/master-password-policy-options";
|
||||
import { Organization } from "../../../admin-console/models/domain/organization";
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
import { ResetPasswordPolicyOptions } from "../../../admin-console/models/domain/reset-password-policy-options";
|
||||
import { POLICIES, PolicyService } from "../../../admin-console/services/policy/policy.service";
|
||||
import { PolicyId, UserId } from "../../../types/guid";
|
||||
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
|
||||
|
||||
describe("PolicyService", () => {
|
||||
const userId = "userId" as UserId;
|
||||
let stateProvider: FakeStateProvider;
|
||||
let organizationService: MockProxy<OrganizationService>;
|
||||
let activeUserState: FakeActiveUserState<Record<PolicyId, PolicyData>>;
|
||||
let singleUserState: FakeSingleUserState<Record<PolicyId, PolicyData>>;
|
||||
|
||||
let policyService: PolicyService;
|
||||
|
||||
beforeEach(() => {
|
||||
const accountService = mockAccountServiceWith(userId);
|
||||
stateProvider = new FakeStateProvider(accountService);
|
||||
organizationService = mock<OrganizationService>();
|
||||
|
||||
activeUserState = stateProvider.activeUser.getFake(POLICIES);
|
||||
singleUserState = stateProvider.singleUser.getFake(activeUserState.userId, POLICIES);
|
||||
|
||||
const organizations$ = of([
|
||||
// User
|
||||
organization("org1", true, true, OrganizationUserStatusType.Confirmed, false),
|
||||
// Owner
|
||||
organization(
|
||||
"org2",
|
||||
true,
|
||||
true,
|
||||
OrganizationUserStatusType.Confirmed,
|
||||
false,
|
||||
OrganizationUserType.Owner,
|
||||
),
|
||||
// Does not use policies
|
||||
organization("org3", true, false, OrganizationUserStatusType.Confirmed, false),
|
||||
// Another User
|
||||
organization("org4", true, true, OrganizationUserStatusType.Confirmed, false),
|
||||
// Another User
|
||||
organization("org5", true, true, OrganizationUserStatusType.Confirmed, false),
|
||||
// Can manage policies
|
||||
organization("org6", true, true, OrganizationUserStatusType.Confirmed, true),
|
||||
]);
|
||||
|
||||
organizationService.organizations$.mockReturnValue(organizations$);
|
||||
|
||||
policyService = new PolicyService(stateProvider, organizationService);
|
||||
});
|
||||
|
||||
it("upsert", async () => {
|
||||
activeUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("1", "test-organization", PolicyType.MaximumVaultTimeout, true, { minutes: 14 }),
|
||||
]),
|
||||
);
|
||||
|
||||
await policyService.upsert(policyData("99", "test-organization", PolicyType.DisableSend, true));
|
||||
|
||||
expect(await firstValueFrom(policyService.policies$)).toEqual([
|
||||
{
|
||||
id: "1",
|
||||
organizationId: "test-organization",
|
||||
type: PolicyType.MaximumVaultTimeout,
|
||||
enabled: true,
|
||||
data: { minutes: 14 },
|
||||
},
|
||||
{
|
||||
id: "99",
|
||||
organizationId: "test-organization",
|
||||
type: PolicyType.DisableSend,
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("replace", async () => {
|
||||
activeUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("1", "test-organization", PolicyType.MaximumVaultTimeout, true, { minutes: 14 }),
|
||||
]),
|
||||
);
|
||||
|
||||
await policyService.replace(
|
||||
{
|
||||
"2": policyData("2", "test-organization", PolicyType.DisableSend, true),
|
||||
},
|
||||
userId,
|
||||
);
|
||||
|
||||
expect(await firstValueFrom(policyService.policies$)).toEqual([
|
||||
{
|
||||
id: "2",
|
||||
organizationId: "test-organization",
|
||||
type: PolicyType.DisableSend,
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
describe("masterPasswordPolicyOptions", () => {
|
||||
it("returns default policy options", async () => {
|
||||
const data: any = {
|
||||
minComplexity: 5,
|
||||
minLength: 20,
|
||||
requireUpper: true,
|
||||
};
|
||||
const model = [
|
||||
new Policy(policyData("1", "test-organization-3", PolicyType.MasterPassword, true, data)),
|
||||
];
|
||||
const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(model));
|
||||
|
||||
expect(result).toEqual({
|
||||
minComplexity: 5,
|
||||
minLength: 20,
|
||||
requireLower: false,
|
||||
requireNumbers: false,
|
||||
requireSpecial: false,
|
||||
requireUpper: true,
|
||||
enforceOnLogin: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null", async () => {
|
||||
const data: any = {};
|
||||
const model = [
|
||||
new Policy(
|
||||
policyData("3", "test-organization-3", PolicyType.DisablePersonalVaultExport, true, data),
|
||||
),
|
||||
new Policy(
|
||||
policyData("4", "test-organization-3", PolicyType.MaximumVaultTimeout, true, data),
|
||||
),
|
||||
];
|
||||
|
||||
const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(model));
|
||||
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
it("returns specified policy options", async () => {
|
||||
const data: any = {
|
||||
minLength: 14,
|
||||
};
|
||||
const model = [
|
||||
new Policy(
|
||||
policyData("3", "test-organization-3", PolicyType.DisablePersonalVaultExport, true, data),
|
||||
),
|
||||
new Policy(policyData("4", "test-organization-3", PolicyType.MasterPassword, true, data)),
|
||||
];
|
||||
|
||||
const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(model));
|
||||
|
||||
expect(result).toEqual({
|
||||
minComplexity: 0,
|
||||
minLength: 14,
|
||||
requireLower: false,
|
||||
requireNumbers: false,
|
||||
requireSpecial: false,
|
||||
requireUpper: false,
|
||||
enforceOnLogin: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("evaluateMasterPassword", () => {
|
||||
it("false", async () => {
|
||||
const enforcedPolicyOptions = new MasterPasswordPolicyOptions();
|
||||
enforcedPolicyOptions.minLength = 14;
|
||||
const result = policyService.evaluateMasterPassword(10, "password", enforcedPolicyOptions);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it("true", async () => {
|
||||
const enforcedPolicyOptions = new MasterPasswordPolicyOptions();
|
||||
const result = policyService.evaluateMasterPassword(0, "password", enforcedPolicyOptions);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getResetPasswordPolicyOptions", () => {
|
||||
it("default", async () => {
|
||||
const result = policyService.getResetPasswordPolicyOptions([], "");
|
||||
|
||||
expect(result).toEqual([new ResetPasswordPolicyOptions(), false]);
|
||||
});
|
||||
|
||||
it("returns autoEnrollEnabled true", async () => {
|
||||
const data: any = {
|
||||
autoEnrollEnabled: true,
|
||||
};
|
||||
const policies = [
|
||||
new Policy(policyData("5", "test-organization-3", PolicyType.ResetPassword, true, data)),
|
||||
];
|
||||
const result = policyService.getResetPasswordPolicyOptions(policies, "test-organization-3");
|
||||
|
||||
expect(result).toEqual([{ autoEnrollEnabled: true }, true]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("get$", () => {
|
||||
it("returns the specified PolicyType", async () => {
|
||||
activeUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("policy1", "org1", PolicyType.ActivateAutofill, true),
|
||||
policyData("policy2", "org1", PolicyType.DisablePersonalVaultExport, true),
|
||||
policyData("policy3", "org1", PolicyType.RemoveUnlockWithPin, true),
|
||||
]),
|
||||
);
|
||||
|
||||
await expect(
|
||||
firstValueFrom(policyService.get$(PolicyType.ActivateAutofill)),
|
||||
).resolves.toMatchObject({
|
||||
id: "policy1",
|
||||
organizationId: "org1",
|
||||
type: PolicyType.ActivateAutofill,
|
||||
enabled: true,
|
||||
});
|
||||
await expect(
|
||||
firstValueFrom(policyService.get$(PolicyType.DisablePersonalVaultExport)),
|
||||
).resolves.toMatchObject({
|
||||
id: "policy2",
|
||||
organizationId: "org1",
|
||||
type: PolicyType.DisablePersonalVaultExport,
|
||||
enabled: true,
|
||||
});
|
||||
await expect(
|
||||
firstValueFrom(policyService.get$(PolicyType.RemoveUnlockWithPin)),
|
||||
).resolves.toMatchObject({
|
||||
id: "policy3",
|
||||
organizationId: "org1",
|
||||
type: PolicyType.RemoveUnlockWithPin,
|
||||
enabled: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not return disabled policies", async () => {
|
||||
activeUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("policy1", "org1", PolicyType.ActivateAutofill, true),
|
||||
policyData("policy2", "org1", PolicyType.DisablePersonalVaultExport, false),
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
policyService.get$(PolicyType.DisablePersonalVaultExport),
|
||||
);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("does not return policies that do not apply to the user because the user's role is exempt", async () => {
|
||||
activeUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("policy1", "org1", PolicyType.ActivateAutofill, true),
|
||||
policyData("policy2", "org2", PolicyType.DisablePersonalVaultExport, false),
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
policyService.get$(PolicyType.DisablePersonalVaultExport),
|
||||
);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["owners", "org2"],
|
||||
["administrators", "org6"],
|
||||
])("returns the password generator policy for %s", async (_, organization) => {
|
||||
activeUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("policy1", "org1", PolicyType.ActivateAutofill, false),
|
||||
policyData("policy2", organization, PolicyType.PasswordGenerator, true),
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(policyService.get$(PolicyType.PasswordGenerator));
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not return policies for organizations that do not use policies", async () => {
|
||||
activeUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("policy1", "org3", PolicyType.ActivateAutofill, true),
|
||||
policyData("policy2", "org2", PolicyType.DisablePersonalVaultExport, true),
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(policyService.get$(PolicyType.ActivateAutofill));
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAll$", () => {
|
||||
it("returns the specified PolicyTypes", async () => {
|
||||
singleUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true),
|
||||
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
|
||||
policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, true),
|
||||
policyData("policy4", "org1", PolicyType.DisablePersonalVaultExport, true),
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
policyService.getAll$(PolicyType.DisablePersonalVaultExport, activeUserState.userId),
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: "policy1",
|
||||
organizationId: "org4",
|
||||
type: PolicyType.DisablePersonalVaultExport,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "policy3",
|
||||
organizationId: "org5",
|
||||
type: PolicyType.DisablePersonalVaultExport,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "policy4",
|
||||
organizationId: "org1",
|
||||
type: PolicyType.DisablePersonalVaultExport,
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not return disabled policies", async () => {
|
||||
singleUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true),
|
||||
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
|
||||
policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, false), // disabled
|
||||
policyData("policy4", "org1", PolicyType.DisablePersonalVaultExport, true),
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
policyService.getAll$(PolicyType.DisablePersonalVaultExport, activeUserState.userId),
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: "policy1",
|
||||
organizationId: "org4",
|
||||
type: PolicyType.DisablePersonalVaultExport,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "policy4",
|
||||
organizationId: "org1",
|
||||
type: PolicyType.DisablePersonalVaultExport,
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not return policies that do not apply to the user because the user's role is exempt", async () => {
|
||||
singleUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true),
|
||||
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
|
||||
policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, true),
|
||||
policyData("policy4", "org2", PolicyType.DisablePersonalVaultExport, true), // owner
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
policyService.getAll$(PolicyType.DisablePersonalVaultExport, activeUserState.userId),
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: "policy1",
|
||||
organizationId: "org4",
|
||||
type: PolicyType.DisablePersonalVaultExport,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "policy3",
|
||||
organizationId: "org5",
|
||||
type: PolicyType.DisablePersonalVaultExport,
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not return policies for organizations that do not use policies", async () => {
|
||||
singleUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true),
|
||||
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
|
||||
policyData("policy3", "org3", PolicyType.DisablePersonalVaultExport, true), // does not use policies
|
||||
policyData("policy4", "org1", PolicyType.DisablePersonalVaultExport, true),
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
policyService.getAll$(PolicyType.DisablePersonalVaultExport, activeUserState.userId),
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: "policy1",
|
||||
organizationId: "org4",
|
||||
type: PolicyType.DisablePersonalVaultExport,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "policy4",
|
||||
organizationId: "org1",
|
||||
type: PolicyType.DisablePersonalVaultExport,
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policyAppliesToActiveUser$", () => {
|
||||
it("returns true when the policyType applies to the user", async () => {
|
||||
activeUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true),
|
||||
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
|
||||
policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, true),
|
||||
policyData("policy4", "org1", PolicyType.DisablePersonalVaultExport, true),
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
policyService.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport),
|
||||
);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when policyType is disabled", async () => {
|
||||
activeUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
|
||||
policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, false), // disabled
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
policyService.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport),
|
||||
);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false when the policyType does not apply to the user because the user's role is exempt", async () => {
|
||||
activeUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
|
||||
policyData("policy4", "org2", PolicyType.DisablePersonalVaultExport, true), // owner
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
policyService.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport),
|
||||
);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for organizations that do not use policies", async () => {
|
||||
activeUserState.nextState(
|
||||
arrayToRecord([
|
||||
policyData("policy2", "org1", PolicyType.ActivateAutofill, true),
|
||||
policyData("policy3", "org3", PolicyType.DisablePersonalVaultExport, true), // does not use policies
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
policyService.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport),
|
||||
);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
function policyData(
|
||||
id: string,
|
||||
organizationId: string,
|
||||
type: PolicyType,
|
||||
enabled: boolean,
|
||||
data?: any,
|
||||
) {
|
||||
const policyData = new PolicyData({} as any);
|
||||
policyData.id = id as PolicyId;
|
||||
policyData.organizationId = organizationId;
|
||||
policyData.type = type;
|
||||
policyData.enabled = enabled;
|
||||
policyData.data = data;
|
||||
|
||||
return policyData;
|
||||
}
|
||||
|
||||
function organizationData(
|
||||
id: string,
|
||||
enabled: boolean,
|
||||
usePolicies: boolean,
|
||||
status: OrganizationUserStatusType,
|
||||
managePolicies: boolean,
|
||||
type: OrganizationUserType = OrganizationUserType.User,
|
||||
) {
|
||||
const organizationData = new OrganizationData({} as any, {} as any);
|
||||
organizationData.id = id;
|
||||
organizationData.enabled = enabled;
|
||||
organizationData.usePolicies = usePolicies;
|
||||
organizationData.status = status;
|
||||
organizationData.permissions = new PermissionsApi({ managePolicies: managePolicies } as any);
|
||||
organizationData.type = type;
|
||||
return organizationData;
|
||||
}
|
||||
|
||||
function organization(
|
||||
id: string,
|
||||
enabled: boolean,
|
||||
usePolicies: boolean,
|
||||
status: OrganizationUserStatusType,
|
||||
managePolicies: boolean,
|
||||
type: OrganizationUserType = OrganizationUserType.User,
|
||||
) {
|
||||
return new Organization(
|
||||
organizationData(id, enabled, usePolicies, status, managePolicies, type),
|
||||
);
|
||||
}
|
||||
|
||||
function arrayToRecord(input: PolicyData[]): Record<PolicyId, PolicyData> {
|
||||
return Object.fromEntries(input.map((i) => [i.id, i]));
|
||||
}
|
||||
});
|
||||
@@ -1,257 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { combineLatest, firstValueFrom, map, Observable, of, switchMap } from "rxjs";
|
||||
|
||||
import { UserKeyDefinition, POLICIES_DISK, StateProvider } from "../../../platform/state";
|
||||
import { PolicyId, UserId } from "../../../types/guid";
|
||||
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
|
||||
import { InternalPolicyService as InternalPolicyServiceAbstraction } from "../../abstractions/policy/policy.service.abstraction";
|
||||
import { OrganizationUserStatusType, PolicyType } from "../../enums";
|
||||
import { PolicyData } from "../../models/data/policy.data";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options";
|
||||
import { Organization } from "../../models/domain/organization";
|
||||
import { Policy } from "../../models/domain/policy";
|
||||
import { ResetPasswordPolicyOptions } from "../../models/domain/reset-password-policy-options";
|
||||
|
||||
const policyRecordToArray = (policiesMap: { [id: string]: PolicyData }) =>
|
||||
Object.values(policiesMap || {}).map((f) => new Policy(f));
|
||||
|
||||
export const POLICIES = UserKeyDefinition.record<PolicyData, PolicyId>(POLICIES_DISK, "policies", {
|
||||
deserializer: (policyData) => policyData,
|
||||
clearOn: ["logout"],
|
||||
});
|
||||
|
||||
export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
private activeUserPolicyState = this.stateProvider.getActive(POLICIES);
|
||||
private activeUserPolicies$ = this.activeUserPolicyState.state$.pipe(
|
||||
map((policyData) => policyRecordToArray(policyData)),
|
||||
);
|
||||
|
||||
policies$ = this.activeUserPolicies$;
|
||||
|
||||
constructor(
|
||||
private stateProvider: StateProvider,
|
||||
private organizationService: OrganizationService,
|
||||
) {}
|
||||
|
||||
get$(policyType: PolicyType): Observable<Policy> {
|
||||
const filteredPolicies$ = this.activeUserPolicies$.pipe(
|
||||
map((policies) => policies.filter((p) => p.type === policyType)),
|
||||
);
|
||||
|
||||
const organizations$ = this.stateProvider.activeUserId$.pipe(
|
||||
switchMap((userId) => this.organizationService.organizations$(userId)),
|
||||
);
|
||||
|
||||
return combineLatest([filteredPolicies$, organizations$]).pipe(
|
||||
map(
|
||||
([policies, organizations]) =>
|
||||
this.enforcedPolicyFilter(policies, organizations)?.at(0) ?? null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
getAll$(policyType: PolicyType, userId: UserId) {
|
||||
const filteredPolicies$ = this.stateProvider.getUserState$(POLICIES, userId).pipe(
|
||||
map((policyData) => policyRecordToArray(policyData)),
|
||||
map((policies) => policies.filter((p) => p.type === policyType)),
|
||||
);
|
||||
|
||||
return combineLatest([filteredPolicies$, this.organizationService.organizations$(userId)]).pipe(
|
||||
map(([policies, organizations]) => this.enforcedPolicyFilter(policies, organizations)),
|
||||
);
|
||||
}
|
||||
|
||||
async getAll(policyType: PolicyType) {
|
||||
return await firstValueFrom(
|
||||
this.policies$.pipe(map((policies) => policies.filter((p) => p.type === policyType))),
|
||||
);
|
||||
}
|
||||
|
||||
policyAppliesToActiveUser$(policyType: PolicyType) {
|
||||
return this.get$(policyType).pipe(map((policy) => policy != null));
|
||||
}
|
||||
|
||||
async policyAppliesToUser(policyType: PolicyType) {
|
||||
return await firstValueFrom(this.policyAppliesToActiveUser$(policyType));
|
||||
}
|
||||
|
||||
private enforcedPolicyFilter(policies: Policy[], organizations: Organization[]) {
|
||||
const orgDict = Object.fromEntries(organizations.map((o) => [o.id, o]));
|
||||
return policies.filter((policy) => {
|
||||
const organization = orgDict[policy.organizationId];
|
||||
|
||||
// This shouldn't happen, i.e. the user should only have policies for orgs they are a member of
|
||||
// But if it does, err on the side of enforcing the policy
|
||||
if (organization == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
policy.enabled &&
|
||||
organization.status >= OrganizationUserStatusType.Accepted &&
|
||||
organization.usePolicies &&
|
||||
!this.isExemptFromPolicy(policy.type, organization)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
masterPasswordPolicyOptions$(policies?: Policy[]): Observable<MasterPasswordPolicyOptions> {
|
||||
const observable = policies ? of(policies) : this.policies$;
|
||||
return observable.pipe(
|
||||
map((obsPolicies) => {
|
||||
let enforcedOptions: MasterPasswordPolicyOptions = null;
|
||||
const filteredPolicies = obsPolicies.filter((p) => p.type === PolicyType.MasterPassword);
|
||||
|
||||
if (filteredPolicies == null || filteredPolicies.length === 0) {
|
||||
return enforcedOptions;
|
||||
}
|
||||
|
||||
filteredPolicies.forEach((currentPolicy) => {
|
||||
if (!currentPolicy.enabled || currentPolicy.data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enforcedOptions == null) {
|
||||
enforcedOptions = new MasterPasswordPolicyOptions();
|
||||
}
|
||||
|
||||
if (
|
||||
currentPolicy.data.minComplexity != null &&
|
||||
currentPolicy.data.minComplexity > enforcedOptions.minComplexity
|
||||
) {
|
||||
enforcedOptions.minComplexity = currentPolicy.data.minComplexity;
|
||||
}
|
||||
|
||||
if (
|
||||
currentPolicy.data.minLength != null &&
|
||||
currentPolicy.data.minLength > enforcedOptions.minLength
|
||||
) {
|
||||
enforcedOptions.minLength = currentPolicy.data.minLength;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireUpper) {
|
||||
enforcedOptions.requireUpper = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireLower) {
|
||||
enforcedOptions.requireLower = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireNumbers) {
|
||||
enforcedOptions.requireNumbers = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireSpecial) {
|
||||
enforcedOptions.requireSpecial = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.enforceOnLogin) {
|
||||
enforcedOptions.enforceOnLogin = true;
|
||||
}
|
||||
});
|
||||
|
||||
return enforcedOptions;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
evaluateMasterPassword(
|
||||
passwordStrength: number,
|
||||
newPassword: string,
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions,
|
||||
): boolean {
|
||||
if (enforcedPolicyOptions == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
enforcedPolicyOptions.minComplexity > 0 &&
|
||||
enforcedPolicyOptions.minComplexity > passwordStrength
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
enforcedPolicyOptions.minLength > 0 &&
|
||||
enforcedPolicyOptions.minLength > newPassword.length
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.requireUpper && newPassword.toLocaleLowerCase() === newPassword) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.requireLower && newPassword.toLocaleUpperCase() === newPassword) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.requireNumbers && !/[0-9]/.test(newPassword)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
if (enforcedPolicyOptions.requireSpecial && !/[!@#$%\^&*]/g.test(newPassword)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getResetPasswordPolicyOptions(
|
||||
policies: Policy[],
|
||||
orgId: string,
|
||||
): [ResetPasswordPolicyOptions, boolean] {
|
||||
const resetPasswordPolicyOptions = new ResetPasswordPolicyOptions();
|
||||
|
||||
if (policies == null || orgId == null) {
|
||||
return [resetPasswordPolicyOptions, false];
|
||||
}
|
||||
|
||||
const policy = policies.find(
|
||||
(p) => p.organizationId === orgId && p.type === PolicyType.ResetPassword && p.enabled,
|
||||
);
|
||||
resetPasswordPolicyOptions.autoEnrollEnabled = policy?.data?.autoEnrollEnabled ?? false;
|
||||
|
||||
return [resetPasswordPolicyOptions, policy?.enabled ?? false];
|
||||
}
|
||||
|
||||
async upsert(policy: PolicyData): Promise<void> {
|
||||
await this.activeUserPolicyState.update((policies) => {
|
||||
policies ??= {};
|
||||
policies[policy.id] = policy;
|
||||
return policies;
|
||||
});
|
||||
}
|
||||
|
||||
async replace(policies: { [id: string]: PolicyData }, userId: UserId): Promise<void> {
|
||||
await this.stateProvider.setUserState(POLICIES, policies, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an orgUser is exempt from a specific policy because of their role
|
||||
* Generally orgUsers who can manage policies are exempt from them, but some policies are stricter
|
||||
*/
|
||||
private isExemptFromPolicy(policyType: PolicyType, organization: Organization) {
|
||||
switch (policyType) {
|
||||
case PolicyType.MaximumVaultTimeout:
|
||||
// Max Vault Timeout applies to everyone except owners
|
||||
return organization.isOwner;
|
||||
case PolicyType.PasswordGenerator:
|
||||
// password generation policy applies to everyone
|
||||
return false;
|
||||
case PolicyType.PersonalOwnership:
|
||||
// individual vault policy applies to everyone except admins and owners
|
||||
return organization.isAdmin;
|
||||
case PolicyType.FreeFamiliesSponsorshipPolicy:
|
||||
// free Bitwarden families policy applies to everyone
|
||||
return false;
|
||||
case PolicyType.RemoveUnlockWithPin:
|
||||
// free Remove Unlock with PIN policy applies to everyone
|
||||
return false;
|
||||
default:
|
||||
return organization.canManagePolicies;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user