mirror of
https://github.com/bitwarden/browser
synced 2025-12-21 18:53:29 +00:00
[PM-6556] reintroduce policy reduction for multi-org accounts (#8409)
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
// FIXME: use index.ts imports once policy abstractions and models
|
||||
// implement ADR-0002
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
import { PolicyId } from "../../../types/guid";
|
||||
|
||||
import { DisabledPasswordGeneratorPolicy, leastPrivilege } from "./password-generator-policy";
|
||||
|
||||
function createPolicy(
|
||||
data: any,
|
||||
type: PolicyType = PolicyType.PasswordGenerator,
|
||||
enabled: boolean = true,
|
||||
) {
|
||||
return new Policy({
|
||||
id: "id" as PolicyId,
|
||||
organizationId: "organizationId",
|
||||
data,
|
||||
enabled,
|
||||
type,
|
||||
});
|
||||
}
|
||||
|
||||
describe("leastPrivilege", () => {
|
||||
it("should return the accumulator when the policy type does not apply", () => {
|
||||
const policy = createPolicy({}, PolicyType.RequireSso);
|
||||
|
||||
const result = leastPrivilege(DisabledPasswordGeneratorPolicy, policy);
|
||||
|
||||
expect(result).toEqual(DisabledPasswordGeneratorPolicy);
|
||||
});
|
||||
|
||||
it("should return the accumulator when the policy is not enabled", () => {
|
||||
const policy = createPolicy({}, PolicyType.PasswordGenerator, false);
|
||||
|
||||
const result = leastPrivilege(DisabledPasswordGeneratorPolicy, policy);
|
||||
|
||||
expect(result).toEqual(DisabledPasswordGeneratorPolicy);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["minLength", 10, "minLength"],
|
||||
["useUpper", true, "useUppercase"],
|
||||
["useLower", true, "useLowercase"],
|
||||
["useNumbers", true, "useNumbers"],
|
||||
["minNumbers", 10, "numberCount"],
|
||||
["useSpecial", true, "useSpecial"],
|
||||
["minSpecial", 10, "specialCount"],
|
||||
])("should take the %p from the policy", (input, value, expected) => {
|
||||
const policy = createPolicy({ [input]: value });
|
||||
|
||||
const result = leastPrivilege(DisabledPasswordGeneratorPolicy, policy);
|
||||
|
||||
expect(result).toEqual({ ...DisabledPasswordGeneratorPolicy, [expected]: value });
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,8 @@
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
// FIXME: use index.ts imports once policy abstractions and models
|
||||
// implement ADR-0002
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
|
||||
/** Policy options enforced during password generation. */
|
||||
export type PasswordGeneratorPolicy = {
|
||||
/** The minimum length of generated passwords.
|
||||
@@ -48,3 +53,25 @@ export const DisabledPasswordGeneratorPolicy: PasswordGeneratorPolicy = Object.f
|
||||
useSpecial: false,
|
||||
specialCount: 0,
|
||||
});
|
||||
|
||||
/** Reduces a policy into an accumulator by accepting the most restrictive
|
||||
* values from each policy.
|
||||
* @param acc the accumulator
|
||||
* @param policy the policy to reduce
|
||||
* @returns the most restrictive values between the policy and accumulator.
|
||||
*/
|
||||
export function leastPrivilege(acc: PasswordGeneratorPolicy, policy: Policy) {
|
||||
if (policy.type !== PolicyType.PasswordGenerator || !policy.enabled) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
return {
|
||||
minLength: Math.max(acc.minLength, policy.data.minLength ?? acc.minLength),
|
||||
useUppercase: policy.data.useUpper || acc.useUppercase,
|
||||
useLowercase: policy.data.useLower || acc.useLowercase,
|
||||
useNumbers: policy.data.useNumbers || acc.useNumbers,
|
||||
numberCount: Math.max(acc.numberCount, policy.data.minNumbers ?? acc.numberCount),
|
||||
useSpecial: policy.data.useSpecial || acc.useSpecial,
|
||||
specialCount: Math.max(acc.specialCount, policy.data.minSpecial ?? acc.specialCount),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { of, firstValueFrom } from "rxjs";
|
||||
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
// FIXME: use index.ts imports once policy abstractions and models
|
||||
@@ -24,17 +25,8 @@ import {
|
||||
const SomeUser = "some user" as UserId;
|
||||
|
||||
describe("Password generation strategy", () => {
|
||||
describe("evaluator()", () => {
|
||||
it("should throw if the policy type is incorrect", () => {
|
||||
const strategy = new PasswordGeneratorStrategy(null, null);
|
||||
const policy = mock<Policy>({
|
||||
type: PolicyType.DisableSend,
|
||||
});
|
||||
|
||||
expect(() => strategy.evaluator(policy)).toThrow(new RegExp("Mismatched policy type\\. .+"));
|
||||
});
|
||||
|
||||
it("should map to the policy evaluator", () => {
|
||||
describe("toEvaluator()", () => {
|
||||
it("should map to a password policy evaluator", async () => {
|
||||
const strategy = new PasswordGeneratorStrategy(null, null);
|
||||
const policy = mock<Policy>({
|
||||
type: PolicyType.PasswordGenerator,
|
||||
@@ -49,7 +41,8 @@ describe("Password generation strategy", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const evaluator = strategy.evaluator(policy);
|
||||
const evaluator$ = of([policy]).pipe(strategy.toEvaluator());
|
||||
const evaluator = await firstValueFrom(evaluator$);
|
||||
|
||||
expect(evaluator).toBeInstanceOf(PasswordGeneratorOptionsEvaluator);
|
||||
expect(evaluator.policy).toMatchObject({
|
||||
@@ -63,13 +56,18 @@ describe("Password generation strategy", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should map `null` to a default policy evaluator", () => {
|
||||
const strategy = new PasswordGeneratorStrategy(null, null);
|
||||
const evaluator = strategy.evaluator(null);
|
||||
it.each([[[]], [null], [undefined]])(
|
||||
"should map `%p` to a disabled password policy evaluator",
|
||||
async (policies) => {
|
||||
const strategy = new PasswordGeneratorStrategy(null, null);
|
||||
|
||||
expect(evaluator).toBeInstanceOf(PasswordGeneratorOptionsEvaluator);
|
||||
expect(evaluator.policy).toMatchObject(DisabledPasswordGeneratorPolicy);
|
||||
});
|
||||
const evaluator$ = of(policies).pipe(strategy.toEvaluator());
|
||||
const evaluator = await firstValueFrom(evaluator$);
|
||||
|
||||
expect(evaluator).toBeInstanceOf(PasswordGeneratorOptionsEvaluator);
|
||||
expect(evaluator.policy).toMatchObject(DisabledPasswordGeneratorPolicy);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("durableState", () => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { map, pipe } from "rxjs";
|
||||
|
||||
import { GeneratorStrategy } from "..";
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
// FIXME: use index.ts imports once policy abstractions and models
|
||||
// implement ADR-0002
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
import { StateProvider } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { PASSWORD_SETTINGS } from "../key-definitions";
|
||||
import { reduceCollection } from "../reduce-collection.operator";
|
||||
|
||||
import { PasswordGenerationOptions } from "./password-generation-options";
|
||||
import { PasswordGenerationServiceAbstraction } from "./password-generation.service.abstraction";
|
||||
@@ -13,6 +13,7 @@ import { PasswordGeneratorOptionsEvaluator } from "./password-generator-options-
|
||||
import {
|
||||
DisabledPasswordGeneratorPolicy,
|
||||
PasswordGeneratorPolicy,
|
||||
leastPrivilege,
|
||||
} from "./password-generator-policy";
|
||||
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
@@ -43,26 +44,12 @@ export class PasswordGeneratorStrategy
|
||||
return ONE_MINUTE;
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.evaluator} */
|
||||
evaluator(policy: Policy): PasswordGeneratorOptionsEvaluator {
|
||||
if (!policy) {
|
||||
return new PasswordGeneratorOptionsEvaluator(DisabledPasswordGeneratorPolicy);
|
||||
}
|
||||
|
||||
if (policy.type !== this.policy) {
|
||||
const details = `Expected: ${this.policy}. Received: ${policy.type}`;
|
||||
throw Error("Mismatched policy type. " + details);
|
||||
}
|
||||
|
||||
return new PasswordGeneratorOptionsEvaluator({
|
||||
minLength: policy.data.minLength,
|
||||
useUppercase: policy.data.useUpper,
|
||||
useLowercase: policy.data.useLower,
|
||||
useNumbers: policy.data.useNumbers,
|
||||
numberCount: policy.data.minNumbers,
|
||||
useSpecial: policy.data.useSpecial,
|
||||
specialCount: policy.data.minSpecial,
|
||||
});
|
||||
/** {@link GeneratorStrategy.toEvaluator} */
|
||||
toEvaluator() {
|
||||
return pipe(
|
||||
reduceCollection(leastPrivilege, DisabledPasswordGeneratorPolicy),
|
||||
map((policy) => new PasswordGeneratorOptionsEvaluator(policy)),
|
||||
);
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.generate} */
|
||||
|
||||
Reference in New Issue
Block a user