mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 21:33:27 +00:00
[PM-19976] Implement SDK generator engines (#15063)
* Hook up sdk engines to feature flag --------- Co-authored-by: ✨ Audrey ✨ <ajensen@bitwarden.com>
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { from, take } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
|
||||
import { SafeInjectionToken } from "@bitwarden/angular/services/injection-tokens";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -83,6 +86,7 @@ const SYSTEM_SERVICE_PROVIDER = new SafeInjectionToken<SystemServiceProvider>("S
|
||||
registry: ExtensionRegistry,
|
||||
logger: LogService,
|
||||
environment: PlatformUtilsService,
|
||||
configService: ConfigService,
|
||||
) => {
|
||||
let log: LogProvider;
|
||||
if (environment.isDev()) {
|
||||
@@ -102,6 +106,7 @@ const SYSTEM_SERVICE_PROVIDER = new SafeInjectionToken<SystemServiceProvider>("S
|
||||
policy,
|
||||
extension,
|
||||
log,
|
||||
configService,
|
||||
};
|
||||
},
|
||||
deps: [
|
||||
@@ -111,6 +116,7 @@ const SYSTEM_SERVICE_PROVIDER = new SafeInjectionToken<SystemServiceProvider>("S
|
||||
ExtensionRegistry,
|
||||
LogService,
|
||||
PlatformUtilsService,
|
||||
ConfigService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
@@ -130,17 +136,26 @@ const SYSTEM_SERVICE_PROVIDER = new SafeInjectionToken<SystemServiceProvider>("S
|
||||
now: Date.now,
|
||||
} satisfies UserStateSubjectDependencyProvider;
|
||||
|
||||
const featureFlagObs$ = from(
|
||||
system.configService.getFeatureFlag(FeatureFlag.UseSdkPasswordGenerators),
|
||||
);
|
||||
let featureFlag: boolean = false;
|
||||
featureFlagObs$.pipe(take(1)).subscribe((ff) => (featureFlag = ff));
|
||||
const metadata = new providers.GeneratorMetadataProvider(
|
||||
userStateDeps,
|
||||
system,
|
||||
Object.values(BuiltIn),
|
||||
);
|
||||
|
||||
const sdkService = featureFlag ? system.sdk : undefined;
|
||||
const profile = new providers.GeneratorProfileProvider(userStateDeps, system.policy);
|
||||
|
||||
const generator: providers.GeneratorDependencyProvider = {
|
||||
randomizer: random,
|
||||
client: new RestClient(api, i18n),
|
||||
i18nService: i18n,
|
||||
sdk: sdkService,
|
||||
now: Date.now,
|
||||
};
|
||||
|
||||
const userState: UserStateSubjectDependencyProvider = {
|
||||
|
||||
@@ -30,7 +30,7 @@ describe("PasswordRandomizer", () => {
|
||||
|
||||
describe("randomAscii", () => {
|
||||
it("returns the empty string when no character sets are specified", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomAscii({
|
||||
all: 1,
|
||||
@@ -41,7 +41,7 @@ describe("PasswordRandomizer", () => {
|
||||
});
|
||||
|
||||
it("generates an uppercase ascii password", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomAscii({
|
||||
all: 0,
|
||||
@@ -54,7 +54,7 @@ describe("PasswordRandomizer", () => {
|
||||
});
|
||||
|
||||
it("generates an uppercase ascii password without ambiguous characters", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomAscii({
|
||||
all: 0,
|
||||
@@ -67,7 +67,7 @@ describe("PasswordRandomizer", () => {
|
||||
});
|
||||
|
||||
it("generates a lowercase ascii password", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomAscii({
|
||||
all: 0,
|
||||
@@ -80,7 +80,7 @@ describe("PasswordRandomizer", () => {
|
||||
});
|
||||
|
||||
it("generates a lowercase ascii password without ambiguous characters", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomAscii({
|
||||
all: 0,
|
||||
@@ -93,7 +93,7 @@ describe("PasswordRandomizer", () => {
|
||||
});
|
||||
|
||||
it("generates a numeric ascii password", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomAscii({
|
||||
all: 0,
|
||||
@@ -106,7 +106,7 @@ describe("PasswordRandomizer", () => {
|
||||
});
|
||||
|
||||
it("generates a numeric password without ambiguous characters", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomAscii({
|
||||
all: 0,
|
||||
@@ -119,7 +119,7 @@ describe("PasswordRandomizer", () => {
|
||||
});
|
||||
|
||||
it("generates a special character password", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomAscii({
|
||||
all: 0,
|
||||
@@ -132,7 +132,7 @@ describe("PasswordRandomizer", () => {
|
||||
});
|
||||
|
||||
it("generates a special character password without ambiguous characters", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomAscii({
|
||||
all: 0,
|
||||
@@ -148,7 +148,7 @@ describe("PasswordRandomizer", () => {
|
||||
[2, "AA"],
|
||||
[3, "AAA"],
|
||||
])("includes %p uppercase characters", async (uppercase, expected) => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomAscii({
|
||||
all: 0,
|
||||
@@ -163,7 +163,7 @@ describe("PasswordRandomizer", () => {
|
||||
[2, "aa"],
|
||||
[3, "aaa"],
|
||||
])("includes %p lowercase characters", async (lowercase, expected) => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomAscii({
|
||||
all: 0,
|
||||
@@ -178,7 +178,7 @@ describe("PasswordRandomizer", () => {
|
||||
[2, "00"],
|
||||
[3, "000"],
|
||||
])("includes %p digits", async (digits, expected) => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomAscii({
|
||||
all: 0,
|
||||
@@ -193,7 +193,7 @@ describe("PasswordRandomizer", () => {
|
||||
[2, "!!"],
|
||||
[3, "!!!"],
|
||||
])("includes %p special characters", async (special, expected) => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomAscii({
|
||||
all: 0,
|
||||
@@ -212,7 +212,7 @@ describe("PasswordRandomizer", () => {
|
||||
])(
|
||||
"mixes character sets for the remaining characters (=%p)",
|
||||
async (setting: Partial<RandomAsciiRequest>, set: CharacterSet) => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
await password.randomAscii({
|
||||
...setting,
|
||||
@@ -225,7 +225,7 @@ describe("PasswordRandomizer", () => {
|
||||
);
|
||||
|
||||
it("shuffles the password characters", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
// Typically `shuffle` randomizes the order of the array it's been
|
||||
// given. In the password generator, the array is generated from the
|
||||
@@ -247,7 +247,7 @@ describe("PasswordRandomizer", () => {
|
||||
|
||||
describe("randomEffLongWords", () => {
|
||||
it("generates the empty string when no words are passed", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomEffLongWords({
|
||||
numberOfWords: 0,
|
||||
@@ -263,7 +263,7 @@ describe("PasswordRandomizer", () => {
|
||||
[1, "foo"],
|
||||
[2, "foofoo"],
|
||||
])("generates a %i-length word list", async (words, expected) => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomEffLongWords({
|
||||
numberOfWords: words,
|
||||
@@ -280,7 +280,7 @@ describe("PasswordRandomizer", () => {
|
||||
});
|
||||
|
||||
it("capitalizes the word list", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
randomizer.pickWord.mockResolvedValueOnce("Foo");
|
||||
|
||||
const result = await password.randomEffLongWords({
|
||||
@@ -298,7 +298,7 @@ describe("PasswordRandomizer", () => {
|
||||
});
|
||||
|
||||
it("includes a random number on a random word", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
randomizer.pickWord.mockResolvedValueOnce("foo");
|
||||
randomizer.pickWord.mockResolvedValueOnce("foo1");
|
||||
|
||||
@@ -324,7 +324,7 @@ describe("PasswordRandomizer", () => {
|
||||
});
|
||||
|
||||
it("includes a separator", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.randomEffLongWords({
|
||||
numberOfWords: 2,
|
||||
@@ -339,7 +339,7 @@ describe("PasswordRandomizer", () => {
|
||||
|
||||
describe("generate", () => {
|
||||
it("processes password generation options", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.generate(
|
||||
{ algorithm: Algorithm.password },
|
||||
@@ -352,7 +352,7 @@ describe("PasswordRandomizer", () => {
|
||||
});
|
||||
|
||||
it("processes passphrase generation options", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = await password.generate(
|
||||
{ algorithm: Algorithm.passphrase },
|
||||
@@ -365,7 +365,7 @@ describe("PasswordRandomizer", () => {
|
||||
});
|
||||
|
||||
it("throws when it cannot recognize the options type", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
const password = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const result = password.generate({ algorithm: Algorithm.username }, {});
|
||||
|
||||
|
||||
@@ -25,7 +25,10 @@ export class PasswordRandomizer
|
||||
/** Instantiates the password randomizer
|
||||
* @param randomizer data source for random data
|
||||
*/
|
||||
constructor(private randomizer: Randomizer) {}
|
||||
constructor(
|
||||
private randomizer: Randomizer,
|
||||
private currentTime: () => number,
|
||||
) {}
|
||||
|
||||
/** create a password from ASCII codepoints
|
||||
* @param request refines the generated password
|
||||
@@ -88,7 +91,7 @@ export class PasswordRandomizer
|
||||
return new GeneratedCredential(
|
||||
password,
|
||||
Type.password,
|
||||
Date.now(),
|
||||
this.currentTime(),
|
||||
request.source,
|
||||
request.website,
|
||||
);
|
||||
@@ -99,7 +102,7 @@ export class PasswordRandomizer
|
||||
return new GeneratedCredential(
|
||||
passphrase,
|
||||
Type.password,
|
||||
Date.now(),
|
||||
this.currentTime(),
|
||||
request.source,
|
||||
request.website,
|
||||
);
|
||||
|
||||
@@ -8,12 +8,6 @@ export const Algorithm = Object.freeze({
|
||||
/** A password composed of random words from the EFF word list */
|
||||
passphrase: "passphrase",
|
||||
|
||||
/** A password composed of random characters, retrieved from SDK */
|
||||
sdkPassword: "sdkPassword",
|
||||
|
||||
/** A password composed of random words from the EFF word list, retrieved from SDK */
|
||||
sdkPassphrase: "sdkPassphrase",
|
||||
|
||||
/** A username composed of words from the EFF word list */
|
||||
username: "username",
|
||||
|
||||
@@ -44,12 +38,7 @@ export const Profile = Object.freeze({
|
||||
/** Credential generation algorithms grouped by purpose. */
|
||||
export const AlgorithmsByType = deepFreeze({
|
||||
/** Algorithms that produce passwords */
|
||||
[Type.password]: [
|
||||
Algorithm.password,
|
||||
Algorithm.passphrase,
|
||||
Algorithm.sdkPassword,
|
||||
Algorithm.sdkPassphrase,
|
||||
] as const,
|
||||
[Type.password]: [Algorithm.password, Algorithm.passphrase] as const,
|
||||
|
||||
/** Algorithms that produce usernames */
|
||||
[Type.username]: [Algorithm.username] as const,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mock } from "jest-mock-extended";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
|
||||
import { PasswordRandomizer } from "../../engine";
|
||||
import { PasswordRandomizer, SdkPasswordRandomizer } from "../../engine";
|
||||
import { PassphrasePolicyConstraints } from "../../policies";
|
||||
import { GeneratorDependencyProvider } from "../../providers";
|
||||
import { PassphraseGenerationOptions } from "../../types";
|
||||
@@ -17,8 +17,18 @@ const dependencyProvider = mock<GeneratorDependencyProvider>();
|
||||
|
||||
describe("password - eff words generator metadata", () => {
|
||||
describe("engine.create", () => {
|
||||
it("returns an email randomizer", () => {
|
||||
expect(effPassphrase.engine.create(dependencyProvider)).toBeInstanceOf(PasswordRandomizer);
|
||||
it("returns an sdk password randomizer", () => {
|
||||
expect(effPassphrase.engine.create(dependencyProvider)).toBeInstanceOf(SdkPasswordRandomizer);
|
||||
});
|
||||
});
|
||||
|
||||
describe("engine.create", () => {
|
||||
const nonSdkDependencyProvider = mock<GeneratorDependencyProvider>();
|
||||
nonSdkDependencyProvider.sdk = undefined;
|
||||
it("returns a password randomizer", () => {
|
||||
expect(effPassphrase.engine.create(nonSdkDependencyProvider)).toBeInstanceOf(
|
||||
PasswordRandomizer,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { GENERATOR_DISK } from "@bitwarden/common/platform/state";
|
||||
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
|
||||
import { ObjectKey } from "@bitwarden/common/tools/state/object-key";
|
||||
|
||||
import { PasswordRandomizer } from "../../engine";
|
||||
import { PasswordRandomizer, SdkPasswordRandomizer } from "../../engine";
|
||||
import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies";
|
||||
import { GeneratorDependencyProvider } from "../../providers";
|
||||
import { CredentialGenerator, PassphraseGenerationOptions } from "../../types";
|
||||
@@ -30,7 +30,10 @@ const passphrase: GeneratorMetadata<PassphraseGenerationOptions> = {
|
||||
create(
|
||||
dependencies: GeneratorDependencyProvider,
|
||||
): CredentialGenerator<PassphraseGenerationOptions> {
|
||||
return new PasswordRandomizer(dependencies.randomizer);
|
||||
if (dependencies.sdk == undefined) {
|
||||
return new PasswordRandomizer(dependencies.randomizer, dependencies.now);
|
||||
}
|
||||
return new SdkPasswordRandomizer(dependencies.sdk, dependencies.now);
|
||||
},
|
||||
},
|
||||
profiles: {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mock } from "jest-mock-extended";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
|
||||
import { PasswordRandomizer } from "../../engine";
|
||||
import { PasswordRandomizer, SdkPasswordRandomizer } from "../../engine";
|
||||
import { DynamicPasswordPolicyConstraints } from "../../policies";
|
||||
import { GeneratorDependencyProvider } from "../../providers";
|
||||
import { PasswordGenerationOptions } from "../../types";
|
||||
@@ -17,8 +17,16 @@ const dependencyProvider = mock<GeneratorDependencyProvider>();
|
||||
|
||||
describe("password - characters generator metadata", () => {
|
||||
describe("engine.create", () => {
|
||||
it("returns an email randomizer", () => {
|
||||
expect(password.engine.create(dependencyProvider)).toBeInstanceOf(PasswordRandomizer);
|
||||
it("returns an sdk password randomizer", () => {
|
||||
expect(password.engine.create(dependencyProvider)).toBeInstanceOf(SdkPasswordRandomizer);
|
||||
});
|
||||
});
|
||||
|
||||
describe("engine.create", () => {
|
||||
const nonSdkDependencyProvider = mock<GeneratorDependencyProvider>();
|
||||
nonSdkDependencyProvider.sdk = undefined;
|
||||
it("returns a password randomizer", () => {
|
||||
expect(password.engine.create(nonSdkDependencyProvider)).toBeInstanceOf(PasswordRandomizer);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { GENERATOR_DISK } from "@bitwarden/common/platform/state";
|
||||
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
|
||||
import { deepFreeze } from "@bitwarden/common/tools/util";
|
||||
|
||||
import { PasswordRandomizer } from "../../engine";
|
||||
import { PasswordRandomizer, SdkPasswordRandomizer } from "../../engine";
|
||||
import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies";
|
||||
import { GeneratorDependencyProvider } from "../../providers";
|
||||
import { CredentialGenerator, PasswordGeneratorSettings } from "../../types";
|
||||
@@ -30,7 +30,10 @@ const password: GeneratorMetadata<PasswordGeneratorSettings> = deepFreeze({
|
||||
create(
|
||||
dependencies: GeneratorDependencyProvider,
|
||||
): CredentialGenerator<PasswordGeneratorSettings> {
|
||||
return new PasswordRandomizer(dependencies.randomizer);
|
||||
if (dependencies.sdk == undefined) {
|
||||
return new PasswordRandomizer(dependencies.randomizer, dependencies.now);
|
||||
}
|
||||
return new SdkPasswordRandomizer(dependencies.sdk, dependencies.now);
|
||||
},
|
||||
},
|
||||
profiles: {
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
|
||||
import { SdkPasswordRandomizer } from "../../engine";
|
||||
import { PassphrasePolicyConstraints } from "../../policies";
|
||||
import { GeneratorDependencyProvider } from "../../providers";
|
||||
import { PassphraseGenerationOptions } from "../../types";
|
||||
import { Profile } from "../data";
|
||||
import { CoreProfileMetadata } from "../profile-metadata";
|
||||
import { isCoreProfile } from "../util";
|
||||
|
||||
import sdkEffPassphrase from "./sdk-eff-word-list";
|
||||
|
||||
const dependencyProvider = mock<GeneratorDependencyProvider>();
|
||||
|
||||
describe("password - eff words generator metadata", () => {
|
||||
describe("engine.create", () => {
|
||||
it("returns an email randomizer", () => {
|
||||
expect(sdkEffPassphrase.engine.create(dependencyProvider)).toBeInstanceOf(
|
||||
SdkPasswordRandomizer,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("profiles[account]", () => {
|
||||
let accountProfile: CoreProfileMetadata<PassphraseGenerationOptions> | null = null;
|
||||
beforeEach(() => {
|
||||
const profile = sdkEffPassphrase.profiles[Profile.account];
|
||||
if (isCoreProfile(profile!)) {
|
||||
accountProfile = profile;
|
||||
} else {
|
||||
accountProfile = null;
|
||||
}
|
||||
});
|
||||
|
||||
describe("storage.options.deserializer", () => {
|
||||
it("returns its input", () => {
|
||||
const value: PassphraseGenerationOptions = { ...accountProfile!.storage.initial };
|
||||
|
||||
const result = accountProfile!.storage.options.deserializer(value);
|
||||
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe("constraints.create", () => {
|
||||
// these tests check that the wiring is correct by exercising the behavior
|
||||
// of functionality encapsulated by `create`. These methods may fail if the
|
||||
// enclosed behaviors change.
|
||||
|
||||
it("creates a passphrase policy constraints", () => {
|
||||
const context = { defaultConstraints: accountProfile!.constraints.default };
|
||||
|
||||
const constraints = accountProfile!.constraints.create([], context);
|
||||
|
||||
expect(constraints).toBeInstanceOf(PassphrasePolicyConstraints);
|
||||
});
|
||||
|
||||
it("forwards the policy to the constraints", () => {
|
||||
const context = { defaultConstraints: accountProfile!.constraints.default };
|
||||
const policies = [
|
||||
{
|
||||
type: PolicyType.PasswordGenerator,
|
||||
data: {
|
||||
minNumberWords: 6,
|
||||
capitalize: false,
|
||||
includeNumber: false,
|
||||
},
|
||||
},
|
||||
] as Policy[];
|
||||
|
||||
const constraints = accountProfile!.constraints.create(policies, context);
|
||||
|
||||
expect(constraints.constraints.numWords?.min).toEqual(6);
|
||||
});
|
||||
|
||||
it("combines multiple policies in the constraints", () => {
|
||||
const context = { defaultConstraints: accountProfile!.constraints.default };
|
||||
const policies = [
|
||||
{
|
||||
type: PolicyType.PasswordGenerator,
|
||||
data: {
|
||||
minNumberWords: 6,
|
||||
capitalize: false,
|
||||
includeNumber: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: PolicyType.PasswordGenerator,
|
||||
data: {
|
||||
minNumberWords: 3,
|
||||
capitalize: true,
|
||||
includeNumber: false,
|
||||
},
|
||||
},
|
||||
] as Policy[];
|
||||
|
||||
const constraints = accountProfile!.constraints.create(policies, context);
|
||||
|
||||
expect(constraints.constraints.numWords?.min).toEqual(6);
|
||||
expect(constraints.constraints.capitalize?.requiredValue).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,89 +0,0 @@
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { GENERATOR_DISK } from "@bitwarden/common/platform/state";
|
||||
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
|
||||
import { ObjectKey } from "@bitwarden/common/tools/state/object-key";
|
||||
import { BitwardenClient } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { SdkPasswordRandomizer } from "../../engine";
|
||||
import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies";
|
||||
import { GeneratorDependencyProvider } from "../../providers";
|
||||
import { CredentialGenerator, PassphraseGenerationOptions } from "../../types";
|
||||
import { Algorithm, Profile, Type } from "../data";
|
||||
import { GeneratorMetadata } from "../generator-metadata";
|
||||
|
||||
const sdkPassphrase: GeneratorMetadata<PassphraseGenerationOptions> = {
|
||||
id: Algorithm.sdkPassphrase,
|
||||
type: Type.password,
|
||||
weight: 130,
|
||||
i18nKeys: {
|
||||
name: "passphrase",
|
||||
credentialType: "passphrase",
|
||||
generateCredential: "generatePassphrase",
|
||||
credentialGenerated: "passphraseGenerated",
|
||||
copyCredential: "copyPassphrase",
|
||||
useCredential: "useThisPassphrase",
|
||||
},
|
||||
capabilities: {
|
||||
autogenerate: false,
|
||||
fields: [],
|
||||
},
|
||||
engine: {
|
||||
create(
|
||||
dependencies: GeneratorDependencyProvider,
|
||||
): CredentialGenerator<PassphraseGenerationOptions> {
|
||||
return new SdkPasswordRandomizer(new BitwardenClient(null), Date.now); // @TODO hook up a real SDK client
|
||||
},
|
||||
},
|
||||
profiles: {
|
||||
[Profile.account]: {
|
||||
type: "core",
|
||||
storage: {
|
||||
key: "passphraseGeneratorSettings",
|
||||
target: "object",
|
||||
format: "plain",
|
||||
classifier: new PublicClassifier<PassphraseGenerationOptions>([
|
||||
"numWords",
|
||||
"wordSeparator",
|
||||
"capitalize",
|
||||
"includeNumber",
|
||||
]),
|
||||
state: GENERATOR_DISK,
|
||||
initial: {
|
||||
numWords: 6,
|
||||
wordSeparator: "-",
|
||||
capitalize: false,
|
||||
includeNumber: false,
|
||||
},
|
||||
options: {
|
||||
deserializer(value) {
|
||||
return value;
|
||||
},
|
||||
clearOn: ["logout"],
|
||||
},
|
||||
} satisfies ObjectKey<PassphraseGenerationOptions>,
|
||||
constraints: {
|
||||
type: PolicyType.PasswordGenerator,
|
||||
default: {
|
||||
wordSeparator: { maxLength: 1 },
|
||||
numWords: {
|
||||
min: 3,
|
||||
max: 20,
|
||||
recommendation: 6,
|
||||
},
|
||||
},
|
||||
create(policies, context) {
|
||||
const initial = {
|
||||
minNumberWords: 0,
|
||||
capitalize: false,
|
||||
includeNumber: false,
|
||||
};
|
||||
const policy = policies.reduce(passphraseLeastPrivilege, initial);
|
||||
const constraints = new PassphrasePolicyConstraints(policy, context.defaultConstraints);
|
||||
return constraints;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default sdkPassphrase;
|
||||
@@ -1,108 +0,0 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
|
||||
import { SdkPasswordRandomizer } from "../../engine";
|
||||
import { DynamicPasswordPolicyConstraints } from "../../policies";
|
||||
import { GeneratorDependencyProvider } from "../../providers";
|
||||
import { PasswordGenerationOptions } from "../../types";
|
||||
import { Profile } from "../data";
|
||||
import { CoreProfileMetadata } from "../profile-metadata";
|
||||
import { isCoreProfile } from "../util";
|
||||
|
||||
import sdkPassword from "./sdk-random-password";
|
||||
|
||||
const dependencyProvider = mock<GeneratorDependencyProvider>();
|
||||
|
||||
describe("password - characters generator metadata", () => {
|
||||
describe("engine.create", () => {
|
||||
it("returns an email randomizer", () => {
|
||||
expect(sdkPassword.engine.create(dependencyProvider)).toBeInstanceOf(SdkPasswordRandomizer);
|
||||
});
|
||||
});
|
||||
|
||||
describe("profiles[account]", () => {
|
||||
let accountProfile: CoreProfileMetadata<PasswordGenerationOptions> = null!;
|
||||
beforeEach(() => {
|
||||
const profile = sdkPassword.profiles[Profile.account];
|
||||
if (isCoreProfile(profile!)) {
|
||||
accountProfile = profile;
|
||||
} else {
|
||||
throw new Error("this branch should never run");
|
||||
}
|
||||
});
|
||||
|
||||
describe("storage.options.deserializer", () => {
|
||||
it("returns its input", () => {
|
||||
const value: PasswordGenerationOptions = { ...accountProfile.storage.initial };
|
||||
|
||||
const result = accountProfile.storage.options.deserializer(value);
|
||||
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe("constraints.create", () => {
|
||||
// these tests check that the wiring is correct by exercising the behavior
|
||||
// of functionality encapsulated by `create`. These methods may fail if the
|
||||
// enclosed behaviors change.
|
||||
|
||||
it("creates a passphrase policy constraints", () => {
|
||||
const context = { defaultConstraints: accountProfile.constraints.default };
|
||||
|
||||
const constraints = accountProfile.constraints.create([], context);
|
||||
|
||||
expect(constraints).toBeInstanceOf(DynamicPasswordPolicyConstraints);
|
||||
});
|
||||
|
||||
it("forwards the policy to the constraints", () => {
|
||||
const context = { defaultConstraints: accountProfile.constraints.default };
|
||||
const policies = [
|
||||
{
|
||||
type: PolicyType.PasswordGenerator,
|
||||
enabled: true,
|
||||
data: {
|
||||
minLength: 10,
|
||||
capitalize: false,
|
||||
useNumbers: false,
|
||||
},
|
||||
},
|
||||
] as Policy[];
|
||||
|
||||
const constraints = accountProfile.constraints.create(policies, context);
|
||||
|
||||
expect(constraints.constraints.length?.min).toEqual(10);
|
||||
});
|
||||
|
||||
it("combines multiple policies in the constraints", () => {
|
||||
const context = { defaultConstraints: accountProfile.constraints.default };
|
||||
const policies = [
|
||||
{
|
||||
type: PolicyType.PasswordGenerator,
|
||||
enabled: true,
|
||||
data: {
|
||||
minLength: 14,
|
||||
useSpecial: false,
|
||||
useNumbers: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: PolicyType.PasswordGenerator,
|
||||
enabled: true,
|
||||
data: {
|
||||
minLength: 10,
|
||||
useSpecial: true,
|
||||
includeNumber: false,
|
||||
},
|
||||
},
|
||||
] as Policy[];
|
||||
|
||||
const constraints = accountProfile.constraints.create(policies, context);
|
||||
|
||||
expect(constraints.constraints.length?.min).toEqual(14);
|
||||
expect(constraints.constraints.special?.requiredValue).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,115 +0,0 @@
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { GENERATOR_DISK } from "@bitwarden/common/platform/state";
|
||||
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
|
||||
import { deepFreeze } from "@bitwarden/common/tools/util";
|
||||
import { BitwardenClient } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { SdkPasswordRandomizer } from "../../engine";
|
||||
import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies";
|
||||
import { GeneratorDependencyProvider } from "../../providers";
|
||||
import { CredentialGenerator, PasswordGeneratorSettings } from "../../types";
|
||||
import { Algorithm, Profile, Type } from "../data";
|
||||
import { GeneratorMetadata } from "../generator-metadata";
|
||||
|
||||
const sdkPassword: GeneratorMetadata<PasswordGeneratorSettings> = deepFreeze({
|
||||
id: Algorithm.sdkPassword,
|
||||
type: Type.password,
|
||||
weight: 120,
|
||||
i18nKeys: {
|
||||
name: "password",
|
||||
generateCredential: "generatePassword",
|
||||
credentialGenerated: "passwordGenerated",
|
||||
credentialType: "password",
|
||||
copyCredential: "copyPassword",
|
||||
useCredential: "useThisPassword",
|
||||
},
|
||||
capabilities: {
|
||||
autogenerate: true,
|
||||
fields: [],
|
||||
},
|
||||
engine: {
|
||||
create(
|
||||
dependencies: GeneratorDependencyProvider,
|
||||
): CredentialGenerator<PasswordGeneratorSettings> {
|
||||
return new SdkPasswordRandomizer(new BitwardenClient(null), Date.now); // @TODO hook up a real SDK client
|
||||
},
|
||||
},
|
||||
profiles: {
|
||||
[Profile.account]: {
|
||||
type: "core",
|
||||
storage: {
|
||||
key: "passwordGeneratorSettings",
|
||||
target: "object",
|
||||
format: "plain",
|
||||
classifier: new PublicClassifier<PasswordGeneratorSettings>([
|
||||
"length",
|
||||
"ambiguous",
|
||||
"uppercase",
|
||||
"minUppercase",
|
||||
"lowercase",
|
||||
"minLowercase",
|
||||
"number",
|
||||
"minNumber",
|
||||
"special",
|
||||
"minSpecial",
|
||||
]),
|
||||
state: GENERATOR_DISK,
|
||||
initial: {
|
||||
length: 14,
|
||||
ambiguous: true,
|
||||
uppercase: true,
|
||||
minUppercase: 1,
|
||||
lowercase: true,
|
||||
minLowercase: 1,
|
||||
number: true,
|
||||
minNumber: 1,
|
||||
special: false,
|
||||
minSpecial: 0,
|
||||
},
|
||||
options: {
|
||||
deserializer(value) {
|
||||
return value;
|
||||
},
|
||||
clearOn: ["logout"],
|
||||
},
|
||||
},
|
||||
constraints: {
|
||||
type: PolicyType.PasswordGenerator,
|
||||
default: {
|
||||
length: {
|
||||
min: 5,
|
||||
max: 128,
|
||||
recommendation: 14,
|
||||
},
|
||||
minNumber: {
|
||||
min: 0,
|
||||
max: 9,
|
||||
},
|
||||
minSpecial: {
|
||||
min: 0,
|
||||
max: 9,
|
||||
},
|
||||
},
|
||||
create(policies, context) {
|
||||
const initial = {
|
||||
minLength: 0,
|
||||
useUppercase: false,
|
||||
useLowercase: false,
|
||||
useNumbers: false,
|
||||
numberCount: 0,
|
||||
useSpecial: false,
|
||||
specialCount: 0,
|
||||
};
|
||||
const policy = policies.reduce(passwordLeastPrivilege, initial);
|
||||
const constraints = new DynamicPasswordPolicyConstraints(
|
||||
policy,
|
||||
context.defaultConstraints,
|
||||
);
|
||||
return constraints;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default sdkPassword;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { RestClient } from "@bitwarden/common/tools/integration/rpc";
|
||||
import { BitwardenClient } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { Randomizer } from "../abstractions";
|
||||
|
||||
@@ -9,4 +10,6 @@ export type GeneratorDependencyProvider = {
|
||||
// FIXME: introduce `I18nKeyOrLiteral` into forwarder
|
||||
// structures and remove this dependency
|
||||
i18nService: I18nService;
|
||||
sdk?: BitwardenClient;
|
||||
now: () => number;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { LegacyEncryptorProvider } from "@bitwarden/common/tools/cryptography/legacy-encryptor-provider";
|
||||
import { UserEncryptor } from "@bitwarden/common/tools/cryptography/user-encryptor.abstraction";
|
||||
import {
|
||||
@@ -22,6 +23,7 @@ import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subje
|
||||
import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider";
|
||||
import { deepFreeze } from "@bitwarden/common/tools/util";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { BitwardenClient } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { FakeAccountService, FakeStateProvider } from "../../../../../common/spec";
|
||||
import { Algorithm, AlgorithmsByType, CredentialAlgorithm, Type, Types } from "../metadata";
|
||||
@@ -89,6 +91,10 @@ const SomePolicyService = mock<PolicyService>();
|
||||
|
||||
const SomeExtensionService = mock<ExtensionService>();
|
||||
|
||||
const SomeConfigService = mock<ConfigService>;
|
||||
|
||||
const SomeSdkService = mock<BitwardenClient>;
|
||||
|
||||
const ApplicationProvider = {
|
||||
/** Policy configured by the administrative console */
|
||||
policy: SomePolicyService,
|
||||
@@ -98,7 +104,13 @@ const ApplicationProvider = {
|
||||
|
||||
/** Event monitoring and diagnostic interfaces */
|
||||
log: disabledSemanticLoggerProvider,
|
||||
} as SystemServiceProvider;
|
||||
|
||||
/** Feature flag retrieval */
|
||||
configService: SomeConfigService,
|
||||
|
||||
/** SDK access for password generation */
|
||||
sdk: SomeSdkService,
|
||||
} as unknown as SystemServiceProvider;
|
||||
|
||||
describe("GeneratorMetadataProvider", () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -25,7 +25,7 @@ export function legacyPasswordGenerationServiceFactory(
|
||||
stateProvider: StateProvider,
|
||||
): PasswordGenerationServiceAbstraction {
|
||||
const randomizer = new KeyServiceRandomizer(keyService);
|
||||
const passwordRandomizer = new PasswordRandomizer(randomizer);
|
||||
const passwordRandomizer = new PasswordRandomizer(randomizer, Date.now);
|
||||
|
||||
const passwords = new DefaultGeneratorService(
|
||||
new PasswordGeneratorStrategy(passwordRandomizer, stateProvider),
|
||||
|
||||
Reference in New Issue
Block a user