1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-18566] Wire up vNextPolicyService for Clients (#13678)

* wire up vNext impl

* wire up vNextPolicyService for browser

* wire up vNextPolicyService for desktop

* wire up vNextPolicyService for cli

* fix test

* fix missed caller

* cleanup

* fix missing property assignment

* fix QA bug for PM-19205

* fix QA bug for PM-19206

* fix QA bug for pm-19228

* cleanup
This commit is contained in:
Brandon Treston
2025-03-25 11:30:47 -04:00
committed by GitHub
parent a9fd16968f
commit 0fd01ed7ee
84 changed files with 723 additions and 1246 deletions

View File

@@ -207,7 +207,7 @@ const providers = {
describe("CredentialGeneratorService", () => {
beforeEach(async () => {
await accountService.switchAccount(SomeUser);
policyService.getAll$.mockImplementation(() => new BehaviorSubject([]).asObservable());
policyService.policiesByType$.mockImplementation(() => new BehaviorSubject([]).asObservable());
i18nService.t.mockImplementation((key: string) => key);
apiService.fetch.mockImplementation(() => Promise.resolve(mock<Response>()));
jest.clearAllMocks();
@@ -567,7 +567,7 @@ describe("CredentialGeneratorService", () => {
// awareness; they exercise the logic without being comprehensive
it("enforces the active user's policy", async () => {
const policy$ = new BehaviorSubject([passwordOverridePolicy]);
policyService.getAll$.mockReturnValue(policy$);
policyService.policiesByType$.mockReturnValue(policy$);
const generator = new CredentialGeneratorService(
randomizer,
policyService,
@@ -578,15 +578,22 @@ describe("CredentialGeneratorService", () => {
const result = await firstValueFrom(generator.algorithms$(["password"], { account$ }));
expect(policyService.getAll$).toHaveBeenCalledWith(PolicyType.PasswordGenerator, SomeUser);
expect(policyService.policiesByType$).toHaveBeenCalledWith(
PolicyType.PasswordGenerator,
SomeUser,
);
expect(result.some((a) => a.id === Generators.password.id)).toBeTruthy();
expect(result.some((a) => a.id === Generators.passphrase.id)).toBeFalsy();
});
it("follows changes to the active user", async () => {
const account$ = new BehaviorSubject(accounts[SomeUser]);
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passwordOverridePolicy]));
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passphraseOverridePolicy]));
policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passwordOverridePolicy]),
);
policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passphraseOverridePolicy]),
);
const generator = new CredentialGeneratorService(
randomizer,
policyService,
@@ -603,7 +610,7 @@ describe("CredentialGeneratorService", () => {
const [someResult, anotherResult] = results;
expect(policyService.getAll$).toHaveBeenNthCalledWith(
expect(policyService.policiesByType$).toHaveBeenNthCalledWith(
1,
PolicyType.PasswordGenerator,
SomeUser,
@@ -611,7 +618,7 @@ describe("CredentialGeneratorService", () => {
expect(someResult.some((a: any) => a.id === Generators.password.id)).toBeTruthy();
expect(someResult.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy();
expect(policyService.getAll$).toHaveBeenNthCalledWith(
expect(policyService.policiesByType$).toHaveBeenNthCalledWith(
2,
PolicyType.PasswordGenerator,
AnotherUser,
@@ -621,7 +628,9 @@ describe("CredentialGeneratorService", () => {
});
it("reads an arbitrary user's settings", async () => {
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passwordOverridePolicy]));
policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passwordOverridePolicy]),
);
const generator = new CredentialGeneratorService(
randomizer,
policyService,
@@ -633,14 +642,21 @@ describe("CredentialGeneratorService", () => {
const result = await firstValueFrom(generator.algorithms$("password", { account$ }));
expect(policyService.getAll$).toHaveBeenCalledWith(PolicyType.PasswordGenerator, AnotherUser);
expect(policyService.policiesByType$).toHaveBeenCalledWith(
PolicyType.PasswordGenerator,
AnotherUser,
);
expect(result.some((a: any) => a.id === Generators.password.id)).toBeTruthy();
expect(result.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy();
});
it("follows changes to the arbitrary user", async () => {
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passwordOverridePolicy]));
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passphraseOverridePolicy]));
policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passwordOverridePolicy]),
);
policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passphraseOverridePolicy]),
);
const generator = new CredentialGeneratorService(
randomizer,
policyService,
@@ -658,17 +674,25 @@ describe("CredentialGeneratorService", () => {
sub.unsubscribe();
const [someResult, anotherResult] = results;
expect(policyService.getAll$).toHaveBeenCalledWith(PolicyType.PasswordGenerator, SomeUser);
expect(policyService.policiesByType$).toHaveBeenCalledWith(
PolicyType.PasswordGenerator,
SomeUser,
);
expect(someResult.some((a: any) => a.id === Generators.password.id)).toBeTruthy();
expect(someResult.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy();
expect(policyService.getAll$).toHaveBeenCalledWith(PolicyType.PasswordGenerator, AnotherUser);
expect(policyService.policiesByType$).toHaveBeenCalledWith(
PolicyType.PasswordGenerator,
AnotherUser,
);
expect(anotherResult.some((a: any) => a.id === Generators.passphrase.id)).toBeTruthy();
expect(anotherResult.some((a: any) => a.id === Generators.password.id)).toBeFalsy();
});
it("errors when the arbitrary user's stream errors", async () => {
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passwordOverridePolicy]));
policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passwordOverridePolicy]),
);
const generator = new CredentialGeneratorService(
randomizer,
policyService,
@@ -692,7 +716,9 @@ describe("CredentialGeneratorService", () => {
});
it("completes when the arbitrary user's stream completes", async () => {
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passwordOverridePolicy]));
policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passwordOverridePolicy]),
);
const generator = new CredentialGeneratorService(
randomizer,
policyService,
@@ -716,7 +742,9 @@ describe("CredentialGeneratorService", () => {
});
it("ignores repeated arbitrary user emissions", async () => {
policyService.getAll$.mockReturnValueOnce(new BehaviorSubject([passwordOverridePolicy]));
policyService.policiesByType$.mockReturnValueOnce(
new BehaviorSubject([passwordOverridePolicy]),
);
const generator = new CredentialGeneratorService(
randomizer,
policyService,
@@ -780,7 +808,7 @@ describe("CredentialGeneratorService", () => {
const settings = { foo: "value" };
await stateProvider.setUserState(SettingsKey, settings, SomeUser);
const policy$ = new BehaviorSubject([somePolicy]);
policyService.getAll$.mockReturnValue(policy$);
policyService.policiesByType$.mockReturnValue(policy$);
const generator = new CredentialGeneratorService(
randomizer,
policyService,
@@ -908,7 +936,7 @@ describe("CredentialGeneratorService", () => {
);
const account$ = new BehaviorSubject(accounts[SomeUser]).asObservable();
const policy$ = new BehaviorSubject([somePolicy]);
policyService.getAll$.mockReturnValue(policy$);
policyService.policiesByType$.mockReturnValue(policy$);
const result = await firstValueFrom(generator.policy$(SomeConfiguration, { account$ }));
@@ -926,7 +954,7 @@ describe("CredentialGeneratorService", () => {
const account = new BehaviorSubject(accounts[SomeUser]);
const account$ = account.asObservable();
const somePolicySubject = new BehaviorSubject([somePolicy]);
policyService.getAll$.mockReturnValueOnce(somePolicySubject.asObservable());
policyService.policiesByType$.mockReturnValueOnce(somePolicySubject.asObservable());
const emissions: GeneratorConstraints<SomeSettings>[] = [];
const sub = generator
.policy$(SomeConfiguration, { account$ })
@@ -954,7 +982,9 @@ describe("CredentialGeneratorService", () => {
const account$ = account.asObservable();
const somePolicy$ = new BehaviorSubject([somePolicy]).asObservable();
const anotherPolicy$ = new BehaviorSubject([]).asObservable();
policyService.getAll$.mockReturnValueOnce(somePolicy$).mockReturnValueOnce(anotherPolicy$);
policyService.policiesByType$
.mockReturnValueOnce(somePolicy$)
.mockReturnValueOnce(anotherPolicy$);
const emissions: GeneratorConstraints<SomeSettings>[] = [];
const sub = generator
.policy$(SomeConfiguration, { account$ })

View File

@@ -114,11 +114,13 @@ export class CredentialGeneratorService {
const algorithms$ = dependencies.account$.pipe(
distinctUntilChanged(),
switchMap((account) => {
const policies$ = this.policyService.getAll$(PolicyType.PasswordGenerator, account.id).pipe(
map((p) => new Set(availableAlgorithms(p))),
// complete policy emissions otherwise `switchMap` holds `algorithms$` open indefinitely
takeUntil(anyComplete(dependencies.account$)),
);
const policies$ = this.policyService
.policiesByType$(PolicyType.PasswordGenerator, account.id)
.pipe(
map((p) => new Set(availableAlgorithms(p))),
// complete policy emissions otherwise `switchMap` holds `algorithms$` open indefinitely
takeUntil(anyComplete(dependencies.account$)),
);
return policies$;
}),
map((available) => {
@@ -280,7 +282,7 @@ export class CredentialGeneratorService {
switchMap(({ userId, email }) => {
// complete policy emissions otherwise `switchMap` holds `policies$` open indefinitely
const policies$ = this.policyService
.getAll$(configuration.policy.type, userId)
.policiesByType$(configuration.policy.type, userId)
.pipe(
mapPolicyToConstraints(configuration.policy, email),
takeUntil(anyComplete(dependencies.account$)),

View File

@@ -19,7 +19,7 @@ function mockPolicyService(config?: { state?: BehaviorSubject<Policy[]> }) {
const service = mock<PolicyService>();
const stateValue = config?.state ?? new BehaviorSubject<Policy[]>([null]);
service.getAll$.mockReturnValue(stateValue);
service.policiesByType$.mockReturnValue(stateValue);
return service;
}
@@ -103,7 +103,7 @@ describe("Password generator service", () => {
await firstValueFrom(service.evaluator$(SomeUser));
expect(policy.getAll$).toHaveBeenCalledWith(PolicyType.PasswordGenerator, SomeUser);
expect(policy.policiesByType$).toHaveBeenCalledWith(PolicyType.PasswordGenerator, SomeUser);
});
it("should map the policy using the generation strategy", async () => {
@@ -150,7 +150,7 @@ describe("Password generator service", () => {
await firstValueFrom(service.evaluator$(SomeUser));
await firstValueFrom(service.evaluator$(SomeUser));
expect(policy.getAll$).toHaveBeenCalledTimes(1);
expect(policy.policiesByType$).toHaveBeenCalledTimes(1);
});
it("should cache the password generator policy for each user", async () => {
@@ -161,8 +161,16 @@ describe("Password generator service", () => {
await firstValueFrom(service.evaluator$(SomeUser));
await firstValueFrom(service.evaluator$(AnotherUser));
expect(policy.getAll$).toHaveBeenNthCalledWith(1, PolicyType.PasswordGenerator, SomeUser);
expect(policy.getAll$).toHaveBeenNthCalledWith(2, PolicyType.PasswordGenerator, AnotherUser);
expect(policy.policiesByType$).toHaveBeenNthCalledWith(
1,
PolicyType.PasswordGenerator,
SomeUser,
);
expect(policy.policiesByType$).toHaveBeenNthCalledWith(
2,
PolicyType.PasswordGenerator,
AnotherUser,
);
});
});

View File

@@ -51,7 +51,7 @@ export class DefaultGeneratorService<Options, Policy> implements GeneratorServic
}
private createEvaluator(userId: UserId) {
const evaluator$ = this.policy.getAll$(this.strategy.policy, userId).pipe(
const evaluator$ = this.policy.policiesByType$(this.strategy.policy, userId).pipe(
// create the evaluator from the policies
this.strategy.toEvaluator(),
);

View File

@@ -155,7 +155,7 @@ const NoPolicyProfile: CoreProfileMetadata<SomeSettings> = {
describe("GeneratorProfileProvider", () => {
beforeEach(async () => {
policyService.getAll$.mockImplementation(() => new BehaviorSubject([]).asObservable());
policyService.policiesByType$.mockImplementation(() => new BehaviorSubject([]).asObservable());
const encryptor$ = new BehaviorSubject({ userId: SomeUser, encryptor });
encryptorProvider.userEncryptor$.mockReturnValue(encryptor$);
jest.clearAllMocks();
@@ -211,7 +211,7 @@ describe("GeneratorProfileProvider", () => {
const profileProvider = new GeneratorProfileProvider(dependencyProvider, policyService);
const account$ = new BehaviorSubject(accounts[SomeUser]).asObservable();
const policy$ = new BehaviorSubject([somePolicy]);
policyService.getAll$.mockReturnValue(policy$);
policyService.policiesByType$.mockReturnValue(policy$);
const result = await firstValueFrom(profileProvider.constraints$(SomeProfile, { account$ }));
@@ -223,7 +223,7 @@ describe("GeneratorProfileProvider", () => {
const account$ = new BehaviorSubject(accounts[SomeUser]).asObservable();
const expectedPolicy = [somePolicy];
const policy$ = new BehaviorSubject(expectedPolicy);
policyService.getAll$.mockReturnValue(policy$);
policyService.policiesByType$.mockReturnValue(policy$);
await firstValueFrom(profileProvider.constraints$(SomeProfile, { account$ }));
@@ -284,7 +284,7 @@ describe("GeneratorProfileProvider", () => {
const account = new BehaviorSubject(accounts[SomeUser]);
const account$ = account.asObservable();
const somePolicySubject = new BehaviorSubject([somePolicy]);
policyService.getAll$.mockReturnValueOnce(somePolicySubject.asObservable());
policyService.policiesByType$.mockReturnValueOnce(somePolicySubject.asObservable());
const emissions: GeneratorConstraints<SomeSettings>[] = [];
const sub = profileProvider
.constraints$(SomeProfile, { account$ })

View File

@@ -86,7 +86,7 @@ export class GeneratorProfileProvider {
);
const policies$ = profile.constraints.type
? this.policyService.getAll$(profile.constraints.type, account.id)
? this.policyService.policiesByType$(profile.constraints.type, account.id)
: of([]);
const context: ProfileContext<Settings> = {