1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 13:23:34 +00:00

[PM-25683] Migrate Cipher model and sub-models (#16974)

* Made domain classes ts-strict compliant and fixed spec files

* Fixed domain base class and other test files

* Added conditional utils and fixed small nits

* removed comments

* removd ts expect errors

* Added removed counter

* renamed test name

* fixed tests
This commit is contained in:
SmithThe4th
2025-10-24 09:43:38 -04:00
committed by GitHub
parent c94f93d0c6
commit 3609127858
28 changed files with 762 additions and 749 deletions

View File

@@ -14,15 +14,15 @@ export type DecryptedObject<
// extracts shared keys from the domain and view types // extracts shared keys from the domain and view types
type EncryptableKeys<D extends Domain, V extends View> = (keyof D & type EncryptableKeys<D extends Domain, V extends View> = (keyof D &
ConditionalKeys<D, EncString | null>) & ConditionalKeys<D, EncString | null | undefined>) &
(keyof V & ConditionalKeys<V, string | null>); (keyof V & ConditionalKeys<V, string | null | undefined>);
type DomainEncryptableKeys<D extends Domain> = { type DomainEncryptableKeys<D extends Domain> = {
[key in ConditionalKeys<D, EncString | null>]: EncString | null; [key in ConditionalKeys<D, EncString | null | undefined>]?: EncString | null | undefined;
}; };
type ViewEncryptableKeys<V extends View> = { type ViewEncryptableKeys<V extends View> = {
[key in ConditionalKeys<V, string | null>]: string | null; [key in ConditionalKeys<V, string | null | undefined>]?: string | null | undefined;
}; };
// https://contributing.bitwarden.com/architecture/clients/data-model#domain // https://contributing.bitwarden.com/architecture/clients/data-model#domain

View File

@@ -24,7 +24,9 @@ export class CipherPermissionsApi extends BaseResponse implements SdkCipherPermi
/** /**
* Converts the SDK CipherPermissionsApi to a CipherPermissionsApi. * Converts the SDK CipherPermissionsApi to a CipherPermissionsApi.
*/ */
static fromSdkCipherPermissions(obj: SdkCipherPermissions): CipherPermissionsApi | undefined { static fromSdkCipherPermissions(
obj: SdkCipherPermissions | undefined,
): CipherPermissionsApi | undefined {
if (!obj) { if (!obj) {
return undefined; return undefined;
} }

View File

@@ -32,12 +32,12 @@ describe("Attachment", () => {
const attachment = new Attachment(data); const attachment = new Attachment(data);
expect(attachment).toEqual({ expect(attachment).toEqual({
id: null, id: undefined,
url: null, url: undefined,
size: undefined, size: undefined,
sizeName: null, sizeName: undefined,
key: null, key: undefined,
fileName: null, fileName: undefined,
}); });
}); });
@@ -79,6 +79,8 @@ describe("Attachment", () => {
attachment.key = mockEnc("key"); attachment.key = mockEnc("key");
attachment.fileName = mockEnc("fileName"); attachment.fileName = mockEnc("fileName");
const userKey = new SymmetricCryptoKey(makeStaticByteArray(64));
keyService.getUserKey.mockResolvedValue(userKey as UserKey);
encryptService.decryptFileData.mockResolvedValue(makeStaticByteArray(32)); encryptService.decryptFileData.mockResolvedValue(makeStaticByteArray(32));
encryptService.unwrapSymmetricKey.mockResolvedValue( encryptService.unwrapSymmetricKey.mockResolvedValue(
new SymmetricCryptoKey(makeStaticByteArray(64)), new SymmetricCryptoKey(makeStaticByteArray(64)),
@@ -152,8 +154,8 @@ describe("Attachment", () => {
expect(actual).toBeInstanceOf(Attachment); expect(actual).toBeInstanceOf(Attachment);
}); });
it("returns null if object is null", () => { it("returns undefined if object is null", () => {
expect(Attachment.fromJSON(null)).toBeNull(); expect(Attachment.fromJSON(null)).toBeUndefined();
}); });
}); });

View File

