;
+// FIXME: once the modernization is complete, switch the type parameters
+// in `PolicyEvaluator` and bake-in the constraints type.
+type Evaluator = PolicyEvaluator & Constraints;
+
+export class CredentialGeneratorService {
+ constructor(
+ private stateProvider: StateProvider,
+ private policyService: PolicyService,
+ ) {}
+
+ /** Get the settings for the provided configuration
+ * @param configuration determines which generator's settings are loaded
+ * @param dependencies.userId$ identifies the user to which the settings are bound.
+ * If this parameter is not provided, the observable follows the active user and
+ * may not complete.
+ * @returns an observable that emits settings
+ * @remarks the observable enforces policies on the settings
+ */
+ settings$(
+ configuration: Configuration,
+ dependencies?: Settings$Dependencies,
+ ) {
+ const userId$ = dependencies?.userId$ ?? this.stateProvider.activeUserId$;
+ const completion$ = userId$.pipe(ignoreElements(), endWith(true));
+
+ const state$ = userId$.pipe(
+ filter((userId) => !!userId),
+ distinctUntilChanged(),
+ switchMap((userId) => {
+ const state$ = this.stateProvider
+ .getUserState$(configuration.settings.account, userId)
+ .pipe(takeUntil(completion$));
+
+ return state$;
+ }),
+ map((settings) => settings ?? structuredClone(configuration.settings.initial)),
+ );
+
+ const settings$ = combineLatest([state$, this.policy$(configuration, { userId$ })]).pipe(
+ map(([settings, policy]) => {
+ // FIXME: create `onLoadApply` that wraps these operations
+ const applied = policy.applyPolicy(settings);
+ const sanitized = policy.sanitize(applied);
+ return sanitized;
+ }),
+ );
+
+ return settings$;
+ }
+
+ /** Get a subject bound to a specific user's settings
+ * @param configuration determines which generator's settings are loaded
+ * @param dependencies.singleUserId$ identifies the user to which the settings are bound
+ * @returns a promise that resolves with the subject once
+ * `dependencies.singleUserId$` becomes available.
+ * @remarks the subject enforces policy for the settings
+ */
+ async settings(
+ configuration: Configuration,
+ dependencies: SingleUserDependency,
+ ) {
+ const userId = await firstValueFrom(
+ dependencies.singleUserId$.pipe(filter((userId) => !!userId)),
+ );
+ const state = this.stateProvider.getUser(userId, configuration.settings.account);
+
+ // FIXME: apply policy to the settings - this should happen *within* the subject.
+ // Note that policies could be evaluated when the settings are saved or when they
+ // are loaded. The existing subject presently could only apply settings on save
+ // (by wiring the policy in as a dependency and applying with "nextState"), and
+ // even that has a limitation since arbitrary dependencies do not trigger state
+ // emissions.
+ const subject = new UserStateSubject(state, dependencies);
+
+ return subject;
+ }
+
+ /** Get the policy for the provided configuration
+ * @param dependencies.userId$ determines which user's policy is loaded
+ * @returns an observable that emits the policy once `dependencies.userId$`
+ * and the policy become available.
+ */
+ policy$(
+ configuration: Configuration,
+ dependencies: Policy$Dependencies,
+ ): Observable> {
+ const completion$ = dependencies.userId$.pipe(ignoreElements(), endWith(true));
+
+ const policy$ = dependencies.userId$.pipe(
+ mergeMap((userId) => {
+ // complete policy emissions otherwise `mergeMap` holds `policy$` open indefinitely
+ const policies$ = this.policyService
+ .getAll$(configuration.policy.type, userId)
+ .pipe(takeUntil(completion$));
+ return policies$;
+ }),
+ mapPolicyToEvaluatorV2(configuration.policy),
+ );
+
+ return policy$;
+ }
+}
diff --git a/libs/tools/generator/core/src/services/index.ts b/libs/tools/generator/core/src/services/index.ts
index 7568f55b68..d7184f684a 100644
--- a/libs/tools/generator/core/src/services/index.ts
+++ b/libs/tools/generator/core/src/services/index.ts
@@ -1 +1,2 @@
export { DefaultGeneratorService } from "./default-generator.service";
+export { CredentialGeneratorService } from "./credential-generator.service";
diff --git a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts
index f3a6046fac..f9b346e02b 100644
--- a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts
+++ b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts
@@ -8,7 +8,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { StateProvider } from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
-import { DefaultPassphraseGenerationOptions, DisabledPassphraseGeneratorPolicy } from "../data";
+import { DefaultPassphraseGenerationOptions, Policies } from "../data";
import { PasswordRandomizer } from "../engine";
import { PassphraseGeneratorOptionsEvaluator } from "../policies";
@@ -50,7 +50,7 @@ describe("Password generation strategy", () => {
const evaluator = await firstValueFrom(evaluator$);
expect(evaluator).toBeInstanceOf(PassphraseGeneratorOptionsEvaluator);
- expect(evaluator.policy).toMatchObject(DisabledPassphraseGeneratorPolicy);
+ expect(evaluator.policy).toMatchObject(Policies.Passphrase.disabledValue);
},
);
});
diff --git a/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts b/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts
index 536d69c9c1..928e0b0dc8 100644
--- a/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts
+++ b/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts
@@ -8,7 +8,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { StateProvider } from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
-import { DefaultPasswordGenerationOptions, DisabledPasswordGeneratorPolicy } from "../data";
+import { DefaultPasswordGenerationOptions, Policies } from "../data";
import { PasswordRandomizer } from "../engine";
import { PasswordGeneratorOptionsEvaluator } from "../policies";
@@ -58,7 +58,7 @@ describe("Password generation strategy", () => {
const evaluator = await firstValueFrom(evaluator$);
expect(evaluator).toBeInstanceOf(PasswordGeneratorOptionsEvaluator);
- expect(evaluator.policy).toMatchObject(DisabledPasswordGeneratorPolicy);
+ expect(evaluator.policy).toMatchObject(Policies.Password.disabledValue);
},
);
});
diff --git a/libs/tools/generator/core/src/types/credential-generator-configuration.ts b/libs/tools/generator/core/src/types/credential-generator-configuration.ts
new file mode 100644
index 0000000000..2a8b07b0e8
--- /dev/null
+++ b/libs/tools/generator/core/src/types/credential-generator-configuration.ts
@@ -0,0 +1,19 @@
+import { UserKeyDefinition } from "@bitwarden/common/platform/state";
+import { Constraints } from "@bitwarden/common/tools/types";
+
+import { PolicyConfiguration } from "../types";
+
+export type CredentialGeneratorConfiguration = {
+ settings: {
+ /** value used when an account's settings haven't been initialized */
+ initial: Readonly>;
+
+ constraints: Constraints;
+
+ /** storage location for account-global settings */
+ account: UserKeyDefinition;
+ };
+
+ /** defines how to construct policy for this settings instance */
+ policy: PolicyConfiguration;
+};
diff --git a/libs/tools/generator/core/src/types/index.ts b/libs/tools/generator/core/src/types/index.ts
index 7a6d49d87c..786b15b9d1 100644
--- a/libs/tools/generator/core/src/types/index.ts
+++ b/libs/tools/generator/core/src/types/index.ts
@@ -1,5 +1,6 @@
export * from "./boundary";
export * from "./catchall-generator-options";
+export * from "./credential-generator-configuration";
export * from "./eff-username-generator-options";
export * from "./forwarder-options";
export * from "./generator-options";
diff --git a/libs/tools/generator/core/src/types/policy-configuration.ts b/libs/tools/generator/core/src/types/policy-configuration.ts
index afecbe7d2b..6ec077bcb6 100644
--- a/libs/tools/generator/core/src/types/policy-configuration.ts
+++ b/libs/tools/generator/core/src/types/policy-configuration.ts
@@ -1,7 +1,13 @@
+import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Policy as AdminPolicy } from "@bitwarden/common/admin-console/models/domain/policy";
+import { Constraints } from "@bitwarden/common/tools/types";
+
+import { PolicyEvaluator } from "../abstractions";
/** Determines how to construct a password generator policy */
-export type PolicyConfiguration = {
+export type PolicyConfiguration = {
+ type: PolicyType;
+
/** The value of the policy when it is not in effect. */
disabledValue: Policy;
@@ -12,5 +18,12 @@ export type PolicyConfiguration = {
/** Converts policy service data into an actionable policy.
*/
- createEvaluator: (policy: Policy) => Evaluator;
+ createEvaluator: (policy: Policy) => PolicyEvaluator;
+
+ /** Converts policy service data into an actionable policy.
+ * @remarks this version includes constraints needed for the reactive forms;
+ * it was introduced so that the constraints can be incrementally introduced
+ * as the new UI is built.
+ */
+ createEvaluatorV2?: (policy: Policy) => PolicyEvaluator & Constraints;
};
diff --git a/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts b/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts
index 4c1dee7a5d..21fbcb2627 100644
--- a/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts
+++ b/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts
@@ -7,8 +7,7 @@ import {
GeneratorService,
DefaultPassphraseGenerationOptions,
DefaultPasswordGenerationOptions,
- DisabledPassphraseGeneratorPolicy,
- DisabledPasswordGeneratorPolicy,
+ Policies,
PassphraseGenerationOptions,
PassphraseGeneratorPolicy,
PasswordGenerationOptions,
@@ -39,7 +38,7 @@ const PasswordGeneratorOptionsEvaluator = policies.PasswordGeneratorOptionsEvalu
function createPassphraseGenerator(
options: PassphraseGenerationOptions = {},
- policy: PassphraseGeneratorPolicy = DisabledPassphraseGeneratorPolicy,
+ policy: PassphraseGeneratorPolicy = Policies.Passphrase.disabledValue,
) {
let savedOptions = options;
const generator = mock>({
@@ -64,7 +63,7 @@ function createPassphraseGenerator(
function createPasswordGenerator(
options: PasswordGenerationOptions = {},
- policy: PasswordGeneratorPolicy = DisabledPasswordGeneratorPolicy,
+ policy: PasswordGeneratorPolicy = Policies.Password.disabledValue,
) {
let savedOptions = options;
const generator = mock>({