1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 13:23:34 +00:00

[PM-24096] replace getOrgKey with orgKey$, refactor collectionAdminService (#15928)

* replace getOrgKey with orgKey$, refactor collectionAdminService

* clean up

* uncomment accidental commet

* remove cache
This commit is contained in:
Brandon Treston
2025-08-12 12:06:55 -04:00
committed by GitHub
parent 04489b9fef
commit d4952d211e
27 changed files with 226 additions and 73 deletions

View File

@@ -1,17 +1,20 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { firstValueFrom } from "rxjs"; import { firstValueFrom, map, switchMap } from "rxjs";
import { import {
OrganizationUserApiService, OrganizationUserApiService,
OrganizationUserConfirmRequest, OrganizationUserConfirmRequest,
} from "@bitwarden/admin-console/common"; } from "@bitwarden/admin-console/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { OrgKey } from "@bitwarden/common/types/key"; import { OrgKey } from "@bitwarden/common/types/key";
import { KeyService } from "@bitwarden/key-management"; import { KeyService } from "@bitwarden/key-management";
import { EncString } from "@bitwarden/sdk-internal"; import { EncString } from "@bitwarden/sdk-internal";
@@ -24,6 +27,7 @@ export class ConfirmCommand {
private keyService: KeyService, private keyService: KeyService,
private encryptService: EncryptService, private encryptService: EncryptService,
private organizationUserApiService: OrganizationUserApiService, private organizationUserApiService: OrganizationUserApiService,
private accountService: AccountService,
private configService: ConfigService, private configService: ConfigService,
private i18nService: I18nService, private i18nService: I18nService,
) {} ) {}
@@ -53,7 +57,14 @@ export class ConfirmCommand {
return Response.badRequest("`" + options.organizationId + "` is not a GUID."); return Response.badRequest("`" + options.organizationId + "` is not a GUID.");
} }
try { try {
const orgKey = await this.keyService.getOrgKey(options.organizationId); const orgKey = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.keyService.orgKeys$(userId)),
map((orgKeys) => orgKeys[options.organizationId as OrganizationId] ?? null),
),
);
if (orgKey == null) { if (orgKey == null) {
throw new Error("No encryption key for this organization."); throw new Error("No encryption key for this organization.");
} }

View File

@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { firstValueFrom } from "rxjs"; import { firstValueFrom, map, switchMap } from "rxjs";
import { CollectionRequest } from "@bitwarden/admin-console/common"; import { CollectionRequest } from "@bitwarden/admin-console/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -14,6 +14,7 @@ import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
import { FolderExport } from "@bitwarden/common/models/export/folder.export"; import { FolderExport } from "@bitwarden/common/models/export/folder.export";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
@@ -201,7 +202,13 @@ export class EditCommand {
return Response.badRequest("`organizationid` option does not match request object."); return Response.badRequest("`organizationid` option does not match request object.");
} }
try { try {
const orgKey = await this.keyService.getOrgKey(req.organizationId); const orgKey = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.keyService.orgKeys$(userId)),
map((orgKeys) => orgKeys[options.organizationId as OrganizationId] ?? null),
),
);
if (orgKey == null) { if (orgKey == null) {
throw new Error("No encryption key for this organization."); throw new Error("No encryption key for this organization.");
} }

View File

@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { firstValueFrom, map } from "rxjs"; import { firstValueFrom, map, switchMap } from "rxjs";
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -485,7 +485,13 @@ export class GetCommand extends DownloadCommand {
return Response.badRequest("`" + options.organizationId + "` is not a GUID."); return Response.badRequest("`" + options.organizationId + "` is not a GUID.");
} }
try { try {
const orgKey = await this.keyService.getOrgKey(options.organizationId); const orgKey = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.keyService.orgKeys$(userId)),
map((orgKeys) => orgKeys[options.organizationId as OrganizationId] ?? null),
),
);
if (orgKey == null) { if (orgKey == null) {
throw new Error("No encryption key for this organization."); throw new Error("No encryption key for this organization.");
} }

View File

@@ -131,6 +131,7 @@ export class OssServeConfigurator {
this.serviceContainer.keyService, this.serviceContainer.keyService,
this.serviceContainer.encryptService, this.serviceContainer.encryptService,
this.serviceContainer.organizationUserApiService, this.serviceContainer.organizationUserApiService,
this.serviceContainer.accountService,
this.serviceContainer.configService, this.serviceContainer.configService,
this.serviceContainer.i18nService, this.serviceContainer.i18nService,
); );

View File

