mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 01:33:33 +00:00
[PM-12423] Migrate Cipher Decryption to Use SDK (#14206)
* Created mappings for client domain object to SDK * Add abstract decrypt observable * Added todo for future consideration * Added implementation to cipher service * Added adapter and unit tests * Created cipher encryption abstraction and service * Register cipher encryption service * Added tests for the cipher encryption service * changed signature * Updated feature flag name * added new function to be used for decrypting ciphers * Added new encryptedKey field * added new function to be used for decrypting ciphers * Manually set fields * Added encrypted key in attachment view * Fixed test * Updated references to use decrypt with feature flag * Added dependency * updated package.json * lint fix * fixed tests * Fixed small mapping issues * Fixed test * Added function to decrypt fido2 key value * Added function to decrypt fido2 key value and updated test * updated to use sdk function without prociding the key * updated localdata sdk type change * decrypt attachment content using sdk * Fixed dependency issues * updated package.json * Refactored service to handle getting decrypted buffer using the legacy and sdk implementations * updated services and component to use refactored version * Updated decryptCiphersWithSdk to use decryptManyLegacy for batch decryption, ensuring the SDK is only called once per batch * Fixed merge conflicts * Fixed merge conflicts * Fixed merge conflicts * Fixed lint issues * Moved getDecryptedAttachmentBuffer to cipher service * Moved getDecryptedAttachmentBuffer to cipher service * ensure CipherView properties are null instead of undefined * Fixed test * ensure AttachmentView properties are null instead of undefined * Linked ticket in comment * removed unused orgKey
This commit is contained in:
@@ -6,15 +6,16 @@ import { BehaviorSubject } from "rxjs";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
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";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { PasswordRepromptService } from "../../services/password-reprompt.service";
|
||||
|
||||
@@ -51,6 +52,21 @@ describe("DownloadAttachmentComponent", () => {
|
||||
},
|
||||
} as CipherView;
|
||||
|
||||
const ciphers$ = new BehaviorSubject({
|
||||
"5555-444-3333": {
|
||||
id: "5555-444-3333",
|
||||
attachments: [
|
||||
{
|
||||
id: "222-3333-4444",
|
||||
fileName: "encrypted-filename",
|
||||
key: "encrypted-key",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const getFeatureFlag = jest.fn().mockResolvedValue(false);
|
||||
|
||||
beforeEach(async () => {
|
||||
showToast.mockClear();
|
||||
getAttachmentData.mockClear();
|
||||
@@ -60,13 +76,22 @@ describe("DownloadAttachmentComponent", () => {
|
||||
imports: [DownloadAttachmentComponent],
|
||||
providers: [
|
||||
{ provide: EncryptService, useValue: mock<EncryptService>() },
|
||||
{ provide: KeyService, useValue: mock<KeyService>() },
|
||||
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
||||
{ provide: StateProvider, useValue: { activeUserId$ } },
|
||||
{ provide: ToastService, useValue: { showToast } },
|
||||
{ provide: ApiService, useValue: { getAttachmentData } },
|
||||
{ provide: FileDownloadService, useValue: { download } },
|
||||
{ provide: PasswordRepromptService, useValue: mock<PasswordRepromptService>() },
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: {
|
||||
getFeatureFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CipherService,
|
||||
useValue: { ciphers$: () => ciphers$, getDecryptedAttachmentBuffer: jest.fn() },
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
@@ -128,10 +153,12 @@ describe("DownloadAttachmentComponent", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("shows an error toast when EncArrayBuffer fails", async () => {
|
||||
it("shows an error toast when getDecryptedAttachmentBuffer fails", async () => {
|
||||
getAttachmentData.mockResolvedValue({ url: "https://www.downloadattachement.com" });
|
||||
fetchMock.mockResolvedValue({ status: 200 });
|
||||
EncArrayBuffer.fromResponse = jest.fn().mockRejectedValue({});
|
||||
|
||||
const cipherService = TestBed.inject(CipherService) as jest.Mocked<CipherService>;
|
||||
cipherService.getDecryptedAttachmentBuffer.mockRejectedValue(new Error());
|
||||
|
||||
await component.download();
|
||||
|
||||
|
||||
@@ -2,23 +2,19 @@
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { NEVER, switchMap } from "rxjs";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { EmergencyAccessId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { OrgKey } from "@bitwarden/common/types/key";
|
||||
import { CipherId, EmergencyAccessId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { AsyncActionsModule, IconButtonModule, ToastService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -42,29 +38,14 @@ export class DownloadAttachmentComponent {
|
||||
/** When owners/admins can mange all items and when accessing from the admin console, use the admin endpoint */
|
||||
@Input() admin?: boolean = false;
|
||||
|
||||
/** The organization key if the cipher is associated with one */
|
||||
private orgKey: OrgKey | null = null;
|
||||
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private apiService: ApiService,
|
||||
private fileDownloadService: FileDownloadService,
|
||||
private toastService: ToastService,
|
||||
private encryptService: EncryptService,
|
||||
private stateProvider: StateProvider,
|
||||
private keyService: KeyService,
|
||||
) {
|
||||
this.stateProvider.activeUserId$
|
||||
.pipe(
|
||||
switchMap((userId) => (userId !== null ? this.keyService.orgKeys$(userId) : NEVER)),
|
||||
takeUntilDestroyed(),
|
||||
)
|
||||
.subscribe((data: Record<OrganizationId, OrgKey> | null) => {
|
||||
if (data) {
|
||||
this.orgKey = data[this.cipher.organizationId as OrganizationId];
|
||||
}
|
||||
});
|
||||
}
|
||||
private cipherService: CipherService,
|
||||
) {}
|
||||
|
||||
/** Download the attachment */
|
||||
download = async () => {
|
||||
@@ -100,9 +81,15 @@ export class DownloadAttachmentComponent {
|
||||
}
|
||||
|
||||
try {
|
||||
const encBuf = await EncArrayBuffer.fromResponse(response);
|
||||
const key = this.attachment.key != null ? this.attachment.key : this.orgKey;
|
||||
const decBuf = await this.encryptService.decryptFileData(encBuf, key);
|
||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
|
||||
const decBuf = await this.cipherService.getDecryptedAttachmentBuffer(
|
||||
this.cipher.id as CipherId,
|
||||
this.attachment,
|
||||
response,
|
||||
userId,
|
||||
);
|
||||
|
||||
this.fileDownloadService.download({
|
||||
fileName: this.attachment.fileName,
|
||||
blobData: decBuf,
|
||||
|
||||
Reference in New Issue
Block a user