From dedd7f1b5cc66038abb120b4fae83666c487ad04 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Tue, 20 Aug 2024 12:00:48 -0400 Subject: [PATCH] [PM-10607] Require userId for getKeyForCipherKeyDecryption (#10509) * updated cipher service to stop using the deprecated getUserKeyWithLegacySupport and use the version that requires a user id * Added account service mock * fixed cipher test * Fixed test * removed async from encryptCipher * updated encryptSharedCipher to pass userId to the encrypt function * Pass userId to getUserKeyWithLegacySupport on encryptSharedCipher * pass in userid when setting masterKeyEncryptedUserKey * Added activer usedId to new web refresh function --- .../notification.background.spec.ts | 17 +++++++- .../background/notification.background.ts | 24 +++++++++-- .../autofill/popup/fido2/fido2.component.ts | 20 +++++++-- .../browser/src/background/main.background.ts | 4 ++ .../assign-collections.component.ts | 19 ++++++--- .../attachments-v2.component.spec.ts | 11 +++++ .../open-attachments.component.spec.ts | 12 +++++- .../open-attachments.component.ts | 8 +++- .../item-more-options.component.ts | 8 +++- .../view-v2/view-v2.component.spec.ts | 11 +++++ .../vault-v2/view-v2/view-v2.component.ts | 11 ++++- .../components/vault/attachments.component.ts | 3 ++ .../components/vault/collections.component.ts | 3 ++ .../vault/password-history.component.ts | 4 +- .../popup/components/vault/share.component.ts | 3 ++ .../popup/components/vault/view.component.ts | 10 ++++- .../vault-popup-autofill.service.spec.ts | 14 ++++++- .../services/vault-popup-autofill.service.ts | 7 +++- .../admin-console/commands/share.command.ts | 18 ++++++-- apps/cli/src/commands/edit.command.ts | 18 ++++++-- apps/cli/src/commands/get.command.ts | 9 +++- apps/cli/src/oss-serve-configurator.ts | 8 +++- .../service-container/service-container.ts | 2 + apps/cli/src/tools/send/send.program.ts | 1 + apps/cli/src/vault.program.ts | 8 +++- apps/cli/src/vault/create.command.ts | 17 ++++++-- .../encrypted-message-handler.service.ts | 12 ++++-- .../vault/app/vault/attachments.component.ts | 3 ++ .../vault/app/vault/collections.component.ts | 3 ++ .../app/vault/password-history.component.ts | 4 +- .../src/vault/app/vault/share.component.ts | 3 ++ .../src/vault/app/vault/view.component.ts | 3 ++ .../emergency-access-attachments.component.ts | 3 ++ .../individual-vault/attachments.component.ts | 3 ++ .../bulk-share-dialog.component.ts | 7 ++++ .../individual-vault/collections.component.ts | 3 ++ .../vault/individual-vault/share.component.ts | 3 ++ .../vault/individual-vault/vault.component.ts | 7 +++- .../app/vault/org-vault/add-edit.component.ts | 8 ++-- .../vault/org-vault/attachments.component.ts | 7 +++- .../vault/org-vault/collections.component.ts | 3 ++ .../app/vault/org-vault/vault.component.ts | 7 +++- .../components/collections.component.ts | 8 +++- .../angular/src/components/share.component.ts | 14 +++++-- .../src/services/jslib-services.module.ts | 2 + .../vault/components/add-edit.component.ts | 15 +++++-- .../vault/components/attachments.component.ts | 27 ++++++++---- .../components/password-history.component.ts | 8 +++- .../src/vault/components/view.component.ts | 9 +++- .../fido2/fido2-authenticator.service.spec.ts | 21 +++++++++- .../fido2/fido2-authenticator.service.ts | 17 ++++++-- .../src/platform/sync/default-sync.service.ts | 2 +- .../src/vault/abstractions/cipher.service.ts | 7 +++- .../src/vault/models/domain/cipher.spec.ts | 12 ++++-- .../src/vault/services/cipher.service.spec.ts | 14 ++++--- .../src/vault/services/cipher.service.ts | 41 ++++++++++++------- ...warden-password-protected-importer.spec.ts | 4 ++ .../src/components/import.component.ts | 2 + .../bitwarden/bitwarden-json-importer.ts | 9 +++- .../bitwarden-password-protected-importer.ts | 4 +- .../src/services/import.service.spec.ts | 3 ++ libs/importer/src/services/import.service.ts | 15 ++++++- .../src/services/org-vault-export.service.ts | 9 +++- .../cipher-attachments.component.spec.ts | 15 ++++++- .../cipher-attachments.component.ts | 14 +++++-- .../services/default-cipher-form.service.ts | 18 ++++++-- .../assign-collections.component.ts | 13 +++++- 67 files changed, 534 insertions(+), 118 deletions(-) diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 111871d57dc..2c9232d8b5c 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -2,6 +2,7 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; +import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { ExtensionCommand } from "@bitwarden/common/autofill/constants"; @@ -12,6 +13,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; @@ -55,6 +57,7 @@ describe("NotificationBackground", () => { const logService = mock(); const themeStateService = mock(); const configService = mock(); + const accountService = mock(); beforeEach(() => { notificationBackground = new NotificationBackground( @@ -69,6 +72,7 @@ describe("NotificationBackground", () => { logService, themeStateService, configService, + accountService, ); }); @@ -691,6 +695,13 @@ describe("NotificationBackground", () => { }); describe("saveOrUpdateCredentials", () => { + const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ + id: "testId" as UserId, + email: "test@example.com", + emailVerified: true, + name: "Test User", + }); + let getDecryptedCipherByIdSpy: jest.SpyInstance; let getAllDecryptedForUrlSpy: jest.SpyInstance; let updatePasswordSpy: jest.SpyInstance; @@ -727,6 +738,8 @@ describe("NotificationBackground", () => { updateWithServerSpy = jest.spyOn(cipherService, "updateWithServer"); folderExistsSpy = jest.spyOn(notificationBackground as any, "folderExists"); cipherEncryptSpy = jest.spyOn(cipherService, "encrypt"); + + accountService.activeAccount$ = activeAccountSubject; }); it("skips saving the cipher if the notification queue does not have a tab that is related to the sender", async () => { @@ -981,7 +994,7 @@ describe("NotificationBackground", () => { queueMessage, null, ); - expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView); + expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView, "testId"); expect(createWithServerSpy).toHaveBeenCalled(); expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, { command: "saveCipherAttemptCompleted", @@ -1020,7 +1033,7 @@ describe("NotificationBackground", () => { sendMockExtensionMessage(message, sender); await flushPromises(); - expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView); + expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView, "testId"); expect(createWithServerSpy).toThrow(errorMessage); expect(tabSendMessageSpy).not.toHaveBeenCalledWith(sender.tab, { command: "addedCipher", diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index a047a3533a0..25f45bd0659 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -1,7 +1,8 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { @@ -90,6 +91,7 @@ export default class NotificationBackground { private logService: LogService, private themeStateService: ThemeStateService, private configService: ConfigService, + private accountService: AccountService, ) {} async init() { @@ -556,7 +558,11 @@ export default class NotificationBackground { return; } - const cipher = await this.cipherService.encrypt(newCipher); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + const cipher = await this.cipherService.encrypt(newCipher, activeUserId); try { await this.cipherService.createWithServer(cipher); await BrowserApi.tabSendMessage(tab, { command: "saveCipherAttemptCompleted" }); @@ -594,7 +600,11 @@ export default class NotificationBackground { return; } - const cipher = await this.cipherService.encrypt(cipherView); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + const cipher = await this.cipherService.encrypt(cipherView, activeUserId); try { // We've only updated the password, no need to broadcast editedCipher message await this.cipherService.updateWithServer(cipher); @@ -634,7 +644,13 @@ export default class NotificationBackground { private async getDecryptedCipherById(cipherId: string) { const cipher = await this.cipherService.get(cipherId); if (cipher != null && cipher.type === CipherType.Login) { - return await cipher.decrypt(await this.cipherService.getKeyForCipherKeyDecryption(cipher)); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + return await cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); } return null; } diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index 43e8ce6809c..8bd667c17fb 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -14,6 +14,7 @@ import { } from "rxjs"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -80,6 +81,7 @@ export class Fido2Component implements OnInit, OnDestroy { private browserMessagingApi: ZonedMessageListenerService, private passwordRepromptService: PasswordRepromptService, private fido2UserVerificationService: Fido2UserVerificationService, + private accountService: AccountService, ) {} ngOnInit() { @@ -156,11 +158,15 @@ export class Fido2Component implements OnInit, OnDestroy { } case "PickCredentialRequest": { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.ciphers = await Promise.all( message.cipherIds.map(async (cipherId) => { const cipher = await this.cipherService.get(cipherId); return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); }), ); @@ -172,11 +178,15 @@ export class Fido2Component implements OnInit, OnDestroy { } case "InformExcludedCredentialRequest": { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.ciphers = await Promise.all( message.existingCipherIds.map(async (cipherId) => { const cipher = await this.cipherService.get(cipherId); return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); }), ); @@ -378,8 +388,12 @@ export class Fido2Component implements OnInit, OnDestroy { } private async createNewCipher(name: string, username: string) { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.buildCipher(name, username); - const cipher = await this.cipherService.encrypt(this.cipher); + const cipher = await this.cipherService.encrypt(this.cipher, activeUserId); try { await this.cipherService.createWithServer(cipher); this.cipher.id = cipher.id; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 3e0e2d42e8b..4081e0e0cd9 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -956,6 +956,7 @@ export default class MainBackground { this.collectionService, this.cryptoService, this.pinService, + this.accountService, ); this.individualVaultExportService = new IndividualVaultExportService( @@ -975,6 +976,7 @@ export default class MainBackground { this.cryptoFunctionService, this.collectionService, this.kdfConfigService, + this.accountService, ); this.exportService = new VaultExportService( @@ -1000,6 +1002,7 @@ export default class MainBackground { this.cipherService, this.fido2UserInterfaceService, this.syncService, + this.accountService, this.logService, ); const fido2ActiveRequestManager = new Fido2ActiveRequestManager(); @@ -1093,6 +1096,7 @@ export default class MainBackground { this.logService, this.themeStateService, this.configService, + this.accountService, ); this.filelessImporterBackground = new FilelessImporterBackground( diff --git a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts index a0ab3401f4d..8c8827336ea 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts @@ -3,9 +3,10 @@ import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ReactiveFormsModule } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { Observable, combineLatest, first, switchMap } from "rxjs"; +import { Observable, combineLatest, first, map, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; @@ -52,18 +53,24 @@ export class AssignCollections { private location: Location, private collectionService: CollectionService, private cipherService: CipherService, + private accountService: AccountService, route: ActivatedRoute, ) { - const $cipher: Observable = route.queryParams.pipe( + const cipher$: Observable = route.queryParams.pipe( switchMap(({ cipherId }) => this.cipherService.get(cipherId)), switchMap((cipherDomain) => - this.cipherService - .getKeyForCipherKeyDecryption(cipherDomain) - .then(cipherDomain.decrypt.bind(cipherDomain)), + this.accountService.activeAccount$.pipe( + map((account) => account?.id), + switchMap((userId) => + this.cipherService + .getKeyForCipherKeyDecryption(cipherDomain, userId) + .then(cipherDomain.decrypt.bind(cipherDomain)), + ), + ), ), ); - combineLatest([$cipher, this.collectionService.decryptedCollections$]) + combineLatest([cipher$, this.collectionService.decryptedCollections$]) .pipe(takeUntilDestroyed(), first()) .subscribe(([cipherView, collections]) => { let availableCollections = collections.filter((c) => !c.readOnly); diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts index ef430bbc176..29793a41ec9 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts @@ -5,10 +5,14 @@ import { ActivatedRoute, Router } from "@angular/router"; import { mock } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { ButtonComponent } from "@bitwarden/components"; import { CipherAttachmentsComponent } from "@bitwarden/vault"; @@ -46,6 +50,9 @@ describe("AttachmentsV2Component", () => { const navigate = jest.fn(); const back = jest.fn().mockResolvedValue(undefined); + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + beforeEach(async () => { back.mockClear(); navigate.mockClear(); @@ -66,6 +73,10 @@ describe("AttachmentsV2Component", () => { queryParams, }, }, + { + provide: AccountService, + useValue: accountService, + }, ], }) .overrideComponent(AttachmentsV2Component, { diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts index b028ffe5831..8c1e0641b03 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts @@ -5,10 +5,13 @@ import { BehaviorSubject } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -51,6 +54,9 @@ describe("OpenAttachmentsComponent", () => { const getOrganization = jest.fn().mockResolvedValue(org); const showFilePopoutMessage = jest.fn().mockReturnValue(false); + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + beforeEach(async () => { openCurrentPagePopout.mockClear(); getCipher.mockClear(); @@ -82,6 +88,10 @@ describe("OpenAttachmentsComponent", () => { provide: FilePopoutUtilsService, useValue: { showFilePopoutMessage }, }, + { + provide: AccountService, + useValue: accountService, + }, ], }).compileComponents(); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts index 03c29d3f9fe..118510695c5 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts @@ -2,9 +2,11 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Router } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -48,6 +50,7 @@ export class OpenAttachmentsComponent implements OnInit { private toastService: ToastService, private i18nService: I18nService, private filePopoutUtilsService: FilePopoutUtilsService, + private accountService: AccountService, ) { this.billingAccountProfileStateService.hasPremiumFromAnySource$ .pipe(takeUntilDestroyed()) @@ -64,8 +67,11 @@ export class OpenAttachmentsComponent implements OnInit { } const cipherDomain = await this.cipherService.get(this.cipherId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const cipher = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), ); if (!cipher.organizationId) { diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts index 4857703d3b1..161a68043f6 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts @@ -1,8 +1,10 @@ import { CommonModule } from "@angular/common"; import { booleanAttribute, Component, Input } from "@angular/core"; import { Router, RouterModule } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; @@ -50,6 +52,7 @@ export class ItemMoreOptionsComponent { private router: Router, private i18nService: I18nService, private vaultPopupAutofillService: VaultPopupAutofillService, + private accountService: AccountService, ) {} get canEdit() { @@ -108,7 +111,10 @@ export class ItemMoreOptionsComponent { */ async toggleFavorite() { this.cipher.favorite = !this.cipher.favorite; - const encryptedCipher = await this.cipherService.encrypt(this.cipher); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const encryptedCipher = await this.cipherService.encrypt(this.cipher, activeUserId); await this.cipherService.updateWithServer(encryptedCipher); this.toastService.showToast({ variant: "success", diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index 15693ff18d0..9943046b1c8 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -3,10 +3,14 @@ import { ActivatedRoute, Router } from "@angular/router"; import { mock } from "jest-mock-extended"; import { Subject } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -30,6 +34,9 @@ describe("ViewV2Component", () => { type: CipherType.Login, }; + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + const mockCipherService = { get: jest.fn().mockResolvedValue({ decrypt: jest.fn().mockResolvedValue(mockCipher) }), getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}), @@ -59,6 +66,10 @@ describe("ViewV2Component", () => { }, }, }, + { + provide: AccountService, + useValue: accountService, + }, ], }).compileComponents(); diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index ccc2658e59e..9b8403cd319 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -3,10 +3,11 @@ import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { Observable, switchMap } from "rxjs"; +import { firstValueFrom, map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -67,6 +68,7 @@ export class ViewV2Component { private dialogService: DialogService, private logService: LogService, private toastService: ToastService, + private accountService: AccountService, ) { this.subscribeToParams(); } @@ -103,7 +105,12 @@ export class ViewV2Component { async getCipherData(id: string) { const cipher = await this.cipherService.get(id); - return await cipher.decrypt(await this.cipherService.getKeyForCipherKeyDecryption(cipher)); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + return await cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); } async editCipher() { diff --git a/apps/browser/src/vault/popup/components/vault/attachments.component.ts b/apps/browser/src/vault/popup/components/vault/attachments.component.ts index d571c6affe6..ee6f1ac7d07 100644 --- a/apps/browser/src/vault/popup/components/vault/attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault/attachments.component.ts @@ -5,6 +5,7 @@ import { first } from "rxjs/operators"; import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; @@ -36,6 +37,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On fileDownloadService: FileDownloadService, dialogService: DialogService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( cipherService, @@ -49,6 +51,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On fileDownloadService, dialogService, billingAccountProfileStateService, + accountService, ); } diff --git a/apps/browser/src/vault/popup/components/vault/collections.component.ts b/apps/browser/src/vault/popup/components/vault/collections.component.ts index b201e6ff338..0a420192c88 100644 --- a/apps/browser/src/vault/popup/components/vault/collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault/collections.component.ts @@ -5,6 +5,7 @@ import { first } from "rxjs/operators"; import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -28,6 +29,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On private location: Location, logService: LogService, configService: ConfigService, + accountService: AccountService, ) { super( collectionService, @@ -37,6 +39,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On organizationService, logService, configService, + accountService, ); } diff --git a/apps/browser/src/vault/popup/components/vault/password-history.component.ts b/apps/browser/src/vault/popup/components/vault/password-history.component.ts index d5c0ff7673d..bf1b4ea7717 100644 --- a/apps/browser/src/vault/popup/components/vault/password-history.component.ts +++ b/apps/browser/src/vault/popup/components/vault/password-history.component.ts @@ -4,6 +4,7 @@ import { ActivatedRoute } from "@angular/router"; import { first } from "rxjs/operators"; import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "@bitwarden/angular/vault/components/password-history.component"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -18,10 +19,11 @@ export class PasswordHistoryComponent extends BasePasswordHistoryComponent imple cipherService: CipherService, platformUtilsService: PlatformUtilsService, i18nService: I18nService, + accountService: AccountService, private location: Location, private route: ActivatedRoute, ) { - super(cipherService, platformUtilsService, i18nService, window); + super(cipherService, platformUtilsService, i18nService, accountService, window); } async ngOnInit() { diff --git a/apps/browser/src/vault/popup/components/vault/share.component.ts b/apps/browser/src/vault/popup/components/vault/share.component.ts index 6aba1e00c04..44c0a24ab9b 100644 --- a/apps/browser/src/vault/popup/components/vault/share.component.ts +++ b/apps/browser/src/vault/popup/components/vault/share.component.ts @@ -4,6 +4,7 @@ import { first } from "rxjs/operators"; import { ShareComponent as BaseShareComponent } from "@bitwarden/angular/components/share.component"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -25,6 +26,7 @@ export class ShareComponent extends BaseShareComponent implements OnInit { private route: ActivatedRoute, private router: Router, organizationService: OrganizationService, + accountService: AccountService, ) { super( collectionService, @@ -33,6 +35,7 @@ export class ShareComponent extends BaseShareComponent implements OnInit { cipherService, logService, organizationService, + accountService, ); } diff --git a/apps/browser/src/vault/popup/components/vault/view.component.ts b/apps/browser/src/vault/popup/components/vault/view.component.ts index f3d95d3d203..f8e7de21dc8 100644 --- a/apps/browser/src/vault/popup/components/vault/view.component.ts +++ b/apps/browser/src/vault/popup/components/vault/view.component.ts @@ -2,12 +2,13 @@ import { DatePipe, Location } from "@angular/common"; import { ChangeDetectorRef, Component, NgZone, OnInit, OnDestroy } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { Subject, firstValueFrom, takeUntil, Subscription } from "rxjs"; -import { first } from "rxjs/operators"; +import { first, map } from "rxjs/operators"; import { ViewComponent as BaseViewComponent } from "@bitwarden/angular/vault/components/view.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; @@ -97,6 +98,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro fileDownloadService: FileDownloadService, dialogService: DialogService, datePipe: DatePipe, + accountService: AccountService, billingAccountProfileStateService: BillingAccountProfileStateService, ) { super( @@ -120,6 +122,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro fileDownloadService, dialogService, datePipe, + accountService, billingAccountProfileStateService, ); } @@ -267,7 +270,10 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro this.cipher.login.uris.push(loginUri); try { - const cipher: Cipher = await this.cipherService.encrypt(this.cipher); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const cipher: Cipher = await this.cipherService.encrypt(this.cipher, activeUserId); await this.cipherService.updateWithServer(cipher); this.platformUtilsService.showToast( "success", diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts index 6e74fd7c231..2df52e6b55a 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts @@ -2,10 +2,13 @@ import { TestBed } from "@angular/core/testing"; import { mock } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { subscribeTo } from "@bitwarden/common/spec"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith, subscribeTo } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; @@ -40,6 +43,9 @@ describe("VaultPopupAutofillService", () => { const mockCipherService = mock(); const mockMessagingService = mock(); + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + beforeEach(() => { jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(false); jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(mockCurrentTab); @@ -55,6 +61,10 @@ describe("VaultPopupAutofillService", () => { { provide: PasswordRepromptService, useValue: mockPasswordRepromptService }, { provide: CipherService, useValue: mockCipherService }, { provide: MessagingService, useValue: mockMessagingService }, + { + provide: AccountService, + useValue: accountService, + }, ], }); @@ -311,7 +321,7 @@ describe("VaultPopupAutofillService", () => { expect(result).toBe(true); expect(mockCipher.login.uris).toHaveLength(1); expect(mockCipher.login.uris[0].uri).toBe(mockCurrentTab.url); - expect(mockCipherService.encrypt).toHaveBeenCalledWith(mockCipher); + expect(mockCipherService.encrypt).toHaveBeenCalledWith(mockCipher, mockUserId); expect(mockCipherService.updateWithServer).toHaveBeenCalledWith(mockEncryptedCipher); }); diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts index ca59ffd9979..66a432efe60 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts @@ -10,6 +10,7 @@ import { switchMap, } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -72,6 +73,7 @@ export class VaultPopupAutofillService { private passwordRepromptService: PasswordRepromptService, private cipherService: CipherService, private messagingService: MessagingService, + private accountService: AccountService, ) { this._currentPageDetails$.subscribe(); } @@ -221,7 +223,10 @@ export class VaultPopupAutofillService { cipher.login.uris.push(loginUri); try { - const encCipher = await this.cipherService.encrypt(cipher); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const encCipher = await this.cipherService.encrypt(cipher, activeUserId); await this.cipherService.updateWithServer(encCipher); this.messagingService.send("editedCipher"); return true; diff --git a/apps/cli/src/admin-console/commands/share.command.ts b/apps/cli/src/admin-console/commands/share.command.ts index d78f4cfa29c..bbd4241e21e 100644 --- a/apps/cli/src/admin-console/commands/share.command.ts +++ b/apps/cli/src/admin-console/commands/share.command.ts @@ -1,3 +1,6 @@ +import { firstValueFrom, map } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Response } from "../../models/response"; @@ -5,7 +8,10 @@ import { CliUtils } from "../../utils"; import { CipherResponse } from "../../vault/models/cipher.response"; export class ShareCommand { - constructor(private cipherService: CipherService) {} + constructor( + private cipherService: CipherService, + private accountService: AccountService, + ) {} async run(id: string, organizationId: string, requestJson: string): Promise { if (process.env.BW_SERVE !== "true" && (requestJson == null || requestJson === "")) { @@ -45,14 +51,18 @@ export class ShareCommand { if (cipher.organizationId != null) { return Response.badRequest("This item already belongs to an organization."); } + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); try { - await this.cipherService.shareWithServer(cipherView, organizationId, req); + await this.cipherService.shareWithServer(cipherView, organizationId, req, activeUserId); const updatedCipher = await this.cipherService.get(cipher.id); const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher), + await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), ); const res = new CipherResponse(decCipher); return Response.success(res); diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 1bba149a35a..bac1cce7c75 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -1,5 +1,8 @@ +import { firstValueFrom, map } from "rxjs"; + import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; import { FolderExport } from "@bitwarden/common/models/export/folder.export"; @@ -24,6 +27,7 @@ export class EditCommand { private cryptoService: CryptoService, private apiService: ApiService, private folderApiService: FolderApiServiceAbstraction, + private accountService: AccountService, ) {} async run( @@ -77,18 +81,21 @@ export class EditCommand { return Response.notFound(); } + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); let cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); if (cipherView.isDeleted) { return Response.badRequest("You may not edit a deleted item. Use the restore command first."); } cipherView = CipherExport.toView(req, cipherView); - const encCipher = await this.cipherService.encrypt(cipherView); + const encCipher = await this.cipherService.encrypt(cipherView, activeUserId); try { const updatedCipher = await this.cipherService.updateWithServer(encCipher); const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher), + await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), ); const res = new CipherResponse(decCipher); return Response.success(res); @@ -110,9 +117,12 @@ export class EditCommand { cipher.collectionIds = req; try { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const updatedCipher = await this.cipherService.saveCollectionsWithServer(cipher); const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher), + await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), ); const res = new CipherResponse(decCipher); return Response.success(res); diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 7e31750583b..923187bfcd0 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -1,4 +1,4 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; @@ -6,6 +6,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; import { CardExport } from "@bitwarden/common/models/export/card.export"; @@ -62,6 +63,7 @@ export class GetCommand extends DownloadCommand { private organizationService: OrganizationService, private eventCollectionService: EventCollectionService, private accountProfileService: BillingAccountProfileStateService, + private accountService: AccountService, ) { super(cryptoService); } @@ -110,9 +112,12 @@ export class GetCommand extends DownloadCommand { let decCipher: CipherView = null; if (Utils.isGuid(id)) { const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); if (cipher != null) { decCipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); } } else if (id.trim() !== "") { diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index 8a38f8f1280..6e0fa1c43c3 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -63,6 +63,7 @@ export class OssServeConfigurator { this.serviceContainer.organizationService, this.serviceContainer.eventCollectionService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); this.listCommand = new ListCommand( this.serviceContainer.cipherService, @@ -82,6 +83,7 @@ export class OssServeConfigurator { this.serviceContainer.folderApiService, this.serviceContainer.billingAccountProfileStateService, this.serviceContainer.organizationService, + this.serviceContainer.accountService, ); this.editCommand = new EditCommand( this.serviceContainer.cipherService, @@ -89,6 +91,7 @@ export class OssServeConfigurator { this.serviceContainer.cryptoService, this.serviceContainer.apiService, this.serviceContainer.folderApiService, + this.serviceContainer.accountService, ); this.generateCommand = new GenerateCommand( this.serviceContainer.passwordGenerationService, @@ -114,7 +117,10 @@ export class OssServeConfigurator { this.serviceContainer.organizationUserService, ); this.restoreCommand = new RestoreCommand(this.serviceContainer.cipherService); - this.shareCommand = new ShareCommand(this.serviceContainer.cipherService); + this.shareCommand = new ShareCommand( + this.serviceContainer.cipherService, + this.serviceContainer.accountService, + ); this.lockCommand = new LockCommand(this.serviceContainer.vaultTimeoutService); this.unlockCommand = new UnlockCommand( this.serviceContainer.accountService, diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 9cb70952593..c3fd55fa8b2 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -714,6 +714,7 @@ export class ServiceContainer { this.collectionService, this.cryptoService, this.pinService, + this.accountService, ); this.individualExportService = new IndividualVaultExportService( @@ -733,6 +734,7 @@ export class ServiceContainer { this.cryptoFunctionService, this.collectionService, this.kdfConfigService, + this.accountService, ); this.exportService = new VaultExportService( diff --git a/apps/cli/src/tools/send/send.program.ts b/apps/cli/src/tools/send/send.program.ts index 670683e7a2a..05e7e7d22d7 100644 --- a/apps/cli/src/tools/send/send.program.ts +++ b/apps/cli/src/tools/send/send.program.ts @@ -149,6 +149,7 @@ export class SendProgram extends BaseProgram { this.serviceContainer.organizationService, this.serviceContainer.eventCollectionService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); const response = await cmd.run("template", object, null); this.processResponse(response); diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index 04ca47ac1e1..9cf30086166 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -184,6 +184,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.organizationService, this.serviceContainer.eventCollectionService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); const response = await command.run(object, id, cmd); this.processResponse(response); @@ -227,6 +228,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.folderApiService, this.serviceContainer.billingAccountProfileStateService, this.serviceContainer.organizationService, + this.serviceContainer.accountService, ); const response = await command.run(object, encodedJson, cmd); this.processResponse(response); @@ -272,6 +274,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.cryptoService, this.serviceContainer.apiService, this.serviceContainer.folderApiService, + this.serviceContainer.accountService, ); const response = await command.run(object, id, encodedJson, cmd); this.processResponse(response); @@ -375,7 +378,10 @@ export class VaultProgram extends BaseProgram { }) .action(async (id, organizationId, encodedJson, cmd) => { await this.exitIfLocked(); - const command = new ShareCommand(this.serviceContainer.cipherService); + const command = new ShareCommand( + this.serviceContainer.cipherService, + this.serviceContainer.accountService, + ); const response = await command.run(id, organizationId, encodedJson); this.processResponse(response); }); diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 5db3bda97c2..0284ccc37bd 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -1,11 +1,12 @@ import * as fs from "fs"; import * as path from "path"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; @@ -34,6 +35,7 @@ export class CreateCommand { private folderApiService: FolderApiServiceAbstraction, private accountProfileService: BillingAccountProfileStateService, private organizationService: OrganizationService, + private accountService: AccountService, ) {} async run( @@ -80,11 +82,14 @@ export class CreateCommand { } private async createCipher(req: CipherExport) { - const cipher = await this.cipherService.encrypt(CipherExport.toView(req)); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId); try { const newCipher = await this.cipherService.createWithServer(cipher); const decCipher = await newCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(newCipher), + await this.cipherService.getKeyForCipherKeyDecryption(newCipher, activeUserId), ); const res = new CipherResponse(decCipher); return Response.success(res); @@ -143,13 +148,17 @@ export class CreateCommand { } try { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const updatedCipher = await this.cipherService.saveAttachmentRawWithServer( cipher, fileName, new Uint8Array(fileBuf).buffer, + activeUserId, ); const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher), + await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), ); return Response.success(new CipherResponse(decCipher)); } catch (e) { diff --git a/apps/desktop/src/services/encrypted-message-handler.service.ts b/apps/desktop/src/services/encrypted-message-handler.service.ts index 519bd91064c..535aef307d7 100644 --- a/apps/desktop/src/services/encrypted-message-handler.service.ts +++ b/apps/desktop/src/services/encrypted-message-handler.service.ts @@ -164,7 +164,10 @@ export class EncryptedMessageHandlerService { cipherView.login.uris[0].uri = credentialCreatePayload.uri; try { - const encrypted = await this.cipherService.encrypt(cipherView); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const encrypted = await this.cipherService.encrypt(cipherView, activeUserId); await this.cipherService.createWithServer(encrypted); // Notify other clients of new login @@ -197,14 +200,17 @@ export class EncryptedMessageHandlerService { if (cipher === null) { return { status: "failure" }; } + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); cipherView.name = credentialUpdatePayload.name; cipherView.login.password = credentialUpdatePayload.password; cipherView.login.username = credentialUpdatePayload.userName; cipherView.login.uris[0].uri = credentialUpdatePayload.uri; - const encrypted = await this.cipherService.encrypt(cipherView); + const encrypted = await this.cipherService.encrypt(cipherView, activeUserId); await this.cipherService.updateWithServer(encrypted); diff --git a/apps/desktop/src/vault/app/vault/attachments.component.ts b/apps/desktop/src/vault/app/vault/attachments.component.ts index 8066da89a2e..b1ddcbc7e7d 100644 --- a/apps/desktop/src/vault/app/vault/attachments.component.ts +++ b/apps/desktop/src/vault/app/vault/attachments.component.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; @@ -28,6 +29,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { fileDownloadService: FileDownloadService, dialogService: DialogService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( cipherService, @@ -41,6 +43,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { fileDownloadService, dialogService, billingAccountProfileStateService, + accountService, ); } } diff --git a/apps/desktop/src/vault/app/vault/collections.component.ts b/apps/desktop/src/vault/app/vault/collections.component.ts index 4b6a88f325a..f659d4352c0 100644 --- a/apps/desktop/src/vault/app/vault/collections.component.ts +++ b/apps/desktop/src/vault/app/vault/collections.component.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -22,6 +23,7 @@ export class CollectionsComponent extends BaseCollectionsComponent { organizationService: OrganizationService, logService: LogService, configService: ConfigService, + accountService: AccountService, ) { super( collectionService, @@ -31,6 +33,7 @@ export class CollectionsComponent extends BaseCollectionsComponent { organizationService, logService, configService, + accountService, ); } } diff --git a/apps/desktop/src/vault/app/vault/password-history.component.ts b/apps/desktop/src/vault/app/vault/password-history.component.ts index 44e2198a15d..12701ac5527 100644 --- a/apps/desktop/src/vault/app/vault/password-history.component.ts +++ b/apps/desktop/src/vault/app/vault/password-history.component.ts @@ -1,6 +1,7 @@ import { Component } from "@angular/core"; import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "@bitwarden/angular/vault/components/password-history.component"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -14,7 +15,8 @@ export class PasswordHistoryComponent extends BasePasswordHistoryComponent { cipherService: CipherService, platformUtilsService: PlatformUtilsService, i18nService: I18nService, + accountService: AccountService, ) { - super(cipherService, platformUtilsService, i18nService, window); + super(cipherService, platformUtilsService, i18nService, accountService, window); } } diff --git a/apps/desktop/src/vault/app/vault/share.component.ts b/apps/desktop/src/vault/app/vault/share.component.ts index 95b22386e45..ddaad8337bc 100644 --- a/apps/desktop/src/vault/app/vault/share.component.ts +++ b/apps/desktop/src/vault/app/vault/share.component.ts @@ -3,6 +3,7 @@ import { Component } from "@angular/core"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; import { ShareComponent as BaseShareComponent } from "@bitwarden/angular/components/share.component"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -21,6 +22,7 @@ export class ShareComponent extends BaseShareComponent { platformUtilsService: PlatformUtilsService, logService: LogService, organizationService: OrganizationService, + accountService: AccountService, private modalRef: ModalRef, ) { super( @@ -30,6 +32,7 @@ export class ShareComponent extends BaseShareComponent { cipherService, logService, organizationService, + accountService, ); } diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index 64279d5fd40..140e1e9ced6 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -14,6 +14,7 @@ import { ViewComponent as BaseViewComponent } from "@bitwarden/angular/vault/com import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; @@ -62,6 +63,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro dialogService: DialogService, datePipe: DatePipe, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( cipherService, @@ -84,6 +86,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro fileDownloadService, dialogService, datePipe, + accountService, billingAccountProfileStateService, ); } diff --git a/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts b/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts index 09c9072cf0e..9d763886fb4 100644 --- a/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; @@ -32,6 +33,7 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen fileDownloadService: FileDownloadService, dialogService: DialogService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( cipherService, @@ -45,6 +47,7 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen fileDownloadService, dialogService, billingAccountProfileStateService, + accountService, ); } diff --git a/apps/web/src/app/vault/individual-vault/attachments.component.ts b/apps/web/src/app/vault/individual-vault/attachments.component.ts index 3bf87ba4e3c..7a5706319ee 100644 --- a/apps/web/src/app/vault/individual-vault/attachments.component.ts +++ b/apps/web/src/app/vault/individual-vault/attachments.component.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; @@ -31,6 +32,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { fileDownloadService: FileDownloadService, dialogService: DialogService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( cipherService, @@ -44,6 +46,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { fileDownloadService, dialogService, billingAccountProfileStateService, + accountService, ); } diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts index de935482b28..166f0feba4d 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts @@ -1,8 +1,10 @@ import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -61,6 +63,7 @@ export class BulkShareDialogComponent implements OnInit, OnDestroy { private collectionService: CollectionService, private organizationService: OrganizationService, private logService: LogService, + private accountService: AccountService, ) { this.ciphers = params.ciphers ?? []; this.organizationId = params.organizationId; @@ -98,10 +101,14 @@ export class BulkShareDialogComponent implements OnInit, OnDestroy { submit = async () => { const checkedCollectionIds = this.collections.filter(isChecked).map((c) => c.id); try { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); await this.cipherService.shareManyWithServer( this.shareableCiphers, this.organizationId, checkedCollectionIds, + activeUserId, ); const orgName = this.organizations.find((o) => o.id === this.organizationId)?.name ?? diff --git a/apps/web/src/app/vault/individual-vault/collections.component.ts b/apps/web/src/app/vault/individual-vault/collections.component.ts index 9795f879776..ed9dfbaf271 100644 --- a/apps/web/src/app/vault/individual-vault/collections.component.ts +++ b/apps/web/src/app/vault/individual-vault/collections.component.ts @@ -3,6 +3,7 @@ import { Component, Inject, OnDestroy } from "@angular/core"; import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -25,6 +26,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On organizationSerivce: OrganizationService, logService: LogService, configService: ConfigService, + accountService: AccountService, protected dialogRef: DialogRef, @Inject(DIALOG_DATA) params: CollectionsDialogParams, ) { @@ -36,6 +38,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On organizationSerivce, logService, configService, + accountService, ); this.cipherId = params?.cipherId; } diff --git a/apps/web/src/app/vault/individual-vault/share.component.ts b/apps/web/src/app/vault/individual-vault/share.component.ts index 6aef8c7097b..e7015fd7c37 100644 --- a/apps/web/src/app/vault/individual-vault/share.component.ts +++ b/apps/web/src/app/vault/individual-vault/share.component.ts @@ -2,6 +2,7 @@ import { Component, OnDestroy } from "@angular/core"; import { ShareComponent as BaseShareComponent } from "@bitwarden/angular/components/share.component"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -21,6 +22,7 @@ export class ShareComponent extends BaseShareComponent implements OnDestroy { cipherService: CipherService, organizationService: OrganizationService, logService: LogService, + accountService: AccountService, ) { super( collectionService, @@ -29,6 +31,7 @@ export class ShareComponent extends BaseShareComponent implements OnDestroy { cipherService, logService, organizationService, + accountService, ); } diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 5a4a6794f3d..de945d52242 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -35,6 +35,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -198,6 +199,7 @@ export class VaultComponent implements OnInit, OnDestroy { private apiService: ApiService, private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -699,9 +701,12 @@ export class VaultComponent implements OnInit, OnDestroy { return; } + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); // Decrypt the cipher. const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); // Open the dialog. diff --git a/apps/web/src/app/vault/org-vault/add-edit.component.ts b/apps/web/src/app/vault/org-vault/add-edit.component.ts index 8fd15cf20e8..6886da371ec 100644 --- a/apps/web/src/app/vault/org-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/org-vault/add-edit.component.ts @@ -14,6 +14,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -107,11 +108,12 @@ export class AddEditComponent extends BaseAddEditComponent { return cipher; } - protected encryptCipher() { + protected encryptCipher(userId: UserId) { if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) { - return super.encryptCipher(); + return super.encryptCipher(userId); } - return this.cipherService.encrypt(this.cipher, null, null, this.originalCipher); + + return this.cipherService.encrypt(this.cipher, userId, null, null, this.originalCipher); } protected async deleteCipher() { diff --git a/apps/web/src/app/vault/org-vault/attachments.component.ts b/apps/web/src/app/vault/org-vault/attachments.component.ts index 71e7842913b..f02ac693108 100644 --- a/apps/web/src/app/vault/org-vault/attachments.component.ts +++ b/apps/web/src/app/vault/org-vault/attachments.component.ts @@ -3,6 +3,7 @@ import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -12,6 +13,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; @@ -41,6 +43,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On fileDownloadService: FileDownloadService, dialogService: DialogService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, private configService: ConfigService, ) { super( @@ -54,6 +57,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On fileDownloadService, dialogService, billingAccountProfileStateService, + accountService, ); } @@ -81,10 +85,11 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On return new Cipher(new CipherData(response)); } - protected saveCipherAttachment(file: File) { + protected saveCipherAttachment(file: File, userId: UserId) { return this.cipherService.saveAttachmentWithServer( this.cipherDomain, file, + userId, this.organization.canEditAllCiphers(this.restrictProviderAccess), ); } diff --git a/apps/web/src/app/vault/org-vault/collections.component.ts b/apps/web/src/app/vault/org-vault/collections.component.ts index 4ee052e32fe..e0c0ce91a7b 100644 --- a/apps/web/src/app/vault/org-vault/collections.component.ts +++ b/apps/web/src/app/vault/org-vault/collections.component.ts @@ -4,6 +4,7 @@ import { Component, Inject } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -37,6 +38,7 @@ export class CollectionsComponent extends BaseCollectionsComponent { private apiService: ApiService, logService: LogService, configService: ConfigService, + accountService: AccountService, protected dialogRef: DialogRef, @Inject(DIALOG_DATA) params: OrgVaultCollectionsDialogParams, ) { @@ -48,6 +50,7 @@ export class CollectionsComponent extends BaseCollectionsComponent { organizationService, logService, configService, + accountService, dialogRef, params, ); diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 1e38cd152e9..6aae3cddd1d 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -39,6 +39,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EventType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; @@ -216,6 +217,7 @@ export class VaultComponent implements OnInit, OnDestroy { private organizationUserService: OrganizationUserService, protected configService: ConfigService, private toastService: ToastService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -893,9 +895,12 @@ export class VaultComponent implements OnInit, OnDestroy { return; } + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); // Decrypt the cipher. const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); // Open the dialog. diff --git a/libs/angular/src/admin-console/components/collections.component.ts b/libs/angular/src/admin-console/components/collections.component.ts index 5a2235e48a2..f185bed7e4a 100644 --- a/libs/angular/src/admin-console/components/collections.component.ts +++ b/libs/angular/src/admin-console/components/collections.component.ts @@ -1,7 +1,9 @@ import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -36,6 +38,7 @@ export class CollectionsComponent implements OnInit { protected organizationService: OrganizationService, private logService: LogService, private configService: ConfigService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -48,8 +51,11 @@ export class CollectionsComponent implements OnInit { async load() { this.cipherDomain = await this.loadCipher(); this.collectionIds = this.loadCipherCollections(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); this.collections = await this.loadCollections(); diff --git a/libs/angular/src/components/share.component.ts b/libs/angular/src/components/share.component.ts index 6687e784f01..f3edbf1f466 100644 --- a/libs/angular/src/components/share.component.ts +++ b/libs/angular/src/components/share.component.ts @@ -4,6 +4,7 @@ import { firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -36,6 +37,7 @@ export class ShareComponent implements OnInit, OnDestroy { protected cipherService: CipherService, private logService: LogService, protected organizationService: OrganizationService, + protected accountService: AccountService, ) {} async ngOnInit() { @@ -67,8 +69,11 @@ export class ShareComponent implements OnInit, OnDestroy { }); const cipherDomain = await this.cipherService.get(this.cipherId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipher = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), ); } @@ -95,8 +100,11 @@ export class ShareComponent implements OnInit, OnDestroy { } const cipherDomain = await this.cipherService.get(this.cipherId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const cipherView = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), ); const orgs = await firstValueFrom(this.organizations$); const orgName = @@ -104,7 +112,7 @@ export class ShareComponent implements OnInit, OnDestroy { try { this.formPromise = this.cipherService - .shareWithServer(cipherView, this.organizationId, selectedCollectionIds) + .shareWithServer(cipherView, this.organizationId, selectedCollectionIds, activeUserId) .then(async () => { this.onSharedCipher.emit(); this.platformUtilsService.showToast( diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index c8b6011c815..b962496fc5a 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -775,6 +775,7 @@ const safeProviders: SafeProvider[] = [ CollectionServiceAbstraction, CryptoServiceAbstraction, PinServiceAbstraction, + AccountServiceAbstraction, ], }), safeProvider({ @@ -800,6 +801,7 @@ const safeProviders: SafeProvider[] = [ CryptoFunctionServiceAbstraction, CollectionServiceAbstraction, KdfConfigServiceAbstraction, + AccountServiceAbstraction, ], }), safeProvider({ diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 38a4c8fe483..909a905e9b0 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -22,6 +22,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -250,8 +251,11 @@ export class AddEditComponent implements OnInit, OnDestroy { if (this.cipher == null) { if (this.editMode) { const cipher = await this.loadCipher(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); // Adjust Cipher Name if Cloning @@ -371,7 +375,10 @@ export class AddEditComponent implements OnInit, OnDestroy { this.cipher.id = null; } - const cipher = await this.encryptCipher(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const cipher = await this.encryptCipher(activeUserId); try { this.formPromise = this.saveCipher(cipher); await this.formPromise; @@ -664,8 +671,8 @@ export class AddEditComponent implements OnInit, OnDestroy { return this.cipherService.get(this.cipherId); } - protected encryptCipher() { - return this.cipherService.encrypt(this.cipher); + protected encryptCipher(userId: UserId) { + return this.cipherService.encrypt(this.cipher, userId); } protected saveCipher(cipher: Cipher) { diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 68b336a8b06..e377427eb89 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -1,7 +1,8 @@ import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -11,6 +12,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; @@ -46,6 +48,7 @@ export class AttachmentsComponent implements OnInit { protected fileDownloadService: FileDownloadService, protected dialogService: DialogService, protected billingAccountProfileStateService: BillingAccountProfileStateService, + protected accountService: AccountService, ) {} async ngOnInit() { @@ -75,10 +78,13 @@ export class AttachmentsComponent implements OnInit { } try { - this.formPromise = this.saveCipherAttachment(files[0]); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.formPromise = this.saveCipherAttachment(files[0], activeUserId); this.cipherDomain = await this.formPromise; this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); this.platformUtilsService.showToast("success", null, this.i18nService.t("attachmentSaved")); this.onUploadedAttachment.emit(); @@ -185,8 +191,11 @@ export class AttachmentsComponent implements OnInit { protected async init() { this.cipherDomain = await this.loadCipher(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); const canAccessPremium = await firstValueFrom( @@ -235,14 +244,18 @@ export class AttachmentsComponent implements OnInit { ? attachment.key : await this.cryptoService.getOrgKey(this.cipher.organizationId); const decBuf = await this.cryptoService.decryptFromBytes(encBuf, key); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer( this.cipherDomain, attachment.fileName, decBuf, + activeUserId, admin, ); this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); // 3. Delete old @@ -278,8 +291,8 @@ export class AttachmentsComponent implements OnInit { return this.cipherService.get(this.cipherId); } - protected saveCipherAttachment(file: File) { - return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file); + protected saveCipherAttachment(file: File, userId: UserId) { + return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, userId); } protected deleteCipherAttachment(attachmentId: string) { diff --git a/libs/angular/src/vault/components/password-history.component.ts b/libs/angular/src/vault/components/password-history.component.ts index 6d8b4015cb0..e2784620a26 100644 --- a/libs/angular/src/vault/components/password-history.component.ts +++ b/libs/angular/src/vault/components/password-history.component.ts @@ -1,5 +1,7 @@ import { Directive, OnInit } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -14,6 +16,7 @@ export class PasswordHistoryComponent implements OnInit { protected cipherService: CipherService, protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService, + protected accountService: AccountService, private win: Window, ) {} @@ -33,8 +36,11 @@ export class PasswordHistoryComponent implements OnInit { protected async init() { const cipher = await this.cipherService.get(this.cipherId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const decCipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory; } diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 27d6e14b118..a6e96bc542a 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -9,11 +9,12 @@ import { OnInit, Output, } from "@angular/core"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; @@ -100,6 +101,7 @@ export class ViewComponent implements OnDestroy, OnInit { protected fileDownloadService: FileDownloadService, protected dialogService: DialogService, protected datePipe: DatePipe, + protected accountService: AccountService, private billingAccountProfileStateService: BillingAccountProfileStateService, ) {} @@ -129,8 +131,11 @@ export class ViewComponent implements OnDestroy, OnInit { this.cleanUp(); const cipher = await this.cipherService.get(this.cipherId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); this.canAccessPremium = await firstValueFrom( this.billingAccountProfileStateService.hasPremiumFromAnySource$, diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts index 806b6592737..adb1adcce48 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts @@ -1,7 +1,10 @@ import { TextEncoder } from "util"; import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; +import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service"; +import { UserId } from "../../../types/guid"; import { CipherService } from "../../../vault/abstractions/cipher.service"; import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction"; import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type"; @@ -30,10 +33,18 @@ import { guidToRawFormat } from "./guid-utils"; const RpId = "bitwarden.com"; describe("FidoAuthenticatorService", () => { + const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ + id: "testId" as UserId, + email: "test@example.com", + emailVerified: true, + name: "Test User", + }); + let cipherService!: MockProxy; let userInterface!: MockProxy; let userInterfaceSession!: MockProxy; let syncService!: MockProxy; + let accountService!: MockProxy; let authenticator!: Fido2AuthenticatorService; let tab!: chrome.tabs.Tab; @@ -43,8 +54,15 @@ describe("FidoAuthenticatorService", () => { userInterfaceSession = mock(); userInterface.newSession.mockResolvedValue(userInterfaceSession); syncService = mock(); - authenticator = new Fido2AuthenticatorService(cipherService, userInterface, syncService); + accountService = mock(); + authenticator = new Fido2AuthenticatorService( + cipherService, + userInterface, + syncService, + accountService, + ); tab = { id: 123, windowId: 456 } as chrome.tabs.Tab; + accountService.activeAccount$ = activeAccountSubject; }); describe("makeCredential", () => { @@ -677,6 +695,7 @@ describe("FidoAuthenticatorService", () => { ], }), }), + "testId", ); }); diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index 70907f98656..08998b965fe 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -1,3 +1,6 @@ +import { firstValueFrom, map } from "rxjs"; + +import { AccountService } from "../../../auth/abstractions/account.service"; import { CipherService } from "../../../vault/abstractions/cipher.service"; import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction"; import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type"; @@ -42,6 +45,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr private cipherService: CipherService, private userInterface: Fido2UserInterfaceService, private syncService: SyncService, + private accountService: AccountService, private logService?: LogService, ) {} @@ -130,8 +134,12 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr keyPair = await createKeyPair(); pubKeyDer = await crypto.subtle.exportKey("spki", keyPair.publicKey); const encrypted = await this.cipherService.get(cipherId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + cipher = await encrypted.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(encrypted), + await this.cipherService.getKeyForCipherKeyDecryption(encrypted, activeUserId), ); if ( @@ -150,7 +158,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr if (Utils.isNullOrEmpty(cipher.login.username)) { cipher.login.username = fido2Credential.userName; } - const reencrypted = await this.cipherService.encrypt(cipher); + const reencrypted = await this.cipherService.encrypt(cipher, activeUserId); await this.cipherService.updateWithServer(reencrypted); credentialId = fido2Credential.credentialId; } catch (error) { @@ -277,7 +285,10 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr }; if (selectedFido2Credential.counter > 0) { - const encrypted = await this.cipherService.encrypt(selectedCipher); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const encrypted = await this.cipherService.encrypt(selectedCipher, activeUserId); await this.cipherService.updateWithServer(encrypted); } diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index 6877f5a1ca5..e48ab0618c3 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -175,7 +175,7 @@ export class DefaultSyncService extends CoreSyncService { throw new Error("Stamp has changed"); } - await this.cryptoService.setMasterKeyEncryptedUserKey(response.key); + await this.cryptoService.setMasterKeyEncryptedUserKey(response.key, response.id); await this.cryptoService.setPrivateKey(response.privateKey, response.id); await this.cryptoService.setProviderKeys(response.providers, response.id); await this.cryptoService.setOrgKeys( diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 83bdf016ed1..c95ae27f612 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -27,6 +27,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; encrypt: ( model: CipherView, + userId: UserId, keyForEncryption?: SymmetricCryptoKey, keyForCipherKeyDecryption?: SymmetricCryptoKey, originalCipher?: Cipher, @@ -83,21 +84,25 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; shareManyWithServer: ( ciphers: CipherView[], organizationId: string, collectionIds: string[], + userId: UserId, ) => Promise; saveAttachmentWithServer: ( cipher: Cipher, unencryptedFile: any, + userId: UserId, admin?: boolean, ) => Promise; saveAttachmentRawWithServer: ( cipher: Cipher, filename: string, data: ArrayBuffer, + userId: UserId, admin?: boolean, ) => Promise; /** @@ -147,7 +152,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; restoreWithServer: (id: string, asAdmin?: boolean) => Promise; restoreManyWithServer: (ids: string[], orgId?: string) => Promise; - getKeyForCipherKeyDecryption: (cipher: Cipher) => Promise; + getKeyForCipherKeyDecryption: (cipher: Cipher, userId: UserId) => Promise; setAddEditCipherInfo: (value: AddEditCipherInfo) => Promise; /** * Returns user ciphers re-encrypted with the new user key. diff --git a/libs/common/src/vault/models/domain/cipher.spec.ts b/libs/common/src/vault/models/domain/cipher.spec.ts index a75d645831f..f10884b55ae 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -1,6 +1,8 @@ import { mock } from "jest-mock-extended"; import { Jsonify } from "type-fest"; +import { UserId } from "@bitwarden/common/types/guid"; + import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils"; import { UriMatchStrategy } from "../../../models/domain/domain-service"; import { CryptoService } from "../../../platform/abstractions/crypto.service"; @@ -247,7 +249,7 @@ describe("Cipher DTO", () => { ); const cipherView = await cipher.decrypt( - await cipherService.getKeyForCipherKeyDecryption(cipher), + await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), ); expect(cipherView).toMatchObject({ @@ -367,7 +369,7 @@ describe("Cipher DTO", () => { ); const cipherView = await cipher.decrypt( - await cipherService.getKeyForCipherKeyDecryption(cipher), + await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), ); expect(cipherView).toMatchObject({ @@ -505,7 +507,7 @@ describe("Cipher DTO", () => { ); const cipherView = await cipher.decrypt( - await cipherService.getKeyForCipherKeyDecryption(cipher), + await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), ); expect(cipherView).toMatchObject({ @@ -667,7 +669,7 @@ describe("Cipher DTO", () => { ); const cipherView = await cipher.decrypt( - await cipherService.getKeyForCipherKeyDecryption(cipher), + await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), ); expect(cipherView).toMatchObject({ @@ -754,3 +756,5 @@ describe("Cipher DTO", () => { }); }); }); + +const mockUserId = "TestUserId" as UserId; diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index e3019ab48d2..b6b35f1886a 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -121,6 +121,8 @@ describe("Cipher Service", () => { accountService = mockAccountServiceWith(mockUserId); const stateProvider = new FakeStateProvider(accountService); + const userId = "TestUserId" as UserId; + let cipherService: CipherService; let cipherObj: Cipher; @@ -168,7 +170,7 @@ describe("Cipher Service", () => { const spy = jest.spyOn(cipherFileUploadService, "upload"); - await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData); + await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData, userId); expect(spy).toHaveBeenCalled(); }); @@ -283,7 +285,7 @@ describe("Cipher Service", () => { { uri: "uri", match: UriMatchStrategy.RegularExpression } as LoginUriView, ]; - const domain = await cipherService.encrypt(cipherView); + const domain = await cipherService.encrypt(cipherView, userId); expect(domain.login.uris).toEqual([ { @@ -299,7 +301,7 @@ describe("Cipher Service", () => { it("is null when enableCipherKeyEncryption flag is false", async () => { setEncryptionKeyFlag(false); - const cipher = await cipherService.encrypt(cipherView); + const cipher = await cipherService.encrypt(cipherView, userId); expect(cipher.key).toBeNull(); }); @@ -307,7 +309,7 @@ describe("Cipher Service", () => { it("is defined when enableCipherKeyEncryption flag is true", async () => { setEncryptionKeyFlag(true); - const cipher = await cipherService.encrypt(cipherView); + const cipher = await cipherService.encrypt(cipherView, userId); expect(cipher.key).toBeDefined(); }); @@ -321,7 +323,7 @@ describe("Cipher Service", () => { it("is not called when enableCipherKeyEncryption is false", async () => { setEncryptionKeyFlag(false); - await cipherService.encrypt(cipherView); + await cipherService.encrypt(cipherView, userId); expect(cipherService["encryptCipherWithCipherKey"]).not.toHaveBeenCalled(); }); @@ -329,7 +331,7 @@ describe("Cipher Service", () => { it("is called when enableCipherKeyEncryption is true", async () => { setEncryptionKeyFlag(true); - await cipherService.encrypt(cipherView); + await cipherService.encrypt(cipherView, userId); expect(cipherService["encryptCipherWithCipherKey"]).toHaveBeenCalled(); }); diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 3eeac75db09..92676aea97b 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -165,6 +165,7 @@ export class CipherService implements CipherServiceAbstraction { async encrypt( model: CipherView, + userId: UserId, keyForEncryption?: SymmetricCryptoKey, keyForCipherKeyDecryption?: SymmetricCryptoKey, originalCipher: Cipher = null, @@ -174,7 +175,7 @@ export class CipherService implements CipherServiceAbstraction { originalCipher = await this.get(model.id); } if (originalCipher != null) { - await this.updateModelfromExistingCipher(model, originalCipher); + await this.updateModelfromExistingCipher(model, originalCipher, userId); } this.adjustPasswordHistoryLength(model); } @@ -192,7 +193,7 @@ export class CipherService implements CipherServiceAbstraction { if (await this.getCipherKeyEncryptionEnabled()) { cipher.key = originalCipher?.key ?? null; - const userOrOrgKey = await this.getKeyForCipherKeyDecryption(cipher); + const userOrOrgKey = await this.getKeyForCipherKeyDecryption(cipher, userId); // The keyForEncryption is only used for encrypting the cipher key, not the cipher itself, since cipher key encryption is enabled. // If the caller has provided a key for cipher key encryption, use it. Otherwise, use the user or org key. keyForEncryption ||= userOrOrgKey; @@ -718,6 +719,7 @@ export class CipherService implements CipherServiceAbstraction { cipher: CipherView, organizationId: string, collectionIds: string[], + userId: UserId, ): Promise { const attachmentPromises: Promise[] = []; if (cipher.attachments != null) { @@ -733,7 +735,7 @@ export class CipherService implements CipherServiceAbstraction { cipher.organizationId = organizationId; cipher.collectionIds = collectionIds; - const encCipher = await this.encryptSharedCipher(cipher); + const encCipher = await this.encryptSharedCipher(cipher, userId); const request = new CipherShareRequest(encCipher); const response = await this.apiService.putShareCipher(cipher.id, request); const data = new CipherData(response, collectionIds); @@ -744,6 +746,7 @@ export class CipherService implements CipherServiceAbstraction { ciphers: CipherView[], organizationId: string, collectionIds: string[], + userId: UserId, ): Promise { const promises: Promise[] = []; const encCiphers: Cipher[] = []; @@ -751,7 +754,7 @@ export class CipherService implements CipherServiceAbstraction { cipher.organizationId = organizationId; cipher.collectionIds = collectionIds; promises.push( - this.encryptSharedCipher(cipher).then((c) => { + this.encryptSharedCipher(cipher, userId).then((c) => { encCiphers.push(c); }), ); @@ -770,7 +773,12 @@ export class CipherService implements CipherServiceAbstraction { await this.upsert(encCiphers.map((c) => c.toCipherData())); } - saveAttachmentWithServer(cipher: Cipher, unencryptedFile: any, admin = false): Promise { + saveAttachmentWithServer( + cipher: Cipher, + unencryptedFile: any, + userId: UserId, + admin = false, + ): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsArrayBuffer(unencryptedFile); @@ -780,6 +788,7 @@ export class CipherService implements CipherServiceAbstraction { cipher, unencryptedFile.name, evt.target.result, + userId, admin, ); resolve(cData); @@ -797,9 +806,10 @@ export class CipherService implements CipherServiceAbstraction { cipher: Cipher, filename: string, data: Uint8Array, + userId: UserId, admin = false, ): Promise { - const encKey = await this.getKeyForCipherKeyDecryption(cipher); + const encKey = await this.getKeyForCipherKeyDecryption(cipher, userId); const cipherKeyEncryptionEnabled = await this.getCipherKeyEncryptionEnabled(); const cipherEncKey = @@ -813,8 +823,8 @@ export class CipherService implements CipherServiceAbstraction { //then we rollback to using the user key as the main key of encryption of the item //in order to keep item and it's attachments with the same encryption level if (cipher.key != null && !cipherKeyEncryptionEnabled) { - const model = await cipher.decrypt(await this.getKeyForCipherKeyDecryption(cipher)); - cipher = await this.encrypt(model); + const model = await cipher.decrypt(await this.getKeyForCipherKeyDecryption(cipher, userId)); + cipher = await this.encrypt(model, userId); await this.updateWithServer(cipher); } @@ -1209,10 +1219,10 @@ export class CipherService implements CipherServiceAbstraction { await this.restore(restores); } - async getKeyForCipherKeyDecryption(cipher: Cipher): Promise { + async getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise { return ( (await this.cryptoService.getOrgKey(cipher.organizationId)) || - ((await this.cryptoService.getUserKeyWithLegacySupport()) as UserKey) + ((await this.cryptoService.getUserKeyWithLegacySupport(userId)) as UserKey) ); } @@ -1247,7 +1257,7 @@ export class CipherService implements CipherServiceAbstraction { } encryptedCiphers = await Promise.all( userCiphers.map(async (cipher) => { - const encryptedCipher = await this.encrypt(cipher, newUserKey, originalUserKey); + const encryptedCipher = await this.encrypt(cipher, userId, newUserKey, originalUserKey); return new CipherWithIdRequest(encryptedCipher); }), ); @@ -1259,17 +1269,18 @@ export class CipherService implements CipherServiceAbstraction { // In the case of a cipher that is being shared with an organization, we want to decrypt the // cipher key with the user's key and then re-encrypt it with the organization's key. - private async encryptSharedCipher(model: CipherView): Promise { - const keyForCipherKeyDecryption = await this.cryptoService.getUserKeyWithLegacySupport(); - return await this.encrypt(model, null, keyForCipherKeyDecryption); + private async encryptSharedCipher(model: CipherView, userId: UserId): Promise { + const keyForCipherKeyDecryption = await this.cryptoService.getUserKeyWithLegacySupport(userId); + return await this.encrypt(model, userId, null, keyForCipherKeyDecryption); } private async updateModelfromExistingCipher( model: CipherView, originalCipher: Cipher, + userId: UserId, ): Promise { const existingCipher = await originalCipher.decrypt( - await this.getKeyForCipherKeyDecryption(originalCipher), + await this.getKeyForCipherKeyDecryption(originalCipher, userId), ); model.passwordHistory = existingCipher.passwordHistory || []; if (model.type === CipherType.Login && existingCipher.type === CipherType.Login) { diff --git a/libs/importer/spec/bitwarden-password-protected-importer.spec.ts b/libs/importer/spec/bitwarden-password-protected-importer.spec.ts index cfe5fa12c0f..d36ce8b9a64 100644 --- a/libs/importer/spec/bitwarden-password-protected-importer.spec.ts +++ b/libs/importer/spec/bitwarden-password-protected-importer.spec.ts @@ -1,6 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { KdfType } from "@bitwarden/common/platform/enums"; @@ -21,6 +22,7 @@ describe("BitwardenPasswordProtectedImporter", () => { let i18nService: MockProxy; let cipherService: MockProxy; let pinService: MockProxy; + let accountService: MockProxy; const password = Utils.newGuid(); const promptForPassword_callback = async () => { return password; @@ -31,12 +33,14 @@ describe("BitwardenPasswordProtectedImporter", () => { i18nService = mock(); cipherService = mock(); pinService = mock(); + accountService = mock(); importer = new BitwardenPasswordProtectedImporter( cryptoService, i18nService, cipherService, pinService, + accountService, promptForPassword_callback, ); }); diff --git a/libs/importer/src/components/import.component.ts b/libs/importer/src/components/import.component.ts index 8e6229c7bf2..8ee882734b3 100644 --- a/libs/importer/src/components/import.component.ts +++ b/libs/importer/src/components/import.component.ts @@ -27,6 +27,7 @@ import { import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ClientType } from "@bitwarden/common/enums"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -89,6 +90,7 @@ const safeProviders: SafeProvider[] = [ CollectionService, CryptoService, PinServiceAbstraction, + AccountService, ], }), ]; diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index bef707c3e53..2248606814b 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -1,6 +1,7 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherWithIdExport, CollectionWithIdExport, @@ -33,6 +34,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { protected i18nService: I18nService, protected cipherService: CipherService, protected pinService: PinServiceAbstraction, + protected accountService: AccountService, ) { super(); } @@ -103,8 +105,11 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { }); } + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const view = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); this.cleanupCipher(view); this.result.ciphers.push(view); diff --git a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts index 83b5b78d62f..a854346bccb 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts @@ -1,4 +1,5 @@ import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { Argon2KdfConfig, KdfConfig, @@ -25,9 +26,10 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im i18nService: I18nService, cipherService: CipherService, pinService: PinServiceAbstraction, + accountService: AccountService, private promptForPassword_callback: () => Promise, ) { - super(cryptoService, i18nService, cipherService, pinService); + super(cryptoService, i18nService, cipherService, pinService, accountService); } async parse(data: string): Promise { diff --git a/libs/importer/src/services/import.service.spec.ts b/libs/importer/src/services/import.service.spec.ts index db360794474..e44c8f6aa98 100644 --- a/libs/importer/src/services/import.service.spec.ts +++ b/libs/importer/src/services/import.service.spec.ts @@ -1,6 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -27,6 +28,7 @@ describe("ImportService", () => { let collectionService: MockProxy; let cryptoService: MockProxy; let pinService: MockProxy; + let accountService: MockProxy; beforeEach(() => { cipherService = mock(); @@ -45,6 +47,7 @@ describe("ImportService", () => { collectionService, cryptoService, pinService, + accountService, ); }); diff --git a/libs/importer/src/services/import.service.ts b/libs/importer/src/services/import.service.ts index 462d33b8d1d..13b77fb5b4e 100644 --- a/libs/importer/src/services/import.service.ts +++ b/libs/importer/src/services/import.service.ts @@ -1,4 +1,7 @@ +import { firstValueFrom, map } from "rxjs"; + import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ImportCiphersRequest } from "@bitwarden/common/models/request/import-ciphers.request"; import { ImportOrganizationCiphersRequest } from "@bitwarden/common/models/request/import-organization-ciphers.request"; import { KvpRequest } from "@bitwarden/common/models/request/kvp.request"; @@ -102,6 +105,7 @@ export class ImportService implements ImportServiceAbstraction { private collectionService: CollectionService, private cryptoService: CryptoService, private pinService: PinServiceAbstraction, + private accountService: AccountService, ) {} getImportOptions(): ImportOption[] { @@ -206,6 +210,7 @@ export class ImportService implements ImportServiceAbstraction { this.i18nService, this.cipherService, this.pinService, + this.accountService, promptForPassword_callback, ); case "lastpasscsv": @@ -332,8 +337,11 @@ export class ImportService implements ImportServiceAbstraction { private async handleIndividualImport(importResult: ImportResult) { const request = new ImportCiphersRequest(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); for (let i = 0; i < importResult.ciphers.length; i++) { - const c = await this.cipherService.encrypt(importResult.ciphers[i]); + const c = await this.cipherService.encrypt(importResult.ciphers[i], activeUserId); request.ciphers.push(new CipherRequest(c)); } if (importResult.folders != null) { @@ -352,9 +360,12 @@ export class ImportService implements ImportServiceAbstraction { private async handleOrganizationalImport(importResult: ImportResult, organizationId: string) { const request = new ImportOrganizationCiphersRequest(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); for (let i = 0; i < importResult.ciphers.length; i++) { importResult.ciphers[i].organizationId = organizationId; - const c = await this.cipherService.encrypt(importResult.ciphers[i]); + const c = await this.cipherService.encrypt(importResult.ciphers[i], activeUserId); request.ciphers.push(new CipherRequest(c)); } if (importResult.collections != null) { diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts index 390b9e0b5a1..0c3e94178f6 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts @@ -1,8 +1,9 @@ import * as papa from "papaparse"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { CipherWithIdExport, CollectionWithIdExport } from "@bitwarden/common/models/export"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; @@ -42,6 +43,7 @@ export class OrganizationVaultExportService cryptoFunctionService: CryptoFunctionService, private collectionService: CollectionService, kdfConfigService: KdfConfigService, + private accountService: AccountService, ) { super(pinService, cryptoService, cryptoFunctionService, kdfConfigService); } @@ -87,6 +89,9 @@ export class OrganizationVaultExportService const decCollections: CollectionView[] = []; const decCiphers: CipherView[] = []; const promises = []; + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); promises.push( this.apiService.getOrganizationExport(organizationId).then((exportData) => { @@ -111,7 +116,7 @@ export class OrganizationVaultExportService const cipher = new Cipher(new CipherData(c)); exportPromises.push( this.cipherService - .getKeyForCipherKeyDecryption(cipher) + .getKeyForCipherKeyDecryption(cipher, activeUserId) .then((key) => cipher.decrypt(key)) .then((decCipher) => { decCiphers.push(decCipher); diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts index 5a2d571c802..1ee9a985f5a 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts @@ -3,11 +3,13 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; import { mock } from "jest-mock-extended"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; @@ -15,6 +17,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ButtonComponent, ToastService } from "@bitwarden/components"; import { DownloadAttachmentComponent } from "@bitwarden/vault"; +import { FakeAccountService, mockAccountServiceWith } from "../../../../../common/spec"; + import { CipherAttachmentsComponent } from "./cipher-attachments.component"; import { DeleteAttachmentComponent } from "./delete-attachment/delete-attachment.component"; @@ -49,6 +53,9 @@ describe("CipherAttachmentsComponent", () => { const cipherServiceGet = jest.fn().mockResolvedValue(cipherDomain); const saveAttachmentWithServer = jest.fn().mockResolvedValue(cipherDomain); + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + beforeEach(async () => { cipherServiceGet.mockClear(); showToast.mockClear(); @@ -75,6 +82,10 @@ describe("CipherAttachmentsComponent", () => { { provide: LogService, useValue: mock() }, { provide: ConfigService, useValue: mock() }, { provide: PlatformUtilsService, useValue: mock() }, + { + provide: AccountService, + useValue: accountService, + }, ], }) .overrideComponent(CipherAttachmentsComponent, { @@ -219,7 +230,7 @@ describe("CipherAttachmentsComponent", () => { it("calls `saveAttachmentWithServer`", async () => { await component.submit(); - expect(saveAttachmentWithServer).toHaveBeenCalledWith(cipherDomain, file); + expect(saveAttachmentWithServer).toHaveBeenCalledWith(cipherDomain, file, mockUserId); }); it("resets form and input values", async () => { diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts index 000963d1849..a6febe48978 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts @@ -19,11 +19,13 @@ import { ReactiveFormsModule, Validators, } from "@angular/forms"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; @@ -90,6 +92,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { }); private cipherDomain: Cipher; + private activeUserId: UserId; private destroy$ = inject(DestroyRef); constructor( @@ -98,6 +101,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { private formBuilder: FormBuilder, private logService: LogService, private toastService: ToastService, + private accountService: AccountService, ) { this.attachmentForm.statusChanges.pipe(takeUntilDestroyed()).subscribe((status) => { if (!this.submitBtn) { @@ -110,8 +114,11 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { async ngOnInit(): Promise { this.cipherDomain = await this.cipherService.get(this.cipherId); + this.activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId), ); // Update the initial state of the submit button @@ -178,11 +185,12 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { this.cipherDomain = await this.cipherService.saveAttachmentWithServer( this.cipherDomain, file, + this.activeUserId, ); // re-decrypt the cipher to update the attachments this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId), ); // Reset reactive form and input element diff --git a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts index e8bb1099d55..8e73d9edd40 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts @@ -1,5 +1,7 @@ import { inject, Injectable } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -14,15 +16,25 @@ function isSetEqual(a: Set, b: Set) { @Injectable() export class DefaultCipherFormService implements CipherFormService { private cipherService: CipherService = inject(CipherService); + private accountService: AccountService = inject(AccountService); async decryptCipher(cipher: Cipher): Promise { - return await cipher.decrypt(await this.cipherService.getKeyForCipherKeyDecryption(cipher)); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + return await cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); } async saveCipher(cipher: CipherView, config: CipherFormConfig): Promise { // Passing the original cipher is important here as it is responsible for appending to password history + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const encryptedCipher = await this.cipherService.encrypt( cipher, + activeUserId, null, null, config.originalCipher ?? null, @@ -34,7 +46,7 @@ export class DefaultCipherFormService implements CipherFormService { if (cipher.id == null) { savedCipher = await this.cipherService.createWithServer(encryptedCipher, config.admin); return await savedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(savedCipher), + await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId), ); } @@ -68,7 +80,7 @@ export class DefaultCipherFormService implements CipherFormService { } return await savedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(savedCipher), + await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId), ); } } diff --git a/libs/vault/src/components/assign-collections.component.ts b/libs/vault/src/components/assign-collections.component.ts index 6d105578ced..188fc543eff 100644 --- a/libs/vault/src/components/assign-collections.component.ts +++ b/libs/vault/src/components/assign-collections.component.ts @@ -14,6 +14,7 @@ import { Observable, Subject, combineLatest, + firstValueFrom, map, shareReplay, switchMap, @@ -25,10 +26,11 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -162,6 +164,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI private get selectedOrgId(): OrganizationId { return this.formGroup.getRawValue().selectedOrg || this.params.organizationId; } + private activeUserId: UserId; private destroy$ = new Subject(); constructor( @@ -172,6 +175,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI private collectionService: CollectionService, private formBuilder: FormBuilder, private toastService: ToastService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -179,6 +183,10 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI FeatureFlag.RestrictProviderAccess, ); + this.activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const onlyPersonalItems = this.params.ciphers.every((c) => c.organizationId == null); if (this.selectedOrgId === MY_VAULT_ID || onlyPersonalItems) { @@ -420,6 +428,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI shareableCiphers, organizationId, selectedCollectionIds, + this.activeUserId, ); this.toastService.showToast({ @@ -460,7 +469,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI private async updateAssignedCollections(cipherView: CipherView) { const { collections } = this.formGroup.getRawValue(); cipherView.collectionIds = collections.map((i) => i.id as CollectionId); - const cipher = await this.cipherService.encrypt(cipherView); + const cipher = await this.cipherService.encrypt(cipherView, this.activeUserId); await this.cipherService.saveCollectionsWithServer(cipher); } }