@@ -432,6 +432,7 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.keyService, this.serviceContainer.keyService,
this.serviceContainer.encryptService, this.serviceContainer.encryptService,
this.serviceContainer.organizationUserApiService, this.serviceContainer.organizationUserApiService,
this.serviceContainer.accountService,
this.serviceContainer.configService, this.serviceContainer.configService,
this.serviceContainer.i18nService, this.serviceContainer.i18nService,
); );

View File

@@ -5,7 +5,7 @@
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Component, EventEmitter, Input, Output } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs"; import { firstValueFrom, switchMap } from "rxjs";
import { import {
CollectionAdminService, CollectionAdminService,
@@ -14,6 +14,8 @@ import {
} from "@bitwarden/admin-console/common"; } from "@bitwarden/admin-console/common";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ProductTierType } from "@bitwarden/common/billing/enums"; import { ProductTierType } from "@bitwarden/common/billing/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
@@ -99,6 +101,7 @@ export class VaultHeaderComponent {
private dialogService: DialogService, private dialogService: DialogService,
private collectionAdminService: CollectionAdminService, private collectionAdminService: CollectionAdminService,
private router: Router, private router: Router,
private accountService: AccountService,
) {} ) {}
get title() { get title() {
@@ -199,7 +202,14 @@ export class VaultHeaderComponent {
async addCollection() { async addCollection() {
if (this.organization.productTierType === ProductTierType.Free) { if (this.organization.productTierType === ProductTierType.Free) {
const collections = await this.collectionAdminService.getAll(this.organization.id); const collections = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.collectionAdminService.collectionAdminViews$(this.organization.id, userId),
),
),
);
if (collections.length === this.organization.maxCollections) { if (collections.length === this.organization.maxCollections) {
this.showFreeOrgUpgradeDialog(); this.showFreeOrgUpgradeDialog();
return; return;

View File

@@ -363,7 +363,12 @@ export class VaultComponent implements OnInit, OnDestroy {
this.allCollectionsWithoutUnassigned$ = this.refresh$.pipe( this.allCollectionsWithoutUnassigned$ = this.refresh$.pipe(
switchMap(() => organizationId$), switchMap(() => organizationId$),
switchMap((orgId) => this.collectionAdminService.getAll(orgId)), switchMap((orgId) =>
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.collectionAdminService.collectionAdminViews$(orgId, userId)),
),
),
shareReplay({ refCount: false, bufferSize: 1 }), shareReplay({ refCount: false, bufferSize: 1 }),
); );

View File

@@ -28,6 +28,7 @@ import {
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -156,7 +157,11 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
private orgCollections$ = from(this.collectionAdminService.getAll(this.organizationId)).pipe( private orgCollections$ = this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.collectionAdminService.collectionAdminViews$(this.organizationId, userId),
),
shareReplay({ refCount: true, bufferSize: 1 }), shareReplay({ refCount: true, bufferSize: 1 }),
); );

View File

@@ -32,6 +32,7 @@ import {
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api"; import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ProductTierType } from "@bitwarden/common/billing/enums"; import { ProductTierType } from "@bitwarden/common/billing/enums";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -276,9 +277,16 @@ export class MemberDialogComponent implements OnDestroy {
), ),
); );
const collections = this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.collectionAdminService.collectionAdminViews$(this.params.organizationId, userId),
),
);
combineLatest({ combineLatest({
organization: this.organization$, organization: this.organization$,
collections: this.collectionAdminService.getAll(this.params.organizationId), collections,
userDetails: userDetails$, userDetails: userDetails$,
groups: groups$, groups: groups$,
}) })

View File

@@ -200,7 +200,14 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
this.organization.canManageUsersPassword && this.organization.canManageUsersPassword &&
!this.organization.hasPublicAndPrivateKeys !this.organization.hasPublicAndPrivateKeys
) { ) {
const orgShareKey = await this.keyService.getOrgKey(this.organization.id); const orgShareKey = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.keyService.orgKeys$(userId)),
map((orgKeys) => orgKeys[this.organization.id] ?? null),
),
);
const orgKeys = await this.keyService.makeKeyPair(orgShareKey); const orgKeys = await this.keyService.makeKeyPair(orgShareKey);
const request = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString); const request = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
const response = await this.organizationApiService.updateKeys( const response = await this.organizationApiService.updateKeys(
@@ -353,7 +360,13 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
this.organizationUserService.confirmUser(this.organization, user, publicKey), this.organizationUserService.confirmUser(this.organization, user, publicKey),
); );
} else { } else {
const orgKey = await this.keyService.getOrgKey(this.organization.id); const orgKey = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.keyService.orgKeys$(userId)),
map((orgKeys) => orgKeys[this.organization.id] ?? null),
),
);
const key = await this.encryptService.encapsulateKeyUnsigned(orgKey, publicKey); const key = await this.encryptService.encapsulateKeyUnsigned(orgKey, publicKey);
const request = new OrganizationUserConfirmRequest(); const request = new OrganizationUserConfirmRequest();
request.key = key.encryptedString; request.key = key.encryptedString;

