1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-28 02:23:25 +00:00
Files
browser/libs/common/src/vault/models/view/cipher.view.spec.ts
Shane Melton 391f540d1f [PM-22136] Implement SDK cipher encryption (#15337)
* [PM-22136] Update sdk cipher view map to support uknown uuid type

* [PM-22136] Add key to CipherView for copying to SdkCipherView for encryption

* [PM-22136] Add fromSdk* helpers to Cipher domain objects

* [PM-22136] Add toSdk* helpers to Cipher View objects

* [PM-22136] Add encrypt() to cipher encryption service

* [PM-22136] Add feature flag

* [PM-22136] Use new SDK encrypt method when feature flag is enabled

* [PM-22136] Filter out null/empty URIs

* [PM-22136] Change default value for cipher view arrays to []. See ADR-0014.

* [PM-22136] Keep encrypted key value on attachment so that it is passed to the SDK

* [PM-22136] Keep encrypted key value on CipherView so that it is passed to the SDK during encryption

* [PM-22136] Update failing attachment test

* [PM-22136] Update failing importer tests due to new default value for arrays

* [PM-22136] Update CipherView.fromJson to handle the prototype of EncString for the cipher key

* [PM-22136] Add tickets for followup work

* [PM-22136] Use new set_fido2_credentials SDK method instead

* [PM-22136] Fix missing prototype when decrypting Fido2Credentials

* [PM-22136] Fix test after sdk change

* [PM-22136] Update @bitwarden/sdk-internal version

* [PM-22136] Fix some strict typing errors

* [PM-23348] Migrate move cipher to org to SDK (#15567)

* [PM-23348] Add moveToOrganization method to cipher-encryption.service.ts

* [PM-23348] Use cipherEncryptionService.moveToOrganization in cipherService shareWithServer and shareManyWithServer methods

* [PM-23348] Update cipherFormService to use the shareWithServer() method instead of encrypt()

* [PM-23348] Fix typo

* [PM-23348] Add missing docs

* [PM-22136] Fix EncString import after merge with main
2025-07-21 23:27:01 -07:00

299 lines
10 KiB
TypeScript

import { Jsonify } from "type-fest";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { CipherPermissionsApi } from "@bitwarden/common/vault/models/api/cipher-permissions.api";
import {
CipherView as SdkCipherView,
CipherType as SdkCipherType,
CipherRepromptType as SdkCipherRepromptType,
AttachmentView as SdkAttachmentView,
LoginUriView as SdkLoginUriView,
LoginView as SdkLoginView,
FieldView as SdkFieldView,
FieldType as SdkFieldType,
} from "@bitwarden/sdk-internal";
import { mockFromJson, mockFromSdk } from "../../../../spec";
import { CipherRepromptType } from "../../enums";
import { CipherType } from "../../enums/cipher-type";
import { AttachmentView } from "./attachment.view";
import { CardView } from "./card.view";
import { CipherView } from "./cipher.view";
import { FieldView } from "./field.view";
import { IdentityView } from "./identity.view";
import { LoginView } from "./login.view";
import { PasswordHistoryView } from "./password-history.view";
import { SecureNoteView } from "./secure-note.view";
import { SshKeyView } from "./ssh-key.view";
jest.mock("../../models/view/login.view");
jest.mock("../../models/view/attachment.view");
jest.mock("../../models/view/field.view");
jest.mock("../../models/view/password-history.view");
describe("CipherView", () => {
beforeEach(() => {
(LoginView as any).mockClear();
(AttachmentView as any).mockClear();
(FieldView as any).mockClear();
(PasswordHistoryView as any).mockClear();
});
describe("fromJSON", () => {
it("initializes nested objects", () => {
jest.spyOn(AttachmentView, "fromJSON").mockImplementation(mockFromJson);
jest.spyOn(FieldView, "fromJSON").mockImplementation(mockFromJson);
jest.spyOn(PasswordHistoryView, "fromJSON").mockImplementation(mockFromJson);
const revisionDate = new Date("2022-08-04T01:06:40.441Z");
const deletedDate = new Date("2022-09-04T01:06:40.441Z");
const actual = CipherView.fromJSON({
revisionDate: revisionDate.toISOString(),
deletedDate: deletedDate.toISOString(),
attachments: ["attachment1", "attachment2"] as any,
fields: ["field1", "field2"] as any,
passwordHistory: ["ph1", "ph2", "ph3"] as any,
});
const expected = {
revisionDate: revisionDate,
deletedDate: deletedDate,
attachments: ["attachment1_fromJSON", "attachment2_fromJSON"],
fields: ["field1_fromJSON", "field2_fromJSON"],
passwordHistory: ["ph1_fromJSON", "ph2_fromJSON", "ph3_fromJSON"],
};
expect(actual).toMatchObject(expected);
});
test.each([
// Test description, CipherType, expected output
["LoginView", CipherType.Login, { login: "myLogin_fromJSON" }],
["CardView", CipherType.Card, { card: "myCard_fromJSON" }],
["IdentityView", CipherType.Identity, { identity: "myIdentity_fromJSON" }],
["Secure Note", CipherType.SecureNote, { secureNote: "mySecureNote_fromJSON" }],
])("initializes %s", (description: string, cipherType: CipherType, expected: any) => {
jest.spyOn(LoginView, "fromJSON").mockImplementation(mockFromJson);
jest.spyOn(IdentityView, "fromJSON").mockImplementation(mockFromJson);
jest.spyOn(CardView, "fromJSON").mockImplementation(mockFromJson);
jest.spyOn(SecureNoteView, "fromJSON").mockImplementation(mockFromJson);
const actual = CipherView.fromJSON({
login: "myLogin",
card: "myCard",
identity: "myIdentity",
secureNote: "mySecureNote",
type: cipherType,
} as any);
expect(actual).toMatchObject(expected);
});
it("handle both string and object inputs for the cipher key", () => {
const cipherKeyString = "cipherKeyString";
const cipherKeyObject = new EncString("cipherKeyObject");
// Test with string input
let actual = CipherView.fromJSON({
key: cipherKeyString,
});
expect(actual.key).toBeInstanceOf(EncString);
expect(actual.key?.toJSON()).toBe(cipherKeyString);
// Test with object input (which can happen when cipher view is stored in an InMemory state provider)
actual = CipherView.fromJSON({
key: cipherKeyObject,
} as Jsonify<CipherView>);
expect(actual.key).toBeInstanceOf(EncString);
expect(actual.key?.toJSON()).toBe(cipherKeyObject.toJSON());
});
});
describe("fromSdkCipherView", () => {
let sdkCipherView: SdkCipherView;
beforeEach(() => {
jest.spyOn(CardView, "fromSdkCardView").mockImplementation(mockFromSdk);
jest.spyOn(IdentityView, "fromSdkIdentityView").mockImplementation(mockFromSdk);
jest.spyOn(LoginView, "fromSdkLoginView").mockImplementation(mockFromSdk);
jest.spyOn(SecureNoteView, "fromSdkSecureNoteView").mockImplementation(mockFromSdk);
jest.spyOn(SshKeyView, "fromSdkSshKeyView").mockImplementation(mockFromSdk);
jest.spyOn(AttachmentView, "fromSdkAttachmentView").mockImplementation(mockFromSdk);
jest.spyOn(FieldView, "fromSdkFieldView").mockImplementation(mockFromSdk);
sdkCipherView = {
id: "id",
organizationId: "orgId",
folderId: "folderId",
collectionIds: ["collectionId"],
key: undefined,
name: "name",
notes: undefined,
type: SdkCipherType.Login,
favorite: true,
edit: true,
reprompt: SdkCipherRepromptType.None,
organizationUseTotp: false,
viewPassword: true,
localData: undefined,
permissions: undefined,
attachments: [{ id: "attachmentId", url: "attachmentUrl" } as SdkAttachmentView],
login: {
username: "username",
password: "password",
uris: [{ uri: "bitwarden.com" } as SdkLoginUriView],
totp: "totp",
autofillOnPageLoad: true,
} as SdkLoginView,
identity: undefined,
card: undefined,
secureNote: undefined,
sshKey: undefined,
fields: [
{
name: "fieldName",
value: "fieldValue",
type: SdkFieldType.Linked,
linkedId: 100,
} as SdkFieldView,
],
passwordHistory: undefined,
creationDate: "2022-01-01T12:00:00.000Z",
revisionDate: "2022-01-02T12:00:00.000Z",
deletedDate: undefined,
};
});
it("returns undefined when input is null", () => {
expect(CipherView.fromSdkCipherView(null as unknown as SdkCipherView)).toBeUndefined();
});
it("maps properties correctly", () => {
const result = CipherView.fromSdkCipherView(sdkCipherView);
expect(result).toMatchObject({
id: "id",
organizationId: "orgId",
folderId: "folderId",
collectionIds: ["collectionId"],
name: "name",
notes: null,
type: CipherType.Login,
favorite: true,
edit: true,
reprompt: CipherRepromptType.None,
organizationUseTotp: false,
viewPassword: true,
localData: undefined,
permissions: undefined,
attachments: [
{
id: "attachmentId",
url: "attachmentUrl",
__fromSdk: true,
},
],
login: {
username: "username",
password: "password",
uris: [
{
uri: "bitwarden.com",
},
],
totp: "totp",
autofillOnPageLoad: true,
__fromSdk: true,
},
identity: new IdentityView(),
card: new CardView(),
secureNote: new SecureNoteView(),
sshKey: new SshKeyView(),
fields: [
{
name: "fieldName",
value: "fieldValue",
type: SdkFieldType.Linked,
linkedId: 100,
__fromSdk: true,
},
],
passwordHistory: [],
creationDate: new Date("2022-01-01T12:00:00.000Z"),
revisionDate: new Date("2022-01-02T12:00:00.000Z"),
deletedDate: null,
});
});
});
describe("toSdkCipherView", () => {
it("maps properties correctly", () => {
const cipherView = new CipherView();
cipherView.id = "0a54d80c-14aa-4ef8-8c3a-7ea99ce5b602";
cipherView.organizationId = "000f2a6e-da5e-4726-87ed-1c5c77322c3c";
cipherView.folderId = "41b22db4-8e2a-4ed2-b568-f1186c72922f";
cipherView.collectionIds = ["b0473506-3c3c-4260-a734-dfaaf833ab6f"];
cipherView.key = new EncString("some-key");
cipherView.name = "name";
cipherView.notes = "notes";
cipherView.type = CipherType.Login;
cipherView.favorite = true;
cipherView.edit = true;
cipherView.viewPassword = false;
cipherView.reprompt = CipherRepromptType.None;
cipherView.organizationUseTotp = false;
cipherView.localData = {
lastLaunched: new Date("2022-01-01T12:00:00.000Z").getTime(),
lastUsedDate: new Date("2022-01-02T12:00:00.000Z").getTime(),
};
cipherView.permissions = new CipherPermissionsApi();
cipherView.permissions.restore = true;
cipherView.permissions.delete = true;
cipherView.attachments = [];
cipherView.fields = [];
cipherView.passwordHistory = [];
cipherView.login = new LoginView();
cipherView.revisionDate = new Date("2022-01-02T12:00:00.000Z");
cipherView.creationDate = new Date("2022-01-02T12:00:00.000Z");
const sdkCipherView = cipherView.toSdkCipherView();
expect(sdkCipherView).toMatchObject({
id: "0a54d80c-14aa-4ef8-8c3a-7ea99ce5b602",
organizationId: "000f2a6e-da5e-4726-87ed-1c5c77322c3c",
folderId: "41b22db4-8e2a-4ed2-b568-f1186c72922f",
collectionIds: ["b0473506-3c3c-4260-a734-dfaaf833ab6f"],
key: "some-key",
name: "name",
notes: "notes",
type: SdkCipherType.Login,
favorite: true,
edit: true,
viewPassword: false,
reprompt: SdkCipherRepromptType.None,
organizationUseTotp: false,
localData: {
lastLaunched: "2022-01-01T12:00:00.000Z",
lastUsedDate: "2022-01-02T12:00:00.000Z",
},
permissions: {
restore: true,
delete: true,
},
deletedDate: undefined,
creationDate: "2022-01-02T12:00:00.000Z",
revisionDate: "2022-01-02T12:00:00.000Z",
attachments: [],
passwordHistory: [],
login: undefined,
identity: undefined,
card: undefined,
secureNote: undefined,
sshKey: undefined,
fields: [],
} as SdkCipherView);
});
});
});