mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +00:00
[PM-23628] Require userId for fetching provider keys (#16993)
* remove getProviderKey and expose providerKeys$ * update consumers
This commit is contained in:
@@ -31,6 +31,7 @@ import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-conso
|
||||
import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { assertNonNullish } from "@bitwarden/common/auth/utils";
|
||||
import { PlanSponsorshipType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { BillingResponse } from "@bitwarden/common/billing/models/response/billing.response";
|
||||
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
|
||||
@@ -41,7 +42,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { OrganizationId, ProviderId, 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 { ToastService } from "@bitwarden/components";
|
||||
@@ -654,7 +655,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
|
||||
|
||||
orgId = this.selfHosted
|
||||
? await this.createSelfHosted(key, collectionCt, orgKeys)
|
||||
: await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1]);
|
||||
: await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1], activeUserId);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
@@ -808,6 +809,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
|
||||
collectionCt: string,
|
||||
orgKeys: [string, EncString],
|
||||
orgKey: SymmetricCryptoKey,
|
||||
activeUserId: UserId,
|
||||
): Promise<string> {
|
||||
const request = new OrganizationCreateRequest();
|
||||
request.key = key;
|
||||
@@ -855,7 +857,14 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
|
||||
this.formGroup.controls.clientOwnerEmail.value,
|
||||
request,
|
||||
);
|
||||
const providerKey = await this.keyService.getProviderKey(this.providerId);
|
||||
|
||||
const providerKey = await firstValueFrom(
|
||||
this.keyService
|
||||
.providerKeys$(activeUserId)
|
||||
.pipe(map((providerKeys) => providerKeys?.[this.providerId as ProviderId] ?? null)),
|
||||
);
|
||||
assertNonNullish(providerKey, "Provider key not found");
|
||||
|
||||
providerRequest.organizationCreateRequest.key = (
|
||||
await this.encryptService.wrapSymmetricKey(orgKey, providerKey)
|
||||
).encryptedString;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
|
||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||
import { AddableOrganizationResponse } from "@bitwarden/common/admin-console/models/response/addable-organization.response";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import {
|
||||
DIALOG_DATA,
|
||||
@@ -46,6 +49,7 @@ export class AddExistingOrganizationDialogComponent implements OnInit {
|
||||
private providerApiService: ProviderApiServiceAbstraction,
|
||||
private toastService: ToastService,
|
||||
private webProviderService: WebProviderService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -57,9 +61,11 @@ export class AddExistingOrganizationDialogComponent implements OnInit {
|
||||
|
||||
addExistingOrganization = async (): Promise<void> => {
|
||||
if (this.selectedOrganization) {
|
||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
await this.webProviderService.addOrganizationToProvider(
|
||||
this.dialogParams.provider.id,
|
||||
this.selectedOrganization.id,
|
||||
userId,
|
||||
);
|
||||
|
||||
this.toastService.showToast({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { firstValueFrom, map, Observable, switchMap } from "rxjs";
|
||||
|
||||
import {
|
||||
OrganizationUserBulkPublicKeyResponse,
|
||||
@@ -12,10 +13,14 @@ import { ProviderUserBulkConfirmRequest } from "@bitwarden/common/admin-console/
|
||||
import { ProviderUserBulkRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk.request";
|
||||
import { ProviderUserBulkPublicKeyResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk-public-key.response";
|
||||
import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { ProviderId } from "@bitwarden/common/types/guid";
|
||||
import { ProviderKey } from "@bitwarden/common/types/key";
|
||||
import { DIALOG_DATA, DialogConfig, DialogService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
import { BaseBulkConfirmComponent } from "@bitwarden/web-vault/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component";
|
||||
@@ -35,6 +40,7 @@ type BulkConfirmDialogParams = {
|
||||
})
|
||||
export class BulkConfirmDialogComponent extends BaseBulkConfirmComponent {
|
||||
providerId: string;
|
||||
providerKey$: Observable<ProviderKey>;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
@@ -42,15 +48,21 @@ export class BulkConfirmDialogComponent extends BaseBulkConfirmComponent {
|
||||
protected encryptService: EncryptService,
|
||||
@Inject(DIALOG_DATA) protected dialogParams: BulkConfirmDialogParams,
|
||||
protected i18nService: I18nService,
|
||||
private accountService: AccountService,
|
||||
) {
|
||||
super(keyService, encryptService, i18nService);
|
||||
|
||||
this.providerId = dialogParams.providerId;
|
||||
this.providerKey$ = this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) => this.keyService.providerKeys$(userId)),
|
||||
map((providerKeysById) => providerKeysById?.[this.providerId as ProviderId]),
|
||||
);
|
||||
this.users = dialogParams.users;
|
||||
}
|
||||
|
||||
protected getCryptoKey = (): Promise<SymmetricCryptoKey> =>
|
||||
this.keyService.getProviderKey(this.providerId);
|
||||
protected getCryptoKey = async (): Promise<SymmetricCryptoKey> =>
|
||||
await firstValueFrom(this.providerKey$);
|
||||
|
||||
protected getPublicKeys = async (): Promise<
|
||||
ListResponse<OrganizationUserBulkPublicKeyResponse | ProviderUserBulkPublicKeyResponse>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Component } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { combineLatest, firstValueFrom, lastValueFrom, switchMap } from "rxjs";
|
||||
import { first } from "rxjs/operators";
|
||||
import { first, map } from "rxjs/operators";
|
||||
|
||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
@@ -16,11 +16,13 @@ import { ProviderUserConfirmRequest } from "@bitwarden/common/admin-console/mode
|
||||
import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { assertNonNullish } from "@bitwarden/common/auth/utils";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { ProviderId } from "@bitwarden/common/types/guid";
|
||||
import { DialogRef, DialogService, ToastService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
import { BaseMembersComponent } from "@bitwarden/web-vault/app/admin-console/common/base-members.component";
|
||||
@@ -204,7 +206,15 @@ export class MembersComponent extends BaseMembersComponent<ProviderUser> {
|
||||
|
||||
async confirmUser(user: ProviderUser, publicKey: Uint8Array): Promise<MemberActionResult> {
|
||||
try {
|
||||
const providerKey = await this.keyService.getProviderKey(this.providerId);
|
||||
const providerKey = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) => this.keyService.providerKeys$(userId)),
|
||||
map((providerKeys) => providerKeys?.[this.providerId as ProviderId] ?? null),
|
||||
),
|
||||
);
|
||||
assertNonNullish(providerKey, "Provider key not found");
|
||||
|
||||
const key = await this.encryptService.encapsulateKeyUnsigned(providerKey, publicKey);
|
||||
const request = new ProviderUserConfirmRequest();
|
||||
request.key = key.encryptedString;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
|
||||
@@ -8,7 +9,6 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract
|
||||
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";
|
||||
@@ -24,16 +24,22 @@ describe("WebProviderService", () => {
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
let stateProvider: MockProxy<StateProvider>;
|
||||
let providerApiService: MockProxy<ProviderApiServiceAbstraction>;
|
||||
|
||||
const activeUserId = newGuid() as UserId;
|
||||
const providerId = "provider-123";
|
||||
const mockOrgKey = new SymmetricCryptoKey(new Uint8Array(64)) as OrgKey;
|
||||
const mockProviderKey = new SymmetricCryptoKey(new Uint8Array(64)) as ProviderKey;
|
||||
const mockProviderKeysById: Record<string, ProviderKey> = {
|
||||
[providerId]: mockProviderKey,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
keyService = mock();
|
||||
syncService = mock();
|
||||
apiService = mock();
|
||||
i18nService = mock();
|
||||
encryptService = mock();
|
||||
stateProvider = mock();
|
||||
providerApiService = mock();
|
||||
|
||||
sut = new WebProviderService(
|
||||
@@ -42,14 +48,69 @@ describe("WebProviderService", () => {
|
||||
apiService,
|
||||
i18nService,
|
||||
encryptService,
|
||||
stateProvider,
|
||||
providerApiService,
|
||||
);
|
||||
});
|
||||
|
||||
describe("addOrganizationToProvider", () => {
|
||||
const organizationId = "org-789";
|
||||
const encryptedOrgKey = new EncString("encrypted-org-key");
|
||||
const mockOrgKeysById: Record<string, OrgKey> = {
|
||||
[organizationId]: mockOrgKey,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
keyService.orgKeys$.mockReturnValue(of(mockOrgKeysById));
|
||||
keyService.providerKeys$.mockReturnValue(of(mockProviderKeysById));
|
||||
encryptService.wrapSymmetricKey.mockResolvedValue(encryptedOrgKey);
|
||||
});
|
||||
|
||||
it("adds an organization to a provider with correct encryption", async () => {
|
||||
await sut.addOrganizationToProvider(providerId, organizationId, activeUserId);
|
||||
|
||||
expect(keyService.orgKeys$).toHaveBeenCalledWith(activeUserId);
|
||||
expect(keyService.providerKeys$).toHaveBeenCalledWith(activeUserId);
|
||||
expect(encryptService.wrapSymmetricKey).toHaveBeenCalledWith(mockOrgKey, mockProviderKey);
|
||||
expect(providerApiService.addOrganizationToProvider).toHaveBeenCalledWith(providerId, {
|
||||
key: encryptedOrgKey.encryptedString,
|
||||
organizationId,
|
||||
});
|
||||
expect(syncService.fullSync).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it("throws an error if organization key is not found", async () => {
|
||||
const invalidOrgId = "invalid-org";
|
||||
|
||||
await expect(
|
||||
sut.addOrganizationToProvider(providerId, invalidOrgId, activeUserId),
|
||||
).rejects.toThrow("Organization key not found");
|
||||
});
|
||||
|
||||
it("throws an error if no organization keys are available", async () => {
|
||||
keyService.orgKeys$.mockReturnValue(of(null));
|
||||
|
||||
await expect(
|
||||
sut.addOrganizationToProvider(providerId, organizationId, activeUserId),
|
||||
).rejects.toThrow("Organization key not found");
|
||||
});
|
||||
|
||||
it("throws an error if provider key is not found", async () => {
|
||||
const invalidProviderId = "invalid-provider";
|
||||
await expect(
|
||||
sut.addOrganizationToProvider(invalidProviderId, organizationId, activeUserId),
|
||||
).rejects.toThrow("Provider key not found");
|
||||
});
|
||||
|
||||
it("throws an error if no provider keys are available", async () => {
|
||||
keyService.providerKeys$.mockReturnValue(of(null));
|
||||
|
||||
await expect(
|
||||
sut.addOrganizationToProvider(providerId, organizationId, activeUserId),
|
||||
).rejects.toThrow("Provider key not found");
|
||||
});
|
||||
});
|
||||
|
||||
describe("createClientOrganization", () => {
|
||||
const activeUserId = newGuid() as UserId;
|
||||
const providerId = "provider-123";
|
||||
const name = "Test Org";
|
||||
const ownerEmail = "owner@example.com";
|
||||
const planType = PlanType.EnterpriseAnnually;
|
||||
@@ -59,15 +120,13 @@ describe("WebProviderService", () => {
|
||||
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);
|
||||
keyService.providerKeys$.mockReturnValue(of(mockProviderKeysById));
|
||||
encryptService.wrapSymmetricKey.mockResolvedValue(encryptedProviderKey);
|
||||
});
|
||||
|
||||
@@ -88,7 +147,7 @@ describe("WebProviderService", () => {
|
||||
defaultCollectionTranslation,
|
||||
mockOrgKey,
|
||||
);
|
||||
expect(keyService.getProviderKey).toHaveBeenCalledWith(providerId);
|
||||
expect(keyService.providerKeys$).toHaveBeenCalledWith(activeUserId);
|
||||
expect(encryptService.wrapSymmetricKey).toHaveBeenCalledWith(mockOrgKey, mockProviderKey);
|
||||
|
||||
expect(providerApiService.createProviderOrganization).toHaveBeenCalledWith(
|
||||
@@ -107,5 +166,27 @@ describe("WebProviderService", () => {
|
||||
expect(apiService.refreshIdentityToken).toHaveBeenCalled();
|
||||
expect(syncService.fullSync).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it("throws an error if provider key is not found", async () => {
|
||||
const invalidProviderId = "invalid-provider";
|
||||
await expect(
|
||||
sut.createClientOrganization(
|
||||
invalidProviderId,
|
||||
name,
|
||||
ownerEmail,
|
||||
planType,
|
||||
seats,
|
||||
activeUserId,
|
||||
),
|
||||
).rejects.toThrow("Provider key not found");
|
||||
});
|
||||
|
||||
it("throws an error if no provider keys are available", async () => {
|
||||
keyService.providerKeys$.mockReturnValue(of(null));
|
||||
|
||||
await expect(
|
||||
sut.createClientOrganization(providerId, name, ownerEmail, planType, seats, activeUserId),
|
||||
).rejects.toThrow("Provider key not found");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Injectable } from "@angular/core";
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
import { switchMap } from "rxjs/operators";
|
||||
import { combineLatest, firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
|
||||
import { CreateProviderOrganizationRequest } from "@bitwarden/common/admin-console/models/request/create-provider-organization.request";
|
||||
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
|
||||
import { assertNonNullish } from "@bitwarden/common/auth/utils";
|
||||
import { PlanType } from "@bitwarden/common/billing/enums";
|
||||
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, UserId } from "@bitwarden/common/types/guid";
|
||||
import { OrganizationId, ProviderId, 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";
|
||||
@@ -25,18 +24,26 @@ export class WebProviderService {
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
private encryptService: EncryptService,
|
||||
private stateProvider: StateProvider,
|
||||
private providerApiService: ProviderApiServiceAbstraction,
|
||||
) {}
|
||||
|
||||
async addOrganizationToProvider(providerId: string, organizationId: string): Promise<void> {
|
||||
const orgKey = await firstValueFrom(
|
||||
this.stateProvider.activeUserId$.pipe(
|
||||
switchMap((userId) => this.keyService.orgKeys$(userId)),
|
||||
map((organizationKeysById) => organizationKeysById[organizationId as OrganizationId]),
|
||||
),
|
||||
async addOrganizationToProvider(
|
||||
providerId: string,
|
||||
organizationId: string,
|
||||
activeUserId: UserId,
|
||||
): Promise<void> {
|
||||
const [orgKeysById, providerKeys] = await firstValueFrom(
|
||||
combineLatest([
|
||||
this.keyService.orgKeys$(activeUserId),
|
||||
this.keyService.providerKeys$(activeUserId),
|
||||
]),
|
||||
);
|
||||
const providerKey = await this.keyService.getProviderKey(providerId);
|
||||
|
||||
const orgKey = orgKeysById?.[organizationId as OrganizationId];
|
||||
const providerKey = providerKeys?.[providerId as ProviderId];
|
||||
assertNonNullish(orgKey, "Organization key not found");
|
||||
assertNonNullish(providerKey, "Provider key not found");
|
||||
|
||||
const encryptedOrgKey = await this.encryptService.wrapSymmetricKey(orgKey, providerKey);
|
||||
await this.providerApiService.addOrganizationToProvider(providerId, {
|
||||
key: encryptedOrgKey.encryptedString,
|
||||
@@ -62,7 +69,12 @@ export class WebProviderService {
|
||||
organizationKey,
|
||||
);
|
||||
|
||||
const providerKey = await this.keyService.getProviderKey(providerId);
|
||||
const providerKey = await firstValueFrom(
|
||||
this.keyService
|
||||
.providerKeys$(activeUserId)
|
||||
.pipe(map((providerKeys) => providerKeys?.[providerId as ProviderId])),
|
||||
);
|
||||
assertNonNullish(providerKey, "Provider key not found");
|
||||
|
||||
const encryptedProviderKey = await this.encryptService.wrapSymmetricKey(
|
||||
organizationKey,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import { WrappedSigningKey } from "@bitwarden/common/key-management/types";
|
||||
import { KeySuffixOptions, HashPurpose } from "@bitwarden/common/platform/enums";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { OrganizationId, ProviderId, UserId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
UserKey,
|
||||
MasterKey,
|
||||
@@ -248,17 +248,19 @@ export abstract class KeyService {
|
||||
|
||||
/**
|
||||
* Stores the provider keys for a given user.
|
||||
* @param orgs The provider orgs for which to save the keys from.
|
||||
* @param providers The provider orgs for which to save the keys from.
|
||||
* @param userId The user id of the user for which to store the keys for.
|
||||
*/
|
||||
abstract setProviderKeys(orgs: ProfileProviderResponse[], userId: UserId): Promise<void>;
|
||||
abstract setProviderKeys(providers: ProfileProviderResponse[], userId: UserId): Promise<void>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @throws Error when providerId is null or no active user
|
||||
* @param providerId The desired provider
|
||||
* @returns The provider's symmetric key
|
||||
* Gets an observable of provider keys for the given user.
|
||||
* @param userId The user to get provider keys for.
|
||||
* @return An observable stream of the users providers keys if they are unlocked, or null if the user is not unlocked.
|
||||
* @throws If an invalid user id is passed in.
|
||||
*/
|
||||
abstract getProviderKey(providerId: string): Promise<ProviderKey | null>;
|
||||
abstract providerKeys$(userId: UserId): Observable<Record<ProviderId, ProviderKey> | null>;
|
||||
|
||||
/**
|
||||
* Creates a new organization key and encrypts it with the user's public key.
|
||||
* This method can also return Provider keys for creating new Provider users.
|
||||
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
FakeSingleUserState,
|
||||
} from "@bitwarden/common/spec";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { OrganizationId, ProviderId, UserId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
UserKey,
|
||||
MasterKey,
|
||||
@@ -1314,6 +1314,49 @@ describe("keyService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("providerKeys$", () => {
|
||||
let mockUserPrivateKey: Uint8Array;
|
||||
let mockProviderKeys: Record<ProviderId, ProviderKey>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockUserPrivateKey = makeStaticByteArray(64, 1);
|
||||
mockProviderKeys = {
|
||||
["provider1" as ProviderId]: makeSymmetricCryptoKey<ProviderKey>(64),
|
||||
["provider2" as ProviderId]: makeSymmetricCryptoKey<ProviderKey>(64),
|
||||
};
|
||||
});
|
||||
|
||||
it("returns null when userPrivateKey is null", async () => {
|
||||
jest.spyOn(keyService, "userPrivateKey$").mockReturnValue(of(null));
|
||||
|
||||
const result = await firstValueFrom(keyService.providerKeys$(mockUserId));
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("returns provider keys when userPrivateKey is available", async () => {
|
||||
jest.spyOn(keyService, "userPrivateKey$").mockReturnValue(of(mockUserPrivateKey as any));
|
||||
jest.spyOn(keyService as any, "providerKeysHelper$").mockReturnValue(of(mockProviderKeys));
|
||||
|
||||
const result = await firstValueFrom(keyService.providerKeys$(mockUserId));
|
||||
|
||||
expect(result).toEqual(mockProviderKeys);
|
||||
expect((keyService as any).providerKeysHelper$).toHaveBeenCalledWith(
|
||||
mockUserId,
|
||||
mockUserPrivateKey,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns null when providerKeysHelper$ returns null", async () => {
|
||||
jest.spyOn(keyService, "userPrivateKey$").mockReturnValue(of(mockUserPrivateKey as any));
|
||||
jest.spyOn(keyService as any, "providerKeysHelper$").mockReturnValue(of(null));
|
||||
|
||||
const result = await firstValueFrom(keyService.providerKeys$(mockUserId));
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("makeKeyPair", () => {
|
||||
test.each([null as unknown as SymmetricCryptoKey, undefined as unknown as SymmetricCryptoKey])(
|
||||
"throws when the provided key is %s",
|
||||
|
||||
@@ -426,20 +426,16 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Deprecate in favor of observable
|
||||
async getProviderKey(providerId: ProviderId): Promise<ProviderKey | null> {
|
||||
if (providerId == null) {
|
||||
return null;
|
||||
}
|
||||
providerKeys$(userId: UserId): Observable<Record<ProviderId, ProviderKey> | null> {
|
||||
return this.userPrivateKey$(userId).pipe(
|
||||
switchMap((userPrivateKey) => {
|
||||
if (userPrivateKey == null) {
|
||||
return of(null);
|
||||
}
|
||||
|
||||
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (activeUserId == null) {
|
||||
throw new Error("No active user found.");
|
||||
}
|
||||
|
||||
const providerKeys = await firstValueFrom(this.providerKeys$(activeUserId));
|
||||
|
||||
return providerKeys?.[providerId] ?? null;
|
||||
return this.providerKeysHelper$(userId, userPrivateKey);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async clearProviderKeys(userId: UserId): Promise<void> {
|
||||
@@ -829,18 +825,6 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
)) as UserPrivateKey;
|
||||
}
|
||||
|
||||
providerKeys$(userId: UserId) {
|
||||
return this.userPrivateKey$(userId).pipe(
|
||||
switchMap((userPrivateKey) => {
|
||||
if (userPrivateKey == null) {
|
||||
return of(null);
|
||||
}
|
||||
|
||||
return this.providerKeysHelper$(userId, userPrivateKey);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper for decrypting provider keys that requires a user id and that users decrypted private key
|
||||
* this is helpful for when you may have already grabbed the user private key and don't want to redo
|
||||
|
||||
Reference in New Issue
Block a user