Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EC-377] Transition Policy service into providing observables #3259

Merged
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
fbaf57c
Added abstractions for PolicyApiService and PolicyService
r-tome Jul 28, 2022
8155a61
Added implementations for PolicyApiService and PolicyService
r-tome Jul 28, 2022
375fd3a
Updated all references to new PolicyApiService and PolicyService
r-tome Jul 28, 2022
454e212
Deleted old PolicyService abstraction and implementation
r-tome Jul 28, 2022
c7f554b
Fixed CLI import path for policy.service
r-tome Jul 29, 2022
386ce3a
Fixed main.background.ts policyApiService dependency for policyService
r-tome Jul 29, 2022
62d843f
Ran prettier
r-tome Jul 29, 2022
007954b
Updated policy-api.service with the correct imports
r-tome Jul 29, 2022
6076ccb
[EC-377] Removed methods from StateService that read policies
r-tome Aug 2, 2022
2be260b
[EC-377] Updated policy service getAll method to use observable colle…
r-tome Aug 2, 2022
c4ba9ab
[EC-377] Added first unit tests for policy service
r-tome Aug 2, 2022
5d4eac6
[EC-377] Added more unit tests for Policy Service
r-tome Aug 3, 2022
6486a3a
[EC-376] Sorted methods order in PolicyApiService
r-tome Aug 4, 2022
7367203
[EC-376] Removed unused clearCache method from PolicyService
r-tome Aug 4, 2022
aaec1cb
[EC-376] Added upsert method to PolicyService
r-tome Aug 4, 2022
fe753f5
[EC-376] PolicyApiService putPolicy method now upserts data to Policy…
r-tome Aug 4, 2022
0547a83
[EC-377] Merge branch 'EC-376-split-out-api-methods-into-api-service'…
r-tome Aug 4, 2022
a431247
[EC-377] Removed tests for deleted clearCache method
r-tome Aug 4, 2022
270863c
[EC-377] Added unit test for PolicyService.upsert
r-tome Aug 4, 2022
9a7026d
Merge remote-tracking branch 'origin/master' into EC-377-transition-p…
r-tome Aug 8, 2022
3d37a30
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
r-tome Aug 8, 2022
25e2a31
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
r-tome Aug 10, 2022
0d8eed9
[EC-377] Updated references to state service observables
r-tome Aug 10, 2022
775ff34
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
r-tome Aug 11, 2022
1372ebf
[EC-377] Removed getAll method from PolicyService and refactored comp…
r-tome Aug 11, 2022
2399f10
[EC-377] Updated components to use concatMap instead of async subscribe
r-tome Aug 11, 2022
a9a804e
[EC-377] Removed getPolicyForOrganization from policyApiService
r-tome Aug 17, 2022
91bc630
[EC-377] Updated policyAppliesToUser to return observable collection
r-tome Aug 17, 2022
82a4e1d
[EC-377] Changed policyService.policyAppliesToUser to return observable
r-tome Aug 18, 2022
2f65298
[EC-377] Fixed browser settings.component.ts getting vault timeout
r-tome Aug 18, 2022
6b7d40c
Updated people.component.ts to get ResetPassword policy through a sub…
r-tome Aug 18, 2022
4295830
[EC-377] Changed passwordGenerationService.getOptions to return obser…
r-tome Aug 19, 2022
8bd62e2
[EC-377] Fixed CLI generate.command.ts getting enforcePasswordGenerat…
r-tome Aug 19, 2022
00fccdf
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
r-tome Aug 22, 2022
429d3af
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
r-tome Aug 25, 2022
af45e8c
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
r-tome Aug 29, 2022
1ed7b52
[EC-377] Fixed eslint errors on rxjs
r-tome Aug 29, 2022
68d9d4b
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
r-tome Aug 30, 2022
e979062
[EC-377] Reverted changes on passwordGeneration.service and vaultTime…
r-tome Aug 30, 2022
b05cbb9
[EC-377] Removed eslint disable on web/vault/add-edit-component
r-tome Aug 30, 2022
8aadf8f
[EC-377] Changed AccountData.policies to TemporaryDataEncryption
r-tome Aug 30, 2022
6db74e9
[EC-377] Updated import.component to be reactive to policyAppliesToUser$
r-tome Aug 30, 2022
197e349
[EC-377] Updated importBlockedByPolicy$
r-tome Aug 30, 2022
aae9446
[EC-377] Fixed missing rename
r-tome Aug 30, 2022
f1d805e
[EC-377] Updated policyService.masterPasswordPolicyOptions to return …
r-tome Aug 30, 2022
53ebeee
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
r-tome Aug 30, 2022
fdbd44e
[EC-377] Fixed vaultTimeout imports from merge
r-tome Aug 30, 2022
0c7013a
[EC-377] Reverted call to passwordGenerationService.getOptions
r-tome Aug 30, 2022
2202e61
[EC-377] Reverted call to enforcePasswordGeneratorPoliciesOnOptions
r-tome Aug 30, 2022
7ddc7e4
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
r-tome Aug 31, 2022
6e91825
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
r-tome Sep 16, 2022
0ce5213
[EC-377] Removed unneeded ngOnDestroy
r-tome Sep 16, 2022
5502112
Apply suggestions from code review
r-tome Sep 16, 2022
1559eb1
Merge branch 'EC-377-transition-policy-service-into-providing-observa…
r-tome Sep 16, 2022
52a1c42
[EC-377] Fixed login.component.ts and register.component.ts
r-tome Sep 16, 2022
909da7e
[EC-377] Updated PolicyService to update vaultTimeout
r-tome Sep 16, 2022
c617fba
[EC-377] Updated PolicyService dependencies
r-tome Sep 16, 2022
e5b625f
[EC-377] Renamed policyAppliesToUser to policyAppliesToActiveUser
r-tome Sep 19, 2022
22f73df
[EC-377] VaultTimeoutSettings service now gets the vault timeout dire…
r-tome Sep 19, 2022
d585998
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
r-tome Sep 19, 2022
3aa20b9
[EC-377] Fixed unit tests by removing unneeded vaultTimeoutSettingsSe…
r-tome Sep 19, 2022
e442163
[EC-377] Set getDecryptedPolicies and setDecryptedPolicies as deprecated
r-tome Sep 20, 2022
1c8f121
[EC-377] Set PolicyService.getAll as deprecated and updated to use pr…
r-tome Sep 20, 2022
3274da9
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
mimartin12 Sep 21, 2022
3e32142
[EC-565] Reverted unintended change to vaultTimeoutSettings that was …
r-tome Sep 22, 2022
b654fd3
[EC-377] Removed unneeded destroy$ from preferences.component.ts
r-tome Sep 22, 2022
3bf3523
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
r-tome Sep 27, 2022
ca6975c
Merge branch 'master' into EC-377-transition-policy-service-into-prov…
r-tome Oct 10, 2022
12129b7
[EC-377] Fixed policy.service.ts import of OrganizationService
r-tome Oct 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
352 changes: 352 additions & 0 deletions libs/common/spec/services/policy.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,352 @@
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.service";
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 { 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 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("non-org-user").resolves([]);
activeAccount = new BehaviorSubject("123");
activeAccountUnlocked = new BehaviorSubject(true);
stateService.getEncryptedPolicies().resolves({
"1": policyData("1", "test-organization", PolicyType.MasterPassword, true, { minLength: 14 }),
});
stateService.activeAccount$.returns(activeAccount);
stateService.activeAccountUnlocked$.returns(activeAccountUnlocked);
(window as any).bitwardenContainerService = new ContainerService(cryptoService);

policyService = new PolicyService(stateService, organizationService);
});

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.MasterPassword,
enabled: true,
data: { minLength: 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 () => {
stateService.getUserId().resolves("1");
await policyService.clear("1");

stateService.received(1).setEncryptedPolicies(Arg.any(), Arg.any());

expect((await firstValueFrom(policyService.policies$)).length).toBe(0);
});

