mirror of
https://github.com/bitwarden/browser
synced 2025-12-22 11:13:46 +00:00
[EC-376] Extract API logic from PolicyService to PolicyApiService (#3203)
* 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 * Updated policy-api.service with the correct imports * [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
This commit is contained in:
120
libs/common/src/services/policy/policy-api.service.ts
Normal file
120
libs/common/src/services/policy/policy-api.service.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
|
||||
import { InternalPolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
import { PolicyData } from "@bitwarden/common/models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
|
||||
import { Policy } from "@bitwarden/common/models/domain/policy";
|
||||
import { PolicyRequest } from "@bitwarden/common/models/request/policyRequest";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/listResponse";
|
||||
import { PolicyResponse } from "@bitwarden/common/models/response/policyResponse";
|
||||
|
||||
export class PolicyApiService implements PolicyApiServiceAbstraction {
|
||||
constructor(
|
||||
private policyService: InternalPolicyService,
|
||||
private apiService: ApiService,
|
||||
private stateService: StateService,
|
||||
private organizationService: OrganizationService
|
||||
) {}
|
||||
|
||||
async getPolicy(organizationId: string, type: PolicyType): Promise<PolicyResponse> {
|
||||
const r = await this.apiService.send(
|
||||
"GET",
|
||||
"/organizations/" + organizationId + "/policies/" + type,
|
||||
null,
|
||||
true,
|
||||
true
|
||||
);
|
||||
return new PolicyResponse(r);
|
||||
}
|
||||
|
||||
async getPolicies(organizationId: string): Promise<ListResponse<PolicyResponse>> {
|
||||
const r = await this.apiService.send(
|
||||
"GET",
|
||||
"/organizations/" + organizationId + "/policies",
|
||||
null,
|
||||
true,
|
||||
true
|
||||
);
|
||||
return new ListResponse(r, PolicyResponse);
|
||||
}
|
||||
|
||||
async getPoliciesByToken(
|
||||
organizationId: string,
|
||||
token: string,
|
||||
email: string,
|
||||
organizationUserId: string
|
||||
): Promise<ListResponse<PolicyResponse>> {
|
||||
const r = await this.apiService.send(
|
||||
"GET",
|
||||
"/organizations/" +
|
||||
organizationId +
|
||||
"/policies/token?" +
|
||||
"token=" +
|
||||
encodeURIComponent(token) +
|
||||
"&email=" +
|
||||
encodeURIComponent(email) +
|
||||
"&organizationUserId=" +
|
||||
organizationUserId,
|
||||
null,
|
||||
false,
|
||||
true
|
||||
);
|
||||
return new ListResponse(r, PolicyResponse);
|
||||
}
|
||||
|
||||
async getPoliciesByInvitedUser(
|
||||
organizationId: string,
|
||||
userId: string
|
||||
): Promise<ListResponse<PolicyResponse>> {
|
||||
const r = await this.apiService.send(
|
||||
"GET",
|
||||
"/organizations/" + organizationId + "/policies/invited-user?" + "userId=" + userId,
|
||||
null,
|
||||
false,
|
||||
true
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
async putPolicy(organizationId: string, type: PolicyType, request: PolicyRequest): Promise<any> {
|
||||
const r = await this.apiService.send(
|
||||
"PUT",
|
||||
"/organizations/" + organizationId + "/policies/" + type,
|
||||
request,
|
||||
true,
|
||||
true
|
||||
);
|
||||
const response = new PolicyResponse(r);
|
||||
const data = new PolicyData(response);
|
||||
await this.policyService.upsert(data);
|
||||
}
|
||||
}
|
||||
227
libs/common/src/services/policy/policy.service.ts
Normal file
227
libs/common/src/services/policy/policy.service.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import { OrganizationService } from "../../abstractions/organization.service";
|
||||
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 { PolicyData } from "../../models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/masterPasswordPolicyOptions";
|
||||
import { Organization } from "../../models/domain/organization";
|
||||
import { Policy } from "../../models/domain/policy";
|
||||
import { ResetPasswordPolicyOptions } from "../../models/domain/resetPasswordPolicyOptions";
|
||||
import { ListResponse } from "../../models/response/listResponse";
|
||||
import { PolicyResponse } from "../../models/response/policyResponse";
|
||||
|
||||
export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
policyCache: Policy[];
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private organizationService: OrganizationService
|
||||
) {}
|
||||
|
||||
async getAll(type?: PolicyType, userId?: string): Promise<Policy[]> {
|
||||
let response: Policy[] = [];
|
||||
const decryptedPolicies = await this.stateService.getDecryptedPolicies({ userId: userId });
|
||||
if (decryptedPolicies != null) {
|
||||
response = decryptedPolicies;
|
||||
} else {
|
||||
const diskPolicies = await this.stateService.getEncryptedPolicies({ userId: userId });
|
||||
for (const id in diskPolicies) {
|
||||
// eslint-disable-next-line
|
||||
if (diskPolicies.hasOwnProperty(id)) {
|
||||
response.push(new Policy(diskPolicies[id]));
|
||||
}
|
||||
}
|
||||
await this.stateService.setDecryptedPolicies(response, { userId: userId });
|
||||
}
|
||||
if (type != null) {
|
||||
return response.filter((policy) => policy.type === type);
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
async getMasterPasswordPolicyOptions(policies?: Policy[]): Promise<MasterPasswordPolicyOptions> {
|
||||
let enforcedOptions: MasterPasswordPolicyOptions = null;
|
||||
|
||||
if (policies == null) {
|
||||
policies = await this.getAll(PolicyType.MasterPassword);
|
||||
} else {
|
||||
policies = policies.filter((p) => p.type === PolicyType.MasterPassword);
|
||||
}
|
||||
|
||||
if (policies == null || policies.length === 0) {
|
||||
return enforcedOptions;
|
||||
}
|
||||
|
||||
policies.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;
|
||||
}
|
||||
});
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
mapPoliciesFromToken(policiesResponse: ListResponse<PolicyResponse>): Policy[] {
|
||||
if (policiesResponse == null || policiesResponse.data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const policiesData = policiesResponse.data.map((p) => new PolicyData(p));
|
||||
return policiesData.map((p) => new Policy(p));
|
||||
}
|
||||
|
||||
async policyAppliesToUser(
|
||||
policyType: PolicyType,
|
||||
policyFilter?: (policy: Policy) => boolean,
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
async upsert(policy: PolicyData): Promise<any> {
|
||||
let policies = await this.stateService.getEncryptedPolicies();
|
||||
if (policies == null) {
|
||||
policies = {};
|
||||
}
|
||||
|
||||
policies[policy.id] = policy;
|
||||
|
||||
await this.stateService.setDecryptedPolicies(null);
|
||||
await this.stateService.setEncryptedPolicies(policies);
|
||||
}
|
||||
|
||||
async replace(policies: { [id: string]: PolicyData }): Promise<any> {
|
||||
await this.stateService.setDecryptedPolicies(null);
|
||||
await this.stateService.setEncryptedPolicies(policies);
|
||||
}
|
||||
|
||||
async clear(userId?: string): Promise<any> {
|
||||
await this.stateService.setDecryptedPolicies(null, { userId: userId });
|
||||
await this.stateService.setEncryptedPolicies(null, { userId: userId });
|
||||
}
|
||||
|
||||
private isExcemptFromPolicies(organization: Organization, policyType: PolicyType) {
|
||||
if (policyType === PolicyType.MaximumVaultTimeout) {
|
||||
return organization.type === OrganizationUserType.Owner;
|
||||
}
|
||||
|
||||
return organization.isExemptFromPolicies;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user