From df1cea0f1b66addbfe2ad820984b9bf78f6fcaad Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 29 Oct 2025 11:56:57 +0100 Subject: [PATCH] Tools changes --- .../send/models/domain/send-access.spec.ts | 7 ++- .../tools/send/models/domain/send-access.ts | 3 +- .../send/models/domain/send-file.spec.ts | 8 +++- .../src/tools/send/models/domain/send-file.ts | 1 + .../send/models/domain/send-text.spec.ts | 16 ++++--- .../src/tools/send/models/domain/send-text.ts | 1 + .../src/tools/send/models/domain/send.spec.ts | 44 +++++++++---------- .../src/tools/send/models/domain/send.ts | 9 +++- .../bitwarden/bitwarden-json-importer.ts | 25 ++++++----- 9 files changed, 72 insertions(+), 42 deletions(-) diff --git a/libs/common/src/tools/send/models/domain/send-access.spec.ts b/libs/common/src/tools/send/models/domain/send-access.spec.ts index 597208d517c..e597e7d7b21 100644 --- a/libs/common/src/tools/send/models/domain/send-access.spec.ts +++ b/libs/common/src/tools/send/models/domain/send-access.spec.ts @@ -1,6 +1,7 @@ import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; -import { mockEnc } from "../../../../../spec"; +import { makeSymmetricCryptoKey, mockContainerService, mockEnc } from "../../../../../spec"; import { SendType } from "../../enums/send-type"; import { SendAccessResponse } from "../response/send-access.response"; @@ -55,6 +56,10 @@ describe("SendAccess", () => { }); it("Decrypt", async () => { + const containerService = mockContainerService(); + containerService.getKeyService().userKey$.mockReturnValue(of(makeSymmetricCryptoKey(64))); + containerService.getEncryptService().decryptString.mockResolvedValue("name"); + const sendAccess = new SendAccess(); sendAccess.id = "id"; sendAccess.type = SendType.Text; diff --git a/libs/common/src/tools/send/models/domain/send-access.ts b/libs/common/src/tools/send/models/domain/send-access.ts index 2251e8e5a5a..898f88bf3ff 100644 --- a/libs/common/src/tools/send/models/domain/send-access.ts +++ b/libs/common/src/tools/send/models/domain/send-access.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore + import { EncString } from "../../../../key-management/crypto/models/enc-string"; import Domain from "../../../../platform/models/domain/domain-base"; import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key"; @@ -54,7 +55,7 @@ export class SendAccess extends Domain { async decrypt(key: SymmetricCryptoKey): Promise { const model = new SendAccessView(this); - await this.decryptObj(this, model, ["name"], null, key); + await this.decryptObj(this, model, ["name"], null, key, null); switch (this.type) { case SendType.File: diff --git a/libs/common/src/tools/send/models/domain/send-file.spec.ts b/libs/common/src/tools/send/models/domain/send-file.spec.ts index 44a84cdc639..f9b0351741c 100644 --- a/libs/common/src/tools/send/models/domain/send-file.spec.ts +++ b/libs/common/src/tools/send/models/domain/send-file.spec.ts @@ -1,4 +1,6 @@ -import { mockEnc } from "../../../../../spec"; +import { of } from "rxjs"; + +import { makeSymmetricCryptoKey, mockContainerService, mockEnc } from "../../../../../spec"; import { SendFileData } from "../data/send-file.data"; import { SendFile } from "./send-file"; @@ -39,6 +41,10 @@ describe("SendFile", () => { }); it("Decrypt", async () => { + const containerService = mockContainerService(); + containerService.getKeyService().userKey$.mockReturnValue(of(makeSymmetricCryptoKey(64))); + containerService.getEncryptService().decryptString.mockResolvedValue("fileName"); + const sendFile = new SendFile(); sendFile.id = "id"; sendFile.size = "1100"; diff --git a/libs/common/src/tools/send/models/domain/send-file.ts b/libs/common/src/tools/send/models/domain/send-file.ts index 228f4ee81ca..3403ab5b67c 100644 --- a/libs/common/src/tools/send/models/domain/send-file.ts +++ b/libs/common/src/tools/send/models/domain/send-file.ts @@ -40,6 +40,7 @@ export class SendFile extends Domain { ["fileName"], null, key, + null, ); } diff --git a/libs/common/src/tools/send/models/domain/send-text.spec.ts b/libs/common/src/tools/send/models/domain/send-text.spec.ts index 6af143ec594..c173f0bf982 100644 --- a/libs/common/src/tools/send/models/domain/send-text.spec.ts +++ b/libs/common/src/tools/send/models/domain/send-text.spec.ts @@ -1,4 +1,6 @@ -import { mockEnc } from "../../../../../spec"; +import { of } from "rxjs"; + +import { makeSymmetricCryptoKey, mockContainerService, mockEnc } from "../../../../../spec"; import { SendTextData } from "../data/send-text.data"; import { SendText } from "./send-text"; @@ -33,11 +35,15 @@ describe("SendText", () => { }); it("Decrypt", async () => { - const secureNote = new SendText(); - secureNote.text = mockEnc("text"); - secureNote.hidden = true; + const containerService = mockContainerService(); + containerService.getKeyService().userKey$.mockReturnValue(of(makeSymmetricCryptoKey(64))); + containerService.getEncryptService().decryptString.mockResolvedValue("text"); - const view = await secureNote.decrypt(null); + const sendText = new SendText(); + sendText.text = mockEnc("text"); + sendText.hidden = true; + + const view = await sendText.decrypt(null); expect(view).toEqual({ text: "text", diff --git a/libs/common/src/tools/send/models/domain/send-text.ts b/libs/common/src/tools/send/models/domain/send-text.ts index 321810292ad..b4bd30937fc 100644 --- a/libs/common/src/tools/send/models/domain/send-text.ts +++ b/libs/common/src/tools/send/models/domain/send-text.ts @@ -36,6 +36,7 @@ export class SendText extends Domain { ["text"], null, key, + null, ); } diff --git a/libs/common/src/tools/send/models/domain/send.spec.ts b/libs/common/src/tools/send/models/domain/send.spec.ts index d465aa97924..22a497ab9ac 100644 --- a/libs/common/src/tools/send/models/domain/send.spec.ts +++ b/libs/common/src/tools/send/models/domain/send.spec.ts @@ -1,16 +1,14 @@ -import { mock } from "jest-mock-extended"; import { of } from "rxjs"; +import mock from "@bitwarden/common/platform/spec/mock-deep"; import { emptyGuid, UserId } from "@bitwarden/common/types/guid"; -// 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 -import { KeyService } from "@bitwarden/key-management"; -import { makeStaticByteArray, mockEnc } from "../../../../../spec"; -import { EncryptService } from "../../../../key-management/crypto/abstractions/encrypt.service"; -import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key"; -import { ContainerService } from "../../../../platform/services/container.service"; -import { UserKey } from "../../../../types/key"; +import { + mockEnc, + makeStaticByteArray, + mockContainerService, + makeSymmetricCryptoKey, +} from "../../../../../spec"; import { SendType } from "../../enums/send-type"; import { SendData } from "../data/send.data"; @@ -96,9 +94,18 @@ describe("Send", () => { }); it("Decrypt", async () => { + const containerService = mockContainerService(); + containerService.getKeyService().userKey$.mockReturnValue(of(makeSymmetricCryptoKey(64))); + containerService + .getEncryptService() + .decryptString.mockResolvedValueOnce("name") + .mockResolvedValueOnce("notes"); + containerService + .getEncryptService() + .decryptBytes.mockResolvedValueOnce(makeStaticByteArray(32)); + const text = mock(); text.decrypt.mockResolvedValue("textView" as any); - const userKey = new SymmetricCryptoKey(new Uint8Array(32)) as UserKey; const userId = emptyGuid as UserId; const send = new Send(); @@ -117,24 +124,15 @@ describe("Send", () => { send.disabled = false; send.hideEmail = true; - const encryptService = mock(); - const keyService = mock(); - encryptService.decryptBytes - .calledWith(send.key, userKey) - .mockResolvedValue(makeStaticByteArray(32)); - keyService.makeSendKey.mockResolvedValue("cryptoKey" as any); - keyService.userKey$.calledWith(userId).mockReturnValue(of(userKey)); - - (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); + containerService.getKeyService().makeSendKey.mockResolvedValue("cryptoKey" as any); const view = await send.decrypt(userId); - expect(text.decrypt).toHaveBeenNthCalledWith(1, "cryptoKey"); - expect(send.name.decrypt).toHaveBeenNthCalledWith( + expect(text.decrypt).toHaveBeenNthCalledWith(1, userId, "cryptoKey"); + expect(containerService.getEncryptService().decryptString).toHaveBeenNthCalledWith( 1, - null, + "name", "cryptoKey", - "Property: name; ObjectContext: No Domain Context", ); expect(view).toMatchObject({ diff --git a/libs/common/src/tools/send/models/domain/send.ts b/libs/common/src/tools/send/models/domain/send.ts index 48129d4314a..90d5ae1ef62 100644 --- a/libs/common/src/tools/send/models/domain/send.ts +++ b/libs/common/src/tools/send/models/domain/send.ts @@ -89,7 +89,14 @@ export class Send extends Domain { model.key = await encryptService.decryptBytes(this.key, sendKeyEncryptionKey); model.cryptoKey = await keyService.makeSendKey(model.key); - await this.decryptObj(this, model, ["name", "notes"], null, model.cryptoKey); + await this.decryptObj( + this, + model, + ["name", "notes"], + null, + model.cryptoKey, + null, + ); switch (this.type) { case SendType.File: diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index 76965a364eb..e624fa5d5e0 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -6,6 +6,7 @@ import { concatMap, firstValueFrom, map } from "rxjs"; // eslint-disable-next-line no-restricted-imports import { Collection, CollectionView } 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 { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { @@ -15,7 +16,7 @@ import { } from "@bitwarden/common/models/export"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { KeyService } from "@bitwarden/key-management"; @@ -52,10 +53,11 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { return this.result; } + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); if (results.encrypted) { - await this.parseEncrypted(results as any); + await this.parseEncrypted(results as any, userId); } else { - await this.parseDecrypted(results as any); + await this.parseDecrypted(results as any, userId); } return this.result; @@ -63,14 +65,13 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { private async parseEncrypted( results: BitwardenEncryptedIndividualJsonExport | BitwardenEncryptedOrgJsonExport, + userId: UserId, ) { - const account = await firstValueFrom(this.accountService.activeAccount$); - if (results.encKeyValidation_DO_NOT_EDIT != null) { - const orgKeys = await firstValueFrom(this.keyService.orgKeys$(account.id)); + const orgKeys = await firstValueFrom(this.keyService.orgKeys$(userId)); let keyForDecryption: SymmetricCryptoKey = orgKeys?.[this.organizationId]; if (keyForDecryption == null) { - keyForDecryption = await firstValueFrom(this.keyService.userKey$(account.id)); + keyForDecryption = await firstValueFrom(this.keyService.userKey$(userId)); } const encKeyValidation = new EncString(results.encKeyValidation_DO_NOT_EDIT); try { @@ -84,7 +85,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { const groupingsMap = this.organization ? await this.parseCollections(results as BitwardenEncryptedOrgJsonExport) - : await this.parseFolders(results as BitwardenEncryptedIndividualJsonExport); + : await this.parseFolders(results as BitwardenEncryptedIndividualJsonExport, userId); for (const c of results.items) { const cipher = CipherWithIdExport.toDomain(c); @@ -114,7 +115,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { }); } - const view = await this.cipherService.decrypt(cipher, account.id); + const view = await this.cipherService.decrypt(cipher, userId); this.cleanupCipher(view); this.result.ciphers.push(view); } @@ -124,10 +125,11 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { private async parseDecrypted( results: BitwardenUnEncryptedIndividualJsonExport | BitwardenUnEncryptedOrgJsonExport, + userId: UserId, ) { const groupingsMap = this.organization ? await this.parseCollections(results as BitwardenUnEncryptedOrgJsonExport) - : await this.parseFolders(results as BitwardenUnEncryptedIndividualJsonExport); + : await this.parseFolders(results as BitwardenUnEncryptedIndividualJsonExport, userId); results.items.forEach((c) => { const cipher = CipherWithIdExport.toView(c); @@ -166,6 +168,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { private async parseFolders( data: BitwardenUnEncryptedIndividualJsonExport | BitwardenEncryptedIndividualJsonExport, + userId: UserId, ): Promise> | null { if (data.folders == null) { return null; @@ -178,6 +181,8 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { if (data.encrypted) { const folder = FolderWithIdExport.toDomain(f); if (folder != null) { + // A follow-up PR in the PR chain https://bitwarden.atlassian.net/browse/PM-24102 will use the userId here + // after vault's changes. folderView = await folder.decrypt(); } } else {