mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 00:03:56 +00:00
[PM-23626] Require userId for makeOrgKey on the key service (#15864)
* Update key service * Update consumers * Add unit test coverage for consumer services * Add unit test coverage for organization-billing service
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
|
||||
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { PlanType } from "@bitwarden/common/billing/enums";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { OrgKey, ProviderKey } from "@bitwarden/common/types/key";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { newGuid } from "@bitwarden/guid";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { WebProviderService } from "./web-provider.service";
|
||||
|
||||
describe("WebProviderService", () => {
|
||||
let sut: WebProviderService;
|
||||
let keyService: MockProxy<KeyService>;
|
||||
let syncService: MockProxy<SyncService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
let billingApiService: MockProxy<BillingApiServiceAbstraction>;
|
||||
let stateProvider: MockProxy<StateProvider>;
|
||||
let providerApiService: MockProxy<ProviderApiServiceAbstraction>;
|
||||
let accountService: MockProxy<AccountService>;
|
||||
|
||||
beforeEach(() => {
|
||||
keyService = mock();
|
||||
syncService = mock();
|
||||
apiService = mock();
|
||||
i18nService = mock();
|
||||
encryptService = mock();
|
||||
billingApiService = mock();
|
||||
stateProvider = mock();
|
||||
providerApiService = mock();
|
||||
accountService = mock();
|
||||
|
||||
sut = new WebProviderService(
|
||||
keyService,
|
||||
syncService,
|
||||
apiService,
|
||||
i18nService,
|
||||
encryptService,
|
||||
billingApiService,
|
||||
stateProvider,
|
||||
providerApiService,
|
||||
accountService,
|
||||
);
|
||||
});
|
||||
|
||||
describe("createClientOrganization", () => {
|
||||
const activeUserId = newGuid() as UserId;
|
||||
const providerId = "provider-123";
|
||||
const name = "Test Org";
|
||||
const ownerEmail = "owner@example.com";
|
||||
const planType = PlanType.EnterpriseAnnually;
|
||||
const seats = 10;
|
||||
const publicKey = "public-key";
|
||||
const encryptedPrivateKey = new EncString("encrypted-private-key");
|
||||
const encryptedProviderKey = new EncString("encrypted-provider-key");
|
||||
const encryptedCollectionName = new EncString("encrypted-collection-name");
|
||||
const defaultCollectionTranslation = "Default Collection";
|
||||
const mockOrgKey = new SymmetricCryptoKey(new Uint8Array(64)) as OrgKey;
|
||||
const mockProviderKey = new SymmetricCryptoKey(new Uint8Array(64)) as ProviderKey;
|
||||
|
||||
beforeEach(() => {
|
||||
keyService.makeOrgKey.mockResolvedValue([new EncString("mockEncryptedKey"), mockOrgKey]);
|
||||
keyService.makeKeyPair.mockResolvedValue([publicKey, encryptedPrivateKey]);
|
||||
i18nService.t.mockReturnValue(defaultCollectionTranslation);
|
||||
encryptService.encryptString.mockResolvedValue(encryptedCollectionName);
|
||||
keyService.getProviderKey.mockResolvedValue(mockProviderKey);
|
||||
encryptService.wrapSymmetricKey.mockResolvedValue(encryptedProviderKey);
|
||||
});
|
||||
|
||||
it("creates a client organization and calls all dependencies with correct arguments", async () => {
|
||||
await sut.createClientOrganization(
|
||||
providerId,
|
||||
name,
|
||||
ownerEmail,
|
||||
planType,
|
||||
seats,
|
||||
activeUserId,
|
||||
);
|
||||
|
||||
expect(keyService.makeOrgKey).toHaveBeenCalledWith(activeUserId);
|
||||
expect(keyService.makeKeyPair).toHaveBeenCalledWith(mockOrgKey);
|
||||
expect(i18nService.t).toHaveBeenCalledWith("defaultCollection");
|
||||
expect(encryptService.encryptString).toHaveBeenCalledWith(
|
||||
defaultCollectionTranslation,
|
||||
mockOrgKey,
|
||||
);
|
||||
expect(keyService.getProviderKey).toHaveBeenCalledWith(providerId);
|
||||
expect(encryptService.wrapSymmetricKey).toHaveBeenCalledWith(mockOrgKey, mockProviderKey);
|
||||
|
||||
expect(billingApiService.createProviderClientOrganization).toHaveBeenCalledWith(
|
||||
providerId,
|
||||
expect.objectContaining({
|
||||
name,
|
||||
ownerEmail,
|
||||
planType,
|
||||
seats,
|
||||
key: encryptedProviderKey.encryptedString,
|
||||
keyPair: expect.any(OrganizationKeysRequest),
|
||||
collectionName: encryptedCollectionName.encryptedString,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(apiService.refreshIdentityToken).toHaveBeenCalled();
|
||||
expect(syncService.fullSync).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -16,7 +16,7 @@ import { CreateClientOrganizationRequest } from "@bitwarden/common/billing/model
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { OrgKey } from "@bitwarden/common/types/key";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
@@ -78,8 +78,9 @@ export class WebProviderService {
|
||||
ownerEmail: string,
|
||||
planType: PlanType,
|
||||
seats: number,
|
||||
activeUserId: UserId,
|
||||
): Promise<void> {
|
||||
const organizationKey = (await this.keyService.makeOrgKey<OrgKey>())[1];
|
||||
const organizationKey = (await this.keyService.makeOrgKey<OrgKey>(activeUserId))[1];
|
||||
|
||||
const [publicKey, encryptedPrivateKey] = await this.keyService.makeKeyPair(organizationKey);
|
||||
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { Subject, switchMap } from "rxjs";
|
||||
import { firstValueFrom, Subject, switchMap } from "rxjs";
|
||||
import { first, takeUntil } from "rxjs/operators";
|
||||
|
||||
import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components";
|
||||
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
|
||||
import { ProviderSetupRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-setup.request";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
|
||||
import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -49,6 +51,7 @@ export class SetupComponent implements OnInit, OnDestroy {
|
||||
private providerApiService: ProviderApiServiceAbstraction,
|
||||
private formBuilder: FormBuilder,
|
||||
private toastService: ToastService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -118,8 +121,8 @@ export class SetupComponent implements OnInit, OnDestroy {
|
||||
if (!paymentValid || !taxInformationValid || !this.formGroup.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const providerKey = await this.keyService.makeOrgKey<ProviderKey>();
|
||||
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
const providerKey = await this.keyService.makeOrgKey<ProviderKey>(activeUserId);
|
||||
const key = providerKey[0].encryptedString;
|
||||
|
||||
const request = new ProviderSetupRequest();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
|
||||
@@ -130,6 +133,7 @@ export class CreateClientDialogComponent implements OnInit {
|
||||
private i18nService: I18nService,
|
||||
private toastService: ToastService,
|
||||
private webProviderService: WebProviderService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
@@ -198,13 +202,14 @@ export class CreateClientDialogComponent implements OnInit {
|
||||
if (!selectedPlanCard) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
await this.webProviderService.createClientOrganization(
|
||||
this.dialogParams.providerId,
|
||||
this.formGroup.controls.organizationName.value,
|
||||
this.formGroup.controls.clientOwnerEmail.value,
|
||||
selectedPlanCard.type,
|
||||
this.formGroup.controls.seats.value,
|
||||
activeUserId,
|
||||
);
|
||||
|
||||
this.toastService.showToast({
|
||||
|
||||
@@ -80,8 +80,9 @@ export class SetupBusinessUnitComponent extends BaseAcceptComponent {
|
||||
map((organizationKeysById) => organizationKeysById[organizationId as OrganizationId]),
|
||||
);
|
||||
|
||||
const userId = await firstValueFrom(activeUserId$);
|
||||
const [{ encryptedString: encryptedProviderKey }, providerKey] =
|
||||
await this.keyService.makeOrgKey<ProviderKey>();
|
||||
await this.keyService.makeOrgKey<ProviderKey>(userId);
|
||||
|
||||
const organizationKey = await firstValueFrom(organizationKey$);
|
||||
|
||||
@@ -92,8 +93,6 @@ export class SetupBusinessUnitComponent extends BaseAcceptComponent {
|
||||
return await fail();
|
||||
}
|
||||
|
||||
const userId = await firstValueFrom(activeUserId$);
|
||||
|
||||
const request = {
|
||||
userId,
|
||||
token,
|
||||
|
||||
Reference in New Issue
Block a user