it("missmatching userId", async () => {
await policyService.clear("12");

stateService.received(1).setEncryptedPolicies(Arg.any(), Arg.any());

expect((await firstValueFrom(policyService.policies$)).length).toBe(1);
});
});

describe("getMasterPasswordPolicyOptions", () => {
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 policyService.getMasterPasswordPolicyOptions(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 policyService.getMasterPasswordPolicyOptions(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 policyService.getMasterPasswordPolicyOptions(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("policyAppliesToUser", () => {
it("non org user", async () => {
const result = await policyService.policyAppliesToUser(
PolicyType.MasterPassword,
null,
"non-org-user"
);

expect(result).toEqual(false);
});

it("policy type applies", async () => {
const result = await policyService.policyAppliesToUser(
PolicyType.MasterPassword,
null,
"user"
);

expect(result).toEqual(true);
});

it("policy type does not apply", async () => {
const result = await policyService.policyAppliesToUser(
PolicyType.DisablePersonalVaultExport,
null,
"user"
);

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;
}
});
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -7,8 +9,9 @@ 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[]>;

getAll: (type?: PolicyType) => Promise<Policy[]>;
getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise<MasterPasswordPolicyOptions>;
r-tome marked this conversation as resolved.
Show resolved Hide resolved
evaluateMasterPassword: (
passwordStrength: number,
Expand All @@ -29,6 +32,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>;
}
Loading