From 33ed9ac6ac030caac343195634d6f2174bd1ccfb Mon Sep 17 00:00:00 2001 From: adudek-bw Date: Mon, 28 Jul 2025 16:52:19 -0400 Subject: [PATCH] [PM-19976] Implement SDK generator engines (#15063) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Hook up sdk engines to feature flag --------- Co-authored-by: ✨ Audrey ✨ --- libs/common/src/enums/feature-flag.enum.ts | 2 + libs/common/src/tools/providers.ts | 9 ++ .../src/generator-services.module.ts | 15 +++ .../src/engine/password-randomizer.spec.ts | 46 +++---- .../core/src/engine/password-randomizer.ts | 9 +- .../tools/generator/core/src/metadata/data.ts | 13 +- .../metadata/password/eff-word-list.spec.ts | 16 ++- .../src/metadata/password/eff-word-list.ts | 7 +- .../metadata/password/random-password.spec.ts | 14 ++- .../src/metadata/password/random-password.ts | 7 +- .../password/sdk-eff-word-list.spec.ts | 107 ---------------- .../metadata/password/sdk-eff-word-list.ts | 89 -------------- .../password/sdk-random-password.spec.ts | 108 ---------------- .../metadata/password/sdk-random-password.ts | 115 ------------------ .../generator-dependency-provider.ts | 3 + .../generator-metadata-provider.spec.ts | 14 ++- ...eate-legacy-password-generation-service.ts | 2 +- 17 files changed, 107 insertions(+), 469 deletions(-) delete mode 100644 libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.spec.ts delete mode 100644 libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts delete mode 100644 libs/tools/generator/core/src/metadata/password/sdk-random-password.spec.ts delete mode 100644 libs/tools/generator/core/src/metadata/password/sdk-random-password.ts diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 419872a6572..2cfbe7a9a4c 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -44,6 +44,7 @@ export enum FeatureFlag { /* Tools */ DesktopSendUIRefresh = "desktop-send-ui-refresh", + UseSdkPasswordGenerators = "pm-19976-use-sdk-password-generators", /* DIRT */ EventBasedOrganizationIntegrations = "event-based-organization-integrations", @@ -90,6 +91,7 @@ export const DefaultFeatureFlagValue = { /* Tools */ [FeatureFlag.DesktopSendUIRefresh]: FALSE, + [FeatureFlag.UseSdkPasswordGenerators]: FALSE, /* DIRT */ [FeatureFlag.EventBasedOrganizationIntegrations]: FALSE, diff --git a/libs/common/src/tools/providers.ts b/libs/common/src/tools/providers.ts index a22a22addc5..181df94be83 100644 --- a/libs/common/src/tools/providers.ts +++ b/libs/common/src/tools/providers.ts @@ -1,4 +1,7 @@ +import { BitwardenClient } from "@bitwarden/sdk-internal"; + import { PolicyService } from "../admin-console/abstractions/policy/policy.service.abstraction"; +import { ConfigService } from "../platform/abstractions/config/config.service"; import { ExtensionService } from "./extension/extension.service"; import { LogProvider } from "./log"; @@ -13,4 +16,10 @@ export type SystemServiceProvider = { /** Event monitoring and diagnostic interfaces */ readonly log: LogProvider; + + /** Config Service to determine flag features */ + readonly configService: ConfigService; + + /** SDK Service */ + readonly sdk: BitwardenClient; }; diff --git a/libs/tools/generator/components/src/generator-services.module.ts b/libs/tools/generator/components/src/generator-services.module.ts index 3a7b771a25d..1088e97e80f 100644 --- a/libs/tools/generator/components/src/generator-services.module.ts +++ b/libs/tools/generator/components/src/generator-services.module.ts @@ -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("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("S policy, extension, log, + configService, }; }, deps: [ @@ -111,6 +116,7 @@ const SYSTEM_SERVICE_PROVIDER = new SafeInjectionToken("S ExtensionRegistry, LogService, PlatformUtilsService, + ConfigService, ], }), safeProvider({ @@ -130,17 +136,26 @@ const SYSTEM_SERVICE_PROVIDER = new SafeInjectionToken("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 = { diff --git a/libs/tools/generator/core/src/engine/password-randomizer.spec.ts b/libs/tools/generator/core/src/engine/password-randomizer.spec.ts index a36c4bb5352..1d9f58fddd7 100644 --- a/libs/tools/generator/core/src/engine/password-randomizer.spec.ts +++ b/libs/tools/generator/core/src/engine/password-randomizer.spec.ts @@ -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, 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 }, {}); diff --git a/libs/tools/generator/core/src/engine/password-randomizer.ts b/libs/tools/generator/core/src/engine/password-randomizer.ts index dc61ee064e1..c5e747477e2 100644 --- a/libs/tools/generator/core/src/engine/password-randomizer.ts +++ b/libs/tools/generator/core/src/engine/password-randomizer.ts @@ -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, ); diff --git a/libs/tools/generator/core/src/metadata/data.ts b/libs/tools/generator/core/src/metadata/data.ts index 5ac6cac7222..2b9dad50557 100644 --- a/libs/tools/generator/core/src/metadata/data.ts +++ b/libs/tools/generator/core/src/metadata/data.ts @@ -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, diff --git a/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts index 0c0693af272..bdf021c50f3 100644 --- a/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts +++ b/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts @@ -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(); 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(); + nonSdkDependencyProvider.sdk = undefined; + it("returns a password randomizer", () => { + expect(effPassphrase.engine.create(nonSdkDependencyProvider)).toBeInstanceOf( + PasswordRandomizer, + ); }); }); diff --git a/libs/tools/generator/core/src/metadata/password/eff-word-list.ts b/libs/tools/generator/core/src/metadata/password/eff-word-list.ts index 021112f7c89..fc96ce46c2b 100644 --- a/libs/tools/generator/core/src/metadata/password/eff-word-list.ts +++ b/libs/tools/generator/core/src/metadata/password/eff-word-list.ts @@ -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 = { create( dependencies: GeneratorDependencyProvider, ): CredentialGenerator { - return new PasswordRandomizer(dependencies.randomizer); + if (dependencies.sdk == undefined) { + return new PasswordRandomizer(dependencies.randomizer, dependencies.now); + } + return new SdkPasswordRandomizer(dependencies.sdk, dependencies.now); }, }, profiles: { diff --git a/libs/tools/generator/core/src/metadata/password/random-password.spec.ts b/libs/tools/generator/core/src/metadata/password/random-password.spec.ts index b22f3e9356d..9efd5350c21 100644 --- a/libs/tools/generator/core/src/metadata/password/random-password.spec.ts +++ b/libs/tools/generator/core/src/metadata/password/random-password.spec.ts @@ -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(); 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(); + nonSdkDependencyProvider.sdk = undefined; + it("returns a password randomizer", () => { + expect(password.engine.create(nonSdkDependencyProvider)).toBeInstanceOf(PasswordRandomizer); }); }); diff --git a/libs/tools/generator/core/src/metadata/password/random-password.ts b/libs/tools/generator/core/src/metadata/password/random-password.ts index e446f1962a5..721be8dc3f0 100644 --- a/libs/tools/generator/core/src/metadata/password/random-password.ts +++ b/libs/tools/generator/core/src/metadata/password/random-password.ts @@ -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 = deepFreeze({ create( dependencies: GeneratorDependencyProvider, ): CredentialGenerator { - return new PasswordRandomizer(dependencies.randomizer); + if (dependencies.sdk == undefined) { + return new PasswordRandomizer(dependencies.randomizer, dependencies.now); + } + return new SdkPasswordRandomizer(dependencies.sdk, dependencies.now); }, }, profiles: { diff --git a/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.spec.ts deleted file mode 100644 index 29378b4cdd3..00000000000 --- a/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.spec.ts +++ /dev/null @@ -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(); - -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 | 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); - }); - }); - }); -}); diff --git a/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts b/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts deleted file mode 100644 index 9a653f88ede..00000000000 --- a/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts +++ /dev/null @@ -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 = { - 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 { - 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([ - "numWords", - "wordSeparator", - "capitalize", - "includeNumber", - ]), - state: GENERATOR_DISK, - initial: { - numWords: 6, - wordSeparator: "-", - capitalize: false, - includeNumber: false, - }, - options: { - deserializer(value) { - return value; - }, - clearOn: ["logout"], - }, - } satisfies ObjectKey, - 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; diff --git a/libs/tools/generator/core/src/metadata/password/sdk-random-password.spec.ts b/libs/tools/generator/core/src/metadata/password/sdk-random-password.spec.ts deleted file mode 100644 index 1e9cf6dbd87..00000000000 --- a/libs/tools/generator/core/src/metadata/password/sdk-random-password.spec.ts +++ /dev/null @@ -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(); - -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 = 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); - }); - }); - }); -}); diff --git a/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts b/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts deleted file mode 100644 index d9e06408d1d..00000000000 --- a/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts +++ /dev/null @@ -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 = 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 { - 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([ - "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; diff --git a/libs/tools/generator/core/src/providers/generator-dependency-provider.ts b/libs/tools/generator/core/src/providers/generator-dependency-provider.ts index 14942698cdb..a6dbbeaa537 100644 --- a/libs/tools/generator/core/src/providers/generator-dependency-provider.ts +++ b/libs/tools/generator/core/src/providers/generator-dependency-provider.ts @@ -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; }; diff --git a/libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts b/libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts index 71fced46fa6..376b46cd6e8 100644 --- a/libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts +++ b/libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts @@ -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(); const SomeExtensionService = mock(); +const SomeConfigService = mock; + +const SomeSdkService = mock; + 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(() => { diff --git a/libs/tools/generator/extensions/legacy/src/create-legacy-password-generation-service.ts b/libs/tools/generator/extensions/legacy/src/create-legacy-password-generation-service.ts index 0df37617b88..0048ce15499 100644 --- a/libs/tools/generator/extensions/legacy/src/create-legacy-password-generation-service.ts +++ b/libs/tools/generator/extensions/legacy/src/create-legacy-password-generation-service.ts @@ -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),