mirror of
https://github.com/bitwarden/browser.git
synced 2024-10-06 05:28:51 +02:00
[PM-5610] add eff long word list generator (#7748)
This commit is contained in:
parent
82cb52a938
commit
6d79231476
@ -0,0 +1,43 @@
|
|||||||
|
import { DefaultPolicyEvaluator } from "./default-policy-evaluator";
|
||||||
|
|
||||||
|
describe("Password generator options builder", () => {
|
||||||
|
describe("policy", () => {
|
||||||
|
it("should return an empty object", () => {
|
||||||
|
const builder = new DefaultPolicyEvaluator();
|
||||||
|
|
||||||
|
expect(builder.policy).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("policyInEffect", () => {
|
||||||
|
it("should return false", () => {
|
||||||
|
const builder = new DefaultPolicyEvaluator();
|
||||||
|
|
||||||
|
expect(builder.policyInEffect).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("applyPolicy(options)", () => {
|
||||||
|
// All tests should freeze the options to ensure they are not modified
|
||||||
|
it("should return the input operations without altering them", () => {
|
||||||
|
const builder = new DefaultPolicyEvaluator();
|
||||||
|
const options = Object.freeze({});
|
||||||
|
|
||||||
|
const sanitizedOptions = builder.applyPolicy(options);
|
||||||
|
|
||||||
|
expect(sanitizedOptions).toEqual(options);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("sanitize(options)", () => {
|
||||||
|
// All tests should freeze the options to ensure they are not modified
|
||||||
|
it("should return the input options without altering them", () => {
|
||||||
|
const builder = new DefaultPolicyEvaluator();
|
||||||
|
const options = Object.freeze({});
|
||||||
|
|
||||||
|
const sanitizedOptions = builder.sanitize(options);
|
||||||
|
|
||||||
|
expect(sanitizedOptions).toEqual(options);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
27
libs/common/src/tools/generator/default-policy-evaluator.ts
Normal file
27
libs/common/src/tools/generator/default-policy-evaluator.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { PolicyEvaluator } from "./abstractions";
|
||||||
|
import { NoPolicy } from "./no-policy";
|
||||||
|
|
||||||
|
/** A policy evaluator that does not apply any policy */
|
||||||
|
export class DefaultPolicyEvaluator<PolicyTarget>
|
||||||
|
implements PolicyEvaluator<NoPolicy, PolicyTarget>
|
||||||
|
{
|
||||||
|
/** {@link PolicyEvaluator.policy} */
|
||||||
|
get policy() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@link PolicyEvaluator.policyInEffect} */
|
||||||
|
get policyInEffect() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@link PolicyEvaluator.applyPolicy} */
|
||||||
|
applyPolicy(options: PolicyTarget) {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@link PolicyEvaluator.sanitize} */
|
||||||
|
sanitize(options: PolicyTarget) {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
ENCRYPTED_HISTORY,
|
ENCRYPTED_HISTORY,
|
||||||
ENCRYPTED_USERNAME_SETTINGS,
|
EFF_USERNAME_SETTINGS,
|
||||||
PASSPHRASE_SETTINGS,
|
PASSPHRASE_SETTINGS,
|
||||||
PASSWORD_SETTINGS,
|
PASSWORD_SETTINGS,
|
||||||
PLAINTEXT_USERNAME_SETTINGS,
|
|
||||||
} from "./key-definitions";
|
} from "./key-definitions";
|
||||||
|
|
||||||
describe("Key definitions", () => {
|
describe("Key definitions", () => {
|
||||||
@ -23,18 +22,10 @@ describe("Key definitions", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("ENCRYPTED_USERNAME_SETTINGS", () => {
|
describe("BASIC_LATIN_SETTINGS", () => {
|
||||||
it("should pass through deserialization", () => {
|
it("should pass through deserialization", () => {
|
||||||
const value = {};
|
const value = {};
|
||||||
const result = ENCRYPTED_USERNAME_SETTINGS.deserializer(value);
|
const result = EFF_USERNAME_SETTINGS.deserializer(value);
|
||||||
expect(result).toBe(value);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("PLAINTEXT_USERNAME_SETTINGS", () => {
|
|
||||||
it("should pass through deserialization", () => {
|
|
||||||
const value = {};
|
|
||||||
const result = PLAINTEXT_USERNAME_SETTINGS.deserializer(value);
|
|
||||||
expect(result).toBe(value);
|
expect(result).toBe(value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { GENERATOR_DISK, GENERATOR_MEMORY, KeyDefinition } from "../../platform/state";
|
import { GENERATOR_DISK, KeyDefinition } from "../../platform/state";
|
||||||
|
|
||||||
import { PassphraseGenerationOptions } from "./passphrase/passphrase-generation-options";
|
import { PassphraseGenerationOptions } from "./passphrase/passphrase-generation-options";
|
||||||
import { GeneratedPasswordHistory } from "./password/generated-password-history";
|
import { GeneratedPasswordHistory } from "./password/generated-password-history";
|
||||||
import { PasswordGenerationOptions } from "./password/password-generation-options";
|
import { PasswordGenerationOptions } from "./password/password-generation-options";
|
||||||
|
import { EffUsernameGenerationOptions } from "./username/eff-username-generator-options";
|
||||||
|
|
||||||
/** plaintext password generation options */
|
/** plaintext password generation options */
|
||||||
export const PASSWORD_SETTINGS = new KeyDefinition<PasswordGenerationOptions>(
|
export const PASSWORD_SETTINGS = new KeyDefinition<PasswordGenerationOptions>(
|
||||||
@ -23,18 +24,9 @@ export const PASSPHRASE_SETTINGS = new KeyDefinition<PassphraseGenerationOptions
|
|||||||
);
|
);
|
||||||
|
|
||||||
/** plaintext username generation options */
|
/** plaintext username generation options */
|
||||||
export const ENCRYPTED_USERNAME_SETTINGS = new KeyDefinition<PasswordGenerationOptions>(
|
export const EFF_USERNAME_SETTINGS = new KeyDefinition<EffUsernameGenerationOptions>(
|
||||||
GENERATOR_DISK,
|
GENERATOR_DISK,
|
||||||
"usernameGeneratorSettings",
|
"effUsernameGeneratorSettings",
|
||||||
{
|
|
||||||
deserializer: (value) => value,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
/** plaintext username generation options */
|
|
||||||
export const PLAINTEXT_USERNAME_SETTINGS = new KeyDefinition<PasswordGenerationOptions>(
|
|
||||||
GENERATOR_MEMORY,
|
|
||||||
"usernameGeneratorSettings",
|
|
||||||
{
|
{
|
||||||
deserializer: (value) => value,
|
deserializer: (value) => value,
|
||||||
},
|
},
|
||||||
|
2
libs/common/src/tools/generator/no-policy.ts
Normal file
2
libs/common/src/tools/generator/no-policy.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/** Type representing an absence of policy. */
|
||||||
|
export type NoPolicy = Record<string, never>;
|
@ -0,0 +1,11 @@
|
|||||||
|
/** Settings supported when generating an ASCII username */
|
||||||
|
export type EffUsernameGenerationOptions = {
|
||||||
|
wordCapitalize?: boolean;
|
||||||
|
wordIncludeNumber?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The default options for EFF long word generation. */
|
||||||
|
export const DefaultEffUsernameOptions: Partial<EffUsernameGenerationOptions> = Object.freeze({
|
||||||
|
wordCapitalize: false,
|
||||||
|
wordIncludeNumber: false,
|
||||||
|
});
|
@ -0,0 +1,80 @@
|
|||||||
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
|
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 { DefaultPolicyEvaluator } from "../default-policy-evaluator";
|
||||||
|
import { EFF_USERNAME_SETTINGS } from "../key-definitions";
|
||||||
|
|
||||||
|
import { EffUsernameGeneratorStrategy, UsernameGenerationServiceAbstraction } from ".";
|
||||||
|
|
||||||
|
describe("EFF long word list generation strategy", () => {
|
||||||
|
describe("evaluator()", () => {
|
||||||
|
it("should throw if the policy type is incorrect", () => {
|
||||||
|
const strategy = new EffUsernameGeneratorStrategy(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", () => {
|
||||||
|
const strategy = new EffUsernameGeneratorStrategy(null);
|
||||||
|
const policy = mock<Policy>({
|
||||||
|
type: PolicyType.PasswordGenerator,
|
||||||
|
data: {
|
||||||
|
minLength: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const evaluator = strategy.evaluator(policy);
|
||||||
|
|
||||||
|
expect(evaluator).toBeInstanceOf(DefaultPolicyEvaluator);
|
||||||
|
expect(evaluator.policy).toMatchObject({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("disk", () => {
|
||||||
|
it("should use password settings key", () => {
|
||||||
|
const legacy = mock<UsernameGenerationServiceAbstraction>();
|
||||||
|
const strategy = new EffUsernameGeneratorStrategy(legacy);
|
||||||
|
|
||||||
|
expect(strategy.disk).toBe(EFF_USERNAME_SETTINGS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("cache_ms", () => {
|
||||||
|
it("should be a positive non-zero number", () => {
|
||||||
|
const legacy = mock<UsernameGenerationServiceAbstraction>();
|
||||||
|
const strategy = new EffUsernameGeneratorStrategy(legacy);
|
||||||
|
|
||||||
|
expect(strategy.cache_ms).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("policy", () => {
|
||||||
|
it("should use password generator policy", () => {
|
||||||
|
const legacy = mock<UsernameGenerationServiceAbstraction>();
|
||||||
|
const strategy = new EffUsernameGeneratorStrategy(legacy);
|
||||||
|
|
||||||
|
expect(strategy.policy).toBe(PolicyType.PasswordGenerator);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("generate()", () => {
|
||||||
|
it("should call the legacy service with the given options", async () => {
|
||||||
|
const legacy = mock<UsernameGenerationServiceAbstraction>();
|
||||||
|
const strategy = new EffUsernameGeneratorStrategy(legacy);
|
||||||
|
const options = {
|
||||||
|
wordCapitalize: false,
|
||||||
|
wordIncludeNumber: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
await strategy.generate(options);
|
||||||
|
|
||||||
|
expect(legacy.generateWord).toHaveBeenCalledWith(options);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,53 @@
|
|||||||
|
import { PolicyType } from "../../../admin-console/enums";
|
||||||
|
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||||
|
import { GeneratorStrategy } from "../abstractions";
|
||||||
|
import { DefaultPolicyEvaluator } from "../default-policy-evaluator";
|
||||||
|
import { EFF_USERNAME_SETTINGS } from "../key-definitions";
|
||||||
|
import { NoPolicy } from "../no-policy";
|
||||||
|
|
||||||
|
import { EffUsernameGenerationOptions } from "./eff-username-generator-options";
|
||||||
|
import { UsernameGenerationServiceAbstraction } from "./username-generation.service.abstraction";
|
||||||
|
|
||||||
|
const ONE_MINUTE = 60 * 1000;
|
||||||
|
|
||||||
|
/** Strategy for creating usernames from the EFF wordlist */
|
||||||
|
export class EffUsernameGeneratorStrategy
|
||||||
|
implements GeneratorStrategy<EffUsernameGenerationOptions, NoPolicy>
|
||||||
|
{
|
||||||
|
/** Instantiates the generation strategy
|
||||||
|
* @param usernameService generates a username from EFF word list
|
||||||
|
*/
|
||||||
|
constructor(private usernameService: UsernameGenerationServiceAbstraction) {}
|
||||||
|
|
||||||
|
/** {@link GeneratorStrategy.disk} */
|
||||||
|
get disk() {
|
||||||
|
return EFF_USERNAME_SETTINGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@link GeneratorStrategy.policy} */
|
||||||
|
get policy() {
|
||||||
|
// Uses password generator since there aren't policies
|
||||||
|
// specific to usernames.
|
||||||
|
return PolicyType.PasswordGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@link GeneratorStrategy.cache_ms} */
|
||||||
|
get cache_ms() {
|
||||||
|
return ONE_MINUTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@link GeneratorStrategy.evaluator} */
|
||||||
|
evaluator(policy: Policy) {
|
||||||
|
if (policy.type !== this.policy) {
|
||||||
|
const details = `Expected: ${this.policy}. Received: ${policy.type}`;
|
||||||
|
throw Error("Mismatched policy type. " + details);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DefaultPolicyEvaluator<EffUsernameGenerationOptions>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@link GeneratorStrategy.generate} */
|
||||||
|
generate(options: EffUsernameGenerationOptions) {
|
||||||
|
return this.usernameService.generateWord(options);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
export { EffUsernameGeneratorStrategy } from "./eff-username-generator-strategy";
|
||||||
export { UsernameGeneratorOptions } from "./username-generation-options";
|
export { UsernameGeneratorOptions } from "./username-generation-options";
|
||||||
export { UsernameGenerationServiceAbstraction } from "./username-generation.service.abstraction";
|
export { UsernameGenerationServiceAbstraction } from "./username-generation.service.abstraction";
|
||||||
export { UsernameGenerationService } from "./username-generation.service";
|
export { UsernameGenerationService } from "./username-generation.service";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export type UsernameGeneratorOptions = {
|
import { EffUsernameGenerationOptions } from "./eff-username-generator-options";
|
||||||
|
|
||||||
|
export type UsernameGeneratorOptions = EffUsernameGenerationOptions & {
|
||||||
type?: "word" | "subaddress" | "catchall" | "forwarded";
|
type?: "word" | "subaddress" | "catchall" | "forwarded";
|
||||||
wordCapitalize?: boolean;
|
|
||||||
wordIncludeNumber?: boolean;
|
|
||||||
subaddressType?: "random" | "website-name";
|
subaddressType?: "random" | "website-name";
|
||||||
subaddressEmail?: string;
|
subaddressEmail?: string;
|
||||||
catchallType?: "random" | "website-name";
|
catchallType?: "random" | "website-name";
|
||||||
|
Loading…
Reference in New Issue
Block a user