@@ -1,23 +1,23 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { OrgKey, UserKey } from "@bitwarden/common/types/key";
import { Attachment as SdkAttachment } from "@bitwarden/sdk-internal"; import { Attachment as SdkAttachment } from "@bitwarden/sdk-internal";
import { EncString } from "../../../key-management/crypto/models/enc-string"; import { EncString } from "../../../key-management/crypto/models/enc-string";
import { Utils } from "../../../platform/misc/utils"; import { Utils } from "../../../platform/misc/utils";
import Domain from "../../../platform/models/domain/domain-base"; import Domain from "../../../platform/models/domain/domain-base";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { conditionalEncString, encStringFrom } from "../../utils/domain-utils";
import { AttachmentData } from "../data/attachment.data"; import { AttachmentData } from "../data/attachment.data";
import { AttachmentView } from "../view/attachment.view"; import { AttachmentView } from "../view/attachment.view";
export class Attachment extends Domain { export class Attachment extends Domain {
id: string; id?: string;
url: string; url?: string;
size: string; size?: string;
sizeName: string; // Readable size, ex: "4.2 KB" or "1.43 GB" sizeName?: string; // Readable size, ex: "4.2 KB" or "1.43 GB"
key: EncString; key?: EncString;
fileName: EncString; fileName?: EncString;
constructor(obj?: AttachmentData) { constructor(obj?: AttachmentData) {
super(); super();
@@ -25,32 +25,24 @@ export class Attachment extends Domain {
return; return;
} }
this.id = obj.id;
this.url = obj.url;
this.size = obj.size; this.size = obj.size;
this.buildDomainModel( this.sizeName = obj.sizeName;
this, this.fileName = conditionalEncString(obj.fileName);
obj, this.key = conditionalEncString(obj.key);
{
id: null,
url: null,
sizeName: null,
fileName: null,
key: null,
},
["id", "url", "sizeName"],
);
} }
async decrypt( async decrypt(
orgId: string, orgId: string | undefined,
context = "No Cipher Context", context = "No Cipher Context",
encKey?: SymmetricCryptoKey, encKey?: SymmetricCryptoKey,
): Promise<AttachmentView> { ): Promise<AttachmentView> {
const view = await this.decryptObj<Attachment, AttachmentView>( const view = await this.decryptObj<Attachment, AttachmentView>(
this, this,
// @ts-expect-error ViewEncryptableKeys type should be fixed to allow for optional values, but is out of scope for now.
new AttachmentView(this), new AttachmentView(this),
["fileName"], ["fileName"],
orgId, orgId ?? null,
encKey, encKey,
"DomainType: Attachment; " + context, "DomainType: Attachment; " + context,
); );
@@ -63,30 +55,46 @@ export class Attachment extends Domain {
return view; return view;
} }
private async decryptAttachmentKey(orgId: string, encKey?: SymmetricCryptoKey) { private async decryptAttachmentKey(
orgId: string | undefined,
encKey?: SymmetricCryptoKey,
): Promise<SymmetricCryptoKey | undefined> {
try { try {
if (this.key == null) {
return undefined;
}
if (encKey == null) { if (encKey == null) {
encKey = await this.getKeyForDecryption(orgId); 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 encryptService = Utils.getContainerService().getEncryptService();
const decValue = await encryptService.unwrapSymmetricKey(this.key, encKey); const decValue = await encryptService.unwrapSymmetricKey(this.key, encKey);
return decValue; return decValue;
// FIXME: Remove when updating file. Eslint update
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) { } catch (e) {
// TODO: error? // eslint-disable-next-line no-console
console.error("[Attachment] Error decrypting attachment", e);
return undefined;
} }
} }
private async getKeyForDecryption(orgId: string) { private async getKeyForDecryption(orgId: string | undefined): Promise<OrgKey | UserKey | null> {
const keyService = Utils.getContainerService().getKeyService(); const keyService = Utils.getContainerService().getKeyService();
return orgId != null ? await keyService.getOrgKey(orgId) : await keyService.getUserKey(); return orgId != null ? await keyService.getOrgKey(orgId) : await keyService.getUserKey();
} }
toAttachmentData(): AttachmentData { toAttachmentData(): AttachmentData {
const a = new AttachmentData(); const a = new AttachmentData();
a.size = this.size; if (this.size != null) {
a.size = this.size;
}
this.buildDataModel( this.buildDataModel(
this, this,
a, a,
@@ -102,18 +110,20 @@ export class Attachment extends Domain {
return a; return a;
} }
static fromJSON(obj: Partial<Jsonify<Attachment>>): Attachment { static fromJSON(obj: Partial<Jsonify<Attachment>> | undefined): Attachment | undefined {
if (obj == null) { if (obj == null) {
return null; return undefined;
} }
const key = EncString.fromJSON(obj.key); const attachment = new Attachment();
const fileName = EncString.fromJSON(obj.fileName); attachment.id = obj.id;
attachment.url = obj.url;
attachment.size = obj.size;
attachment.sizeName = obj.sizeName;
attachment.key = encStringFrom(obj.key);
attachment.fileName = encStringFrom(obj.fileName);
return Object.assign(new Attachment(), obj, { return attachment;
key,
fileName,
});
} }
/** /**
@@ -136,7 +146,7 @@ export class Attachment extends Domain {
* Maps an SDK Attachment object to an Attachment * Maps an SDK Attachment object to an Attachment
* @param obj - The SDK attachment object * @param obj - The SDK attachment object
*/ */
static fromSdkAttachment(obj: SdkAttachment): Attachment | undefined { static fromSdkAttachment(obj?: SdkAttachment): Attachment | undefined {
if (!obj) { if (!obj) {
return undefined; return undefined;
} }
@@ -146,8 +156,8 @@ export class Attachment extends Domain {
attachment.url = obj.url; attachment.url = obj.url;
attachment.size = obj.size; attachment.size = obj.size;
attachment.sizeName = obj.sizeName; attachment.sizeName = obj.sizeName;
attachment.fileName = EncString.fromJSON(obj.fileName); attachment.fileName = encStringFrom(obj.fileName);
attachment.key = EncString.fromJSON(obj.key); attachment.key = encStringFrom(obj.key);
return attachment; return attachment;
} }

View File

@@ -22,12 +22,12 @@ describe("Card", () => {
const card = new Card(data); const card = new Card(data);
expect(card).toEqual({ expect(card).toEqual({
cardholderName: null, cardholderName: undefined,
brand: null, brand: undefined,
number: null, number: undefined,
expMonth: null, expMonth: undefined,
expYear: null, expYear: undefined,
code: null, code: undefined,
}); });
}); });
@@ -94,8 +94,8 @@ describe("Card", () => {
expect(actual).toBeInstanceOf(Card); expect(actual).toBeInstanceOf(Card);
}); });
it("returns null if object is null", () => { it("returns undefined if object is null", () => {
expect(Card.fromJSON(null)).toBeNull(); expect(Card.fromJSON(null)).toBeUndefined();
}); });
}); });

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { Card as SdkCard } from "@bitwarden/sdk-internal"; import { Card as SdkCard } from "@bitwarden/sdk-internal";
@@ -7,16 +5,17 @@ import { Card as SdkCard } from "@bitwarden/sdk-internal";
import { EncString } from "../../../key-management/crypto/models/enc-string"; import { EncString } from "../../../key-management/crypto/models/enc-string";
import Domain from "../../../platform/models/domain/domain-base"; import Domain from "../../../platform/models/domain/domain-base";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { conditionalEncString, encStringFrom } from "../../utils/domain-utils";
import { CardData } from "../data/card.data"; import { CardData } from "../data/card.data";
import { CardView } from "../view/card.view"; import { CardView } from "../view/card.view";
export class Card extends Domain { export class Card extends Domain {
cardholderName: EncString; cardholderName?: EncString;
brand: EncString; brand?: EncString;
number: EncString; number?: EncString;
expMonth: EncString; expMonth?: EncString;
expYear: EncString; expYear?: EncString;
code: EncString; code?: EncString;
constructor(obj?: CardData) { constructor(obj?: CardData) {
super(); super();
@@ -24,23 +23,16 @@ export class Card extends Domain {
return; return;
} }
this.buildDomainModel( this.cardholderName = conditionalEncString(obj.cardholderName);
this, this.brand = conditionalEncString(obj.brand);
obj, this.number = conditionalEncString(obj.number);
{ this.expMonth = conditionalEncString(obj.expMonth);
cardholderName: null, this.expYear = conditionalEncString(obj.expYear);
brand: null, this.code = conditionalEncString(obj.code);
number: null,
expMonth: null,
expYear: null,
code: null,
},
[],
);
} }
async decrypt( async decrypt(
orgId: string, orgId: string | undefined,
context = "No Cipher Context", context = "No Cipher Context",
encKey?: SymmetricCryptoKey, encKey?: SymmetricCryptoKey,
): Promise<CardView> { ): Promise<CardView> {
@@ -48,7 +40,7 @@ export class Card extends Domain {
this, this,
new CardView(), new CardView(),
["cardholderName", "brand", "number", "expMonth", "expYear", "code"], ["cardholderName", "brand", "number", "expMonth", "expYear", "code"],
orgId, orgId ?? null,
encKey, encKey,
"DomainType: Card; " + context, "DomainType: Card; " + context,
); );
@@ -67,25 +59,20 @@ export class Card extends Domain {
return c; return c;
} }
static fromJSON(obj: Partial<Jsonify<Card>>): Card { static fromJSON(obj: Partial<Jsonify<Card>> | undefined): Card | undefined {
if (obj == null) { if (obj == null) {
return null; return undefined;
} }
const cardholderName = EncString.fromJSON(obj.cardholderName); const card = new Card();
const brand = EncString.fromJSON(obj.brand); card.cardholderName = encStringFrom(obj.cardholderName);
const number = EncString.fromJSON(obj.number); card.brand = encStringFrom(obj.brand);
const expMonth = EncString.fromJSON(obj.expMonth); card.number = encStringFrom(obj.number);
const expYear = EncString.fromJSON(obj.expYear); card.expMonth = encStringFrom(obj.expMonth);
const code = EncString.fromJSON(obj.code); card.expYear = encStringFrom(obj.expYear);
return Object.assign(new Card(), obj, { card.code = encStringFrom(obj.code);
cardholderName,
brand, return card;
number,
expMonth,
expYear,
code,
});
} }
/** /**
@@ -108,18 +95,18 @@ export class Card extends Domain {
* Maps an SDK Card object to a Card * Maps an SDK Card object to a Card
* @param obj - The SDK Card object * @param obj - The SDK Card object
*/ */
static fromSdkCard(obj: SdkCard): Card | undefined { static fromSdkCard(obj?: SdkCard): Card | undefined {
if (obj == null) { if (!obj) {
return undefined; return undefined;
} }
const card = new Card(); const card = new Card();
card.cardholderName = EncString.fromJSON(obj.cardholderName); card.cardholderName = encStringFrom(obj.cardholderName);
card.brand = EncString.fromJSON(obj.brand); card.brand = encStringFrom(obj.brand);
card.number = EncString.fromJSON(obj.number); card.number = encStringFrom(obj.number);
card.expMonth = EncString.fromJSON(obj.expMonth); card.expMonth = encStringFrom(obj.expMonth);
card.expYear = EncString.fromJSON(obj.expYear); card.expYear = encStringFrom(obj.expYear);
card.code = EncString.fromJSON(obj.code); card.code = encStringFrom(obj.code);
return card; return card;
} }

View File

@@ -44,31 +44,28 @@ describe("Cipher DTO", () => {
const data = new CipherData(); const data = new CipherData();
const cipher = new Cipher(data); const cipher = new Cipher(data);
expect(cipher).toEqual({ expect(cipher.id).toBeUndefined();
initializerKey: InitializerKey.Cipher, expect(cipher.organizationId).toBeUndefined();
id: null, expect(cipher.folderId).toBeUndefined();
organizationId: null, expect(cipher.name).toBeInstanceOf(EncString);
folderId: null, expect(cipher.notes).toBeUndefined();
name: null, expect(cipher.type).toBeUndefined();
notes: null, expect(cipher.favorite).toBeUndefined();
type: undefined, expect(cipher.organizationUseTotp).toBeUndefined();
favorite: undefined, expect(cipher.edit).toBeUndefined();
organizationUseTotp: undefined, expect(cipher.viewPassword).toBeUndefined();
edit: undefined, expect(cipher.revisionDate).toBeInstanceOf(Date);
viewPassword: true, expect(cipher.collectionIds).toEqual([]);
revisionDate: null, expect(cipher.localData).toBeUndefined();
collectionIds: undefined, expect(cipher.creationDate).toBeInstanceOf(Date);
localData: null, expect(cipher.deletedDate).toBeUndefined();
creationDate: null, expect(cipher.reprompt).toBeUndefined();
deletedDate: undefined, expect(cipher.attachments).toBeUndefined();
reprompt: undefined, expect(cipher.fields).toBeUndefined();
attachments: null, expect(cipher.passwordHistory).toBeUndefined();
fields: null, expect(cipher.key).toBeUndefined();
passwordHistory: null, expect(cipher.permissions).toBeUndefined();
key: null, expect(cipher.archivedDate).toBeUndefined();
permissions: undefined,
archivedDate: undefined,
});
}); });
it("Decrypt should handle cipher key error", async () => { it("Decrypt should handle cipher key error", async () => {
@@ -121,7 +118,7 @@ describe("Cipher DTO", () => {
edit: true, edit: true,
viewPassword: true, viewPassword: true,
decryptionFailure: true, decryptionFailure: true,
collectionIds: undefined, collectionIds: [],
revisionDate: new Date("2022-01-31T12:00:00.000Z"), revisionDate: new Date("2022-01-31T12:00:00.000Z"),
creationDate: new Date("2022-01-01T12:00:00.000Z"), creationDate: new Date("2022-01-01T12:00:00.000Z"),
deletedDate: undefined, deletedDate: undefined,
@@ -155,6 +152,7 @@ describe("Cipher DTO", () => {
reprompt: CipherRepromptType.None, reprompt: CipherRepromptType.None,
key: "EncryptedString", key: "EncryptedString",
archivedDate: undefined, archivedDate: undefined,
collectionIds: [],
login: { login: {
uris: [ uris: [
{ {
@@ -223,8 +221,8 @@ describe("Cipher DTO", () => {
edit: true, edit: true,
viewPassword: true, viewPassword: true,
revisionDate: new Date("2022-01-31T12:00:00.000Z"), revisionDate: new Date("2022-01-31T12:00:00.000Z"),
collectionIds: undefined, collectionIds: [],
localData: null, localData: undefined,
creationDate: new Date("2022-01-01T12:00:00.000Z"), creationDate: new Date("2022-01-01T12:00:00.000Z"),
deletedDate: undefined, deletedDate: undefined,
permissions: new CipherPermissionsApi(), permissions: new CipherPermissionsApi(),
@@ -265,13 +263,13 @@ describe("Cipher DTO", () => {
], ],
fields: [ fields: [
{ {
linkedId: null, linkedId: undefined,
name: { encryptedString: "EncryptedString", encryptionType: 0 }, name: { encryptedString: "EncryptedString", encryptionType: 0 },
type: 0, type: 0,
value: { encryptedString: "EncryptedString", encryptionType: 0 }, value: { encryptedString: "EncryptedString", encryptionType: 0 },
}, },
{ {
linkedId: null, linkedId: undefined,
name: { encryptedString: "EncryptedString", encryptionType: 0 }, name: { encryptedString: "EncryptedString", encryptionType: 0 },
type: 1, type: 1,
value: { encryptedString: "EncryptedString", encryptionType: 0 }, value: { encryptedString: "EncryptedString", encryptionType: 0 },
@@ -348,7 +346,7 @@ describe("Cipher DTO", () => {
attachments: [], attachments: [],
fields: [], fields: [],
passwordHistory: [], passwordHistory: [],
collectionIds: undefined, collectionIds: [],
revisionDate: new Date("2022-01-31T12:00:00.000Z"), revisionDate: new Date("2022-01-31T12:00:00.000Z"),
creationDate: new Date("2022-01-01T12:00:00.000Z"), creationDate: new Date("2022-01-01T12:00:00.000Z"),
deletedDate: undefined, deletedDate: undefined,
@@ -380,6 +378,7 @@ describe("Cipher DTO", () => {
deletedDate: undefined, deletedDate: undefined,
reprompt: CipherRepromptType.None, reprompt: CipherRepromptType.None,
key: "EncKey", key: "EncKey",
collectionIds: [],
secureNote: { secureNote: {
type: SecureNoteType.Generic, type: SecureNoteType.Generic,
}, },
@@ -404,15 +403,15 @@ describe("Cipher DTO", () => {
edit: true, edit: true,
viewPassword: true, viewPassword: true,
revisionDate: new Date("2022-01-31T12:00:00.000Z"), revisionDate: new Date("2022-01-31T12:00:00.000Z"),
collectionIds: undefined, collectionIds: [],
localData: null, localData: undefined,
creationDate: new Date("2022-01-01T12:00:00.000Z"), creationDate: new Date("2022-01-01T12:00:00.000Z"),
deletedDate: undefined, deletedDate: undefined,
reprompt: 0, reprompt: 0,
secureNote: { type: SecureNoteType.Generic }, secureNote: { type: SecureNoteType.Generic },
attachments: null, attachments: undefined,
fields: null, fields: undefined,
passwordHistory: null, passwordHistory: undefined,
key: { encryptedString: "EncKey", encryptionType: 0 }, key: { encryptedString: "EncKey", encryptionType: 0 },
permissions: new CipherPermissionsApi(), permissions: new CipherPermissionsApi(),
archivedDate: undefined, archivedDate: undefined,
@@ -475,7 +474,7 @@ describe("Cipher DTO", () => {
attachments: [], attachments: [],
fields: [], fields: [],
passwordHistory: [], passwordHistory: [],
collectionIds: undefined, collectionIds: [],
revisionDate: new Date("2022-01-31T12:00:00.000Z"), revisionDate: new Date("2022-01-31T12:00:00.000Z"),
creationDate: new Date("2022-01-01T12:00:00.000Z"), creationDate: new Date("2022-01-01T12:00:00.000Z"),
deletedDate: undefined, deletedDate: undefined,
@@ -507,6 +506,7 @@ describe("Cipher DTO", () => {
deletedDate: undefined, deletedDate: undefined,
permissions: new CipherPermissionsApi(), permissions: new CipherPermissionsApi(),
reprompt: CipherRepromptType.None, reprompt: CipherRepromptType.None,
collectionIds: [],
card: { card: {
cardholderName: "EncryptedString", cardholderName: "EncryptedString",
brand: "EncryptedString", brand: "EncryptedString",
@@ -536,8 +536,8 @@ describe("Cipher DTO", () => {
edit: true, edit: true,
viewPassword: true, viewPassword: true,
revisionDate: new Date("2022-01-31T12:00:00.000Z"), revisionDate: new Date("2022-01-31T12:00:00.000Z"),
collectionIds: undefined, collectionIds: [],
localData: null, localData: undefined,
creationDate: new Date("2022-01-01T12:00:00.000Z"), creationDate: new Date("2022-01-01T12:00:00.000Z"),
deletedDate: undefined, deletedDate: undefined,
reprompt: 0, reprompt: 0,
@@ -549,9 +549,9 @@ describe("Cipher DTO", () => {
expYear: { encryptedString: "EncryptedString", encryptionType: 0 }, expYear: { encryptedString: "EncryptedString", encryptionType: 0 },
code: { encryptedString: "EncryptedString", encryptionType: 0 }, code: { encryptedString: "EncryptedString", encryptionType: 0 },
}, },
attachments: null, attachments: undefined,
fields: null, fields: undefined,
passwordHistory: null, passwordHistory: undefined,
key: { encryptedString: "EncKey", encryptionType: 0 }, key: { encryptedString: "EncKey", encryptionType: 0 },
permissions: new CipherPermissionsApi(), permissions: new CipherPermissionsApi(),
archivedDate: undefined, archivedDate: undefined,
@@ -620,7 +620,7 @@ describe("Cipher DTO", () => {
attachments: [], attachments: [],
fields: [], fields: [],
passwordHistory: [], passwordHistory: [],
collectionIds: undefined, collectionIds: [],
revisionDate: new Date("2022-01-31T12:00:00.000Z"), revisionDate: new Date("2022-01-31T12:00:00.000Z"),
creationDate: new Date("2022-01-01T12:00:00.000Z"), creationDate: new Date("2022-01-01T12:00:00.000Z"),
deletedDate: undefined, deletedDate: undefined,
@@ -654,6 +654,7 @@ describe("Cipher DTO", () => {
reprompt: CipherRepromptType.None, reprompt: CipherRepromptType.None,
key: "EncKey", key: "EncKey",
archivedDate: undefined, archivedDate: undefined,
collectionIds: [],
identity: { identity: {
title: "EncryptedString", title: "EncryptedString",
firstName: "EncryptedString", firstName: "EncryptedString",
@@ -693,8 +694,8 @@ describe("Cipher DTO", () => {
edit: true, edit: true,
viewPassword: true, viewPassword: true,
revisionDate: new Date("2022-01-31T12:00:00.000Z"), revisionDate: new Date("2022-01-31T12:00:00.000Z"),
collectionIds: undefined, collectionIds: [],
localData: null, localData: undefined,
creationDate: new Date("2022-01-01T12:00:00.000Z"), creationDate: new Date("2022-01-01T12:00:00.000Z"),
deletedDate: undefined, deletedDate: undefined,
reprompt: 0, reprompt: 0,
@@ -719,9 +720,9 @@ describe("Cipher DTO", () => {
passportNumber: { encryptedString: "EncryptedString", encryptionType: 0 }, passportNumber: { encryptedString: "EncryptedString", encryptionType: 0 },
licenseNumber: { encryptedString: "EncryptedString", encryptionType: 0 }, licenseNumber: { encryptedString: "EncryptedString", encryptionType: 0 },
}, },
attachments: null, attachments: undefined,
fields: null, fields: undefined,
passwordHistory: null, passwordHistory: undefined,
key: { encryptedString: "EncKey", encryptionType: 0 }, key: { encryptedString: "EncKey", encryptionType: 0 },
permissions: new CipherPermissionsApi(), permissions: new CipherPermissionsApi(),
}); });
@@ -789,7 +790,7 @@ describe("Cipher DTO", () => {
attachments: [], attachments: [],
fields: [], fields: [],
passwordHistory: [], passwordHistory: [],
collectionIds: undefined, collectionIds: [],
revisionDate: new Date("2022-01-31T12:00:00.000Z"), revisionDate: new Date("2022-01-31T12:00:00.000Z"),
creationDate: new Date("2022-01-01T12:00:00.000Z"), creationDate: new Date("2022-01-01T12:00:00.000Z"),
deletedDate: undefined, deletedDate: undefined,
@@ -858,8 +859,8 @@ describe("Cipher DTO", () => {
expect(actual).toMatchObject(expected); expect(actual).toMatchObject(expected);
}); });
it("returns null if object is null", () => { it("returns undefined if object is undefined", () => {
expect(Cipher.fromJSON(null)).toBeNull(); expect(Cipher.fromJSON(undefined)).toBeUndefined();
}); });
}); });

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { Cipher as SdkCipher } from "@bitwarden/sdk-internal"; import { Cipher as SdkCipher } from "@bitwarden/sdk-internal";
@@ -13,6 +11,7 @@ import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-cr
import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; import { InitializerKey } from "../../../platform/services/cryptography/initializer-key";
import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
import { CipherType } from "../../enums/cipher-type"; import { CipherType } from "../../enums/cipher-type";
import { conditionalEncString, encStringFrom } from "../../utils/domain-utils";
import { CipherPermissionsApi } from "../api/cipher-permissions.api"; import { CipherPermissionsApi } from "../api/cipher-permissions.api";
import { CipherData } from "../data/cipher.data"; import { CipherData } from "../data/cipher.data";
import { LocalData, fromSdkLocalData, toSdkLocalData } from "../data/local.data"; import { LocalData, fromSdkLocalData, toSdkLocalData } from "../data/local.data";
@@ -33,71 +32,60 @@ import { SshKey } from "./ssh-key";
export class Cipher extends Domain implements Decryptable<CipherView> { export class Cipher extends Domain implements Decryptable<CipherView> {
readonly initializerKey = InitializerKey.Cipher; readonly initializerKey = InitializerKey.Cipher;
id: string; id: string = "";
organizationId: string; organizationId?: string;
folderId: string; folderId?: string;
name: EncString; name: EncString = new EncString("");
notes: EncString; notes?: EncString;
type: CipherType; type: CipherType = CipherType.Login;
favorite: boolean; favorite: boolean = false;
organizationUseTotp: boolean; organizationUseTotp: boolean = false;
edit: boolean; edit: boolean = false;
viewPassword: boolean; viewPassword: boolean = true;
permissions: CipherPermissionsApi; permissions?: CipherPermissionsApi;
revisionDate: Date; revisionDate: Date;
localData: LocalData; localData?: LocalData;
login: Login; login?: Login;
identity: Identity; identity?: Identity;
card: Card; card?: Card;
secureNote: SecureNote; secureNote?: SecureNote;
sshKey: SshKey; sshKey?: SshKey;
attachments: Attachment[]; attachments?: Attachment[];
fields: Field[]; fields?: Field[];
passwordHistory: Password[]; passwordHistory?: Password[];
collectionIds: string[]; collectionIds: string[] = [];
creationDate: Date; creationDate: Date;
deletedDate: Date | undefined; deletedDate?: Date;
archivedDate: Date | undefined; archivedDate?: Date;
reprompt: CipherRepromptType; reprompt: CipherRepromptType = CipherRepromptType.None;
key: EncString; key?: EncString;
constructor(obj?: CipherData, localData: LocalData = null) { constructor(obj?: CipherData, localData?: LocalData) {
super(); super();
if (obj == null) { if (obj == null) {
this.creationDate = this.revisionDate = new Date();
return; return;
} }
this.buildDomainModel( this.id = obj.id;
this, this.organizationId = obj.organizationId;
obj, this.folderId = obj.folderId;
{ this.name = new EncString(obj.name);
id: null, this.notes = conditionalEncString(obj.notes);
organizationId: null,
folderId: null,
name: null,
notes: null,
key: null,
},
["id", "organizationId", "folderId"],
);
this.type = obj.type; this.type = obj.type;
this.favorite = obj.favorite; this.favorite = obj.favorite;
this.organizationUseTotp = obj.organizationUseTotp; this.organizationUseTotp = obj.organizationUseTotp;
this.edit = obj.edit; this.edit = obj.edit;
if (obj.viewPassword != null) { this.viewPassword = obj.viewPassword;
this.viewPassword = obj.viewPassword;
} else {
this.viewPassword = true; // Default for already synced Ciphers without viewPassword
}
this.permissions = obj.permissions; this.permissions = obj.permissions;
this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null; this.revisionDate = new Date(obj.revisionDate);
this.collectionIds = obj.collectionIds;
this.localData = localData; this.localData = localData;
this.creationDate = obj.creationDate != null ? new Date(obj.creationDate) : null; this.collectionIds = obj.collectionIds ?? [];
this.creationDate = new Date(obj.creationDate);
this.deletedDate = obj.deletedDate != null ? new Date(obj.deletedDate) : undefined; this.deletedDate = obj.deletedDate != null ? new Date(obj.deletedDate) : undefined;
this.archivedDate = obj.archivedDate != null ? new Date(obj.archivedDate) : undefined; this.archivedDate = obj.archivedDate != null ? new Date(obj.archivedDate) : undefined;
this.reprompt = obj.reprompt; this.reprompt = obj.reprompt;
this.key = conditionalEncString(obj.key);
switch (this.type) { switch (this.type) {
case CipherType.Login: case CipherType.Login:
@@ -121,20 +109,14 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
if (obj.attachments != null) { if (obj.attachments != null) {
this.attachments = obj.attachments.map((a) => new Attachment(a)); this.attachments = obj.attachments.map((a) => new Attachment(a));
} else {
this.attachments = null;
} }
if (obj.fields != null) { if (obj.fields != null) {
this.fields = obj.fields.map((f) => new Field(f)); this.fields = obj.fields.map((f) => new Field(f));
} else {
this.fields = null;
} }
if (obj.passwordHistory != null) { if (obj.passwordHistory != null) {
this.passwordHistory = obj.passwordHistory.map((ph) => new Password(ph)); this.passwordHistory = obj.passwordHistory.map((ph) => new Password(ph));
} else {
this.passwordHistory = null;
} }
} }
@@ -161,46 +143,54 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
await this.decryptObj<Cipher, CipherView>( await this.decryptObj<Cipher, CipherView>(
this, this,
// @ts-expect-error Ciphers have optional Ids which are getting swallowed by the ViewEncryptableKeys type
// The ViewEncryptableKeys type should be fixed to allow for optional Ids, but is out of scope for now.
model, model,
["name", "notes"], ["name", "notes"],
this.organizationId, this.organizationId ?? null,
encKey, encKey,
); );
switch (this.type) { switch (this.type) {
case CipherType.Login: case CipherType.Login:
model.login = await this.login.decrypt( if (this.login != null) {
this.organizationId, model.login = await this.login.decrypt(
bypassValidation, this.organizationId,
`Cipher Id: ${this.id}`, bypassValidation,
encKey, `Cipher Id: ${this.id}`,
); encKey,
);
}
break; break;
case CipherType.SecureNote: case CipherType.SecureNote:
model.secureNote = await this.secureNote.decrypt( if (this.secureNote != null) {
this.organizationId, model.secureNote = await this.secureNote.decrypt();
`Cipher Id: ${this.id}`, }
encKey,
);
break; break;
case CipherType.Card: case CipherType.Card:
model.card = await this.card.decrypt(this.organizationId, `Cipher Id: ${this.id}`, encKey); if (this.card != null) {
model.card = await this.card.decrypt(
this.organizationId,
`Cipher Id: ${this.id}`,
encKey,
);
}
break; break;
case CipherType.Identity: case CipherType.Identity:
model.identity = await this.identity.decrypt( if (this.identity != null) {
this.organizationId, model.identity = await this.identity.decrypt(
`Cipher Id: ${this.id}`, this.organizationId,
encKey, `Cipher Id: ${this.id}`,
); encKey,
);
}
break; break;
case CipherType.SshKey: case CipherType.SshKey:
model.sshKey = await this.sshKey.decrypt( if (this.sshKey != null) {
this.organizationId, model.sshKey = await this.sshKey.decrypt(
`Cipher Id: ${this.id}`, this.organizationId,
encKey, `Cipher Id: ${this.id}`,
); encKey,
);
}
break; break;
default: default:
break; break;
@@ -209,9 +199,12 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
if (this.attachments != null && this.attachments.length > 0) { if (this.attachments != null && this.attachments.length > 0) {
const attachments: AttachmentView[] = []; const attachments: AttachmentView[] = [];
for (const attachment of this.attachments) { for (const attachment of this.attachments) {
attachments.push( const decryptedAttachment = await attachment.decrypt(
await attachment.decrypt(this.organizationId, `Cipher Id: ${this.id}`, encKey), this.organizationId,
`Cipher Id: ${this.id}`,
encKey,
); );
attachments.push(decryptedAttachment);
} }
model.attachments = attachments; model.attachments = attachments;
} }
@@ -219,7 +212,8 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
if (this.fields != null && this.fields.length > 0) { if (this.fields != null && this.fields.length > 0) {
const fields: FieldView[] = []; const fields: FieldView[] = [];
for (const field of this.fields) { for (const field of this.fields) {
fields.push(await field.decrypt(this.organizationId, encKey)); const decryptedField = await field.decrypt(this.organizationId, encKey);
fields.push(decryptedField);
} }
model.fields = fields; model.fields = fields;
} }
@@ -227,7 +221,8 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
if (this.passwordHistory != null && this.passwordHistory.length > 0) { if (this.passwordHistory != null && this.passwordHistory.length > 0) {
const passwordHistory: PasswordHistoryView[] = []; const passwordHistory: PasswordHistoryView[] = [];
for (const ph of this.passwordHistory) { for (const ph of this.passwordHistory) {
passwordHistory.push(await ph.decrypt(this.organizationId, encKey)); const decryptedPh = await ph.decrypt(this.organizationId, encKey);
passwordHistory.push(decryptedPh);
} }
model.passwordHistory = passwordHistory; model.passwordHistory = passwordHistory;
} }
@@ -238,20 +233,32 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
toCipherData(): CipherData { toCipherData(): CipherData {
const c = new CipherData(); const c = new CipherData();
c.id = this.id; c.id = this.id;
c.organizationId = this.organizationId; if (this.organizationId != null) {
c.folderId = this.folderId; c.organizationId = this.organizationId;
}
if (this.folderId != null) {
c.folderId = this.folderId;
}
c.edit = this.edit; c.edit = this.edit;
c.viewPassword = this.viewPassword; c.viewPassword = this.viewPassword;
c.organizationUseTotp = this.organizationUseTotp; c.organizationUseTotp = this.organizationUseTotp;
c.favorite = this.favorite; c.favorite = this.favorite;
c.revisionDate = this.revisionDate != null ? this.revisionDate.toISOString() : null; c.revisionDate = this.revisionDate.toISOString();
c.type = this.type; c.type = this.type;
c.collectionIds = this.collectionIds; c.collectionIds = this.collectionIds;
c.creationDate = this.creationDate != null ? this.creationDate.toISOString() : null; c.creationDate = this.creationDate.toISOString();
c.deletedDate = this.deletedDate != null ? this.deletedDate.toISOString() : undefined; c.deletedDate = this.deletedDate != null ? this.deletedDate.toISOString() : undefined;
c.reprompt = this.reprompt; c.reprompt = this.reprompt;
c.key = this.key?.encryptedString;
c.permissions = this.permissions; if (this.key != null && this.key.encryptedString != null) {
c.key = this.key.encryptedString;
}
if (this.permissions != null) {
c.permissions = this.permissions;
}
c.archivedDate = this.archivedDate != null ? this.archivedDate.toISOString() : undefined; c.archivedDate = this.archivedDate != null ? this.archivedDate.toISOString() : undefined;
this.buildDataModel(this, c, { this.buildDataModel(this, c, {
@@ -261,19 +268,29 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
switch (c.type) { switch (c.type) {
case CipherType.Login: case CipherType.Login:
c.login = this.login.toLoginData(); if (this.login != null) {
c.login = this.login.toLoginData();
}
break; break;
case CipherType.SecureNote: case CipherType.SecureNote:
c.secureNote = this.secureNote.toSecureNoteData(); if (this.secureNote != null) {
c.secureNote = this.secureNote.toSecureNoteData();
}
break; break;
case CipherType.Card: case CipherType.Card:
c.card = this.card.toCardData(); if (this.card != null) {
c.card = this.card.toCardData();
}
break; break;
case CipherType.Identity: case CipherType.Identity:
c.identity = this.identity.toIdentityData(); if (this.identity != null) {
c.identity = this.identity.toIdentityData();
}
break; break;
case CipherType.SshKey: case CipherType.SshKey:
c.sshKey = this.sshKey.toSshKeyData(); if (this.sshKey != null) {
c.sshKey = this.sshKey.toSshKeyData();
}
break; break;
default: default:
break; break;
@@ -291,51 +308,71 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
return c; return c;
} }
static fromJSON(obj: Jsonify<Cipher>) { static fromJSON(obj: Jsonify<Cipher> | undefined): Cipher | undefined {
if (obj == null) { if (obj == null) {
return null; return undefined;
} }
const domain = new Cipher(); const domain = new Cipher();
const name = EncString.fromJSON(obj.name);
const notes = EncString.fromJSON(obj.notes);
const creationDate = obj.creationDate == null ? null : new Date(obj.creationDate);
const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate);
const deletedDate = obj.deletedDate == null ? undefined : new Date(obj.deletedDate);
const attachments = obj.attachments?.map((a: any) => Attachment.fromJSON(a));
const fields = obj.fields?.map((f: any) => Field.fromJSON(f));
const passwordHistory = obj.passwordHistory?.map((ph: any) => Password.fromJSON(ph));
const key = EncString.fromJSON(obj.key);
const archivedDate = obj.archivedDate == null ? undefined : new Date(obj.archivedDate);
Object.assign(domain, obj, { domain.id = obj.id;
name, domain.organizationId = obj.organizationId;
notes, domain.folderId = obj.folderId;
creationDate, domain.type = obj.type;
revisionDate, domain.favorite = obj.favorite;
deletedDate, domain.organizationUseTotp = obj.organizationUseTotp;
attachments, domain.edit = obj.edit;
fields, domain.viewPassword = obj.viewPassword;
passwordHistory,
key, if (obj.permissions != null) {
archivedDate, domain.permissions = new CipherPermissionsApi(obj.permissions);
}); }
domain.collectionIds = obj.collectionIds;
domain.localData = obj.localData;
domain.reprompt = obj.reprompt;
domain.creationDate = new Date(obj.creationDate);
domain.revisionDate = new Date(obj.revisionDate);
domain.deletedDate = obj.deletedDate != null ? new Date(obj.deletedDate) : undefined;
domain.archivedDate = obj.archivedDate != null ? new Date(obj.archivedDate) : undefined;
domain.name = EncString.fromJSON(obj.name);
domain.notes = encStringFrom(obj.notes);
domain.key = encStringFrom(obj.key);
domain.attachments = obj.attachments
?.map((a: any) => Attachment.fromJSON(a))
.filter((a): a is Attachment => a != null);
domain.fields = obj.fields
?.map((f: any) => Field.fromJSON(f))
.filter((f): f is Field => f != null);
domain.passwordHistory = obj.passwordHistory
?.map((ph: any) => Password.fromJSON(ph))
.filter((ph): ph is Password => ph != null);
switch (obj.type) { switch (obj.type) {
case CipherType.Card: case CipherType.Card:
domain.card = Card.fromJSON(obj.card); if (obj.card != null) {
domain.card = Card.fromJSON(obj.card);
}
break; break;
case CipherType.Identity: case CipherType.Identity:
domain.identity = Identity.fromJSON(obj.identity); if (obj.identity != null) {
domain.identity = Identity.fromJSON(obj.identity);
}
break; break;
case CipherType.Login: case CipherType.Login:
domain.login = Login.fromJSON(obj.login); if (obj.login != null) {
domain.login = Login.fromJSON(obj.login);
}
break; break;
case CipherType.SecureNote: case CipherType.SecureNote:
domain.secureNote = SecureNote.fromJSON(obj.secureNote); if (obj.secureNote != null) {
domain.secureNote = SecureNote.fromJSON(obj.secureNote);
}
break; break;
case CipherType.SshKey: case CipherType.SshKey:
domain.sshKey = SshKey.fromJSON(obj.sshKey); if (obj.sshKey != null) {
domain.sshKey = SshKey.fromJSON(obj.sshKey);
}
break; break;
default: default:
break; break;
@@ -359,22 +396,22 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
name: this.name.toSdk(), name: this.name.toSdk(),
notes: this.notes?.toSdk(), notes: this.notes?.toSdk(),
type: this.type, type: this.type,
favorite: this.favorite ?? false, favorite: this.favorite,
organizationUseTotp: this.organizationUseTotp ?? false, organizationUseTotp: this.organizationUseTotp,
edit: this.edit ?? true, edit: this.edit,
permissions: this.permissions permissions: this.permissions
? { ? {
delete: this.permissions.delete, delete: this.permissions.delete,
restore: this.permissions.restore, restore: this.permissions.restore,
} }
: undefined, : undefined,
viewPassword: this.viewPassword ?? true, viewPassword: this.viewPassword,
localData: toSdkLocalData(this.localData), localData: toSdkLocalData(this.localData),
attachments: this.attachments?.map((a) => a.toSdkAttachment()), attachments: this.attachments?.map((a) => a.toSdkAttachment()),
fields: this.fields?.map((f) => f.toSdkField()), fields: this.fields?.map((f) => f.toSdkField()),
passwordHistory: this.passwordHistory?.map((ph) => ph.toSdkPasswordHistory()), passwordHistory: this.passwordHistory?.map((ph) => ph.toSdkPasswordHistory()),
revisionDate: this.revisionDate?.toISOString(), revisionDate: this.revisionDate.toISOString(),
creationDate: this.creationDate?.toISOString(), creationDate: this.creationDate.toISOString(),
deletedDate: this.deletedDate?.toISOString(), deletedDate: this.deletedDate?.toISOString(),
archivedDate: this.archivedDate?.toISOString(), archivedDate: this.archivedDate?.toISOString(),
reprompt: this.reprompt, reprompt: this.reprompt,
@@ -388,19 +425,29 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
switch (this.type) { switch (this.type) {
case CipherType.Login: case CipherType.Login:
sdkCipher.login = this.login.toSdkLogin(); if (this.login != null) {
sdkCipher.login = this.login.toSdkLogin();
}
break; break;
case CipherType.SecureNote: case CipherType.SecureNote:
sdkCipher.secureNote = this.secureNote.toSdkSecureNote(); if (this.secureNote != null) {
sdkCipher.secureNote = this.secureNote.toSdkSecureNote();
}
break; break;
case CipherType.Card: case CipherType.Card:
sdkCipher.card = this.card.toSdkCard(); if (this.card != null) {
sdkCipher.card = this.card.toSdkCard();
}
break; break;
case CipherType.Identity: case CipherType.Identity:
sdkCipher.identity = this.identity.toSdkIdentity(); if (this.identity != null) {
sdkCipher.identity = this.identity.toSdkIdentity();
}
break; break;
case CipherType.SshKey: case CipherType.SshKey:
sdkCipher.sshKey = this.sshKey.toSdkSshKey(); if (this.sshKey != null) {
sdkCipher.sshKey = this.sshKey.toSdkSshKey();
}
break; break;
default: default:
break; break;
@@ -413,22 +460,22 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
* Maps an SDK Cipher object to a Cipher * Maps an SDK Cipher object to a Cipher
* @param sdkCipher - The SDK Cipher object * @param sdkCipher - The SDK Cipher object
*/ */
static fromSdkCipher(sdkCipher: SdkCipher | null): Cipher | undefined { static fromSdkCipher(sdkCipher?: SdkCipher): Cipher | undefined {
if (sdkCipher == null) { if (sdkCipher == null) {
return undefined; return undefined;
} }
const cipher = new Cipher(); const cipher = new Cipher();
cipher.id = sdkCipher.id ? uuidAsString(sdkCipher.id) : undefined; cipher.id = sdkCipher.id ? uuidAsString(sdkCipher.id) : "";
cipher.organizationId = sdkCipher.organizationId cipher.organizationId = sdkCipher.organizationId
? uuidAsString(sdkCipher.organizationId) ? uuidAsString(sdkCipher.organizationId)
: undefined; : undefined;
cipher.folderId = sdkCipher.folderId ? uuidAsString(sdkCipher.folderId) : undefined; cipher.folderId = sdkCipher.folderId ? uuidAsString(sdkCipher.folderId) : undefined;
cipher.collectionIds = sdkCipher.collectionIds ? sdkCipher.collectionIds.map(uuidAsString) : []; cipher.collectionIds = sdkCipher.collectionIds ? sdkCipher.collectionIds.map(uuidAsString) : [];
cipher.key = EncString.fromJSON(sdkCipher.key); cipher.key = encStringFrom(sdkCipher.key);
cipher.name = EncString.fromJSON(sdkCipher.name); cipher.name = EncString.fromJSON(sdkCipher.name);
cipher.notes = EncString.fromJSON(sdkCipher.notes); cipher.notes = encStringFrom(sdkCipher.notes);
cipher.type = sdkCipher.type; cipher.type = sdkCipher.type;
cipher.favorite = sdkCipher.favorite; cipher.favorite = sdkCipher.favorite;
cipher.organizationUseTotp = sdkCipher.organizationUseTotp; cipher.organizationUseTotp = sdkCipher.organizationUseTotp;
@@ -436,10 +483,15 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
cipher.permissions = CipherPermissionsApi.fromSdkCipherPermissions(sdkCipher.permissions); cipher.permissions = CipherPermissionsApi.fromSdkCipherPermissions(sdkCipher.permissions);
cipher.viewPassword = sdkCipher.viewPassword; cipher.viewPassword = sdkCipher.viewPassword;
cipher.localData = fromSdkLocalData(sdkCipher.localData); cipher.localData = fromSdkLocalData(sdkCipher.localData);
cipher.attachments = sdkCipher.attachments?.map((a) => Attachment.fromSdkAttachment(a)) ?? []; cipher.attachments = sdkCipher.attachments
cipher.fields = sdkCipher.fields?.map((f) => Field.fromSdkField(f)) ?? []; ?.map((a) => Attachment.fromSdkAttachment(a))
cipher.passwordHistory = .filter((a): a is Attachment => a != null);
sdkCipher.passwordHistory?.map((ph) => Password.fromSdkPasswordHistory(ph)) ?? []; cipher.fields = sdkCipher.fields
?.map((f) => Field.fromSdkField(f))
.filter((f): f is Field => f != null);
cipher.passwordHistory = sdkCipher.passwordHistory
?.map((ph) => Password.fromSdkPasswordHistory(ph))
.filter((ph): ph is Password => ph != null);
cipher.creationDate = new Date(sdkCipher.creationDate); cipher.creationDate = new Date(sdkCipher.creationDate);
cipher.revisionDate = new Date(sdkCipher.revisionDate); cipher.revisionDate = new Date(sdkCipher.revisionDate);
cipher.deletedDate = sdkCipher.deletedDate ? new Date(sdkCipher.deletedDate) : undefined; cipher.deletedDate = sdkCipher.deletedDate ? new Date(sdkCipher.deletedDate) : undefined;

View File

@@ -13,25 +13,23 @@ describe("Fido2Credential", () => {
}); });
describe("constructor", () => { describe("constructor", () => {
it("returns all fields null when given empty data parameter", () => { it("returns all fields undefined when given empty data parameter", () => {
const data = new Fido2CredentialData(); const data = new Fido2CredentialData();
const credential = new Fido2Credential(data); const credential = new Fido2Credential(data);
expect(credential).toEqual({ expect(credential.credentialId).toBeDefined();
credentialId: null, expect(credential.keyType).toBeDefined();
keyType: null, expect(credential.keyAlgorithm).toBeDefined();
keyAlgorithm: null, expect(credential.keyCurve).toBeDefined();
keyCurve: null, expect(credential.keyValue).toBeDefined();
keyValue: null, expect(credential.rpId).toBeDefined();
rpId: null, expect(credential.counter).toBeDefined();
userHandle: null, expect(credential.discoverable).toBeDefined();
userName: null, expect(credential.userHandle).toBeUndefined();
rpName: null, expect(credential.userName).toBeUndefined();
userDisplayName: null, expect(credential.rpName).toBeUndefined();
counter: null, expect(credential.userDisplayName).toBeUndefined();
discoverable: null, expect(credential.creationDate).toBeInstanceOf(Date);
creationDate: null,
});
}); });
it("returns all fields as EncStrings except creationDate when given full Fido2CredentialData", () => { it("returns all fields as EncStrings except creationDate when given full Fido2CredentialData", () => {
@@ -69,12 +67,22 @@ describe("Fido2Credential", () => {
}); });
}); });
it("should not populate fields when data parameter is not given", () => { it("should not populate fields when data parameter is not given except creationDate", () => {
const credential = new Fido2Credential(); const credential = new Fido2Credential();
expect(credential).toEqual({ expect(credential.credentialId).toBeUndefined();
credentialId: null, expect(credential.keyType).toBeUndefined();
}); expect(credential.keyAlgorithm).toBeUndefined();
expect(credential.keyCurve).toBeUndefined();
expect(credential.keyValue).toBeUndefined();
expect(credential.rpId).toBeUndefined();
expect(credential.userHandle).toBeUndefined();
expect(credential.userName).toBeUndefined();
expect(credential.counter).toBeUndefined();
expect(credential.rpName).toBeUndefined();
expect(credential.userDisplayName).toBeUndefined();
expect(credential.discoverable).toBeUndefined();
expect(credential.creationDate).toBeInstanceOf(Date);
}); });
}); });
@@ -163,8 +171,8 @@ describe("Fido2Credential", () => {
expect(result).toEqual(credential); expect(result).toEqual(credential);
}); });
it("returns null if input is null", () => { it("returns undefined if input is null", () => {
expect(Fido2Credential.fromJSON(null)).toBeNull(); expect(Fido2Credential.fromJSON(null)).toBeUndefined();
}); });
}); });

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { Fido2Credential as SdkFido2Credential } from "@bitwarden/sdk-internal"; import { Fido2Credential as SdkFido2Credential } from "@bitwarden/sdk-internal";
@@ -7,56 +5,53 @@ import { Fido2Credential as SdkFido2Credential } from "@bitwarden/sdk-internal";
import { EncString } from "../../../key-management/crypto/models/enc-string"; import { EncString } from "../../../key-management/crypto/models/enc-string";
import Domain from "../../../platform/models/domain/domain-base"; import Domain from "../../../platform/models/domain/domain-base";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { conditionalEncString, encStringFrom } from "../../utils/domain-utils";
import { Fido2CredentialData } from "../data/fido2-credential.data"; import { Fido2CredentialData } from "../data/fido2-credential.data";
import { Fido2CredentialView } from "../view/fido2-credential.view"; import { Fido2CredentialView } from "../view/fido2-credential.view";
export class Fido2Credential extends Domain { export class Fido2Credential extends Domain {
credentialId: EncString | null = null; credentialId!: EncString;
keyType: EncString; keyType!: EncString;
keyAlgorithm: EncString; keyAlgorithm!: EncString;
keyCurve: EncString; keyCurve!: EncString;
keyValue: EncString; keyValue!: EncString;
rpId: EncString; rpId!: EncString;
userHandle: EncString; userHandle?: EncString;
userName: EncString; userName?: EncString;
counter: EncString; counter!: EncString;
rpName: EncString; rpName?: EncString;
userDisplayName: EncString; userDisplayName?: EncString;
discoverable: EncString; discoverable!: EncString;
creationDate: Date; creationDate!: Date;
constructor(obj?: Fido2CredentialData) { constructor(obj?: Fido2CredentialData) {
super(); super();
if (obj == null) { if (obj == null) {
this.creationDate = new Date();
return; return;
} }
this.buildDomainModel( this.credentialId = new EncString(obj.credentialId);
this, this.keyType = new EncString(obj.keyType);
obj, this.keyAlgorithm = new EncString(obj.keyAlgorithm);
{ this.keyCurve = new EncString(obj.keyCurve);
credentialId: null, this.keyValue = new EncString(obj.keyValue);
keyType: null, this.rpId = new EncString(obj.rpId);
keyAlgorithm: null, this.counter = new EncString(obj.counter);
keyCurve: null, this.discoverable = new EncString(obj.discoverable);
keyValue: null, this.userHandle = conditionalEncString(obj.userHandle);
rpId: null, this.userName = conditionalEncString(obj.userName);
userHandle: null, this.rpName = conditionalEncString(obj.rpName);
userName: null, this.userDisplayName = conditionalEncString(obj.userDisplayName);
counter: null, this.creationDate = new Date(obj.creationDate);
rpName: null,
userDisplayName: null,
discoverable: null,
},
[],
);
this.creationDate = obj.creationDate != null ? new Date(obj.creationDate) : null;
} }
async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<Fido2CredentialView> { async decrypt(
orgId: string | undefined,
encKey?: SymmetricCryptoKey,
): Promise<Fido2CredentialView> {
const view = await this.decryptObj<Fido2Credential, Fido2CredentialView>( const view = await this.decryptObj<Fido2Credential, Fido2CredentialView>(
this, this,
// @ts-expect-error ViewEncryptableKeys type should be fixed to allow for optional values, but is out of scope for now.
new Fido2CredentialView(), new Fido2CredentialView(),
[ [
"credentialId", "credentialId",
@@ -70,7 +65,7 @@ export class Fido2Credential extends Domain {
"rpName", "rpName",
"userDisplayName", "userDisplayName",
], ],
orgId, orgId ?? null,
encKey, encKey,
); );
@@ -79,7 +74,7 @@ export class Fido2Credential extends Domain {
{ {
counter: string; counter: string;
} }
>(this, { counter: "" }, ["counter"], orgId, encKey); >(this, { counter: "" }, ["counter"], orgId ?? null, encKey);
// Counter will end up as NaN if this fails // Counter will end up as NaN if this fails
view.counter = parseInt(counter); view.counter = parseInt(counter);
@@ -87,7 +82,7 @@ export class Fido2Credential extends Domain {
this, this,
{ discoverable: "" }, { discoverable: "" },
["discoverable"], ["discoverable"],
orgId, orgId ?? null,
encKey, encKey,
); );
view.discoverable = discoverable === "true"; view.discoverable = discoverable === "true";
@@ -116,40 +111,28 @@ export class Fido2Credential extends Domain {
return i; return i;
} }
static fromJSON(obj: Jsonify<Fido2Credential>): Fido2Credential { static fromJSON(obj: Jsonify<Fido2Credential> | undefined): Fido2Credential | undefined {
if (obj == null) { if (obj == null) {
return null; return undefined;
} }
const credentialId = EncString.fromJSON(obj.credentialId); const credential = new Fido2Credential();
const keyType = EncString.fromJSON(obj.keyType);
const keyAlgorithm = EncString.fromJSON(obj.keyAlgorithm);
const keyCurve = EncString.fromJSON(obj.keyCurve);
const keyValue = EncString.fromJSON(obj.keyValue);
const rpId = EncString.fromJSON(obj.rpId);
const userHandle = EncString.fromJSON(obj.userHandle);
const userName = EncString.fromJSON(obj.userName);
const counter = EncString.fromJSON(obj.counter);
const rpName = EncString.fromJSON(obj.rpName);
const userDisplayName = EncString.fromJSON(obj.userDisplayName);
const discoverable = EncString.fromJSON(obj.discoverable);
const creationDate = obj.creationDate != null ? new Date(obj.creationDate) : null;
return Object.assign(new Fido2Credential(), obj, { credential.credentialId = EncString.fromJSON(obj.credentialId);
credentialId, credential.keyType = EncString.fromJSON(obj.keyType);
keyType, credential.keyAlgorithm = EncString.fromJSON(obj.keyAlgorithm);
keyAlgorithm, credential.keyCurve = EncString.fromJSON(obj.keyCurve);
keyCurve, credential.keyValue = EncString.fromJSON(obj.keyValue);
keyValue, credential.rpId = EncString.fromJSON(obj.rpId);
rpId, credential.userHandle = encStringFrom(obj.userHandle);
userHandle, credential.userName = encStringFrom(obj.userName);
userName, credential.counter = EncString.fromJSON(obj.counter);
counter, credential.rpName = encStringFrom(obj.rpName);
rpName, credential.userDisplayName = encStringFrom(obj.userDisplayName);
userDisplayName, credential.discoverable = EncString.fromJSON(obj.discoverable);
discoverable, credential.creationDate = new Date(obj.creationDate);
creationDate,
}); return credential;
} }
/** /**
@@ -179,8 +162,8 @@ export class Fido2Credential extends Domain {
* Maps an SDK Fido2Credential object to a Fido2Credential * Maps an SDK Fido2Credential object to a Fido2Credential
* @param obj - The SDK Fido2Credential object * @param obj - The SDK Fido2Credential object
*/ */
static fromSdkFido2Credential(obj: SdkFido2Credential): Fido2Credential | undefined { static fromSdkFido2Credential(obj?: SdkFido2Credential): Fido2Credential | undefined {
if (!obj) { if (obj == null) {
return undefined; return undefined;
} }
@@ -192,11 +175,11 @@ export class Fido2Credential extends Domain {
credential.keyCurve = EncString.fromJSON(obj.keyCurve); credential.keyCurve = EncString.fromJSON(obj.keyCurve);
credential.keyValue = EncString.fromJSON(obj.keyValue); credential.keyValue = EncString.fromJSON(obj.keyValue);
credential.rpId = EncString.fromJSON(obj.rpId); credential.rpId = EncString.fromJSON(obj.rpId);
credential.userHandle = EncString.fromJSON(obj.userHandle);
credential.userName = EncString.fromJSON(obj.userName);
credential.counter = EncString.fromJSON(obj.counter); credential.counter = EncString.fromJSON(obj.counter);
credential.rpName = EncString.fromJSON(obj.rpName); credential.userHandle = encStringFrom(obj.userHandle);
credential.userDisplayName = EncString.fromJSON(obj.userDisplayName); credential.userName = encStringFrom(obj.userName);
credential.rpName = encStringFrom(obj.rpName);
credential.userDisplayName = encStringFrom(obj.userDisplayName);
credential.discoverable = EncString.fromJSON(obj.discoverable); credential.discoverable = EncString.fromJSON(obj.discoverable);
credential.creationDate = new Date(obj.creationDate); credential.creationDate = new Date(obj.creationDate);

View File

@@ -30,8 +30,8 @@ describe("Field", () => {
expect(field).toEqual({ expect(field).toEqual({
type: undefined, type: undefined,
name: null, name: undefined,
value: null, value: undefined,
linkedId: undefined, linkedId: undefined,
}); });
}); });
@@ -41,9 +41,9 @@ describe("Field", () => {
expect(field).toEqual({ expect(field).toEqual({
type: FieldType.Text, type: FieldType.Text,
name: { encryptedString: "encName", encryptionType: 0 }, name: new EncString("encName"),
value: { encryptedString: "encValue", encryptionType: 0 }, value: new EncString("encValue"),
linkedId: null, linkedId: undefined,
}); });
}); });
@@ -82,12 +82,14 @@ describe("Field", () => {
expect(actual).toEqual({ expect(actual).toEqual({
name: "myName_fromJSON", name: "myName_fromJSON",
value: "myValue_fromJSON", value: "myValue_fromJSON",
type: FieldType.Text,
linkedId: undefined,
}); });
expect(actual).toBeInstanceOf(Field); expect(actual).toBeInstanceOf(Field);
}); });
it("returns null if object is null", () => { it("returns undefined if object is null", () => {
expect(Field.fromJSON(null)).toBeNull(); expect(Field.fromJSON(null)).toBeUndefined();
}); });
}); });

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { Field as SdkField, LinkedIdType as SdkLinkedIdType } from "@bitwarden/sdk-internal"; import { Field as SdkField, LinkedIdType as SdkLinkedIdType } from "@bitwarden/sdk-internal";
@@ -8,14 +6,15 @@ import { EncString } from "../../../key-management/crypto/models/enc-string";
import Domain from "../../../platform/models/domain/domain-base"; import Domain from "../../../platform/models/domain/domain-base";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { FieldType, LinkedIdType } from "../../enums"; import { FieldType, LinkedIdType } from "../../enums";
import { conditionalEncString, encStringFrom } from "../../utils/domain-utils";
import { FieldData } from "../data/field.data"; import { FieldData } from "../data/field.data";
import { FieldView } from "../view/field.view"; import { FieldView } from "../view/field.view";
export class Field extends Domain { export class Field extends Domain {
name: EncString; name?: EncString;
value: EncString; value?: EncString;
type: FieldType; type: FieldType = FieldType.Text;
linkedId: LinkedIdType; linkedId?: LinkedIdType;
constructor(obj?: FieldData) { constructor(obj?: FieldData) {
super(); super();
@@ -24,25 +23,17 @@ export class Field extends Domain {
} }
this.type = obj.type; this.type = obj.type;
this.linkedId = obj.linkedId; this.linkedId = obj.linkedId ?? undefined;
this.buildDomainModel( this.name = conditionalEncString(obj.name);
this, this.value = conditionalEncString(obj.value);
obj,
{
name: null,
value: null,
},
[],
);
} }
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<FieldView> { decrypt(orgId: string | undefined, encKey?: SymmetricCryptoKey): Promise<FieldView> {
return this.decryptObj<Field, FieldView>( return this.decryptObj<Field, FieldView>(
this, this,
// @ts-expect-error ViewEncryptableKeys type should be fixed to allow for optional values, but is out of scope for now.
new FieldView(this), new FieldView(this),
["name", "value"], ["name", "value"],
orgId, orgId ?? null,
encKey, encKey,
); );
} }
@@ -63,18 +54,18 @@ export class Field extends Domain {
return f; return f;
} }
static fromJSON(obj: Partial<Jsonify<Field>>): Field { static fromJSON(obj: Partial<Jsonify<Field>> | undefined): Field | undefined {
if (obj == null) { if (obj == null) {
return null; return undefined;
} }
const name = EncString.fromJSON(obj.name); const field = new Field();
const value = EncString.fromJSON(obj.value); field.type = obj.type ?? FieldType.Text;
field.linkedId = obj.linkedId ?? undefined;
field.name = encStringFrom(obj.name);
field.value = encStringFrom(obj.value);
return Object.assign(new Field(), obj, { return field;
name,
value,
});
} }
/** /**
@@ -96,14 +87,14 @@ export class Field extends Domain {
* Maps SDK Field to Field * Maps SDK Field to Field
* @param obj The SDK Field object to map * @param obj The SDK Field object to map
*/ */
static fromSdkField(obj: SdkField): Field | undefined { static fromSdkField(obj?: SdkField): Field | undefined {
if (!obj) { if (obj == null) {
return undefined; return undefined;
} }
const field = new Field(); const field = new Field();
field.name = EncString.fromJSON(obj.name); field.name = encStringFrom(obj.name);
field.value = EncString.fromJSON(obj.value); field.value = encStringFrom(obj.value);
field.type = obj.type; field.type = obj.type;
field.linkedId = obj.linkedId; field.linkedId = obj.linkedId;

View File

@@ -34,24 +34,24 @@ describe("Identity", () => {
const identity = new Identity(data); const identity = new Identity(data);
expect(identity).toEqual({ expect(identity).toEqual({
address1: null, address1: undefined,
address2: null, address2: undefined,
address3: null, address3: undefined,
city: null, city: undefined,
company: null, company: undefined,
country: null, country: undefined,
email: null, email: undefined,
firstName: null, firstName: undefined,
lastName: null, lastName: undefined,
licenseNumber: null, licenseNumber: undefined,
middleName: null, middleName: undefined,
passportNumber: null, passportNumber: undefined,
phone: null, phone: undefined,
postalCode: null, postalCode: undefined,
ssn: null, ssn: undefined,
state: null, state: undefined,
title: null, title: undefined,
username: null, username: undefined,
}); });
}); });
@@ -179,8 +179,8 @@ describe("Identity", () => {
expect(actual).toBeInstanceOf(Identity); expect(actual).toBeInstanceOf(Identity);
}); });
it("returns null if object is null", () => { it("returns undefined if object is null", () => {
expect(Identity.fromJSON(null)).toBeNull(); expect(Identity.fromJSON(null)).toBeUndefined();
}); });
}); });

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { Identity as SdkIdentity } from "@bitwarden/sdk-internal"; import { Identity as SdkIdentity } from "@bitwarden/sdk-internal";
@@ -7,28 +5,29 @@ import { Identity as SdkIdentity } from "@bitwarden/sdk-internal";
import { EncString } from "../../../key-management/crypto/models/enc-string"; import { EncString } from "../../../key-management/crypto/models/enc-string";
import Domain from "../../../platform/models/domain/domain-base"; import Domain from "../../../platform/models/domain/domain-base";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { conditionalEncString, encStringFrom } from "../../utils/domain-utils";
import { IdentityData } from "../data/identity.data"; import { IdentityData } from "../data/identity.data";
import { IdentityView } from "../view/identity.view"; import { IdentityView } from "../view/identity.view";
export class Identity extends Domain { export class Identity extends Domain {
title: EncString; title?: EncString;
firstName: EncString; firstName?: EncString;
middleName: EncString; middleName?: EncString;
lastName: EncString; lastName?: EncString;
address1: EncString; address1?: EncString;
address2: EncString; address2?: EncString;
address3: EncString; address3?: EncString;
city: EncString; city?: EncString;
state: EncString; state?: EncString;
postalCode: EncString; postalCode?: EncString;
country: EncString; country?: EncString;
company: EncString; company?: EncString;
email: EncString; email?: EncString;
phone: EncString; phone?: EncString;
ssn: EncString; ssn?: EncString;
username: EncString; username?: EncString;
passportNumber: EncString; passportNumber?: EncString;
licenseNumber: EncString; licenseNumber?: EncString;
constructor(obj?: IdentityData) { constructor(obj?: IdentityData) {
super(); super();
@@ -36,35 +35,28 @@ export class Identity extends Domain {
return; return;
} }
this.buildDomainModel( this.title = conditionalEncString(obj.title);
this, this.firstName = conditionalEncString(obj.firstName);
obj, this.middleName = conditionalEncString(obj.middleName);
{ this.lastName = conditionalEncString(obj.lastName);
title: null, this.address1 = conditionalEncString(obj.address1);
firstName: null, this.address2 = conditionalEncString(obj.address2);
middleName: null, this.address3 = conditionalEncString(obj.address3);
lastName: null, this.city = conditionalEncString(obj.city);
address1: null, this.state = conditionalEncString(obj.state);
address2: null, this.postalCode = conditionalEncString(obj.postalCode);
address3: null, this.country = conditionalEncString(obj.country);
city: null, this.company = conditionalEncString(obj.company);
state: null, this.email = conditionalEncString(obj.email);
postalCode: null, this.phone = conditionalEncString(obj.phone);
country: null, this.ssn = conditionalEncString(obj.ssn);
company: null, this.username = conditionalEncString(obj.username);
email: null, this.passportNumber = conditionalEncString(obj.passportNumber);
phone: null, this.licenseNumber = conditionalEncString(obj.licenseNumber);
ssn: null,
username: null,
passportNumber: null,
licenseNumber: null,
},
[],
);
} }
decrypt( decrypt(
orgId: string, orgId: string | undefined,
context: string = "No Cipher Context", context: string = "No Cipher Context",
encKey?: SymmetricCryptoKey, encKey?: SymmetricCryptoKey,
): Promise<IdentityView> { ): Promise<IdentityView> {
@@ -91,7 +83,7 @@ export class Identity extends Domain {
"passportNumber", "passportNumber",
"licenseNumber", "licenseNumber",
], ],
orgId, orgId ?? null,
encKey, encKey,
"DomainType: Identity; " + context, "DomainType: Identity; " + context,
); );
@@ -122,50 +114,32 @@ export class Identity extends Domain {
return i; return i;
} }
static fromJSON(obj: Jsonify<Identity>): Identity { static fromJSON(obj: Jsonify<Identity> | undefined): Identity | undefined {
if (obj == null) { if (obj == null) {
return null; return undefined;
} }
const title = EncString.fromJSON(obj.title); const identity = new Identity();
const firstName = EncString.fromJSON(obj.firstName); identity.title = encStringFrom(obj.title);
const middleName = EncString.fromJSON(obj.middleName); identity.firstName = encStringFrom(obj.firstName);
const lastName = EncString.fromJSON(obj.lastName); identity.middleName = encStringFrom(obj.middleName);
const address1 = EncString.fromJSON(obj.address1); identity.lastName = encStringFrom(obj.lastName);
const address2 = EncString.fromJSON(obj.address2); identity.address1 = encStringFrom(obj.address1);
const address3 = EncString.fromJSON(obj.address3); identity.address2 = encStringFrom(obj.address2);
const city = EncString.fromJSON(obj.city); identity.address3 = encStringFrom(obj.address3);
const state = EncString.fromJSON(obj.state); identity.city = encStringFrom(obj.city);
const postalCode = EncString.fromJSON(obj.postalCode); identity.state = encStringFrom(obj.state);
const country = EncString.fromJSON(obj.country); identity.postalCode = encStringFrom(obj.postalCode);
const company = EncString.fromJSON(obj.company); identity.country = encStringFrom(obj.country);
const email = EncString.fromJSON(obj.email); identity.company = encStringFrom(obj.company);
const phone = EncString.fromJSON(obj.phone); identity.email = encStringFrom(obj.email);
const ssn = EncString.fromJSON(obj.ssn); identity.phone = encStringFrom(obj.phone);
const username = EncString.fromJSON(obj.username); identity.ssn = encStringFrom(obj.ssn);
const passportNumber = EncString.fromJSON(obj.passportNumber); identity.username = encStringFrom(obj.username);
const licenseNumber = EncString.fromJSON(obj.licenseNumber); identity.passportNumber = encStringFrom(obj.passportNumber);
identity.licenseNumber = encStringFrom(obj.licenseNumber);
return Object.assign(new Identity(), obj, { return identity;
title,
firstName,
middleName,
lastName,
address1,
address2,
address3,
city,
state,
postalCode,
country,
company,
email,
phone,
ssn,
username,
passportNumber,
licenseNumber,
});
} }
/** /**
@@ -200,30 +174,30 @@ export class Identity extends Domain {
* Maps an SDK Identity object to an Identity * Maps an SDK Identity object to an Identity
* @param obj - The SDK Identity object * @param obj - The SDK Identity object
*/ */
static fromSdkIdentity(obj: SdkIdentity): Identity | undefined { static fromSdkIdentity(obj?: SdkIdentity): Identity | undefined {
if (obj == null) { if (obj == null) {
return undefined; return undefined;
} }
const identity = new Identity(); const identity = new Identity();
identity.title = EncString.fromJSON(obj.title); identity.title = encStringFrom(obj.title);
identity.firstName = EncString.fromJSON(obj.firstName); identity.firstName = encStringFrom(obj.firstName);
identity.middleName = EncString.fromJSON(obj.middleName); identity.middleName = encStringFrom(obj.middleName);
identity.lastName = EncString.fromJSON(obj.lastName); identity.lastName = encStringFrom(obj.lastName);
identity.address1 = EncString.fromJSON(obj.address1); identity.address1 = encStringFrom(obj.address1);
identity.address2 = EncString.fromJSON(obj.address2); identity.address2 = encStringFrom(obj.address2);
identity.address3 = EncString.fromJSON(obj.address3); identity.address3 = encStringFrom(obj.address3);
identity.city = EncString.fromJSON(obj.city); identity.city = encStringFrom(obj.city);
identity.state = EncString.fromJSON(obj.state); identity.state = encStringFrom(obj.state);
identity.postalCode = EncString.fromJSON(obj.postalCode); identity.postalCode = encStringFrom(obj.postalCode);
identity.country = EncString.fromJSON(obj.country); identity.country = encStringFrom(obj.country);
identity.company = EncString.fromJSON(obj.company); identity.company = encStringFrom(obj.company);
identity.email = EncString.fromJSON(obj.email); identity.email = encStringFrom(obj.email);
identity.phone = EncString.fromJSON(obj.phone); identity.phone = encStringFrom(obj.phone);
identity.ssn = EncString.fromJSON(obj.ssn); identity.ssn = encStringFrom(obj.ssn);
identity.username = EncString.fromJSON(obj.username); identity.username = encStringFrom(obj.username);
identity.passportNumber = EncString.fromJSON(obj.passportNumber); identity.passportNumber = encStringFrom(obj.passportNumber);
identity.licenseNumber = EncString.fromJSON(obj.licenseNumber); identity.licenseNumber = encStringFrom(obj.licenseNumber);
return identity; return identity;
} }

View File

@@ -27,9 +27,9 @@ describe("LoginUri", () => {
const loginUri = new LoginUri(data); const loginUri = new LoginUri(data);
expect(loginUri).toEqual({ expect(loginUri).toEqual({
match: null, match: undefined,
uri: null, uri: undefined,
uriChecksum: null, uriChecksum: undefined,
}); });
}); });
@@ -77,7 +77,7 @@ describe("LoginUri", () => {
loginUri.uriChecksum = mockEnc("checksum"); loginUri.uriChecksum = mockEnc("checksum");
encryptService.hash.mockResolvedValue("checksum"); encryptService.hash.mockResolvedValue("checksum");
const actual = await loginUri.validateChecksum("uri", null, null); const actual = await loginUri.validateChecksum("uri", undefined, undefined);
expect(actual).toBe(true); expect(actual).toBe(true);
expect(encryptService.hash).toHaveBeenCalledWith("uri", "sha256"); expect(encryptService.hash).toHaveBeenCalledWith("uri", "sha256");
@@ -88,7 +88,7 @@ describe("LoginUri", () => {
loginUri.uriChecksum = mockEnc("checksum"); loginUri.uriChecksum = mockEnc("checksum");
encryptService.hash.mockResolvedValue("incorrect checksum"); encryptService.hash.mockResolvedValue("incorrect checksum");
const actual = await loginUri.validateChecksum("uri", null, null); const actual = await loginUri.validateChecksum("uri", undefined, undefined);
expect(actual).toBe(false); expect(actual).toBe(false);
}); });
@@ -112,8 +112,8 @@ describe("LoginUri", () => {
expect(actual).toBeInstanceOf(LoginUri); expect(actual).toBeInstanceOf(LoginUri);
}); });
it("returns null if object is null", () => { it("returns undefined if object is null", () => {
expect(LoginUri.fromJSON(null)).toBeNull(); expect(LoginUri.fromJSON(null)).toBeUndefined();
}); });
}); });

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { LoginUri as SdkLoginUri } from "@bitwarden/sdk-internal"; import { LoginUri as SdkLoginUri } from "@bitwarden/sdk-internal";
@@ -9,13 +7,14 @@ import { UriMatchStrategySetting } from "../../../models/domain/domain-service";
import { Utils } from "../../../platform/misc/utils"; import { Utils } from "../../../platform/misc/utils";
import Domain from "../../../platform/models/domain/domain-base"; import Domain from "../../../platform/models/domain/domain-base";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { conditionalEncString, encStringFrom } from "../../utils/domain-utils";
import { LoginUriData } from "../data/login-uri.data"; import { LoginUriData } from "../data/login-uri.data";
import { LoginUriView } from "../view/login-uri.view"; import { LoginUriView } from "../view/login-uri.view";
export class LoginUri extends Domain { export class LoginUri extends Domain {
uri: EncString; uri?: EncString;
uriChecksum: EncString | undefined; uriChecksum?: EncString;
match: UriMatchStrategySetting; match?: UriMatchStrategySetting;
constructor(obj?: LoginUriData) { constructor(obj?: LoginUriData) {
super(); super();
@@ -23,20 +22,13 @@ export class LoginUri extends Domain {
return; return;
} }
this.match = obj.match; this.uri = conditionalEncString(obj.uri);
this.buildDomainModel( this.uriChecksum = conditionalEncString(obj.uriChecksum);
this, this.match = obj.match ?? undefined;
obj,
{
uri: null,
uriChecksum: null,
},
[],
);
} }
decrypt( decrypt(
orgId: string, orgId: string | undefined,
context: string = "No Cipher Context", context: string = "No Cipher Context",
encKey?: SymmetricCryptoKey, encKey?: SymmetricCryptoKey,
): Promise<LoginUriView> { ): Promise<LoginUriView> {
@@ -44,13 +36,13 @@ export class LoginUri extends Domain {
this, this,
new LoginUriView(this), new LoginUriView(this),
["uri"], ["uri"],
orgId, orgId ?? null,
encKey, encKey,
context, context,
); );
} }
async validateChecksum(clearTextUri: string, orgId: string, encKey: SymmetricCryptoKey) { async validateChecksum(clearTextUri: string, orgId?: string, encKey?: SymmetricCryptoKey) {
if (this.uriChecksum == null) { if (this.uriChecksum == null) {
return false; return false;
} }
@@ -58,7 +50,7 @@ export class LoginUri extends Domain {
const keyService = Utils.getContainerService().getEncryptService(); const keyService = Utils.getContainerService().getEncryptService();
const localChecksum = await keyService.hash(clearTextUri, "sha256"); const localChecksum = await keyService.hash(clearTextUri, "sha256");
const remoteChecksum = await this.uriChecksum.decrypt(orgId, encKey); const remoteChecksum = await this.uriChecksum.decrypt(orgId ?? null, encKey);
return remoteChecksum === localChecksum; return remoteChecksum === localChecksum;
} }
@@ -77,17 +69,17 @@ export class LoginUri extends Domain {
return u; return u;
} }
static fromJSON(obj: Jsonify<LoginUri>): LoginUri { static fromJSON(obj: Jsonify<LoginUri> | undefined): LoginUri | undefined {
if (obj == null) { if (obj == null) {
return null; return undefined;
} }
const uri = EncString.fromJSON(obj.uri); const loginUri = new LoginUri();
const uriChecksum = EncString.fromJSON(obj.uriChecksum); loginUri.uri = encStringFrom(obj.uri);
return Object.assign(new LoginUri(), obj, { loginUri.match = obj.match ?? undefined;
uri, loginUri.uriChecksum = encStringFrom(obj.uriChecksum);
uriChecksum,
}); return loginUri;
} }
/** /**
@@ -103,16 +95,16 @@ export class LoginUri extends Domain {
}; };
} }
static fromSdkLoginUri(obj: SdkLoginUri): LoginUri | undefined { static fromSdkLoginUri(obj?: SdkLoginUri): LoginUri | undefined {
if (obj == null) { if (obj == null) {
return undefined; return undefined;
} }
const view = new LoginUri(); const loginUri = new LoginUri();
view.uri = EncString.fromJSON(obj.uri); loginUri.uri = encStringFrom(obj.uri);
view.uriChecksum = obj.uriChecksum ? EncString.fromJSON(obj.uriChecksum) : undefined; loginUri.uriChecksum = encStringFrom(obj.uriChecksum);
view.match = obj.match; loginUri.match = obj.match;
return view; return loginUri;
} }
} }

View File

@@ -19,11 +19,11 @@ describe("Login DTO", () => {
const login = new Login(data); const login = new Login(data);
expect(login).toEqual({ expect(login).toEqual({
passwordRevisionDate: null, passwordRevisionDate: undefined,
autofillOnPageLoad: undefined, autofillOnPageLoad: undefined,
username: null, username: undefined,
password: null, password: undefined,
totp: null, totp: undefined,
}); });
}); });
@@ -193,8 +193,8 @@ describe("Login DTO", () => {
expect(actual).toBeInstanceOf(Login); expect(actual).toBeInstanceOf(Login);
}); });
it("returns null if object is null", () => { it("returns undefined if object is null", () => {
expect(Login.fromJSON(null)).toBeNull(); expect(Login.fromJSON(null)).toBeUndefined();
}); });
}); });

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { Login as SdkLogin } from "@bitwarden/sdk-internal"; import { Login as SdkLogin } from "@bitwarden/sdk-internal";
@@ -7,6 +5,7 @@ import { Login as SdkLogin } from "@bitwarden/sdk-internal";
import { EncString } from "../../../key-management/crypto/models/enc-string"; import { EncString } from "../../../key-management/crypto/models/enc-string";
import Domain from "../../../platform/models/domain/domain-base"; import Domain from "../../../platform/models/domain/domain-base";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { conditionalEncString, encStringFrom } from "../../utils/domain-utils";
import { LoginData } from "../data/login.data"; import { LoginData } from "../data/login.data";
import { LoginView } from "../view/login.view"; import { LoginView } from "../view/login.view";
@@ -14,13 +13,13 @@ import { Fido2Credential } from "./fido2-credential";
import { LoginUri } from "./login-uri"; import { LoginUri } from "./login-uri";
export class Login extends Domain { export class Login extends Domain {
uris: LoginUri[]; uris?: LoginUri[];
username: EncString; username?: EncString;
password: EncString; password?: EncString;
passwordRevisionDate?: Date; passwordRevisionDate?: Date;
totp: EncString; totp?: EncString;
autofillOnPageLoad: boolean; autofillOnPageLoad?: boolean;
fido2Credentials: Fido2Credential[]; fido2Credentials?: Fido2Credential[];
constructor(obj?: LoginData) { constructor(obj?: LoginData) {
super(); super();
@@ -29,24 +28,14 @@ export class Login extends Domain {
} }
this.passwordRevisionDate = this.passwordRevisionDate =
obj.passwordRevisionDate != null ? new Date(obj.passwordRevisionDate) : null; obj.passwordRevisionDate != null ? new Date(obj.passwordRevisionDate) : undefined;
this.autofillOnPageLoad = obj.autofillOnPageLoad; this.autofillOnPageLoad = obj.autofillOnPageLoad;
this.buildDomainModel( this.username = conditionalEncString(obj.username);
this, this.password = conditionalEncString(obj.password);
obj, this.totp = conditionalEncString(obj.totp);
{
username: null,
password: null,
totp: null,
},
[],
);
if (obj.uris) { if (obj.uris) {
this.uris = []; this.uris = obj.uris.map((u) => new LoginUri(u));
obj.uris.forEach((u) => {
this.uris.push(new LoginUri(u));
});
} }
if (obj.fido2Credentials) { if (obj.fido2Credentials) {
@@ -55,7 +44,7 @@ export class Login extends Domain {
} }
async decrypt( async decrypt(
orgId: string, orgId: string | undefined,
bypassValidation: boolean, bypassValidation: boolean,
context: string = "No Cipher Context", context: string = "No Cipher Context",
encKey?: SymmetricCryptoKey, encKey?: SymmetricCryptoKey,
@@ -64,7 +53,7 @@ export class Login extends Domain {
this, this,
new LoginView(this), new LoginView(this),
["username", "password", "totp"], ["username", "password", "totp"],
orgId, orgId ?? null,
encKey, encKey,
`DomainType: Login; ${context}`, `DomainType: Login; ${context}`,
); );
@@ -78,12 +67,21 @@ export class Login extends Domain {
} }
const uri = await this.uris[i].decrypt(orgId, context, encKey); const uri = await this.uris[i].decrypt(orgId, context, encKey);
const uriString = uri.uri;
if (uriString == null) {
continue;
}
// URIs are shared remotely after decryption // URIs are shared remotely after decryption
// we need to validate that the string hasn't been changed by a compromised server // we need to validate that the string hasn't been changed by a compromised server
// This validation is tied to the existence of cypher.key for backwards compatibility // This validation is tied to the existence of cypher.key for backwards compatibility
// So we bypass the validation if there's no cipher.key or procceed with the validation and // 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. // Skip the value if it's been tampered with.
if (bypassValidation || (await this.uris[i].validateChecksum(uri.uri, orgId, encKey))) { const isValidUri =
bypassValidation || (await this.uris[i].validateChecksum(uriString, orgId, encKey));
if (isValidUri) {
view.uris.push(uri); view.uris.push(uri);
} }
} }
@@ -100,9 +98,12 @@ export class Login extends Domain {
toLoginData(): LoginData { toLoginData(): LoginData {
const l = new LoginData(); const l = new LoginData();
l.passwordRevisionDate = if (this.passwordRevisionDate != null) {
this.passwordRevisionDate != null ? this.passwordRevisionDate.toISOString() : null; l.passwordRevisionDate = this.passwordRevisionDate.toISOString();
l.autofillOnPageLoad = this.autofillOnPageLoad; }
if (this.autofillOnPageLoad != null) {
l.autofillOnPageLoad = this.autofillOnPageLoad;
}
this.buildDataModel(this, l, { this.buildDataModel(this, l, {
username: null, username: null,
password: null, password: null,
@@ -123,28 +124,27 @@ export class Login extends Domain {
return l; return l;
} }
static fromJSON(obj: Partial<Jsonify<Login>>): Login { static fromJSON(obj: Partial<Jsonify<Login>> | undefined): Login | undefined {
if (obj == null) { if (obj == null) {
return null; return undefined;
} }
const username = EncString.fromJSON(obj.username); const login = new Login();
const password = EncString.fromJSON(obj.password); login.passwordRevisionDate =
const totp = EncString.fromJSON(obj.totp); obj.passwordRevisionDate != null ? new Date(obj.passwordRevisionDate) : undefined;
const passwordRevisionDate = login.autofillOnPageLoad = obj.autofillOnPageLoad;
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); login.username = encStringFrom(obj.username);
const uris = obj.uris?.map((uri: any) => LoginUri.fromJSON(uri)); login.password = encStringFrom(obj.password);
const fido2Credentials = login.totp = encStringFrom(obj.totp);
obj.fido2Credentials?.map((key) => Fido2Credential.fromJSON(key)) ?? []; login.uris = obj.uris
?.map((uri: any) => LoginUri.fromJSON(uri))
.filter((u): u is LoginUri => u != null);
login.fido2Credentials =
obj.fido2Credentials
?.map((key) => Fido2Credential.fromJSON(key))
.filter((c): c is Fido2Credential => c != null) ?? undefined;
return Object.assign(new Login(), obj, { return login;
username,
password,
totp,
passwordRevisionDate,
uris,
fido2Credentials,
});
} }
/** /**
@@ -168,25 +168,27 @@ export class Login extends Domain {
* Maps an SDK Login object to a Login * Maps an SDK Login object to a Login
* @param obj - The SDK Login object * @param obj - The SDK Login object
*/ */
static fromSdkLogin(obj: SdkLogin): Login | undefined { static fromSdkLogin(obj?: SdkLogin): Login | undefined {
if (!obj) { if (!obj) {
return undefined; return undefined;
} }
const login = new Login(); const login = new Login();
login.passwordRevisionDate =
login.uris = obj.passwordRevisionDate != null ? new Date(obj.passwordRevisionDate) : undefined;
obj.uris?.filter((u) => u.uri != null).map((uri) => LoginUri.fromSdkLoginUri(uri)) ?? [];
login.username = EncString.fromJSON(obj.username);
login.password = EncString.fromJSON(obj.password);
login.passwordRevisionDate = obj.passwordRevisionDate
? new Date(obj.passwordRevisionDate)
: undefined;
login.totp = EncString.fromJSON(obj.totp);
login.autofillOnPageLoad = obj.autofillOnPageLoad; login.autofillOnPageLoad = obj.autofillOnPageLoad;
login.fido2Credentials = obj.fido2Credentials?.map((f) => login.username = encStringFrom(obj.username);
Fido2Credential.fromSdkFido2Credential(f), login.password = encStringFrom(obj.password);
); login.totp = encStringFrom(obj.totp);
login.uris =
obj.uris
?.filter((u) => u.uri != null)
.map((uri) => LoginUri.fromSdkLoginUri(uri))
.filter((u): u is LoginUri => u != null) ?? undefined;
login.fido2Credentials =
obj.fido2Credentials
?.map((f) => Fido2Credential.fromSdkFido2Credential(f))
.filter((c): c is Fido2Credential => c != null) ?? undefined;
return login; return login;
} }

View File

@@ -17,9 +17,9 @@ describe("Password", () => {
const data = new PasswordHistoryData(); const data = new PasswordHistoryData();
const password = new Password(data); const password = new Password(data);
expect(password).toMatchObject({ expect(password).toBeInstanceOf(Password);
password: null, expect(password.password).toBeInstanceOf(EncString);
}); expect(password.lastUsedDate).toBeInstanceOf(Date);
}); });
it("Convert", () => { it("Convert", () => {
@@ -66,8 +66,8 @@ describe("Password", () => {
expect(actual).toBeInstanceOf(Password); expect(actual).toBeInstanceOf(Password);
}); });
it("returns null if object is null", () => { it("returns undefined if object is null", () => {
expect(Password.fromJSON(null)).toBeNull(); expect(Password.fromJSON(null)).toBeUndefined();
}); });
}); });

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { PasswordHistory } from "@bitwarden/sdk-internal"; import { PasswordHistory } from "@bitwarden/sdk-internal";
@@ -11,8 +9,8 @@ import { PasswordHistoryData } from "../data/password-history.data";
import { PasswordHistoryView } from "../view/password-history.view"; import { PasswordHistoryView } from "../view/password-history.view";
export class Password extends Domain { export class Password extends Domain {
password: EncString; password!: EncString;
lastUsedDate: Date; lastUsedDate!: Date;
constructor(obj?: PasswordHistoryData) { constructor(obj?: PasswordHistoryData) {
super(); super();
@@ -20,18 +18,16 @@ export class Password extends Domain {
return; return;
} }
this.buildDomainModel(this, obj, { this.password = new EncString(obj.password);
password: null,
});
this.lastUsedDate = new Date(obj.lastUsedDate); this.lastUsedDate = new Date(obj.lastUsedDate);
} }
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<PasswordHistoryView> { decrypt(orgId: string | undefined, encKey?: SymmetricCryptoKey): Promise<PasswordHistoryView> {
return this.decryptObj<Password, PasswordHistoryView>( return this.decryptObj<Password, PasswordHistoryView>(
this, this,
new PasswordHistoryView(this), new PasswordHistoryView(this),
["password"], ["password"],
orgId, orgId ?? null,
encKey, encKey,
"DomainType: PasswordHistory", "DomainType: PasswordHistory",
); );
@@ -46,18 +42,16 @@ export class Password extends Domain {
return ph; return ph;
} }
static fromJSON(obj: Partial<Jsonify<Password>>): Password { static fromJSON(obj: Jsonify<Password> | undefined): Password | undefined {
if (obj == null) { if (obj == null) {
return null; return undefined;
} }
const password = EncString.fromJSON(obj.password); const passwordHistory = new Password();
const lastUsedDate = obj.lastUsedDate == null ? null : new Date(obj.lastUsedDate); passwordHistory.password = EncString.fromJSON(obj.password);
passwordHistory.lastUsedDate = new Date(obj.lastUsedDate);
return Object.assign(new Password(), obj, { return passwordHistory;
password,
lastUsedDate,
});
} }
/** /**
@@ -76,7 +70,7 @@ export class Password extends Domain {
* Maps an SDK PasswordHistory object to a Password * Maps an SDK PasswordHistory object to a Password
* @param obj - The SDK PasswordHistory object * @param obj - The SDK PasswordHistory object
*/ */
static fromSdkPasswordHistory(obj: PasswordHistory): Password | undefined { static fromSdkPasswordHistory(obj?: PasswordHistory): Password | undefined {
if (!obj) { if (!obj) {
return undefined; return undefined;
} }

View File

@@ -38,7 +38,7 @@ describe("SecureNote", () => {
const secureNote = new SecureNote(); const secureNote = new SecureNote();
secureNote.type = SecureNoteType.Generic; secureNote.type = SecureNoteType.Generic;
const view = await secureNote.decrypt(null); const view = await secureNote.decrypt();
expect(view).toEqual({ expect(view).toEqual({
type: 0, type: 0,
@@ -46,8 +46,8 @@ describe("SecureNote", () => {
}); });
describe("fromJSON", () => { describe("fromJSON", () => {
it("returns null if object is null", () => { it("returns undefined if object is null", () => {
expect(SecureNote.fromJSON(null)).toBeNull(); expect(SecureNote.fromJSON(null)).toBeUndefined();
}); });
}); });

View File

@@ -1,17 +1,14 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { SecureNote as SdkSecureNote } from "@bitwarden/sdk-internal"; import { SecureNote as SdkSecureNote } from "@bitwarden/sdk-internal";
import Domain from "../../../platform/models/domain/domain-base"; import Domain from "../../../platform/models/domain/domain-base";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { SecureNoteType } from "../../enums"; import { SecureNoteType } from "../../enums";
import { SecureNoteData } from "../data/secure-note.data"; import { SecureNoteData } from "../data/secure-note.data";
import { SecureNoteView } from "../view/secure-note.view"; import { SecureNoteView } from "../view/secure-note.view";
export class SecureNote extends Domain { export class SecureNote extends Domain {
type: SecureNoteType; type: SecureNoteType = SecureNoteType.Generic;
constructor(obj?: SecureNoteData) { constructor(obj?: SecureNoteData) {
super(); super();
@@ -22,11 +19,7 @@ export class SecureNote extends Domain {
this.type = obj.type; this.type = obj.type;
} }
async decrypt( async decrypt(): Promise<SecureNoteView> {
orgId: string,
context = "No Cipher Context",
encKey?: SymmetricCryptoKey,
): Promise<SecureNoteView> {
return new SecureNoteView(this); return new SecureNoteView(this);
} }
@@ -36,12 +29,14 @@ export class SecureNote extends Domain {
return n; return n;
} }
static fromJSON(obj: Jsonify<SecureNote>): SecureNote { static fromJSON(obj: Jsonify<SecureNote> | undefined): SecureNote | undefined {
if (obj == null) { if (obj == null) {
return null; return undefined;
} }
return Object.assign(new SecureNote(), obj); const secureNote = new SecureNote();
secureNote.type = obj.type;
return secureNote;
} }
/** /**
@@ -59,7 +54,7 @@ export class SecureNote extends Domain {
* Maps an SDK SecureNote object to a SecureNote * Maps an SDK SecureNote object to a SecureNote
* @param obj - The SDK SecureNote object * @param obj - The SDK SecureNote object
*/ */
static fromSdkSecureNote(obj: SdkSecureNote): SecureNote | undefined { static fromSdkSecureNote(obj?: SdkSecureNote): SecureNote | undefined {
if (obj == null) { if (obj == null) {
return undefined; return undefined;
} }

View File

@@ -1,3 +1,5 @@
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { mockEnc } from "../../../../spec"; import { mockEnc } from "../../../../spec";
import { SshKeyApi } from "../api/ssh-key.api"; import { SshKeyApi } from "../api/ssh-key.api";
import { SshKeyData } from "../data/ssh-key.data"; import { SshKeyData } from "../data/ssh-key.data";
@@ -31,11 +33,10 @@ describe("Sshkey", () => {
const data = new SshKeyData(); const data = new SshKeyData();
const sshKey = new SshKey(data); const sshKey = new SshKey(data);
expect(sshKey).toEqual({ expect(sshKey).toBeInstanceOf(SshKey);
privateKey: null, expect(sshKey.privateKey).toBeInstanceOf(EncString);
publicKey: null, expect(sshKey.publicKey).toBeInstanceOf(EncString);
keyFingerprint: null, expect(sshKey.keyFingerprint).toBeInstanceOf(EncString);
});
}); });
it("toSshKeyData", () => { it("toSshKeyData", () => {
@@ -60,8 +61,8 @@ describe("Sshkey", () => {
}); });
describe("fromJSON", () => { describe("fromJSON", () => {
it("returns null if object is null", () => { it("returns undefined if object is null", () => {
expect(SshKey.fromJSON(null)).toBeNull(); expect(SshKey.fromJSON(null)).toBeUndefined();
}); });
}); });

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { SshKey as SdkSshKey } from "@bitwarden/sdk-internal"; import { SshKey as SdkSshKey } from "@bitwarden/sdk-internal";
@@ -11,9 +9,9 @@ import { SshKeyData } from "../data/ssh-key.data";
import { SshKeyView } from "../view/ssh-key.view"; import { SshKeyView } from "../view/ssh-key.view";
export class SshKey extends Domain { export class SshKey extends Domain {
privateKey: EncString; privateKey!: EncString;
publicKey: EncString; publicKey!: EncString;
keyFingerprint: EncString; keyFingerprint!: EncString;
constructor(obj?: SshKeyData) { constructor(obj?: SshKeyData) {
super(); super();
@@ -21,20 +19,13 @@ export class SshKey extends Domain {
return; return;
} }
this.buildDomainModel( this.privateKey = new EncString(obj.privateKey);
this, this.publicKey = new EncString(obj.publicKey);
obj, this.keyFingerprint = new EncString(obj.keyFingerprint);
{
privateKey: null,
publicKey: null,
keyFingerprint: null,
},
[],
);
} }
decrypt( decrypt(
orgId: string, orgId: string | undefined,
context = "No Cipher Context", context = "No Cipher Context",
encKey?: SymmetricCryptoKey, encKey?: SymmetricCryptoKey,
): Promise<SshKeyView> { ): Promise<SshKeyView> {
@@ -42,7 +33,7 @@ export class SshKey extends Domain {
this, this,
new SshKeyView(), new SshKeyView(),
["privateKey", "publicKey", "keyFingerprint"], ["privateKey", "publicKey", "keyFingerprint"],
orgId, orgId ?? null,
encKey, encKey,
"DomainType: SshKey; " + context, "DomainType: SshKey; " + context,
); );
@@ -58,19 +49,17 @@ export class SshKey extends Domain {
return c; return c;
} }
static fromJSON(obj: Partial<Jsonify<SshKey>>): SshKey { static fromJSON(obj: Jsonify<SshKey> | undefined): SshKey | undefined {
if (obj == null) { if (obj == null) {
return null; return undefined;
} }
const privateKey = EncString.fromJSON(obj.privateKey); const sshKey = new SshKey();
const publicKey = EncString.fromJSON(obj.publicKey); sshKey.privateKey = EncString.fromJSON(obj.privateKey);
const keyFingerprint = EncString.fromJSON(obj.keyFingerprint); sshKey.publicKey = EncString.fromJSON(obj.publicKey);
return Object.assign(new SshKey(), obj, { sshKey.keyFingerprint = EncString.fromJSON(obj.keyFingerprint);
privateKey,
publicKey, return sshKey;
keyFingerprint,
});
} }
/** /**
@@ -90,7 +79,7 @@ export class SshKey extends Domain {
* Maps an SDK SshKey object to a SshKey * Maps an SDK SshKey object to a SshKey
* @param obj - The SDK SshKey object * @param obj - The SDK SshKey object
*/ */
static fromSdkSshKey(obj: SdkSshKey): SshKey | undefined { static fromSdkSshKey(obj?: SdkSshKey): SshKey | undefined {
if (obj == null) { if (obj == null) {
return undefined; return undefined;
} }

View File

@@ -1,7 +1,7 @@
import { Cipher } from "../domain/cipher"; import { Cipher } from "../domain/cipher";
export class CipherPartialRequest { export class CipherPartialRequest {
folderId: string; folderId?: string;
favorite: boolean; favorite: boolean;
constructor(cipher: Cipher) { constructor(cipher: Cipher) {

View File

@@ -869,13 +869,14 @@ export class CipherService implements CipherServiceAbstraction {
response = await this.apiService.postCipherAdmin(request); response = await this.apiService.postCipherAdmin(request);
const data = new CipherData(response, cipher.collectionIds); const data = new CipherData(response, cipher.collectionIds);
return new Cipher(data); return new Cipher(data);
} else if (cipher.collectionIds != null) { } else if (cipher.collectionIds != null && cipher.collectionIds.length > 0) {
const request = new CipherCreateRequest({ cipher, encryptedFor }); const request = new CipherCreateRequest({ cipher, encryptedFor });
response = await this.apiService.postCipherCreate(request); response = await this.apiService.postCipherCreate(request);
} else { } else {
const request = new CipherRequest({ cipher, encryptedFor }); const request = new CipherRequest({ cipher, encryptedFor });
response = await this.apiService.postCipher(request); response = await this.apiService.postCipher(request);
} }
cipher.id = response.id; cipher.id = response.id;
const data = new CipherData(response, cipher.collectionIds); const data = new CipherData(response, cipher.collectionIds);

View File

@@ -0,0 +1,27 @@
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { EncString as SdkEncString } from "@bitwarden/sdk-internal";
/**
* Converts a string value to an EncString, handling null/undefined gracefully.
*
* @param value - The string value to convert, or undefined
* @returns An EncString instance if value is defined, otherwise undefined
*
*/
export const conditionalEncString = (value?: string): EncString | undefined => {
return value != null ? new EncString(value) : undefined;
};
/**
* Converts an EncString representation (from JSON or SDK) to a domain EncString instance.
* Handles both serialized JSON representations and SDK EncString objects.
*
* @param value - The EncString representation (string, object, or SdkEncString), or undefined
* @returns A domain EncString instance if value is defined, otherwise undefined
*
*/
export const encStringFrom = <T extends string | SdkEncString>(
value?: T,
): EncString | undefined => {
return value != null ? EncString.fromJSON(value) : undefined;
};

View File

@@ -343,7 +343,7 @@ describe("VaultExportService", () => {
const exportData: BitwardenJsonExport = JSON.parse(data); const exportData: BitwardenJsonExport = JSON.parse(data);
expect(exportData.items.length).toBe(1); expect(exportData.items.length).toBe(1);
expect(exportData.items[0].id).toBe("mock-id"); expect(exportData.items[0].id).toBe("mock-id");
expect(exportData.items[0].organizationId).toBe(null); expect(exportData.items[0].organizationId).toBeUndefined();
}); });
it.each([[400], [401], [404], [500]])( it.each([[400], [401], [404], [500]])(