View File

@@ -17,8 +17,9 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { EncryptionType } from "@bitwarden/common/platform/enums"; import { EncryptionType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { CsprngArray } from "@bitwarden/common/types/csprng"; import { CsprngArray } from "@bitwarden/common/types/csprng";
import { UserId } from "@bitwarden/common/types/guid"; import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { UserKey, OrgKey, MasterKey } from "@bitwarden/common/types/key"; import { UserKey, OrgKey, MasterKey } from "@bitwarden/common/types/key";
import { KdfType, KeyService } from "@bitwarden/key-management"; import { KdfType, KeyService } from "@bitwarden/key-management";
@@ -36,6 +37,8 @@ describe("OrganizationUserResetPasswordService", () => {
let organizationUserApiService: MockProxy<OrganizationUserApiService>; let organizationUserApiService: MockProxy<OrganizationUserApiService>;
let organizationApiService: MockProxy<OrganizationApiService>; let organizationApiService: MockProxy<OrganizationApiService>;
let i18nService: MockProxy<I18nService>; let i18nService: MockProxy<I18nService>;
const mockUserId = Utils.newGuid() as UserId;
let accountService: FakeAccountService;
beforeAll(() => { beforeAll(() => {
keyService = mock<KeyService>(); keyService = mock<KeyService>();
@@ -44,6 +47,7 @@ describe("OrganizationUserResetPasswordService", () => {
organizationUserApiService = mock<OrganizationUserApiService>(); organizationUserApiService = mock<OrganizationUserApiService>();
organizationApiService = mock<OrganizationApiService>(); organizationApiService = mock<OrganizationApiService>();
i18nService = mock<I18nService>(); i18nService = mock<I18nService>();
accountService = mockAccountServiceWith(mockUserId);
sut = new OrganizationUserResetPasswordService( sut = new OrganizationUserResetPasswordService(
keyService, keyService,
@@ -52,6 +56,7 @@ describe("OrganizationUserResetPasswordService", () => {
organizationUserApiService, organizationUserApiService,
organizationApiService, organizationApiService,
i18nService, i18nService,
accountService,
); );
}); });
@@ -142,7 +147,10 @@ describe("OrganizationUserResetPasswordService", () => {
const mockRandomBytes = new Uint8Array(64) as CsprngArray; const mockRandomBytes = new Uint8Array(64) as CsprngArray;
const mockOrgKey = new SymmetricCryptoKey(mockRandomBytes) as OrgKey; const mockOrgKey = new SymmetricCryptoKey(mockRandomBytes) as OrgKey;
keyService.getOrgKey.mockResolvedValue(mockOrgKey); keyService.orgKeys$.mockReturnValue(
of({ [mockOrgId]: mockOrgKey } as Record<OrganizationId, OrgKey>),
);
encryptService.decryptToBytes.mockResolvedValue(mockRandomBytes); encryptService.decryptToBytes.mockResolvedValue(mockRandomBytes);
encryptService.rsaDecrypt.mockResolvedValue(mockRandomBytes); encryptService.rsaDecrypt.mockResolvedValue(mockRandomBytes);
@@ -170,7 +178,7 @@ describe("OrganizationUserResetPasswordService", () => {
}); });
it("should throw an error if the org key is null", async () => { it("should throw an error if the org key is null", async () => {
keyService.getOrgKey.mockResolvedValue(null); keyService.orgKeys$.mockReturnValue(of(null));
await expect( await expect(
sut.resetMasterPassword(mockNewMP, mockEmail, mockOrgUserId, mockOrgId), sut.resetMasterPassword(mockNewMP, mockEmail, mockOrgUserId, mockOrgId),
).rejects.toThrow(); ).rejects.toThrow();

View File

@@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { firstValueFrom } from "rxjs"; import { firstValueFrom, map, switchMap } from "rxjs";
import { import {
OrganizationUserApiService, OrganizationUserApiService,
@@ -10,6 +10,8 @@ import {
} from "@bitwarden/admin-console/common"; } from "@bitwarden/admin-console/common";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
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 { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { import {
EncryptedString, EncryptedString,
@@ -47,6 +49,7 @@ export class OrganizationUserResetPasswordService
private organizationUserApiService: OrganizationUserApiService, private organizationUserApiService: OrganizationUserApiService,
private organizationApiService: OrganizationApiServiceAbstraction, private organizationApiService: OrganizationApiServiceAbstraction,
private i18nService: I18nService, private i18nService: I18nService,
private accountService: AccountService,
) {} ) {}
/** /**
@@ -111,7 +114,14 @@ export class OrganizationUserResetPasswordService
} }
// Decrypt Organization's encrypted Private Key with org key // Decrypt Organization's encrypted Private Key with org key
const orgSymKey = await this.keyService.getOrgKey(orgId); const orgSymKey = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.keyService.orgKeys$(userId)),
map((orgKeys) => orgKeys[orgId as OrganizationId] ?? null),
),
);
if (orgSymKey == null) { if (orgSymKey == null) {
throw new Error("No org key found"); throw new Error("No org key found");
} }

View File

@@ -8,6 +8,7 @@ import {
firstValueFrom, firstValueFrom,
from, from,
lastValueFrom, lastValueFrom,
map,
of, of,
Subject, Subject,
switchMap, switchMap,
@@ -28,6 +29,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { DialogService, ToastService } from "@bitwarden/components"; import { DialogService, ToastService } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management"; import { KeyService } from "@bitwarden/key-management";
@@ -179,7 +181,13 @@ export class AccountComponent implements OnInit, OnDestroy {
// Backfill pub/priv key if necessary // Backfill pub/priv key if necessary
if (!this.org.hasPublicAndPrivateKeys) { if (!this.org.hasPublicAndPrivateKeys) {
const orgShareKey = await this.keyService.getOrgKey(this.organizationId); const orgShareKey = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.keyService.orgKeys$(userId)),
map((orgKeys) => orgKeys[this.organizationId as OrganizationId] ?? null),
),
);
const orgKeys = await this.keyService.makeKeyPair(orgShareKey); const orgKeys = await this.keyService.makeKeyPair(orgShareKey);
request.keys = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString); request.keys = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
} }

View File

@@ -240,9 +240,15 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
return this.groupService.getAll(orgId); return this.groupService.getAll(orgId);
}), }),
); );
const collections = this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.collectionAdminService.collectionAdminViews$(orgId, userId)),
);
combineLatest({ combineLatest({
organization: organization$, organization: organization$,
collections: this.collectionAdminService.getAll(orgId), collections,
groups: groups$, groups: groups$,
users: this.organizationUserApiService.getAllMiniUserDetails(orgId), users: this.organizationUserApiService.getAllMiniUserDetails(orgId),
}) })

View File

@@ -1,13 +1,20 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { CollectionAdminService, CollectionAdminView } from "@bitwarden/admin-console/common"; import { CollectionAdminService, CollectionAdminView } from "@bitwarden/admin-console/common";
import { ImportCollectionServiceAbstraction } from "@bitwarden/importer-core"; import { ImportCollectionServiceAbstraction } from "@bitwarden/importer-core";
import { UserId } from "@bitwarden/user-core";
@Injectable() @Injectable()
export class ImportCollectionAdminService implements ImportCollectionServiceAbstraction { export class ImportCollectionAdminService implements ImportCollectionServiceAbstraction {
constructor(private collectionAdminService: CollectionAdminService) {} constructor(private collectionAdminService: CollectionAdminService) {}
async getAllAdminCollections(organizationId: string): Promise<CollectionAdminView[]> { async getAllAdminCollections(
return await this.collectionAdminService.getAll(organizationId); organizationId: string,
userId: UserId,
): Promise<CollectionAdminView[]> {
return await firstValueFrom(
this.collectionAdminService.collectionAdminViews$(organizationId, userId),
);
} }
} }

View File

@@ -1,7 +1,7 @@
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core"; import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs"; import { firstValueFrom, switchMap } from "rxjs";
import { import {
Unassigned, Unassigned,
@@ -10,6 +10,8 @@ import {
} from "@bitwarden/admin-console/common"; } from "@bitwarden/admin-console/common";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ProductTierType } from "@bitwarden/common/billing/enums"; import { ProductTierType } from "@bitwarden/common/billing/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@@ -93,6 +95,7 @@ export class VaultHeaderComponent {
private dialogService: DialogService, private dialogService: DialogService,
private router: Router, private router: Router,
private configService: ConfigService, private configService: ConfigService,
private accountService: AccountService,
) {} ) {}
/** /**
@@ -225,7 +228,14 @@ export class VaultHeaderComponent {
); );
if (this.organizations?.length == 1 && !!organization) { if (this.organizations?.length == 1 && !!organization) {
const collections = await this.collectionAdminService.getAll(organization.id); const collections = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.collectionAdminService.collectionAdminViews$(organization.id, userId),
),
),
);
if (collections.length === organization.maxCollections) { if (collections.length === organization.maxCollections) {
await this.showFreeOrgUpgradeDialog(organization); await this.showFreeOrgUpgradeDialog(organization);
return; return;

View File

@@ -1,5 +1,5 @@
import { TestBed } from "@angular/core/testing"; import { TestBed } from "@angular/core/testing";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject, of } from "rxjs";
import { CollectionAdminService, CollectionAdminView } from "@bitwarden/admin-console/common"; import { CollectionAdminService, CollectionAdminView } from "@bitwarden/admin-console/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -71,7 +71,7 @@ describe("AdminConsoleCipherFormConfigService", () => {
{ provide: OrganizationService, useValue: { organizations$: () => orgs$ } }, { provide: OrganizationService, useValue: { organizations$: () => orgs$ } },
{ {
provide: CollectionAdminService, provide: CollectionAdminService,
useValue: { getAll: () => Promise.resolve([collection, collection2]) }, useValue: { collectionAdminViews$: () => of([collection, collection2]) },
}, },
{ {
provide: PolicyService, provide: PolicyService,

View File

@@ -31,8 +31,9 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
private apiService: ApiService = inject(ApiService); private apiService: ApiService = inject(ApiService);
private accountService: AccountService = inject(AccountService); private accountService: AccountService = inject(AccountService);
private organizationDataOwnershipDisabled$ = this.accountService.activeAccount$.pipe( private userId$ = this.accountService.activeAccount$.pipe(getUserId);
getUserId,
private organizationDataOwnershipDisabled$ = this.userId$.pipe(
switchMap((userId) => switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId), this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
), ),
@@ -44,9 +45,9 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
filter((filter) => filter !== undefined), filter((filter) => filter !== undefined),
); );
private allOrganizations$ = this.accountService.activeAccount$.pipe( private allOrganizations$ = this.userId$.pipe(
switchMap((account) => switchMap((userId) =>
this.organizationService.organizations$(account?.id).pipe( this.organizationService.organizations$(userId).pipe(
map((orgs) => { map((orgs) => {
return orgs.filter( return orgs.filter(
(o) => o.isMember && o.enabled && o.status === OrganizationUserStatusType.Confirmed, (o) => o.isMember && o.enabled && o.status === OrganizationUserStatusType.Confirmed,
@@ -60,8 +61,8 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
map(([orgs, orgId]) => orgs.find((o) => o.id === orgId)), map(([orgs, orgId]) => orgs.find((o) => o.id === orgId)),
); );
private allCollections$ = this.organization$.pipe( private allCollections$ = combineLatest([this.organization$, this.userId$]).pipe(
switchMap(async (org) => await this.collectionAdminService.getAll(org.id)), switchMap(([org, userId]) => this.collectionAdminService.collectionAdminViews$(org.id, userId)),
); );
async buildConfig( async buildConfig(

View File

@@ -22,6 +22,7 @@ export class ServiceContainer extends OssServiceContainer {
this.keyService, this.keyService,
this.encryptService, this.encryptService,
this.organizationUserApiService, this.organizationUserApiService,
this.accountService,
); );
} }
} }

View File

@@ -1,4 +1,5 @@
import { MockProxy, mock } from "jest-mock-extended"; import { MockProxy, mock } from "jest-mock-extended";
import { of } from "rxjs";
import { import {
OrganizationUserApiService, OrganizationUserApiService,
@@ -8,30 +9,42 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { newGuid } from "@bitwarden/guid";
import { KeyService } from "@bitwarden/key-management"; import { KeyService } from "@bitwarden/key-management";
import { UserId } from "@bitwarden/user-core";
import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service"; import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service";
import { OrganizationAuthRequestUpdateRequest } from "./organization-auth-request-update.request"; import { OrganizationAuthRequestUpdateRequest } from "./organization-auth-request-update.request";
import { OrganizationAuthRequestService } from "./organization-auth-request.service"; import { OrganizationAuthRequestService } from "./organization-auth-request.service";
import { PendingAuthRequestView } from "./pending-auth-request.view"; import { PendingAuthRequestView } from "./pending-auth-request.view";
import {
FakeAccountService,
mockAccountServiceWith,
} from "@bitwarden/common/../spec/fake-account-service";
describe("OrganizationAuthRequestService", () => { describe("OrganizationAuthRequestService", () => {
let organizationAuthRequestApiService: MockProxy<OrganizationAuthRequestApiService>; let organizationAuthRequestApiService: MockProxy<OrganizationAuthRequestApiService>;
let keyService: MockProxy<KeyService>; let keyService: MockProxy<KeyService>;
let encryptService: MockProxy<EncryptService>; let encryptService: MockProxy<EncryptService>;
let organizationUserApiService: MockProxy<OrganizationUserApiService>; let organizationUserApiService: MockProxy<OrganizationUserApiService>;
let organizationAuthRequestService: OrganizationAuthRequestService; let organizationAuthRequestService: OrganizationAuthRequestService;
const mockUserId = newGuid() as UserId;
let accountService: FakeAccountService;
beforeEach(() => { beforeEach(() => {
organizationAuthRequestApiService = mock<OrganizationAuthRequestApiService>(); organizationAuthRequestApiService = mock<OrganizationAuthRequestApiService>();
keyService = mock<KeyService>(); keyService = mock<KeyService>();
encryptService = mock<EncryptService>(); encryptService = mock<EncryptService>();
organizationUserApiService = mock<OrganizationUserApiService>(); organizationUserApiService = mock<OrganizationUserApiService>();
accountService = mockAccountServiceWith(mockUserId);
organizationAuthRequestService = new OrganizationAuthRequestService( organizationAuthRequestService = new OrganizationAuthRequestService(
organizationAuthRequestApiService, organizationAuthRequestApiService,
keyService, keyService,
encryptService, encryptService,
organizationUserApiService, organizationUserApiService,
accountService,
); );
}); });
@@ -162,6 +175,7 @@ describe("OrganizationAuthRequestService", () => {
describe("approvePendingRequests", () => { describe("approvePendingRequests", () => {
it("should approve the specified pending auth requests", async () => { it("should approve the specified pending auth requests", async () => {
jest.spyOn(organizationAuthRequestApiService, "bulkUpdatePendingRequests"); jest.spyOn(organizationAuthRequestApiService, "bulkUpdatePendingRequests");
jest.spyOn(keyService, "orgKeys$").mockReturnValue(of({ key: "fake-key" }));
const organizationId = "organizationId"; const organizationId = "organizationId";
@@ -213,6 +227,7 @@ describe("OrganizationAuthRequestService", () => {
describe("approvePendingRequest", () => { describe("approvePendingRequest", () => {
it("should approve the specified pending auth request", async () => { it("should approve the specified pending auth request", async () => {
jest.spyOn(organizationAuthRequestApiService, "approvePendingRequest"); jest.spyOn(organizationAuthRequestApiService, "approvePendingRequest");
jest.spyOn(keyService, "orgKeys$").mockReturnValue(of({ key: "fake-key" }));
const organizationId = "organizationId"; const organizationId = "organizationId";

View File

@@ -1,12 +1,17 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { firstValueFrom, map, switchMap } from "rxjs";
import { import {
OrganizationUserApiService, OrganizationUserApiService,
OrganizationUserResetPasswordDetailsResponse, OrganizationUserResetPasswordDetailsResponse,
} from "@bitwarden/admin-console/common"; } from "@bitwarden/admin-console/common";
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 { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { KeyService } from "@bitwarden/key-management"; import { KeyService } from "@bitwarden/key-management";
import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service"; import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service";
@@ -20,6 +25,7 @@ export class OrganizationAuthRequestService {
private keyService: KeyService, private keyService: KeyService,
private encryptService: EncryptService, private encryptService: EncryptService,
private organizationUserApiService: OrganizationUserApiService, private organizationUserApiService: OrganizationUserApiService,
private accountService: AccountService,
) {} ) {}
async listPendingRequests(organizationId: string): Promise<PendingAuthRequestView[]> { async listPendingRequests(organizationId: string): Promise<PendingAuthRequestView[]> {
@@ -122,7 +128,13 @@ export class OrganizationAuthRequestService {
const devicePubKey = Utils.fromB64ToArray(devicePublicKey); const devicePubKey = Utils.fromB64ToArray(devicePublicKey);
// Decrypt Organization's encrypted Private Key with org key // Decrypt Organization's encrypted Private Key with org key
const orgSymKey = await this.keyService.getOrgKey(organizationId); const orgSymKey = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.keyService.orgKeys$(userId)),
map((orgKeys) => orgKeys[organizationId as OrganizationId] ?? null),
),
);
const decOrgPrivateKey = await this.encryptService.decryptBytes( const decOrgPrivateKey = await this.encryptService.decryptBytes(
new EncString(encryptedOrgPrivateKey), new EncString(encryptedOrgPrivateKey),
orgSymKey, orgSymKey,

View File

@@ -11,6 +11,7 @@ import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-cons
import { PendingAuthRequestWithFingerprintView } from "@bitwarden/bit-common/admin-console/auth-requests/pending-auth-request-with-fingerprint.view"; import { PendingAuthRequestWithFingerprintView } from "@bitwarden/bit-common/admin-console/auth-requests/pending-auth-request-with-fingerprint.view";
import { PendingAuthRequestView } from "@bitwarden/bit-common/admin-console/auth-requests/pending-auth-request.view"; import { PendingAuthRequestView } from "@bitwarden/bit-common/admin-console/auth-requests/pending-auth-request.view";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -38,6 +39,7 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module";
KeyService, KeyService,
EncryptService, EncryptService,
OrganizationUserApiService, OrganizationUserApiService,
AccountService,
], ],
}), }),
] satisfies SafeProvider[], ] satisfies SafeProvider[],

View File

@@ -8,6 +8,8 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; 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 { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { ProviderAddOrganizationRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-add-organization.request"; import { ProviderAddOrganizationRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-add-organization.request";
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 { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { PlanType } from "@bitwarden/common/billing/enums"; import { PlanType } from "@bitwarden/common/billing/enums";
import { CreateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/create-client-organization.request"; import { CreateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/create-client-organization.request";
@@ -30,10 +32,17 @@ export class WebProviderService {
private billingApiService: BillingApiServiceAbstraction, private billingApiService: BillingApiServiceAbstraction,
private stateProvider: StateProvider, private stateProvider: StateProvider,
private providerApiService: ProviderApiServiceAbstraction, private providerApiService: ProviderApiServiceAbstraction,
private accountService: AccountService,
) {} ) {}
async addOrganizationToProvider(providerId: string, organizationId: string) { async addOrganizationToProvider(providerId: string, organizationId: string) {
const orgKey = await this.keyService.getOrgKey(organizationId); const orgKey = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.keyService.orgKeys$(userId)),
map((orgKeys) => orgKeys[organizationId as OrganizationId] ?? null),
),
);
const providerKey = await this.keyService.getProviderKey(providerId); const providerKey = await this.keyService.getProviderKey(providerId);
const encryptedOrgKey = await this.encryptService.wrapSymmetricKey(orgKey, providerKey); const encryptedOrgKey = await this.encryptService.wrapSymmetricKey(orgKey, providerKey);

View File

@@ -1,14 +1,15 @@
import { Observable } from "rxjs";
import { CollectionDetailsResponse } from "@bitwarden/admin-console/common"; import { CollectionDetailsResponse } from "@bitwarden/admin-console/common";
import { UserId } from "@bitwarden/common/types/guid"; import { UserId } from "@bitwarden/common/types/guid";
import { CollectionAccessSelectionView, CollectionAdminView } from "../models"; import { CollectionAccessSelectionView, CollectionAdminView } from "../models";
export abstract class CollectionAdminService { export abstract class CollectionAdminService {
abstract getAll(organizationId: string): Promise<CollectionAdminView[]>; abstract collectionAdminViews$(
abstract get(
organizationId: string, organizationId: string,
collectionId: string, userId: UserId,
): Promise<CollectionAdminView | undefined>; ): Observable<CollectionAdminView[]>;
abstract save( abstract save(
collection: CollectionAdminView, collection: CollectionAdminView,
userId: UserId, userId: UserId,

View File

@@ -1,11 +1,14 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { combineLatest, firstValueFrom, from, map, Observable, of, switchMap } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { CollectionId, UserId } from "@bitwarden/common/types/guid"; import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { OrgKey } from "@bitwarden/common/types/key";
import { KeyService } from "@bitwarden/key-management"; import { KeyService } from "@bitwarden/key-management";
import { CollectionAdminService, CollectionService } from "../abstractions"; import { CollectionAdminService, CollectionService } from "../abstractions";
@@ -28,37 +31,23 @@ export class DefaultCollectionAdminService implements CollectionAdminService {
private collectionService: CollectionService, private collectionService: CollectionService,
) {} ) {}
async getAll(organizationId: string): Promise<CollectionAdminView[]> { collectionAdminViews$(organizationId: string, userId: UserId): Observable<CollectionAdminView[]> {
const collectionResponse = return combineLatest([
await this.apiService.getManyCollectionsWithAccessDetails(organizationId); this.keyService.orgKeys$(userId),
from(this.apiService.getManyCollectionsWithAccessDetails(organizationId)),
]).pipe(
switchMap(([orgKey, res]) => {
if (res?.data == null || res.data.length === 0) {
return of([]);
}
if (collectionResponse?.data == null || collectionResponse.data.length === 0) { return this.decryptMany(organizationId, res.data, orgKey);
return []; }),
}
return await this.decryptMany(organizationId, collectionResponse.data);
}
async get(
organizationId: string,
collectionId: string,
): Promise<CollectionAdminView | undefined> {
const collectionResponse = await this.apiService.getCollectionAccessDetails(
organizationId,
collectionId,
); );
if (collectionResponse == null) {
return undefined;
}
const [view] = await this.decryptMany(organizationId, [collectionResponse]);
return view;
} }
async save(collection: CollectionAdminView, userId: UserId): Promise<CollectionDetailsResponse> { async save(collection: CollectionAdminView, userId: UserId): Promise<CollectionDetailsResponse> {
const request = await this.encrypt(collection); const request = await this.encrypt(collection, userId);
let response: CollectionDetailsResponse; let response: CollectionDetailsResponse;
if (collection.id == null) { if (collection.id == null) {
@@ -112,13 +101,15 @@ export class DefaultCollectionAdminService implements CollectionAdminService {
private async decryptMany( private async decryptMany(
organizationId: string, organizationId: string,
collections: CollectionResponse[] | CollectionAccessDetailsResponse[], collections: CollectionResponse[] | CollectionAccessDetailsResponse[],
orgKeys: Record<OrganizationId, OrgKey>,
): Promise<CollectionAdminView[]> { ): Promise<CollectionAdminView[]> {
const orgKey = await this.keyService.getOrgKey(organizationId);
const promises = collections.map(async (c) => { const promises = collections.map(async (c) => {
const view = new CollectionAdminView(); const view = new CollectionAdminView();
view.id = c.id; view.id = c.id;
view.name = await this.encryptService.decryptString(new EncString(c.name), orgKey); view.name = await this.encryptService.decryptString(
new EncString(c.name),
orgKeys[organizationId as OrganizationId],
);
view.externalId = c.externalId; view.externalId = c.externalId;
view.organizationId = c.organizationId; view.organizationId = c.organizationId;
@@ -138,11 +129,15 @@ export class DefaultCollectionAdminService implements CollectionAdminService {
return await Promise.all(promises); return await Promise.all(promises);
} }
private async encrypt(model: CollectionAdminView): Promise<CollectionRequest> { private async encrypt(model: CollectionAdminView, userId: UserId): Promise<CollectionRequest> {
if (model.organizationId == null) { if (model.organizationId == null) {
throw new Error("Collection has no organization id."); throw new Error("Collection has no organization id.");
} }
const key = await this.keyService.getOrgKey(model.organizationId); const key = await firstValueFrom(
this.keyService
.orgKeys$(userId)
.pipe(map((orgKeys) => orgKeys[model.organizationId] ?? null)),
);
if (key == null) { if (key == null) {
throw new Error("No key for this collection's organization."); throw new Error("No key for this collection's organization.");
} }

View File

@@ -279,7 +279,7 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit {
this.collections$ = Utils.asyncToObservable(() => this.collections$ = Utils.asyncToObservable(() =>
this.importCollectionService this.importCollectionService
.getAllAdminCollections(this.organizationId) .getAllAdminCollections(this.organizationId, userId)
.then((collections) => collections.sort(Utils.getSortFunction(this.i18nService, "name"))), .then((collections) => collections.sort(Utils.getSortFunction(this.i18nService, "name"))),
); );

View File

@@ -3,7 +3,8 @@
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { CollectionView } from "@bitwarden/admin-console/common"; import { CollectionView } from "@bitwarden/admin-console/common";
import { UserId } from "@bitwarden/user-core";
export abstract class ImportCollectionServiceAbstraction { export abstract class ImportCollectionServiceAbstraction {
getAllAdminCollections: (organizationId: string) => Promise<CollectionView[]>; getAllAdminCollections: (organizationId: string, userId: UserId) => Promise<CollectionView[]>;
} }