mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +00:00
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-importer-for-mac-app-store-build
This commit is contained in:
@@ -186,15 +186,15 @@ export class EditCommand {
|
||||
return Response.notFound();
|
||||
}
|
||||
|
||||
let folderView = await folder.decrypt();
|
||||
const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId));
|
||||
let folderView = await folder.decrypt(userKey);
|
||||
folderView = FolderExport.toView(req, folderView);
|
||||
|
||||
const userKey = await this.keyService.getUserKey(activeUserId);
|
||||
const encFolder = await this.folderService.encrypt(folderView, userKey);
|
||||
try {
|
||||
const folder = await this.folderApiService.save(encFolder, activeUserId);
|
||||
const updatedFolder = new Folder(folder);
|
||||
const decFolder = await updatedFolder.decrypt();
|
||||
const decFolder = await updatedFolder.decrypt(userKey);
|
||||
const res = new FolderResponse(decFolder);
|
||||
return Response.success(res);
|
||||
} catch (e) {
|
||||
|
||||
@@ -417,10 +417,11 @@ export class GetCommand extends DownloadCommand {
|
||||
private async getFolder(id: string) {
|
||||
let decFolder: FolderView = null;
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId));
|
||||
if (Utils.isGuid(id)) {
|
||||
const folder = await this.folderService.getFromState(id, activeUserId);
|
||||
if (folder != null) {
|
||||
decFolder = await folder.decrypt();
|
||||
decFolder = await folder.decrypt(userKey);
|
||||
}
|
||||
} else if (id.trim() !== "") {
|
||||
let folders = await this.folderService.getAllDecryptedFromState(activeUserId);
|
||||
|
||||
@@ -3,6 +3,8 @@ import * as sdk from "@bitwarden/sdk-internal";
|
||||
|
||||
export class CliSdkLoadService extends SdkLoadService {
|
||||
async load(): Promise<void> {
|
||||
// CLI uses stdout for user interaction / automations so we cannot log info / debug here.
|
||||
SdkLoadService.logLevel = sdk.LogLevel.Error;
|
||||
const module = await import("@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm");
|
||||
(sdk as any).init(module);
|
||||
}
|
||||
|
||||
@@ -181,12 +181,12 @@ export class CreateCommand {
|
||||
|
||||
private async createFolder(req: FolderExport) {
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const userKey = await this.keyService.getUserKey(activeUserId);
|
||||
const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId));
|
||||
const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey);
|
||||
try {
|
||||
const folderData = await this.folderApiService.save(folder, activeUserId);
|
||||
const newFolder = new Folder(folderData);
|
||||
const decFolder = await newFolder.decrypt();
|
||||
const decFolder = await newFolder.decrypt(userKey);
|
||||
const res = new FolderResponse(decFolder);
|
||||
return Response.success(res);
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<bit-layout>
|
||||
<bit-layout class="!tw-h-full">
|
||||
<app-side-nav slot="side-nav">
|
||||
<bit-nav-logo [openIcon]="logo" route="." [label]="'passwordManager' | i18n"></bit-nav-logo>
|
||||
|
||||
|
||||
15
apps/desktop/src/scss/migration.scss
Normal file
15
apps/desktop/src/scss/migration.scss
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Desktop UI Migration
|
||||
*
|
||||
* These are temporary styles during the desktop ui migration.
|
||||
**/
|
||||
|
||||
/**
|
||||
* This removes any padding applied by the bit-layout to content.
|
||||
* This should be revisited once the table is migrated, and again once drawers are migrated.
|
||||
**/
|
||||
bit-layout {
|
||||
#main-content {
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
}
|
||||
@@ -15,5 +15,6 @@
|
||||
@import "left-nav.scss";
|
||||
@import "loading.scss";
|
||||
@import "plugins.scss";
|
||||
@import "migration.scss";
|
||||
@import "../../../../libs/angular/src/scss/icons.scss";
|
||||
@import "../../../../libs/components/src/multi-select/scss/bw.theme";
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
// @ts-strict-ignore
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { EncryptionType } from "../src/platform/enums";
|
||||
import { Utils } from "../src/platform/misc/utils";
|
||||
@@ -29,6 +32,7 @@ export function BuildTestObject<T, K extends keyof T = keyof T>(
|
||||
|
||||
export function mockEnc(s: string): MockProxy<EncString> {
|
||||
const mocked = mock<EncString>();
|
||||
mocked.decryptedValue = s;
|
||||
mocked.decrypt.mockResolvedValue(s);
|
||||
|
||||
return mocked;
|
||||
@@ -77,4 +81,14 @@ export const mockFromSdk = (stub: any) => {
|
||||
return `${stub}_fromSdk`;
|
||||
};
|
||||
|
||||
export const mockContainerService = () => {
|
||||
const keyService = mock<KeyService>();
|
||||
const encryptService = mock<EncryptService>();
|
||||
encryptService.decryptString.mockImplementation(async (encStr, _key) => {
|
||||
return encStr.decryptedValue;
|
||||
});
|
||||
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
|
||||
return (window as any).bitwardenContainerService;
|
||||
};
|
||||
|
||||
export { trackEmissions, awaitAsync } from "@bitwarden/core-test-utils";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { init_sdk } from "@bitwarden/sdk-internal";
|
||||
import { init_sdk, LogLevel } from "@bitwarden/sdk-internal";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- used in docs
|
||||
import type { SdkService } from "./sdk.service";
|
||||
@@ -10,6 +10,7 @@ export class SdkLoadFailedError extends Error {
|
||||
}
|
||||
|
||||
export abstract class SdkLoadService {
|
||||
protected static logLevel: LogLevel = LogLevel.Info;
|
||||
private static markAsReady: () => void;
|
||||
private static markAsFailed: (error: unknown) => void;
|
||||
|
||||
@@ -41,7 +42,7 @@ export abstract class SdkLoadService {
|
||||
async loadAndInit(): Promise<void> {
|
||||
try {
|
||||
await this.load();
|
||||
init_sdk();
|
||||
init_sdk(SdkLoadService.logLevel);
|
||||
SdkLoadService.markAsReady();
|
||||
} catch (error) {
|
||||
SdkLoadService.markAsFailed(error);
|
||||
|
||||
@@ -73,14 +73,13 @@ export default class Domain {
|
||||
domain: DomainEncryptableKeys<D>,
|
||||
viewModel: ViewEncryptableKeys<V>,
|
||||
props: EncryptableKeys<D, V>[],
|
||||
orgId: string | null,
|
||||
key: SymmetricCryptoKey | null = null,
|
||||
objectContext: string = "No Domain Context",
|
||||
): Promise<V> {
|
||||
for (const prop of props) {
|
||||
viewModel[prop] =
|
||||
(await domain[prop]?.decrypt(
|
||||
orgId,
|
||||
null,
|
||||
key,
|
||||
`Property: ${prop as string}; ObjectContext: ${objectContext}`,
|
||||
)) ?? null;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { mockEnc } from "../../../../../spec";
|
||||
import { mockContainerService, mockEnc } from "../../../../../spec";
|
||||
import { SendType } from "../../enums/send-type";
|
||||
import { SendAccessResponse } from "../response/send-access.response";
|
||||
|
||||
@@ -23,6 +23,8 @@ describe("SendAccess", () => {
|
||||
expirationDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
creatorIdentifier: "creatorIdentifier",
|
||||
} as SendAccessResponse;
|
||||
|
||||
mockContainerService();
|
||||
});
|
||||
|
||||
it("Convert from empty", () => {
|
||||
|
||||
@@ -54,7 +54,7 @@ export class SendAccess extends Domain {
|
||||
async decrypt(key: SymmetricCryptoKey): Promise<SendAccessView> {
|
||||
const model = new SendAccessView(this);
|
||||
|
||||
await this.decryptObj<SendAccess, SendAccessView>(this, model, ["name"], null, key);
|
||||
await this.decryptObj<SendAccess, SendAccessView>(this, model, ["name"], key);
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.File:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mockEnc } from "../../../../../spec";
|
||||
import { mockContainerService, mockEnc } from "../../../../../spec";
|
||||
import { SendFileData } from "../data/send-file.data";
|
||||
|
||||
import { SendFile } from "./send-file";
|
||||
@@ -39,6 +39,7 @@ describe("SendFile", () => {
|
||||
});
|
||||
|
||||
it("Decrypt", async () => {
|
||||
mockContainerService();
|
||||
const sendFile = new SendFile();
|
||||
sendFile.id = "id";
|
||||
sendFile.size = "1100";
|
||||
|
||||
@@ -38,7 +38,6 @@ export class SendFile extends Domain {
|
||||
this,
|
||||
new SendFileView(this),
|
||||
["fileName"],
|
||||
null,
|
||||
key,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mockEnc } from "../../../../../spec";
|
||||
import { mockContainerService, mockEnc } from "../../../../../spec";
|
||||
import { SendTextData } from "../data/send-text.data";
|
||||
|
||||
import { SendText } from "./send-text";
|
||||
@@ -11,6 +11,8 @@ describe("SendText", () => {
|
||||
text: "encText",
|
||||
hidden: false,
|
||||
};
|
||||
|
||||
mockContainerService();
|
||||
});
|
||||
|
||||
it("Convert from empty", () => {
|
||||
|
||||
@@ -30,13 +30,7 @@ export class SendText extends Domain {
|
||||
}
|
||||
|
||||
decrypt(key: SymmetricCryptoKey): Promise<SendTextView> {
|
||||
return this.decryptObj<SendText, SendTextView>(
|
||||
this,
|
||||
new SendTextView(this),
|
||||
["text"],
|
||||
null,
|
||||
key,
|
||||
);
|
||||
return this.decryptObj<SendText, SendTextView>(this, new SendTextView(this), ["text"], key);
|
||||
}
|
||||
|
||||
static fromJSON(obj: Jsonify<SendText>) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { emptyGuid, UserId } from "@bitwarden/common/types/guid";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { makeStaticByteArray, mockEnc } from "../../../../../spec";
|
||||
import { makeStaticByteArray, mockContainerService, mockEnc } from "../../../../../spec";
|
||||
import { EncryptService } from "../../../../key-management/crypto/abstractions/encrypt.service";
|
||||
import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { ContainerService } from "../../../../platform/services/container.service";
|
||||
@@ -43,6 +43,8 @@ describe("Send", () => {
|
||||
disabled: false,
|
||||
hideEmail: true,
|
||||
};
|
||||
|
||||
mockContainerService();
|
||||
});
|
||||
|
||||
it("Convert from empty", () => {
|
||||
|
||||
@@ -89,7 +89,7 @@ export class Send extends Domain {
|
||||
model.key = await encryptService.decryptBytes(this.key, sendKeyEncryptionKey);
|
||||
model.cryptoKey = await keyService.makeSendKey(model.key);
|
||||
|
||||
await this.decryptObj<Send, SendView>(this, model, ["name", "notes"], null, model.cryptoKey);
|
||||
await this.decryptObj<Send, SendView>(this, model, ["name", "notes"], model.cryptoKey);
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.File:
|
||||
|
||||
@@ -4,12 +4,10 @@ import { mock, MockProxy } from "jest-mock-extended";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec";
|
||||
import { makeStaticByteArray, mockContainerService, mockEnc, mockFromJson } from "../../../../spec";
|
||||
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { ContainerService } from "../../../platform/services/container.service";
|
||||
import { OrgKey, UserKey } from "../../../types/key";
|
||||
import { AttachmentData } from "../../models/data/attachment.data";
|
||||
import { Attachment } from "../../models/domain/attachment";
|
||||
|
||||
@@ -70,10 +68,9 @@ describe("Attachment", () => {
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
|
||||
beforeEach(() => {
|
||||
keyService = mock<KeyService>();
|
||||
encryptService = mock<EncryptService>();
|
||||
|
||||
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
|
||||
const containerService = mockContainerService();
|
||||
keyService = containerService.keyService as MockProxy<KeyService>;
|
||||
encryptService = containerService.encryptService as MockProxy<EncryptService>;
|
||||
});
|
||||
|
||||
it("expected output", async () => {
|
||||
@@ -85,14 +82,13 @@ describe("Attachment", () => {
|
||||
attachment.key = mockEnc("key");
|
||||
attachment.fileName = mockEnc("fileName");
|
||||
|
||||
const userKey = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
keyService.getUserKey.mockResolvedValue(userKey as UserKey);
|
||||
encryptService.decryptFileData.mockResolvedValue(makeStaticByteArray(32));
|
||||
encryptService.unwrapSymmetricKey.mockResolvedValue(
|
||||
new SymmetricCryptoKey(makeStaticByteArray(64)),
|
||||
);
|
||||
|
||||
const view = await attachment.decrypt(null);
|
||||
const userKey = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const view = await attachment.decrypt(userKey);
|
||||
|
||||
expect(view).toEqual({
|
||||
id: "id",
|
||||
@@ -116,31 +112,11 @@ describe("Attachment", () => {
|
||||
it("uses the provided key without depending on KeyService", async () => {
|
||||
const providedKey = mock<SymmetricCryptoKey>();
|
||||
|
||||
await attachment.decrypt(null, "", providedKey);
|
||||
await attachment.decrypt(providedKey, "");
|
||||
|
||||
expect(keyService.getUserKey).not.toHaveBeenCalled();
|
||||
expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith(attachment.key, providedKey);
|
||||
});
|
||||
|
||||
it("gets an organization key if required", async () => {
|
||||
const orgKey = mock<OrgKey>();
|
||||
keyService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey);
|
||||
|
||||
await attachment.decrypt("orgId", "", null);
|
||||
|
||||
expect(keyService.getOrgKey).toHaveBeenCalledWith("orgId");
|
||||
expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith(attachment.key, orgKey);
|
||||
});
|
||||
|
||||
it("gets the user's decryption key if required", async () => {
|
||||
const userKey = mock<UserKey>();
|
||||
keyService.getUserKey.mockResolvedValue(userKey);
|
||||
|
||||
await attachment.decrypt(null, "", null);
|
||||
|
||||
expect(keyService.getUserKey).toHaveBeenCalled();
|
||||
expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith(attachment.key, userKey);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { OrgKey, UserKey } from "@bitwarden/common/types/key";
|
||||
import { Attachment as SdkAttachment } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
@@ -34,21 +33,19 @@ export class Attachment extends Domain {
|
||||
}
|
||||
|
||||
async decrypt(
|
||||
orgId: string | undefined,
|
||||
decryptionKey: SymmetricCryptoKey,
|
||||
context = "No Cipher Context",
|
||||
encKey?: SymmetricCryptoKey,
|
||||
): Promise<AttachmentView> {
|
||||
const view = await this.decryptObj<Attachment, AttachmentView>(
|
||||
this,
|
||||
new AttachmentView(this),
|
||||
["fileName"],
|
||||
orgId ?? null,
|
||||
encKey,
|
||||
decryptionKey,
|
||||
"DomainType: Attachment; " + context,
|
||||
);
|
||||
|
||||
if (this.key != null) {
|
||||
view.key = await this.decryptAttachmentKey(orgId, encKey);
|
||||
view.key = await this.decryptAttachmentKey(decryptionKey);
|
||||
view.encryptedKey = this.key; // Keep the encrypted key for the view
|
||||
}
|
||||
|
||||
@@ -56,27 +53,15 @@ export class Attachment extends Domain {
|
||||
}
|
||||
|
||||
private async decryptAttachmentKey(
|
||||
orgId: string | undefined,
|
||||
encKey?: SymmetricCryptoKey,
|
||||
decryptionKey: SymmetricCryptoKey,
|
||||
): Promise<SymmetricCryptoKey | undefined> {
|
||||
try {
|
||||
if (this.key == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (encKey == null) {
|
||||
const key = await this.getKeyForDecryption(orgId);
|
||||
|
||||
// If we don't have a key, we can't decrypt
|
||||
if (key == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
encKey = key;
|
||||
}
|
||||
|
||||
const encryptService = Utils.getContainerService().getEncryptService();
|
||||
const decValue = await encryptService.unwrapSymmetricKey(this.key, encKey);
|
||||
const decValue = await encryptService.unwrapSymmetricKey(this.key, decryptionKey);
|
||||
return decValue;
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -85,11 +70,6 @@ export class Attachment extends Domain {
|
||||
}
|
||||
}
|
||||
|
||||
private async getKeyForDecryption(orgId: string | undefined): Promise<OrgKey | UserKey | null> {
|
||||
const keyService = Utils.getContainerService().getKeyService();
|
||||
return orgId != null ? await keyService.getOrgKey(orgId) : await keyService.getUserKey();
|
||||
}
|
||||
|
||||
toAttachmentData(): AttachmentData {
|
||||
const a = new AttachmentData();
|
||||
if (this.size != null) {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { mockEnc, mockFromJson } from "../../../../spec";
|
||||
import {
|
||||
makeSymmetricCryptoKey,
|
||||
mockContainerService,
|
||||
mockEnc,
|
||||
mockFromJson,
|
||||
} from "../../../../spec";
|
||||
import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { CardData } from "../../../vault/models/data/card.data";
|
||||
import { Card } from "../../models/domain/card";
|
||||
@@ -65,7 +70,10 @@ describe("Card", () => {
|
||||
card.expYear = mockEnc("expYear");
|
||||
card.code = mockEnc("code");
|
||||
|
||||
const view = await card.decrypt(null);
|
||||
const userKey = makeSymmetricCryptoKey(64);
|
||||
|
||||
mockContainerService();
|
||||
const view = await card.decrypt(userKey);
|
||||
|
||||
expect(view).toEqual({
|
||||
_brand: "brand",
|
||||
|
||||
@@ -31,16 +31,11 @@ export class Card extends Domain {
|
||||
this.code = conditionalEncString(obj.code);
|
||||
}
|
||||
|
||||
async decrypt(
|
||||
orgId: string | undefined,
|
||||
context = "No Cipher Context",
|
||||
encKey?: SymmetricCryptoKey,
|
||||
): Promise<CardView> {
|
||||
async decrypt(encKey: SymmetricCryptoKey, context = "No Cipher Context"): Promise<CardView> {
|
||||
return this.decryptObj<Card, CardView>(
|
||||
this,
|
||||
new CardView(),
|
||||
["cardholderName", "brand", "number", "expMonth", "expYear", "code"],
|
||||
orgId ?? null,
|
||||
encKey,
|
||||
"DomainType: Card; " + context,
|
||||
);
|
||||
|
||||
@@ -2,9 +2,7 @@ import { mock } from "jest-mock-extended";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
import { MockProxy } from "@bitwarden/common/platform/spec/mock-deep";
|
||||
import {
|
||||
CipherType as SdkCipherType,
|
||||
UriMatchType,
|
||||
@@ -14,11 +12,15 @@ import {
|
||||
EncString as SdkEncString,
|
||||
} from "@bitwarden/sdk-internal";
|
||||
|
||||
import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils";
|
||||
import {
|
||||
makeStaticByteArray,
|
||||
mockContainerService,
|
||||
mockEnc,
|
||||
mockFromJson,
|
||||
} from "../../../../spec/utils";
|
||||
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { UriMatchStrategy } from "../../../models/domain/domain-service";
|
||||
import { ContainerService } from "../../../platform/services/container.service";
|
||||
import { InitializerKey } from "../../../platform/services/cryptography/initializer-key";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { CipherService } from "../../abstractions/cipher.service";
|
||||
@@ -39,7 +41,16 @@ import { IdentityView } from "../../models/view/identity.view";
|
||||
import { LoginView } from "../../models/view/login.view";
|
||||
import { CipherPermissionsApi } from "../api/cipher-permissions.api";
|
||||
|
||||
const mockSymmetricKey = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
|
||||
describe("Cipher DTO", () => {
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
|
||||
beforeEach(() => {
|
||||
const containerService = mockContainerService();
|
||||
encryptService = containerService.encryptService;
|
||||
});
|
||||
|
||||
it("Convert from empty CipherData", () => {
|
||||
const data = new CipherData();
|
||||
const cipher = new Cipher(data);
|
||||
@@ -95,13 +106,12 @@ describe("Cipher DTO", () => {
|
||||
login.decrypt.mockResolvedValue(loginView);
|
||||
cipher.login = login;
|
||||
|
||||
const keyService = mock<KeyService>();
|
||||
const encryptService = mock<EncryptService>();
|
||||
const cipherService = mock<CipherService>();
|
||||
|
||||
encryptService.unwrapSymmetricKey.mockRejectedValue(new Error("Failed to unwrap key"));
|
||||
|
||||
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
|
||||
cipherService.getKeyForCipherKeyDecryption.mockResolvedValue(
|
||||
new SymmetricCryptoKey(makeStaticByteArray(64)),
|
||||
);
|
||||
|
||||
const cipherView = await cipher.decrypt(
|
||||
await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId),
|
||||
@@ -317,19 +327,11 @@ describe("Cipher DTO", () => {
|
||||
login.decrypt.mockResolvedValue(loginView);
|
||||
cipher.login = login;
|
||||
|
||||
const keyService = mock<KeyService>();
|
||||
const encryptService = mock<EncryptService>();
|
||||
const cipherService = mock<CipherService>();
|
||||
|
||||
encryptService.unwrapSymmetricKey.mockResolvedValue(
|
||||
new SymmetricCryptoKey(makeStaticByteArray(64)),
|
||||
);
|
||||
|
||||
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
|
||||
|
||||
const cipherView = await cipher.decrypt(
|
||||
await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId),
|
||||
);
|
||||
const cipherView = await cipher.decrypt(mockSymmetricKey);
|
||||
|
||||
expect(cipherView).toMatchObject({
|
||||
id: "id",
|
||||
@@ -445,19 +447,11 @@ describe("Cipher DTO", () => {
|
||||
cipher.permissions = new CipherPermissionsApi();
|
||||
cipher.archivedDate = undefined;
|
||||
|
||||
const keyService = mock<KeyService>();
|
||||
const encryptService = mock<EncryptService>();
|
||||
const cipherService = mock<CipherService>();
|
||||
|
||||
encryptService.unwrapSymmetricKey.mockResolvedValue(
|
||||
new SymmetricCryptoKey(makeStaticByteArray(64)),
|
||||
);
|
||||
|
||||
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
|
||||
|
||||
const cipherView = await cipher.decrypt(
|
||||
await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId),
|
||||
);
|
||||
const cipherView = await cipher.decrypt(mockSymmetricKey);
|
||||
|
||||
expect(cipherView).toMatchObject({
|
||||
id: "id",
|
||||
@@ -591,19 +585,11 @@ describe("Cipher DTO", () => {
|
||||
card.decrypt.mockResolvedValue(cardView);
|
||||
cipher.card = card;
|
||||
|
||||
const keyService = mock<KeyService>();
|
||||
const encryptService = mock<EncryptService>();
|
||||
const cipherService = mock<CipherService>();
|
||||
|
||||
encryptService.unwrapSymmetricKey.mockResolvedValue(
|
||||
new SymmetricCryptoKey(makeStaticByteArray(64)),
|
||||
);
|
||||
|
||||
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
|
||||
|
||||
const cipherView = await cipher.decrypt(
|
||||
await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId),
|
||||
);
|
||||
const cipherView = await cipher.decrypt(mockSymmetricKey);
|
||||
|
||||
expect(cipherView).toMatchObject({
|
||||
id: "id",
|
||||
@@ -761,19 +747,11 @@ describe("Cipher DTO", () => {
|
||||
identity.decrypt.mockResolvedValue(identityView);
|
||||
cipher.identity = identity;
|
||||
|
||||
const keyService = mock<KeyService>();
|
||||
const encryptService = mock<EncryptService>();
|
||||
const cipherService = mock<CipherService>();
|
||||
|
||||
encryptService.unwrapSymmetricKey.mockResolvedValue(
|
||||
new SymmetricCryptoKey(makeStaticByteArray(64)),
|
||||
);
|
||||
|
||||
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
|
||||
|
||||
const cipherView = await cipher.decrypt(
|
||||
await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId),
|
||||
);
|
||||
const cipherView = await cipher.decrypt(mockSymmetricKey);
|
||||
|
||||
expect(cipherView).toMatchObject({
|
||||
id: "id",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { assertNonNullish } from "@bitwarden/common/auth/utils";
|
||||
import { Cipher as SdkCipher } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
@@ -123,19 +124,22 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
}
|
||||
}
|
||||
|
||||
// We are passing the organizationId into the EncString.decrypt() method here, but because the encKey will always be
|
||||
// present and so the organizationId will not be used.
|
||||
// We will refactor the EncString.decrypt() in https://bitwarden.atlassian.net/browse/PM-3762 to remove the dependency on the organizationId.
|
||||
async decrypt(encKey: SymmetricCryptoKey): Promise<CipherView> {
|
||||
async decrypt(userKeyOrOrgKey: SymmetricCryptoKey): Promise<CipherView> {
|
||||
assertNonNullish(userKeyOrOrgKey, "userKeyOrOrgKey", "Cipher decryption");
|
||||
|
||||
const model = new CipherView(this);
|
||||
let bypassValidation = true;
|
||||
|
||||
// By default, the user/organization key is used for decryption
|
||||
let cipherDecryptionKey = userKeyOrOrgKey;
|
||||
|
||||
// If there is a cipher key present, unwrap it and use it for decryption
|
||||
if (this.key != null) {
|
||||
const encryptService = Utils.getContainerService().getEncryptService();
|
||||
|
||||
try {
|
||||
const cipherKey = await encryptService.unwrapSymmetricKey(this.key, encKey);
|
||||
encKey = cipherKey;
|
||||
const cipherKey = await encryptService.unwrapSymmetricKey(this.key, userKeyOrOrgKey);
|
||||
cipherDecryptionKey = cipherKey;
|
||||
bypassValidation = false;
|
||||
} catch {
|
||||
model.name = "[error: cannot decrypt]";
|
||||
@@ -144,22 +148,15 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
}
|
||||
}
|
||||
|
||||
await this.decryptObj<Cipher, CipherView>(
|
||||
this,
|
||||
model,
|
||||
["name", "notes"],
|
||||
this.organizationId ?? null,
|
||||
encKey,
|
||||
);
|
||||
await this.decryptObj<Cipher, CipherView>(this, model, ["name", "notes"], cipherDecryptionKey);
|
||||
|
||||
switch (this.type) {
|
||||
case CipherType.Login:
|
||||
if (this.login != null) {
|
||||
model.login = await this.login.decrypt(
|
||||
this.organizationId,
|
||||
bypassValidation,
|
||||
userKeyOrOrgKey,
|
||||
`Cipher Id: ${this.id}`,
|
||||
encKey,
|
||||
);
|
||||
}
|
||||
break;
|
||||
@@ -170,29 +167,17 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
break;
|
||||
case CipherType.Card:
|
||||
if (this.card != null) {
|
||||
model.card = await this.card.decrypt(
|
||||
this.organizationId,
|
||||
`Cipher Id: ${this.id}`,
|
||||
encKey,
|
||||
);
|
||||
model.card = await this.card.decrypt(userKeyOrOrgKey, `Cipher Id: ${this.id}`);
|
||||
}
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
if (this.identity != null) {
|
||||
model.identity = await this.identity.decrypt(
|
||||
this.organizationId,
|
||||
`Cipher Id: ${this.id}`,
|
||||
encKey,
|
||||
);
|
||||
model.identity = await this.identity.decrypt(userKeyOrOrgKey, `Cipher Id: ${this.id}`);
|
||||
}
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
if (this.sshKey != null) {
|
||||
model.sshKey = await this.sshKey.decrypt(
|
||||
this.organizationId,
|
||||
`Cipher Id: ${this.id}`,
|
||||
encKey,
|
||||
);
|
||||
model.sshKey = await this.sshKey.decrypt(userKeyOrOrgKey, `Cipher Id: ${this.id}`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -203,9 +188,8 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
const attachments: AttachmentView[] = [];
|
||||
for (const attachment of this.attachments) {
|
||||
const decryptedAttachment = await attachment.decrypt(
|
||||
this.organizationId,
|
||||
userKeyOrOrgKey,
|
||||
`Cipher Id: ${this.id}`,
|
||||
encKey,
|
||||
);
|
||||
attachments.push(decryptedAttachment);
|
||||
}
|
||||
@@ -215,7 +199,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
if (this.fields != null && this.fields.length > 0) {
|
||||
const fields: FieldView[] = [];
|
||||
for (const field of this.fields) {
|
||||
const decryptedField = await field.decrypt(this.organizationId, encKey);
|
||||
const decryptedField = await field.decrypt(userKeyOrOrgKey);
|
||||
fields.push(decryptedField);
|
||||
}
|
||||
model.fields = fields;
|
||||
@@ -224,7 +208,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
if (this.passwordHistory != null && this.passwordHistory.length > 0) {
|
||||
const passwordHistory: PasswordHistoryView[] = [];
|
||||
for (const ph of this.passwordHistory) {
|
||||
const decryptedPh = await ph.decrypt(this.organizationId, encKey);
|
||||
const decryptedPh = await ph.decrypt(userKeyOrOrgKey);
|
||||
passwordHistory.push(decryptedPh);
|
||||
}
|
||||
model.passwordHistory = passwordHistory;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mockEnc } from "../../../../spec";
|
||||
import { makeSymmetricCryptoKey, mockContainerService, mockEnc } from "../../../../spec";
|
||||
import { EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { EncryptionType } from "../../../platform/enums";
|
||||
import { Fido2CredentialData } from "../data/fido2-credential.data";
|
||||
@@ -103,7 +103,10 @@ describe("Fido2Credential", () => {
|
||||
credential.discoverable = mockEnc("true");
|
||||
credential.creationDate = mockDate;
|
||||
|
||||
const credentialView = await credential.decrypt(null);
|
||||
mockContainerService();
|
||||
|
||||
const cipherKey = makeSymmetricCryptoKey(64);
|
||||
const credentialView = await credential.decrypt(cipherKey);
|
||||
|
||||
expect(credentialView).toEqual({
|
||||
credentialId: "credentialId",
|
||||
|
||||
@@ -46,10 +46,7 @@ export class Fido2Credential extends Domain {
|
||||
this.creationDate = new Date(obj.creationDate);
|
||||
}
|
||||
|
||||
async decrypt(
|
||||
orgId: string | undefined,
|
||||
encKey?: SymmetricCryptoKey,
|
||||
): Promise<Fido2CredentialView> {
|
||||
async decrypt(decryptionKey: SymmetricCryptoKey): Promise<Fido2CredentialView> {
|
||||
const view = await this.decryptObj<Fido2Credential, Fido2CredentialView>(
|
||||
this,
|
||||
new Fido2CredentialView(),
|
||||
@@ -65,8 +62,7 @@ export class Fido2Credential extends Domain {
|
||||
"rpName",
|
||||
"userDisplayName",
|
||||
],
|
||||
orgId ?? null,
|
||||
encKey,
|
||||
decryptionKey,
|
||||
);
|
||||
|
||||
const { counter } = await this.decryptObj<
|
||||
@@ -74,7 +70,7 @@ export class Fido2Credential extends Domain {
|
||||
{
|
||||
counter: string;
|
||||
}
|
||||
>(this, { counter: "" }, ["counter"], orgId ?? null, encKey);
|
||||
>(this, { counter: "" }, ["counter"], decryptionKey);
|
||||
// Counter will end up as NaN if this fails
|
||||
view.counter = parseInt(counter);
|
||||
|
||||
@@ -82,8 +78,7 @@ export class Fido2Credential extends Domain {
|
||||
this,
|
||||
{ discoverable: "" },
|
||||
["discoverable"],
|
||||
orgId ?? null,
|
||||
encKey,
|
||||
decryptionKey,
|
||||
);
|
||||
view.discoverable = discoverable === "true";
|
||||
view.creationDate = this.creationDate;
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
IdentityLinkedIdType,
|
||||
} from "@bitwarden/sdk-internal";
|
||||
|
||||
import { mockEnc, mockFromJson } from "../../../../spec";
|
||||
import { mockContainerService, mockEnc, mockFromJson } from "../../../../spec";
|
||||
import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { CardLinkedId, IdentityLinkedId, LoginLinkedId } from "../../enums";
|
||||
import { FieldData } from "../../models/data/field.data";
|
||||
@@ -22,6 +22,7 @@ describe("Field", () => {
|
||||
value: "encValue",
|
||||
linkedId: null,
|
||||
};
|
||||
mockContainerService();
|
||||
});
|
||||
|
||||
it("Convert from empty", () => {
|
||||
|
||||
@@ -33,14 +33,8 @@ export class Field extends Domain {
|
||||
this.value = conditionalEncString(obj.value);
|
||||
}
|
||||
|
||||
decrypt(orgId: string | undefined, encKey?: SymmetricCryptoKey): Promise<FieldView> {
|
||||
return this.decryptObj<Field, FieldView>(
|
||||
this,
|
||||
new FieldView(this),
|
||||
["name", "value"],
|
||||
orgId ?? null,
|
||||
encKey,
|
||||
);
|
||||
decrypt(encKey: SymmetricCryptoKey): Promise<FieldView> {
|
||||
return this.decryptObj<Field, FieldView>(this, new FieldView(this), ["name", "value"], encKey);
|
||||
}
|
||||
|
||||
toFieldData(): FieldData {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { makeEncString, makeSymmetricCryptoKey, mockEnc, mockFromJson } from "../../../../spec";
|
||||
import {
|
||||
makeEncString,
|
||||
makeSymmetricCryptoKey,
|
||||
mockContainerService,
|
||||
mockEnc,
|
||||
mockFromJson,
|
||||
} from "../../../../spec";
|
||||
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { FolderData } from "../../models/data/folder.data";
|
||||
@@ -15,6 +21,7 @@ describe("Folder", () => {
|
||||
name: "encName",
|
||||
revisionDate: "2022-01-31T12:00:00.000Z",
|
||||
};
|
||||
mockContainerService();
|
||||
});
|
||||
|
||||
it("Convert", () => {
|
||||
@@ -33,7 +40,7 @@ describe("Folder", () => {
|
||||
folder.name = mockEnc("encName");
|
||||
folder.revisionDate = new Date("2022-01-31T12:00:00.000Z");
|
||||
|
||||
const view = await folder.decrypt();
|
||||
const view = await folder.decrypt(null);
|
||||
|
||||
expect(view).toEqual({
|
||||
id: "id",
|
||||
|
||||
@@ -39,8 +39,8 @@ export class Folder extends Domain {
|
||||
this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null;
|
||||
}
|
||||
|
||||
decrypt(): Promise<FolderView> {
|
||||
return this.decryptObj<Folder, FolderView>(this, new FolderView(this), ["name"], null);
|
||||
decrypt(key: SymmetricCryptoKey): Promise<FolderView> {
|
||||
return this.decryptObj<Folder, FolderView>(this, new FolderView(this), ["name"], key);
|
||||
}
|
||||
|
||||
async decryptWithKey(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mockEnc, mockFromJson } from "../../../../spec";
|
||||
import { mockContainerService, mockEnc, mockFromJson } from "../../../../spec";
|
||||
import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { IdentityData } from "../../models/data/identity.data";
|
||||
import { Identity } from "../../models/domain/identity";
|
||||
@@ -27,6 +27,8 @@ describe("Identity", () => {
|
||||
passportNumber: "encpassportNumber",
|
||||
licenseNumber: "enclicenseNumber",
|
||||
};
|
||||
|
||||
mockContainerService();
|
||||
});
|
||||
|
||||
it("Convert from empty", () => {
|
||||
|
||||
@@ -56,9 +56,8 @@ export class Identity extends Domain {
|
||||
}
|
||||
|
||||
decrypt(
|
||||
orgId: string | undefined,
|
||||
encKey: SymmetricCryptoKey,
|
||||
context: string = "No Cipher Context",
|
||||
encKey?: SymmetricCryptoKey,
|
||||
): Promise<IdentityView> {
|
||||
return this.decryptObj<Identity, IdentityView>(
|
||||
this,
|
||||
@@ -83,7 +82,6 @@ export class Identity extends Domain {
|
||||
"passportNumber",
|
||||
"licenseNumber",
|
||||
],
|
||||
orgId ?? null,
|
||||
encKey,
|
||||
"DomainType: Identity; " + context,
|
||||
);
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { MockProxy } from "jest-mock-extended";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { UriMatchType } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { mockEnc, mockFromJson } from "../../../../spec";
|
||||
import {
|
||||
makeSymmetricCryptoKey,
|
||||
mockContainerService,
|
||||
mockEnc,
|
||||
mockFromJson,
|
||||
} from "../../../../spec";
|
||||
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { UriMatchStrategy } from "../../../models/domain/domain-service";
|
||||
@@ -14,6 +19,7 @@ import { LoginUri } from "./login-uri";
|
||||
|
||||
describe("LoginUri", () => {
|
||||
let data: LoginUriData;
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
@@ -21,6 +27,9 @@ describe("LoginUri", () => {
|
||||
uriChecksum: "encUriChecksum",
|
||||
match: UriMatchStrategy.Domain,
|
||||
};
|
||||
|
||||
const containerService = mockContainerService();
|
||||
encryptService = containerService.getEncryptService();
|
||||
});
|
||||
|
||||
it("Convert from empty", () => {
|
||||
@@ -83,22 +92,13 @@ describe("LoginUri", () => {
|
||||
});
|
||||
|
||||
describe("validateChecksum", () => {
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
|
||||
beforeEach(() => {
|
||||
encryptService = mock();
|
||||
global.bitwardenContainerService = {
|
||||
getEncryptService: () => encryptService,
|
||||
getKeyService: () => null,
|
||||
};
|
||||
});
|
||||
|
||||
it("returns true if checksums match", async () => {
|
||||
const loginUri = new LoginUri();
|
||||
loginUri.uriChecksum = mockEnc("checksum");
|
||||
encryptService.hash.mockResolvedValue("checksum");
|
||||
|
||||
const actual = await loginUri.validateChecksum("uri", undefined, undefined);
|
||||
const key = makeSymmetricCryptoKey(64);
|
||||
const actual = await loginUri.validateChecksum("uri", key);
|
||||
|
||||
expect(actual).toBe(true);
|
||||
expect(encryptService.hash).toHaveBeenCalledWith("uri", "sha256");
|
||||
@@ -109,7 +109,7 @@ describe("LoginUri", () => {
|
||||
loginUri.uriChecksum = mockEnc("checksum");
|
||||
encryptService.hash.mockResolvedValue("incorrect checksum");
|
||||
|
||||
const actual = await loginUri.validateChecksum("uri", undefined, undefined);
|
||||
const actual = await loginUri.validateChecksum("uri", undefined);
|
||||
|
||||
expect(actual).toBe(false);
|
||||
});
|
||||
|
||||
@@ -31,29 +31,27 @@ export class LoginUri extends Domain {
|
||||
}
|
||||
|
||||
decrypt(
|
||||
orgId: string | undefined,
|
||||
encKey: SymmetricCryptoKey,
|
||||
context: string = "No Cipher Context",
|
||||
encKey?: SymmetricCryptoKey,
|
||||
): Promise<LoginUriView> {
|
||||
return this.decryptObj<LoginUri, LoginUriView>(
|
||||
this,
|
||||
new LoginUriView(this),
|
||||
["uri"],
|
||||
orgId ?? null,
|
||||
encKey,
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
async validateChecksum(clearTextUri: string, orgId?: string, encKey?: SymmetricCryptoKey) {
|
||||
async validateChecksum(clearTextUri: string, encKey: SymmetricCryptoKey) {
|
||||
if (this.uriChecksum == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const keyService = Utils.getContainerService().getEncryptService();
|
||||
const localChecksum = await keyService.hash(clearTextUri, "sha256");
|
||||
const encryptService = Utils.getContainerService().getEncryptService();
|
||||
const localChecksum = await encryptService.hash(clearTextUri, "sha256");
|
||||
|
||||
const remoteChecksum = await this.uriChecksum.decrypt(orgId ?? null, encKey);
|
||||
const remoteChecksum = await encryptService.decryptString(this.uriChecksum, encKey);
|
||||
return remoteChecksum === localChecksum;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
|
||||
import { mockEnc, mockFromJson } from "../../../../spec";
|
||||
import { mockContainerService, mockEnc, mockFromJson } from "../../../../spec";
|
||||
import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { UriMatchStrategy } from "../../../models/domain/domain-service";
|
||||
import { LoginData } from "../../models/data/login.data";
|
||||
@@ -14,6 +14,10 @@ import { Fido2CredentialView } from "../view/fido2-credential.view";
|
||||
import { Fido2Credential } from "./fido2-credential";
|
||||
|
||||
describe("Login DTO", () => {
|
||||
beforeEach(() => {
|
||||
mockContainerService();
|
||||
});
|
||||
|
||||
it("Convert from empty LoginData", () => {
|
||||
const data = new LoginData();
|
||||
const login = new Login(data);
|
||||
@@ -107,7 +111,7 @@ describe("Login DTO", () => {
|
||||
loginUri.validateChecksum.mockResolvedValue(true);
|
||||
login.uris = [loginUri];
|
||||
|
||||
const loginView = await login.decrypt(null, true);
|
||||
const loginView = await login.decrypt(true, null);
|
||||
expect(loginView).toEqual(expectedView);
|
||||
});
|
||||
|
||||
@@ -119,7 +123,7 @@ describe("Login DTO", () => {
|
||||
.mockResolvedValueOnce(true);
|
||||
login.uris = [loginUri, loginUri, loginUri];
|
||||
|
||||
const loginView = await login.decrypt(null, false);
|
||||
const loginView = await login.decrypt(false, null);
|
||||
expect(loginView).toEqual(expectedView);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,16 +44,14 @@ export class Login extends Domain {
|
||||
}
|
||||
|
||||
async decrypt(
|
||||
orgId: string | undefined,
|
||||
bypassValidation: boolean,
|
||||
encKey: SymmetricCryptoKey,
|
||||
context: string = "No Cipher Context",
|
||||
encKey?: SymmetricCryptoKey,
|
||||
): Promise<LoginView> {
|
||||
const view = await this.decryptObj<Login, LoginView>(
|
||||
this,
|
||||
new LoginView(this),
|
||||
["username", "password", "totp"],
|
||||
orgId ?? null,
|
||||
encKey,
|
||||
`DomainType: Login; ${context}`,
|
||||
);
|
||||
@@ -66,7 +64,7 @@ export class Login extends Domain {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uri = await this.uris[i].decrypt(orgId, context, encKey);
|
||||
const uri = await this.uris[i].decrypt(encKey, context);
|
||||
const uriString = uri.uri;
|
||||
|
||||
if (uriString == null) {
|
||||
@@ -79,7 +77,7 @@ export class Login extends Domain {
|
||||
// So we bypass the validation if there's no cipher.key or proceed with the validation and
|
||||
// Skip the value if it's been tampered with.
|
||||
const isValidUri =
|
||||
bypassValidation || (await this.uris[i].validateChecksum(uriString, orgId, encKey));
|
||||
bypassValidation || (await this.uris[i].validateChecksum(uriString, encKey));
|
||||
|
||||
if (isValidUri) {
|
||||
view.uris.push(uri);
|
||||
@@ -89,7 +87,7 @@ export class Login extends Domain {
|
||||
|
||||
if (this.fido2Credentials != null) {
|
||||
view.fido2Credentials = await Promise.all(
|
||||
this.fido2Credentials.map((key) => key.decrypt(orgId, encKey)),
|
||||
this.fido2Credentials.map((key) => key.decrypt(encKey)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mockEnc, mockFromJson } from "../../../../spec";
|
||||
import { mockContainerService, mockEnc, mockFromJson } from "../../../../spec";
|
||||
import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { PasswordHistoryData } from "../../models/data/password-history.data";
|
||||
import { Password } from "../../models/domain/password";
|
||||
@@ -11,6 +11,7 @@ describe("Password", () => {
|
||||
password: "encPassword",
|
||||
lastUsedDate: "2022-01-31T12:00:00.000Z",
|
||||
};
|
||||
mockContainerService();
|
||||
});
|
||||
|
||||
it("Convert from empty", () => {
|
||||
|
||||
@@ -22,12 +22,11 @@ export class Password extends Domain {
|
||||
this.lastUsedDate = new Date(obj.lastUsedDate);
|
||||
}
|
||||
|
||||
decrypt(orgId: string | undefined, encKey?: SymmetricCryptoKey): Promise<PasswordHistoryView> {
|
||||
decrypt(encKey: SymmetricCryptoKey): Promise<PasswordHistoryView> {
|
||||
return this.decryptObj<Password, PasswordHistoryView>(
|
||||
this,
|
||||
new PasswordHistoryView(this),
|
||||
["password"],
|
||||
orgId ?? null,
|
||||
encKey,
|
||||
"DomainType: PasswordHistory",
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { mockContainerService } from "../../../../spec";
|
||||
import { SecureNoteType } from "../../enums";
|
||||
import { SecureNoteData } from "../data/secure-note.data";
|
||||
|
||||
@@ -10,6 +11,8 @@ describe("SecureNote", () => {
|
||||
data = {
|
||||
type: SecureNoteType.Generic,
|
||||
};
|
||||
|
||||
mockContainerService();
|
||||
});
|
||||
|
||||
it("Convert from empty", () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { EncString as SdkEncString, SshKey as SdkSshKey } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { mockEnc } from "../../../../spec";
|
||||
import { mockContainerService, mockEnc } from "../../../../spec";
|
||||
import { SshKeyApi } from "../api/ssh-key.api";
|
||||
import { SshKeyData } from "../data/ssh-key.data";
|
||||
|
||||
@@ -18,6 +18,8 @@ describe("Sshkey", () => {
|
||||
KeyFingerprint: "keyFingerprint",
|
||||
}),
|
||||
);
|
||||
|
||||
mockContainerService();
|
||||
});
|
||||
|
||||
it("Convert", () => {
|
||||
|
||||
@@ -24,16 +24,11 @@ export class SshKey extends Domain {
|
||||
this.keyFingerprint = new EncString(obj.keyFingerprint);
|
||||
}
|
||||
|
||||
decrypt(
|
||||
orgId: string | undefined,
|
||||
context = "No Cipher Context",
|
||||
encKey?: SymmetricCryptoKey,
|
||||
): Promise<SshKeyView> {
|
||||
decrypt(encKey: SymmetricCryptoKey, context = "No Cipher Context"): Promise<SshKeyView> {
|
||||
return this.decryptObj<SshKey, SshKeyView>(
|
||||
this,
|
||||
new SshKeyView(),
|
||||
["privateKey", "publicKey", "keyFingerprint"],
|
||||
orgId ?? null,
|
||||
encKey,
|
||||
"DomainType: SshKey; " + context,
|
||||
);
|
||||
|
||||
@@ -55,7 +55,7 @@ const ENCRYPTED_BYTES = mock<EncArrayBuffer>();
|
||||
|
||||
const cipherData: CipherData = {
|
||||
id: "id",
|
||||
organizationId: "orgId",
|
||||
organizationId: "4ff8c0b2-1d3e-4f8c-9b2d-1d3e4f8c0b2" as OrganizationId,
|
||||
folderId: "folderId",
|
||||
edit: true,
|
||||
viewPassword: true,
|
||||
@@ -119,6 +119,8 @@ describe("Cipher Service", () => {
|
||||
beforeEach(() => {
|
||||
encryptService.encryptFileData.mockReturnValue(Promise.resolve(ENCRYPTED_BYTES));
|
||||
encryptService.encryptString.mockReturnValue(Promise.resolve(new EncString(ENCRYPTED_TEXT)));
|
||||
keyService.orgKeys$.mockReturnValue(of({ [orgId]: makeSymmetricCryptoKey(32) as OrgKey }));
|
||||
keyService.userKey$.mockReturnValue(of(makeSymmetricCryptoKey(64) as UserKey));
|
||||
|
||||
// Mock i18nService collator
|
||||
i18nService.collator = {
|
||||
@@ -181,9 +183,6 @@ describe("Cipher Service", () => {
|
||||
const testCipher = new Cipher(cipherData);
|
||||
const expectedRevisionDate = "2022-01-31T12:00:00.000Z";
|
||||
|
||||
keyService.getOrgKey.mockReturnValue(
|
||||
Promise.resolve<any>(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey),
|
||||
);
|
||||
keyService.makeDataEncKey.mockReturnValue(
|
||||
Promise.resolve([
|
||||
new SymmetricCryptoKey(new Uint8Array(32)),
|
||||
|
||||
@@ -1564,11 +1564,16 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
}
|
||||
|
||||
async getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise<UserKey | OrgKey> {
|
||||
return (
|
||||
(await this.keyService.getOrgKey(cipher.organizationId)) ||
|
||||
((await this.keyService.getUserKey(userId)) as UserKey)
|
||||
if (cipher.organizationId == null) {
|
||||
return await firstValueFrom(this.keyService.userKey$(userId));
|
||||
} else {
|
||||
return await firstValueFrom(
|
||||
this.keyService
|
||||
.orgKeys$(userId)
|
||||
.pipe(map((orgKeys) => orgKeys[cipher.organizationId as OrganizationId] as OrgKey)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async setAddEditCipherInfo(value: AddEditCipherInfo, userId: UserId) {
|
||||
await this.addEditCipherInfoState(userId).update(() => value, {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<span class="tw-relative tw-inline-block tw-leading-[0px]">
|
||||
<span class="tw-inline-block tw-leading-[0px]" [ngClass]="{ 'tw-invisible': showLoadingStyle() }">
|
||||
<i class="bwi" [ngClass]="iconClass" aria-hidden="true"></i>
|
||||
<i class="bwi" [ngClass]="iconClass()" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span
|
||||
class="tw-absolute tw-inset-0 tw-flex tw-items-center tw-justify-center"
|
||||
|
||||
@@ -20,7 +20,16 @@ import { SpinnerComponent } from "../spinner";
|
||||
import { TooltipDirective } from "../tooltip";
|
||||
import { ariaDisableElement } from "../utils";
|
||||
|
||||
export type IconButtonType = "primary" | "danger" | "contrast" | "main" | "muted" | "nav-contrast";
|
||||
export const IconButtonTypes = [
|
||||
"primary",
|
||||
"danger",
|
||||
"contrast",
|
||||
"main",
|
||||
"muted",
|
||||
"nav-contrast",
|
||||
] as const;
|
||||
|
||||
export type IconButtonType = (typeof IconButtonTypes)[number];
|
||||
|
||||
const focusRing = [
|
||||
// Workaround for box-shadow with transparent offset issue:
|
||||
@@ -148,9 +157,7 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE
|
||||
);
|
||||
}
|
||||
|
||||
get iconClass() {
|
||||
return [this.icon(), "!tw-m-0"];
|
||||
}
|
||||
readonly iconClass = computed(() => [this.icon(), "!tw-m-0"]);
|
||||
|
||||
protected readonly disabledAttr = computed(() => {
|
||||
const disabled = this.disabled() != null && this.disabled() !== false;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet";
|
||||
import { I18nMockService } from "../utils";
|
||||
|
||||
import { BitIconButtonComponent } from "./icon-button.component";
|
||||
import { BitIconButtonComponent, IconButtonTypes } from "./icon-button.component";
|
||||
|
||||
export default {
|
||||
title: "Component Library/Icon Button",
|
||||
@@ -30,7 +30,7 @@ export default {
|
||||
},
|
||||
argTypes: {
|
||||
buttonType: {
|
||||
options: ["primary", "secondary", "danger", "unstyled", "contrast", "main", "muted", "light"],
|
||||
options: IconButtonTypes,
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { filter, firstValueFrom } from "rxjs";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Collection, CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import {
|
||||
@@ -46,6 +45,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||
}
|
||||
|
||||
async parse(data: string): Promise<ImportResult> {
|
||||
const account = await firstValueFrom(this.accountService.activeAccount$);
|
||||
this.result = new ImportResult();
|
||||
const results: BitwardenJsonExport = JSON.parse(data);
|
||||
if (results == null || results.items == null) {
|
||||
@@ -54,9 +54,9 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||
}
|
||||
|
||||
if (results.encrypted) {
|
||||
await this.parseEncrypted(results as any);
|
||||
await this.parseEncrypted(results as any, account.id);
|
||||
} else {
|
||||
await this.parseDecrypted(results as any);
|
||||
await this.parseDecrypted(results as any, account.id);
|
||||
}
|
||||
|
||||
return this.result;
|
||||
@@ -64,9 +64,8 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||
|
||||
private async parseEncrypted(
|
||||
results: BitwardenEncryptedIndividualJsonExport | BitwardenEncryptedOrgJsonExport,
|
||||
userId: UserId,
|
||||
) {
|
||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
|
||||
if (results.encKeyValidation_DO_NOT_EDIT != null) {
|
||||
const orgKeys = await firstValueFrom(this.keyService.orgKeys$(userId));
|
||||
let keyForDecryption: SymmetricCryptoKey = orgKeys?.[this.organizationId];
|
||||
@@ -84,8 +83,8 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||
}
|
||||
|
||||
const groupingsMap = this.organization
|
||||
? await this.parseCollections(userId, results as BitwardenEncryptedOrgJsonExport)
|
||||
: await this.parseFolders(results as BitwardenEncryptedIndividualJsonExport);
|
||||
? await this.parseCollections(results as BitwardenEncryptedOrgJsonExport, userId)
|
||||
: await this.parseFolders(results as BitwardenEncryptedIndividualJsonExport, userId);
|
||||
|
||||
for (const c of results.items) {
|
||||
const cipher = CipherWithIdExport.toDomain(c);
|
||||
@@ -125,12 +124,11 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||
|
||||
private async parseDecrypted(
|
||||
results: BitwardenUnEncryptedIndividualJsonExport | BitwardenUnEncryptedOrgJsonExport,
|
||||
userId: UserId,
|
||||
) {
|
||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
|
||||
const groupingsMap = this.organization
|
||||
? await this.parseCollections(userId, results as BitwardenUnEncryptedOrgJsonExport)
|
||||
: await this.parseFolders(results as BitwardenUnEncryptedIndividualJsonExport);
|
||||
? await this.parseCollections(results as BitwardenUnEncryptedOrgJsonExport, userId)
|
||||
: await this.parseFolders(results as BitwardenUnEncryptedIndividualJsonExport, userId);
|
||||
|
||||
results.items.forEach((c) => {
|
||||
const cipher = CipherWithIdExport.toView(c);
|
||||
@@ -169,11 +167,14 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||
|
||||
private async parseFolders(
|
||||
data: BitwardenUnEncryptedIndividualJsonExport | BitwardenEncryptedIndividualJsonExport,
|
||||
userId: UserId,
|
||||
): Promise<Map<string, number>> | null {
|
||||
if (data.folders == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
|
||||
|
||||
const groupingsMap = new Map<string, number>();
|
||||
|
||||
for (const f of data.folders) {
|
||||
@@ -181,7 +182,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||
if (data.encrypted) {
|
||||
const folder = FolderWithIdExport.toDomain(f);
|
||||
if (folder != null) {
|
||||
folderView = await folder.decrypt();
|
||||
folderView = await folder.decrypt(userKey);
|
||||
}
|
||||
} else {
|
||||
folderView = FolderWithIdExport.toView(f);
|
||||
@@ -196,8 +197,8 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||
}
|
||||
|
||||
private async parseCollections(
|
||||
userId: UserId,
|
||||
data: BitwardenUnEncryptedOrgJsonExport | BitwardenEncryptedOrgJsonExport,
|
||||
userId: UserId,
|
||||
): Promise<Map<string, number>> | null {
|
||||
if (data.collections == null) {
|
||||
return null;
|
||||
|
||||
996
package-lock.json
generated
996
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -44,7 +44,7 @@
|
||||
"@angular/compiler-cli": "20.3.15",
|
||||
"@babel/core": "7.28.5",
|
||||
"@babel/preset-env": "7.28.5",
|
||||
"@compodoc/compodoc": "1.1.26",
|
||||
"@compodoc/compodoc": "1.1.32",
|
||||
"@electron/notarize": "3.0.1",
|
||||
"@electron/rebuild": "4.0.1",
|
||||
"@eslint/compat": "2.0.0",
|
||||
@@ -87,7 +87,7 @@
|
||||
"axe-playwright": "2.2.2",
|
||||
"babel-loader": "9.2.1",
|
||||
"base64-loader": "1.0.0",
|
||||
"browserslist": "4.28.0",
|
||||
"browserslist": "4.28.1",
|
||||
"chromatic": "13.3.1",
|
||||
"concurrently": "9.2.0",
|
||||
"copy-webpack-plugin": "13.0.1",
|
||||
|
||||
Reference in New Issue
Block a user