mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 02:03:39 +00:00
[PM-22136] Implement SDK cipher encryption (#15337)
* [PM-22136] Update sdk cipher view map to support uknown uuid type * [PM-22136] Add key to CipherView for copying to SdkCipherView for encryption * [PM-22136] Add fromSdk* helpers to Cipher domain objects * [PM-22136] Add toSdk* helpers to Cipher View objects * [PM-22136] Add encrypt() to cipher encryption service * [PM-22136] Add feature flag * [PM-22136] Use new SDK encrypt method when feature flag is enabled * [PM-22136] Filter out null/empty URIs * [PM-22136] Change default value for cipher view arrays to []. See ADR-0014. * [PM-22136] Keep encrypted key value on attachment so that it is passed to the SDK * [PM-22136] Keep encrypted key value on CipherView so that it is passed to the SDK during encryption * [PM-22136] Update failing attachment test * [PM-22136] Update failing importer tests due to new default value for arrays * [PM-22136] Update CipherView.fromJson to handle the prototype of EncString for the cipher key * [PM-22136] Add tickets for followup work * [PM-22136] Use new set_fido2_credentials SDK method instead * [PM-22136] Fix missing prototype when decrypting Fido2Credentials * [PM-22136] Fix test after sdk change * [PM-22136] Update @bitwarden/sdk-internal version * [PM-22136] Fix some strict typing errors * [PM-23348] Migrate move cipher to org to SDK (#15567) * [PM-23348] Add moveToOrganization method to cipher-encryption.service.ts * [PM-23348] Use cipherEncryptionService.moveToOrganization in cipherService shareWithServer and shareManyWithServer methods * [PM-23348] Update cipherFormService to use the shareWithServer() method instead of encrypt() * [PM-23348] Fix typo * [PM-23348] Add missing docs * [PM-22136] Fix EncString import after merge with main
This commit is contained in:
@@ -4,7 +4,7 @@ import { CipherPermissions as SdkCipherPermissions } from "@bitwarden/sdk-intern
|
||||
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class CipherPermissionsApi extends BaseResponse {
|
||||
export class CipherPermissionsApi extends BaseResponse implements SdkCipherPermissions {
|
||||
delete: boolean = false;
|
||||
restore: boolean = false;
|
||||
|
||||
@@ -35,4 +35,11 @@ export class CipherPermissionsApi extends BaseResponse {
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the CipherPermissionsApi to an SdkCipherPermissions
|
||||
*/
|
||||
toSdkCipherPermissions(): SdkCipherPermissions {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,45 @@
|
||||
import {
|
||||
LocalDataView as SdkLocalDataView,
|
||||
LocalData as SdkLocalData,
|
||||
} from "@bitwarden/sdk-internal";
|
||||
|
||||
export type LocalData = {
|
||||
lastUsedDate?: number;
|
||||
lastLaunched?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert the SdkLocalDataView to LocalData
|
||||
* @param localData
|
||||
*/
|
||||
export function fromSdkLocalData(
|
||||
localData: SdkLocalDataView | SdkLocalData | undefined,
|
||||
): LocalData | undefined {
|
||||
if (localData == null) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
lastUsedDate: localData.lastUsedDate ? new Date(localData.lastUsedDate).getTime() : undefined,
|
||||
lastLaunched: localData.lastLaunched ? new Date(localData.lastLaunched).getTime() : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the LocalData to SdkLocalData
|
||||
* @param localData
|
||||
*/
|
||||
export function toSdkLocalData(
|
||||
localData: LocalData | undefined,
|
||||
): (SdkLocalDataView & SdkLocalData) | undefined {
|
||||
if (localData == null) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
lastUsedDate: localData.lastUsedDate
|
||||
? new Date(localData.lastUsedDate).toISOString()
|
||||
: undefined,
|
||||
lastLaunched: localData.lastLaunched
|
||||
? new Date(localData.lastLaunched).toISOString()
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ describe("Attachment", () => {
|
||||
sizeName: "1.1 KB",
|
||||
fileName: "fileName",
|
||||
key: expect.any(SymmetricCryptoKey),
|
||||
encryptedKey: attachment.key,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ export class Attachment extends Domain {
|
||||
|
||||
if (this.key != null) {
|
||||
view.key = await this.decryptAttachmentKey(orgId, encKey);
|
||||
view.encryptedKey = this.key; // Keep the encrypted key for the view
|
||||
}
|
||||
|
||||
return view;
|
||||
@@ -131,4 +132,24 @@ export class Attachment extends Domain {
|
||||
key: this.key?.toJSON(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an SDK Attachment object to an Attachment
|
||||
* @param obj - The SDK attachment object
|
||||
*/
|
||||
static fromSdkAttachment(obj: SdkAttachment): Attachment | undefined {
|
||||
if (!obj) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const attachment = new Attachment();
|
||||
attachment.id = obj.id;
|
||||
attachment.url = obj.url;
|
||||
attachment.size = obj.size;
|
||||
attachment.sizeName = obj.sizeName;
|
||||
attachment.fileName = EncString.fromJSON(obj.fileName);
|
||||
attachment.key = EncString.fromJSON(obj.key);
|
||||
|
||||
return attachment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,4 +103,24 @@ export class Card extends Domain {
|
||||
code: this.code?.toJSON(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an SDK Card object to a Card
|
||||
* @param obj - The SDK Card object
|
||||
*/
|
||||
static fromSdkCard(obj: SdkCard): Card | undefined {
|
||||
if (obj == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const card = new Card();
|
||||
card.cardholderName = EncString.fromJSON(obj.cardholderName);
|
||||
card.brand = EncString.fromJSON(obj.brand);
|
||||
card.number = EncString.fromJSON(obj.number);
|
||||
card.expMonth = EncString.fromJSON(obj.expMonth);
|
||||
card.expYear = EncString.fromJSON(obj.expYear);
|
||||
card.code = EncString.fromJSON(obj.code);
|
||||
|
||||
return card;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
UriMatchType,
|
||||
CipherRepromptType as SdkCipherRepromptType,
|
||||
LoginLinkedIdType,
|
||||
Cipher as SdkCipher,
|
||||
} from "@bitwarden/sdk-internal";
|
||||
|
||||
import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils";
|
||||
@@ -206,7 +207,7 @@ describe("Cipher DTO", () => {
|
||||
it("Convert", () => {
|
||||
const cipher = new Cipher(cipherData);
|
||||
|
||||
expect(cipher).toEqual({
|
||||
expect(cipher).toMatchObject({
|
||||
initializerKey: InitializerKey.Cipher,
|
||||
id: "id",
|
||||
organizationId: "orgId",
|
||||
@@ -339,9 +340,9 @@ describe("Cipher DTO", () => {
|
||||
edit: true,
|
||||
viewPassword: true,
|
||||
login: loginView,
|
||||
attachments: null,
|
||||
fields: null,
|
||||
passwordHistory: null,
|
||||
attachments: [],
|
||||
fields: [],
|
||||
passwordHistory: [],
|
||||
collectionIds: undefined,
|
||||
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
@@ -462,9 +463,9 @@ describe("Cipher DTO", () => {
|
||||
edit: true,
|
||||
viewPassword: true,
|
||||
secureNote: { type: 0 },
|
||||
attachments: null,
|
||||
fields: null,
|
||||
passwordHistory: null,
|
||||
attachments: [],
|
||||
fields: [],
|
||||
passwordHistory: [],
|
||||
collectionIds: undefined,
|
||||
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
@@ -603,9 +604,9 @@ describe("Cipher DTO", () => {
|
||||
edit: true,
|
||||
viewPassword: true,
|
||||
card: cardView,
|
||||
attachments: null,
|
||||
fields: null,
|
||||
passwordHistory: null,
|
||||
attachments: [],
|
||||
fields: [],
|
||||
passwordHistory: [],
|
||||
collectionIds: undefined,
|
||||
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
@@ -768,9 +769,9 @@ describe("Cipher DTO", () => {
|
||||
edit: true,
|
||||
viewPassword: true,
|
||||
identity: identityView,
|
||||
attachments: null,
|
||||
fields: null,
|
||||
passwordHistory: null,
|
||||
attachments: [],
|
||||
fields: [],
|
||||
passwordHistory: [],
|
||||
collectionIds: undefined,
|
||||
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
@@ -1001,6 +1002,167 @@ describe("Cipher DTO", () => {
|
||||
revisionDate: "2022-01-31T12:00:00.000Z",
|
||||
});
|
||||
});
|
||||
|
||||
it("should map from SDK Cipher", () => {
|
||||
jest.restoreAllMocks();
|
||||
const sdkCipher: SdkCipher = {
|
||||
id: "id",
|
||||
organizationId: "orgId",
|
||||
folderId: "folderId",
|
||||
collectionIds: [],
|
||||
key: "EncryptedString",
|
||||
name: "EncryptedString",
|
||||
notes: "EncryptedString",
|
||||
type: SdkCipherType.Login,
|
||||
login: {
|
||||
username: "EncryptedString",
|
||||
password: "EncryptedString",
|
||||
passwordRevisionDate: "2022-01-31T12:00:00.000Z",
|
||||
uris: [
|
||||
{
|
||||
uri: "EncryptedString",
|
||||
uriChecksum: "EncryptedString",
|
||||
match: UriMatchType.Domain,
|
||||
},
|
||||
],
|
||||
totp: "EncryptedString",
|
||||
autofillOnPageLoad: false,
|
||||
fido2Credentials: undefined,
|
||||
},
|
||||
identity: undefined,
|
||||
card: undefined,
|
||||
secureNote: undefined,
|
||||
sshKey: undefined,
|
||||
favorite: false,
|
||||
reprompt: SdkCipherRepromptType.None,
|
||||
organizationUseTotp: true,
|
||||
edit: true,
|
||||
permissions: new CipherPermissionsApi(),
|
||||
viewPassword: true,
|
||||
localData: {
|
||||
lastUsedDate: "2025-04-15T12:00:00.000Z",
|
||||
lastLaunched: "2025-04-15T12:00:00.000Z",
|
||||
},
|
||||
attachments: [
|
||||
{
|
||||
id: "a1",
|
||||
url: "url",
|
||||
size: "1100",
|
||||
sizeName: "1.1 KB",
|
||||
fileName: "file",
|
||||
key: "EncKey",
|
||||
},
|
||||
{
|
||||
id: "a2",
|
||||
url: "url",
|
||||
size: "1100",
|
||||
sizeName: "1.1 KB",
|
||||
fileName: "file",
|
||||
key: "EncKey",
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
name: "EncryptedString",
|
||||
value: "EncryptedString",
|
||||
type: FieldType.Linked,
|
||||
linkedId: LoginLinkedIdType.Username,
|
||||
},
|
||||
{
|
||||
name: "EncryptedString",
|
||||
value: "EncryptedString",
|
||||
type: FieldType.Linked,
|
||||
linkedId: LoginLinkedIdType.Password,
|
||||
},
|
||||
],
|
||||
passwordHistory: [
|
||||
{
|
||||
password: "EncryptedString",
|
||||
lastUsedDate: "2022-01-31T12:00:00.000Z",
|
||||
},
|
||||
],
|
||||
creationDate: "2022-01-01T12:00:00.000Z",
|
||||
deletedDate: undefined,
|
||||
revisionDate: "2022-01-31T12:00:00.000Z",
|
||||
};
|
||||
|
||||
const lastUsedDate = new Date("2025-04-15T12:00:00.000Z").getTime();
|
||||
const lastLaunched = new Date("2025-04-15T12:00:00.000Z").getTime();
|
||||
|
||||
const cipherData: CipherData = {
|
||||
id: "id",
|
||||
organizationId: "orgId",
|
||||
folderId: "folderId",
|
||||
edit: true,
|
||||
permissions: new CipherPermissionsApi(),
|
||||
collectionIds: [],
|
||||
viewPassword: true,
|
||||
organizationUseTotp: true,
|
||||
favorite: false,
|
||||
revisionDate: "2022-01-31T12:00:00.000Z",
|
||||
type: CipherType.Login,
|
||||
name: "EncryptedString",
|
||||
notes: "EncryptedString",
|
||||
creationDate: "2022-01-01T12:00:00.000Z",
|
||||
deletedDate: null,
|
||||
reprompt: CipherRepromptType.None,
|
||||
key: "EncryptedString",
|
||||
login: {
|
||||
uris: [
|
||||
{
|
||||
uri: "EncryptedString",
|
||||
uriChecksum: "EncryptedString",
|
||||
match: UriMatchStrategy.Domain,
|
||||
},
|
||||
],
|
||||
username: "EncryptedString",
|
||||
password: "EncryptedString",
|
||||
passwordRevisionDate: "2022-01-31T12:00:00.000Z",
|
||||
totp: "EncryptedString",
|
||||
autofillOnPageLoad: false,
|
||||
},
|
||||
passwordHistory: [
|
||||
{ password: "EncryptedString", lastUsedDate: "2022-01-31T12:00:00.000Z" },
|
||||
],
|
||||
attachments: [
|
||||
{
|
||||
id: "a1",
|
||||
url: "url",
|
||||
size: "1100",
|
||||
sizeName: "1.1 KB",
|
||||
fileName: "file",
|
||||
key: "EncKey",
|
||||
},
|
||||
{
|
||||
id: "a2",
|
||||
url: "url",
|
||||
size: "1100",
|
||||
sizeName: "1.1 KB",
|
||||
fileName: "file",
|
||||
key: "EncKey",
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
name: "EncryptedString",
|
||||
value: "EncryptedString",
|
||||
type: FieldType.Linked,
|
||||
linkedId: LoginLinkedId.Username,
|
||||
},
|
||||
{
|
||||
name: "EncryptedString",
|
||||
value: "EncryptedString",
|
||||
type: FieldType.Linked,
|
||||
linkedId: LoginLinkedId.Password,
|
||||
},
|
||||
],
|
||||
};
|
||||
const expectedCipher = new Cipher(cipherData, { lastUsedDate, lastLaunched });
|
||||
|
||||
const cipher = Cipher.fromSdkCipher(sdkCipher);
|
||||
|
||||
expect(cipher).toEqual(expectedCipher);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// @ts-strict-ignore
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { uuidToString } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||
import { Cipher as SdkCipher } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
@@ -14,7 +15,7 @@ import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
|
||||
import { CipherType } from "../../enums/cipher-type";
|
||||
import { CipherPermissionsApi } from "../api/cipher-permissions.api";
|
||||
import { CipherData } from "../data/cipher.data";
|
||||
import { LocalData } from "../data/local.data";
|
||||
import { LocalData, fromSdkLocalData, toSdkLocalData } from "../data/local.data";
|
||||
import { AttachmentView } from "../view/attachment.view";
|
||||
import { CipherView } from "../view/cipher.view";
|
||||
import { FieldView } from "../view/field.view";
|
||||
@@ -361,16 +362,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
}
|
||||
: undefined,
|
||||
viewPassword: this.viewPassword ?? true,
|
||||
localData: this.localData
|
||||
? {
|
||||
lastUsedDate: this.localData.lastUsedDate
|
||||
? new Date(this.localData.lastUsedDate).toISOString()
|
||||
: undefined,
|
||||
lastLaunched: this.localData.lastLaunched
|
||||
? new Date(this.localData.lastLaunched).toISOString()
|
||||
: undefined,
|
||||
}
|
||||
: undefined,
|
||||
localData: toSdkLocalData(this.localData),
|
||||
attachments: this.attachments?.map((a) => a.toSdkAttachment()),
|
||||
fields: this.fields?.map((f) => f.toSdkField()),
|
||||
passwordHistory: this.passwordHistory?.map((ph) => ph.toSdkPasswordHistory()),
|
||||
@@ -408,4 +400,50 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
|
||||
return sdkCipher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an SDK Cipher object to a Cipher
|
||||
* @param sdkCipher - The SDK Cipher object
|
||||
*/
|
||||
static fromSdkCipher(sdkCipher: SdkCipher | null): Cipher | undefined {
|
||||
if (sdkCipher == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cipher = new Cipher();
|
||||
|
||||
cipher.id = sdkCipher.id ? uuidToString(sdkCipher.id) : undefined;
|
||||
cipher.organizationId = sdkCipher.organizationId
|
||||
? uuidToString(sdkCipher.organizationId)
|
||||
: undefined;
|
||||
cipher.folderId = sdkCipher.folderId ? uuidToString(sdkCipher.folderId) : undefined;
|
||||
cipher.collectionIds = sdkCipher.collectionIds ? sdkCipher.collectionIds.map(uuidToString) : [];
|
||||
cipher.key = EncString.fromJSON(sdkCipher.key);
|
||||
cipher.name = EncString.fromJSON(sdkCipher.name);
|
||||
cipher.notes = EncString.fromJSON(sdkCipher.notes);
|
||||
cipher.type = sdkCipher.type;
|
||||
cipher.favorite = sdkCipher.favorite;
|
||||
cipher.organizationUseTotp = sdkCipher.organizationUseTotp;
|
||||
cipher.edit = sdkCipher.edit;
|
||||
cipher.permissions = CipherPermissionsApi.fromSdkCipherPermissions(sdkCipher.permissions);
|
||||
cipher.viewPassword = sdkCipher.viewPassword;
|
||||
cipher.localData = fromSdkLocalData(sdkCipher.localData);
|
||||
cipher.attachments = sdkCipher.attachments?.map((a) => Attachment.fromSdkAttachment(a)) ?? [];
|
||||
cipher.fields = sdkCipher.fields?.map((f) => Field.fromSdkField(f)) ?? [];
|
||||
cipher.passwordHistory =
|
||||
sdkCipher.passwordHistory?.map((ph) => Password.fromSdkPasswordHistory(ph)) ?? [];
|
||||
cipher.creationDate = new Date(sdkCipher.creationDate);
|
||||
cipher.revisionDate = new Date(sdkCipher.revisionDate);
|
||||
cipher.deletedDate = sdkCipher.deletedDate ? new Date(sdkCipher.deletedDate) : null;
|
||||
cipher.reprompt = sdkCipher.reprompt;
|
||||
|
||||
// Cipher type specific properties
|
||||
cipher.login = Login.fromSdkLogin(sdkCipher.login);
|
||||
cipher.secureNote = SecureNote.fromSdkSecureNote(sdkCipher.secureNote);
|
||||
cipher.card = Card.fromSdkCard(sdkCipher.card);
|
||||
cipher.identity = Identity.fromSdkIdentity(sdkCipher.identity);
|
||||
cipher.sshKey = SshKey.fromSdkSshKey(sdkCipher.sshKey);
|
||||
|
||||
return cipher;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,4 +173,32 @@ export class Fido2Credential extends Domain {
|
||||
creationDate: this.creationDate.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an SDK Fido2Credential object to a Fido2Credential
|
||||
* @param obj - The SDK Fido2Credential object
|
||||
*/
|
||||
static fromSdkFido2Credential(obj: SdkFido2Credential): Fido2Credential | undefined {
|
||||
if (!obj) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const credential = new Fido2Credential();
|
||||
|
||||
credential.credentialId = EncString.fromJSON(obj.credentialId);
|
||||
credential.keyType = EncString.fromJSON(obj.keyType);
|
||||
credential.keyAlgorithm = EncString.fromJSON(obj.keyAlgorithm);
|
||||
credential.keyCurve = EncString.fromJSON(obj.keyCurve);
|
||||
credential.keyValue = EncString.fromJSON(obj.keyValue);
|
||||
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.rpName = EncString.fromJSON(obj.rpName);
|
||||
credential.userDisplayName = EncString.fromJSON(obj.userDisplayName);
|
||||
credential.discoverable = EncString.fromJSON(obj.discoverable);
|
||||
credential.creationDate = new Date(obj.creationDate);
|
||||
|
||||
return credential;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import {
|
||||
Field as SdkField,
|
||||
FieldType,
|
||||
LoginLinkedIdType,
|
||||
CardLinkedIdType,
|
||||
IdentityLinkedIdType,
|
||||
} from "@bitwarden/sdk-internal";
|
||||
|
||||
import { mockEnc, mockFromJson } from "../../../../spec";
|
||||
import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { CardLinkedId, FieldType, IdentityLinkedId, LoginLinkedId } from "../../enums";
|
||||
import { CardLinkedId, IdentityLinkedId, LoginLinkedId } from "../../enums";
|
||||
import { FieldData } from "../../models/data/field.data";
|
||||
import { Field } from "../../models/domain/field";
|
||||
|
||||
@@ -103,5 +111,34 @@ describe("Field", () => {
|
||||
identityField.linkedId = IdentityLinkedId.LicenseNumber;
|
||||
expect(identityField.toSdkField().linkedId).toBe(415);
|
||||
});
|
||||
|
||||
it("should map from SDK Field", () => {
|
||||
// Test Login LinkedId
|
||||
const loginField: SdkField = {
|
||||
name: undefined,
|
||||
value: undefined,
|
||||
type: FieldType.Linked,
|
||||
linkedId: LoginLinkedIdType.Username,
|
||||
};
|
||||
expect(Field.fromSdkField(loginField)!.linkedId).toBe(100);
|
||||
|
||||
// Test Card LinkedId
|
||||
const cardField: SdkField = {
|
||||
name: undefined,
|
||||
value: undefined,
|
||||
type: FieldType.Linked,
|
||||
linkedId: CardLinkedIdType.Number,
|
||||
};
|
||||
expect(Field.fromSdkField(cardField)!.linkedId).toBe(305);
|
||||
|
||||
// Test Identity LinkedId
|
||||
const identityFieldSdkField: SdkField = {
|
||||
name: undefined,
|
||||
value: undefined,
|
||||
type: FieldType.Linked,
|
||||
linkedId: IdentityLinkedIdType.LicenseNumber,
|
||||
};
|
||||
expect(Field.fromSdkField(identityFieldSdkField)!.linkedId).toBe(415);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -90,4 +90,22 @@ export class Field extends Domain {
|
||||
linkedId: this.linkedId as unknown as SdkLinkedIdType,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps SDK Field to Field
|
||||
* @param obj The SDK Field object to map
|
||||
*/
|
||||
static fromSdkField(obj: SdkField): Field | undefined {
|
||||
if (!obj) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const field = new Field();
|
||||
field.name = EncString.fromJSON(obj.name);
|
||||
field.value = EncString.fromJSON(obj.value);
|
||||
field.type = obj.type;
|
||||
field.linkedId = obj.linkedId;
|
||||
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,4 +195,36 @@ export class Identity extends Domain {
|
||||
licenseNumber: this.licenseNumber?.toJSON(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an SDK Identity object to an Identity
|
||||
* @param obj - The SDK Identity object
|
||||
*/
|
||||
static fromSdkIdentity(obj: SdkIdentity): Identity | undefined {
|
||||
if (obj == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const identity = new Identity();
|
||||
identity.title = EncString.fromJSON(obj.title);
|
||||
identity.firstName = EncString.fromJSON(obj.firstName);
|
||||
identity.middleName = EncString.fromJSON(obj.middleName);
|
||||
identity.lastName = EncString.fromJSON(obj.lastName);
|
||||
identity.address1 = EncString.fromJSON(obj.address1);
|
||||
identity.address2 = EncString.fromJSON(obj.address2);
|
||||
identity.address3 = EncString.fromJSON(obj.address3);
|
||||
identity.city = EncString.fromJSON(obj.city);
|
||||
identity.state = EncString.fromJSON(obj.state);
|
||||
identity.postalCode = EncString.fromJSON(obj.postalCode);
|
||||
identity.country = EncString.fromJSON(obj.country);
|
||||
identity.company = EncString.fromJSON(obj.company);
|
||||
identity.email = EncString.fromJSON(obj.email);
|
||||
identity.phone = EncString.fromJSON(obj.phone);
|
||||
identity.ssn = EncString.fromJSON(obj.ssn);
|
||||
identity.username = EncString.fromJSON(obj.username);
|
||||
identity.passportNumber = EncString.fromJSON(obj.passportNumber);
|
||||
identity.licenseNumber = EncString.fromJSON(obj.licenseNumber);
|
||||
|
||||
return identity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,4 +102,17 @@ export class LoginUri extends Domain {
|
||||
match: this.match,
|
||||
};
|
||||
}
|
||||
|
||||
static fromSdkLoginUri(obj: SdkLoginUri): LoginUri | undefined {
|
||||
if (obj == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const view = new LoginUri();
|
||||
view.uri = EncString.fromJSON(obj.uri);
|
||||
view.uriChecksum = obj.uriChecksum ? EncString.fromJSON(obj.uriChecksum) : undefined;
|
||||
view.match = obj.match;
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,4 +163,31 @@ export class Login extends Domain {
|
||||
fido2Credentials: this.fido2Credentials?.map((f) => f.toSdkFido2Credential()),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an SDK Login object to a Login
|
||||
* @param obj - The SDK Login object
|
||||
*/
|
||||
static fromSdkLogin(obj: SdkLogin): Login | undefined {
|
||||
if (!obj) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const login = new Login();
|
||||
|
||||
login.uris =
|
||||
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 ?? false;
|
||||
login.fido2Credentials = obj.fido2Credentials?.map((f) =>
|
||||
Fido2Credential.fromSdkFido2Credential(f),
|
||||
);
|
||||
|
||||
return login;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,4 +71,20 @@ export class Password extends Domain {
|
||||
lastUsedDate: this.lastUsedDate.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an SDK PasswordHistory object to a Password
|
||||
* @param obj - The SDK PasswordHistory object
|
||||
*/
|
||||
static fromSdkPasswordHistory(obj: PasswordHistory): Password | undefined {
|
||||
if (!obj) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const passwordHistory = new Password();
|
||||
passwordHistory.password = EncString.fromJSON(obj.password);
|
||||
passwordHistory.lastUsedDate = new Date(obj.lastUsedDate);
|
||||
|
||||
return passwordHistory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,4 +54,19 @@ export class SecureNote extends Domain {
|
||||
type: this.type,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an SDK SecureNote object to a SecureNote
|
||||
* @param obj - The SDK SecureNote object
|
||||
*/
|
||||
static fromSdkSecureNote(obj: SdkSecureNote): SecureNote | undefined {
|
||||
if (obj == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const secureNote = new SecureNote();
|
||||
secureNote.type = obj.type;
|
||||
|
||||
return secureNote;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,4 +85,21 @@ export class SshKey extends Domain {
|
||||
fingerprint: this.keyFingerprint.toJSON(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an SDK SshKey object to a SshKey
|
||||
* @param obj - The SDK SshKey object
|
||||
*/
|
||||
static fromSdkSshKey(obj: SdkSshKey): SshKey | undefined {
|
||||
if (obj == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sshKey = new SshKey();
|
||||
sshKey.privateKey = EncString.fromJSON(obj.privateKey);
|
||||
sshKey.publicKey = EncString.fromJSON(obj.publicKey);
|
||||
sshKey.keyFingerprint = EncString.fromJSON(obj.fingerprint);
|
||||
|
||||
return sshKey;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { linkedFieldOption } from "../../linked-field-option.decorator";
|
||||
|
||||
import { ItemView } from "./item.view";
|
||||
|
||||
export class CardView extends ItemView {
|
||||
export class CardView extends ItemView implements SdkCardView {
|
||||
@linkedFieldOption(LinkedId.CardholderName, { sortPosition: 0 })
|
||||
cardholderName: string = null;
|
||||
@linkedFieldOption(LinkedId.ExpMonth, { sortPosition: 3, i18nKey: "expirationMonth" })
|
||||
@@ -168,4 +168,12 @@ export class CardView extends ItemView {
|
||||
|
||||
return cardView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the CardView to an SDK CardView.
|
||||
* The view implements the SdkView so we can safely return `this`
|
||||
*/
|
||||
toSdkCardView(): SdkCardView {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { CipherPermissionsApi } from "@bitwarden/common/vault/models/api/cipher-permissions.api";
|
||||
import {
|
||||
CipherView as SdkCipherView,
|
||||
CipherType as SdkCipherType,
|
||||
@@ -85,6 +89,25 @@ describe("CipherView", () => {
|
||||
|
||||
expect(actual).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it("handle both string and object inputs for the cipher key", () => {
|
||||
const cipherKeyString = "cipherKeyString";
|
||||
const cipherKeyObject = new EncString("cipherKeyObject");
|
||||
|
||||
// Test with string input
|
||||
let actual = CipherView.fromJSON({
|
||||
key: cipherKeyString,
|
||||
});
|
||||
expect(actual.key).toBeInstanceOf(EncString);
|
||||
expect(actual.key?.toJSON()).toBe(cipherKeyString);
|
||||
|
||||
// Test with object input (which can happen when cipher view is stored in an InMemory state provider)
|
||||
actual = CipherView.fromJSON({
|
||||
key: cipherKeyObject,
|
||||
} as Jsonify<CipherView>);
|
||||
expect(actual.key).toBeInstanceOf(EncString);
|
||||
expect(actual.key?.toJSON()).toBe(cipherKeyObject.toJSON());
|
||||
});
|
||||
});
|
||||
|
||||
describe("fromSdkCipherView", () => {
|
||||
@@ -196,11 +219,80 @@ describe("CipherView", () => {
|
||||
__fromSdk: true,
|
||||
},
|
||||
],
|
||||
passwordHistory: null,
|
||||
passwordHistory: [],
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
revisionDate: new Date("2022-01-02T12:00:00.000Z"),
|
||||
deletedDate: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("toSdkCipherView", () => {
|
||||
it("maps properties correctly", () => {
|
||||
const cipherView = new CipherView();
|
||||
cipherView.id = "0a54d80c-14aa-4ef8-8c3a-7ea99ce5b602";
|
||||
cipherView.organizationId = "000f2a6e-da5e-4726-87ed-1c5c77322c3c";
|
||||
cipherView.folderId = "41b22db4-8e2a-4ed2-b568-f1186c72922f";
|
||||
cipherView.collectionIds = ["b0473506-3c3c-4260-a734-dfaaf833ab6f"];
|
||||
cipherView.key = new EncString("some-key");
|
||||
cipherView.name = "name";
|
||||
cipherView.notes = "notes";
|
||||
cipherView.type = CipherType.Login;
|
||||
cipherView.favorite = true;
|
||||
cipherView.edit = true;
|
||||
cipherView.viewPassword = false;
|
||||
cipherView.reprompt = CipherRepromptType.None;
|
||||
cipherView.organizationUseTotp = false;
|
||||
cipherView.localData = {
|
||||
lastLaunched: new Date("2022-01-01T12:00:00.000Z").getTime(),
|
||||
lastUsedDate: new Date("2022-01-02T12:00:00.000Z").getTime(),
|
||||
};
|
||||
cipherView.permissions = new CipherPermissionsApi();
|
||||
cipherView.permissions.restore = true;
|
||||
cipherView.permissions.delete = true;
|
||||
cipherView.attachments = [];
|
||||
cipherView.fields = [];
|
||||
cipherView.passwordHistory = [];
|
||||
cipherView.login = new LoginView();
|
||||
cipherView.revisionDate = new Date("2022-01-02T12:00:00.000Z");
|
||||
cipherView.creationDate = new Date("2022-01-02T12:00:00.000Z");
|
||||
|
||||
const sdkCipherView = cipherView.toSdkCipherView();
|
||||
|
||||
expect(sdkCipherView).toMatchObject({
|
||||
id: "0a54d80c-14aa-4ef8-8c3a-7ea99ce5b602",
|
||||
organizationId: "000f2a6e-da5e-4726-87ed-1c5c77322c3c",
|
||||
folderId: "41b22db4-8e2a-4ed2-b568-f1186c72922f",
|
||||
collectionIds: ["b0473506-3c3c-4260-a734-dfaaf833ab6f"],
|
||||
key: "some-key",
|
||||
name: "name",
|
||||
notes: "notes",
|
||||
type: SdkCipherType.Login,
|
||||
favorite: true,
|
||||
edit: true,
|
||||
viewPassword: false,
|
||||
reprompt: SdkCipherRepromptType.None,
|
||||
organizationUseTotp: false,
|
||||
localData: {
|
||||
lastLaunched: "2022-01-01T12:00:00.000Z",
|
||||
lastUsedDate: "2022-01-02T12:00:00.000Z",
|
||||
},
|
||||
permissions: {
|
||||
restore: true,
|
||||
delete: true,
|
||||
},
|
||||
deletedDate: undefined,
|
||||
creationDate: "2022-01-02T12:00:00.000Z",
|
||||
revisionDate: "2022-01-02T12:00:00.000Z",
|
||||
attachments: [],
|
||||
passwordHistory: [],
|
||||
login: undefined,
|
||||
identity: undefined,
|
||||
card: undefined,
|
||||
secureNote: undefined,
|
||||
sshKey: undefined,
|
||||
fields: [],
|
||||
} as SdkCipherView);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { uuidToString, asUuid } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||
import { CipherView as SdkCipherView } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { View } from "../../../models/view/view";
|
||||
@@ -9,7 +11,7 @@ import { DeepJsonify } from "../../../types/deep-jsonify";
|
||||
import { CipherType, LinkedIdType } from "../../enums";
|
||||
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
|
||||
import { CipherPermissionsApi } from "../api/cipher-permissions.api";
|
||||
import { LocalData } from "../data/local.data";
|
||||
import { LocalData, toSdkLocalData, fromSdkLocalData } from "../data/local.data";
|
||||
import { Cipher } from "../domain/cipher";
|
||||
|
||||
import { AttachmentView } from "./attachment.view";
|
||||
@@ -41,14 +43,17 @@ export class CipherView implements View, InitializerMetadata {
|
||||
card = new CardView();
|
||||
secureNote = new SecureNoteView();
|
||||
sshKey = new SshKeyView();
|
||||
attachments: AttachmentView[] = null;
|
||||
fields: FieldView[] = null;
|
||||
passwordHistory: PasswordHistoryView[] = null;
|
||||
attachments: AttachmentView[] = [];
|
||||
fields: FieldView[] = [];
|
||||
passwordHistory: PasswordHistoryView[] = [];
|
||||
collectionIds: string[] = null;
|
||||
revisionDate: Date = null;
|
||||
creationDate: Date = null;
|
||||
deletedDate: Date = null;
|
||||
reprompt: CipherRepromptType = CipherRepromptType.None;
|
||||
// We need a copy of the encrypted key so we can pass it to
|
||||
// the SdkCipherView during encryption
|
||||
key?: EncString;
|
||||
|
||||
/**
|
||||
* Flag to indicate if the cipher decryption failed.
|
||||
@@ -76,6 +81,7 @@ export class CipherView implements View, InitializerMetadata {
|
||||
this.deletedDate = c.deletedDate;
|
||||
// Old locally stored ciphers might have reprompt == null. If so set it to None.
|
||||
this.reprompt = c.reprompt ?? CipherRepromptType.None;
|
||||
this.key = c.key;
|
||||
}
|
||||
|
||||
private get item() {
|
||||
@@ -194,6 +200,18 @@ export class CipherView implements View, InitializerMetadata {
|
||||
const attachments = obj.attachments?.map((a: any) => AttachmentView.fromJSON(a));
|
||||
const fields = obj.fields?.map((f: any) => FieldView.fromJSON(f));
|
||||
const passwordHistory = obj.passwordHistory?.map((ph: any) => PasswordHistoryView.fromJSON(ph));
|
||||
const permissions = CipherPermissionsApi.fromJSON(obj.permissions);
|
||||
let key: EncString | undefined;
|
||||
|
||||
if (obj.key != null) {
|
||||
if (typeof obj.key === "string") {
|
||||
// If the key is a string, we need to parse it as EncString
|
||||
key = EncString.fromJSON(obj.key);
|
||||
} else if ((obj.key as any) instanceof EncString) {
|
||||
// If the key is already an EncString instance, we can use it directly
|
||||
key = obj.key;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(view, obj, {
|
||||
creationDate: creationDate,
|
||||
@@ -202,6 +220,8 @@ export class CipherView implements View, InitializerMetadata {
|
||||
attachments: attachments,
|
||||
fields: fields,
|
||||
passwordHistory: passwordHistory,
|
||||
permissions: permissions,
|
||||
key: key,
|
||||
});
|
||||
|
||||
switch (obj.type) {
|
||||
@@ -236,9 +256,9 @@ export class CipherView implements View, InitializerMetadata {
|
||||
}
|
||||
|
||||
const cipherView = new CipherView();
|
||||
cipherView.id = obj.id ?? null;
|
||||
cipherView.organizationId = obj.organizationId ?? null;
|
||||
cipherView.folderId = obj.folderId ?? null;
|
||||
cipherView.id = uuidToString(obj.id) ?? null;
|
||||
cipherView.organizationId = uuidToString(obj.organizationId) ?? null;
|
||||
cipherView.folderId = uuidToString(obj.folderId) ?? null;
|
||||
cipherView.name = obj.name;
|
||||
cipherView.notes = obj.notes ?? null;
|
||||
cipherView.type = obj.type;
|
||||
@@ -247,26 +267,18 @@ export class CipherView implements View, InitializerMetadata {
|
||||
cipherView.permissions = CipherPermissionsApi.fromSdkCipherPermissions(obj.permissions);
|
||||
cipherView.edit = obj.edit;
|
||||
cipherView.viewPassword = obj.viewPassword;
|
||||
cipherView.localData = obj.localData
|
||||
? {
|
||||
lastUsedDate: obj.localData.lastUsedDate
|
||||
? new Date(obj.localData.lastUsedDate).getTime()
|
||||
: undefined,
|
||||
lastLaunched: obj.localData.lastLaunched
|
||||
? new Date(obj.localData.lastLaunched).getTime()
|
||||
: undefined,
|
||||
}
|
||||
: undefined;
|
||||
cipherView.localData = fromSdkLocalData(obj.localData);
|
||||
cipherView.attachments =
|
||||
obj.attachments?.map((a) => AttachmentView.fromSdkAttachmentView(a)) ?? null;
|
||||
cipherView.fields = obj.fields?.map((f) => FieldView.fromSdkFieldView(f)) ?? null;
|
||||
obj.attachments?.map((a) => AttachmentView.fromSdkAttachmentView(a)) ?? [];
|
||||
cipherView.fields = obj.fields?.map((f) => FieldView.fromSdkFieldView(f)) ?? [];
|
||||
cipherView.passwordHistory =
|
||||
obj.passwordHistory?.map((ph) => PasswordHistoryView.fromSdkPasswordHistoryView(ph)) ?? null;
|
||||
cipherView.collectionIds = obj.collectionIds ?? null;
|
||||
obj.passwordHistory?.map((ph) => PasswordHistoryView.fromSdkPasswordHistoryView(ph)) ?? [];
|
||||
cipherView.collectionIds = obj.collectionIds?.map((i) => uuidToString(i)) ?? [];
|
||||
cipherView.revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate);
|
||||
cipherView.creationDate = obj.creationDate == null ? null : new Date(obj.creationDate);
|
||||
cipherView.deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate);
|
||||
cipherView.reprompt = obj.reprompt ?? CipherRepromptType.None;
|
||||
cipherView.key = EncString.fromJSON(obj.key);
|
||||
|
||||
switch (obj.type) {
|
||||
case CipherType.Card:
|
||||
@@ -290,4 +302,66 @@ export class CipherView implements View, InitializerMetadata {
|
||||
|
||||
return cipherView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps CipherView to SdkCipherView
|
||||
*
|
||||
* @returns {SdkCipherView} The SDK cipher view object
|
||||
*/
|
||||
toSdkCipherView(): SdkCipherView {
|
||||
const sdkCipherView: SdkCipherView = {
|
||||
id: this.id ? asUuid(this.id) : undefined,
|
||||
organizationId: this.organizationId ? asUuid(this.organizationId) : undefined,
|
||||
folderId: this.folderId ? asUuid(this.folderId) : undefined,
|
||||
name: this.name ?? "",
|
||||
notes: this.notes,
|
||||
type: this.type ?? CipherType.Login,
|
||||
favorite: this.favorite,
|
||||
organizationUseTotp: this.organizationUseTotp,
|
||||
permissions: this.permissions?.toSdkCipherPermissions(),
|
||||
edit: this.edit,
|
||||
viewPassword: this.viewPassword,
|
||||
localData: toSdkLocalData(this.localData),
|
||||
attachments: this.attachments?.map((a) => a.toSdkAttachmentView()),
|
||||
fields: this.fields?.map((f) => f.toSdkFieldView()),
|
||||
passwordHistory: this.passwordHistory?.map((ph) => ph.toSdkPasswordHistoryView()),
|
||||
collectionIds: this.collectionIds?.map((i) => i) ?? [],
|
||||
// Revision and creation dates are non-nullable in SDKCipherView
|
||||
revisionDate: (this.revisionDate ?? new Date()).toISOString(),
|
||||
creationDate: (this.creationDate ?? new Date()).toISOString(),
|
||||
deletedDate: this.deletedDate?.toISOString(),
|
||||
reprompt: this.reprompt ?? CipherRepromptType.None,
|
||||
key: this.key?.toJSON(),
|
||||
// Cipher type specific properties are set in the switch statement below
|
||||
// CipherView initializes each with default constructors (undefined values)
|
||||
// The SDK does not expect those undefined values and will throw exceptions
|
||||
login: undefined,
|
||||
card: undefined,
|
||||
identity: undefined,
|
||||
secureNote: undefined,
|
||||
sshKey: undefined,
|
||||
};
|
||||
|
||||
switch (this.type) {
|
||||
case CipherType.Card:
|
||||
sdkCipherView.card = this.card.toSdkCardView();
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
sdkCipherView.identity = this.identity.toSdkIdentityView();
|
||||
break;
|
||||
case CipherType.Login:
|
||||
sdkCipherView.login = this.login.toSdkLoginView();
|
||||
break;
|
||||
case CipherType.SecureNote:
|
||||
sdkCipherView.secureNote = this.secureNote.toSdkSecureNoteView();
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
sdkCipherView.sshKey = this.sshKey.toSdkSshKeyView();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return sdkCipherView;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
// @ts-strict-ignore
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { Fido2CredentialView as SdkFido2CredentialView } from "@bitwarden/sdk-internal";
|
||||
import {
|
||||
Fido2CredentialView as SdkFido2CredentialView,
|
||||
Fido2CredentialFullView,
|
||||
} from "@bitwarden/sdk-internal";
|
||||
|
||||
import { ItemView } from "./item.view";
|
||||
|
||||
@@ -56,4 +59,22 @@ export class Fido2CredentialView extends ItemView {
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
toSdkFido2CredentialFullView(): Fido2CredentialFullView {
|
||||
return {
|
||||
credentialId: this.credentialId,
|
||||
keyType: this.keyType,
|
||||
keyAlgorithm: this.keyAlgorithm,
|
||||
keyCurve: this.keyCurve,
|
||||
keyValue: this.keyValue,
|
||||
rpId: this.rpId,
|
||||
userHandle: this.userHandle,
|
||||
userName: this.userName,
|
||||
counter: this.counter.toString(),
|
||||
rpName: this.rpName,
|
||||
userDisplayName: this.userDisplayName,
|
||||
discoverable: this.discoverable ? "true" : "false",
|
||||
creationDate: this.creationDate?.toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// @ts-strict-ignore
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { FieldView as SdkFieldView } from "@bitwarden/sdk-internal";
|
||||
import { FieldView as SdkFieldView, FieldType as SdkFieldType } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { View } from "../../../models/view/view";
|
||||
import { FieldType, LinkedIdType } from "../../enums";
|
||||
@@ -50,4 +50,16 @@ export class FieldView implements View {
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the FieldView to an SDK FieldView.
|
||||
*/
|
||||
toSdkFieldView(): SdkFieldView {
|
||||
return {
|
||||
name: this.name ?? undefined,
|
||||
value: this.value ?? undefined,
|
||||
type: this.type ?? SdkFieldType.Text,
|
||||
linkedId: this.linkedId ?? undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { linkedFieldOption } from "../../linked-field-option.decorator";
|
||||
|
||||
import { ItemView } from "./item.view";
|
||||
|
||||
export class IdentityView extends ItemView {
|
||||
export class IdentityView extends ItemView implements SdkIdentityView {
|
||||
@linkedFieldOption(LinkedId.Title, { sortPosition: 0 })
|
||||
title: string = null;
|
||||
@linkedFieldOption(LinkedId.MiddleName, { sortPosition: 2 })
|
||||
@@ -192,4 +192,12 @@ export class IdentityView extends ItemView {
|
||||
|
||||
return identityView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the IdentityView to an SDK IdentityView.
|
||||
* The view implements the SdkView so we can safely return `this`
|
||||
*/
|
||||
toSdkIdentityView(): SdkIdentityView {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,15 @@ export class LoginUriView implements View {
|
||||
return view;
|
||||
}
|
||||
|
||||
/** Converts a LoginUriView object to an SDK LoginUriView object. */
|
||||
toSdkLoginUriView(): SdkLoginUriView {
|
||||
return {
|
||||
uri: this.uri ?? undefined,
|
||||
match: this.match ?? undefined,
|
||||
uriChecksum: undefined, // SDK handles uri checksum generation internally
|
||||
};
|
||||
}
|
||||
|
||||
matchesUri(
|
||||
targetUri: string,
|
||||
equivalentDomains: Set<string>,
|
||||
|
||||
@@ -124,10 +124,30 @@ export class LoginView extends ItemView {
|
||||
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
|
||||
loginView.totp = obj.totp ?? null;
|
||||
loginView.autofillOnPageLoad = obj.autofillOnPageLoad ?? null;
|
||||
loginView.uris = obj.uris?.map((uri) => LoginUriView.fromSdkLoginUriView(uri)) || [];
|
||||
loginView.uris =
|
||||
obj.uris
|
||||
?.filter((uri) => uri.uri != null && uri.uri !== "")
|
||||
.map((uri) => LoginUriView.fromSdkLoginUriView(uri)) || [];
|
||||
// FIDO2 credentials are not decrypted here, they remain encrypted
|
||||
loginView.fido2Credentials = null;
|
||||
|
||||
return loginView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the LoginView to an SDK LoginView.
|
||||
*
|
||||
* Note: FIDO2 credentials remain encrypted in the SDK view so they are not included here.
|
||||
*/
|
||||
toSdkLoginView(): SdkLoginView {
|
||||
return {
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
passwordRevisionDate: this.passwordRevisionDate?.toISOString(),
|
||||
totp: this.totp,
|
||||
autofillOnPageLoad: this.autofillOnPageLoad ?? undefined,
|
||||
uris: this.uris?.map((uri) => uri.toSdkLoginUriView()),
|
||||
fido2Credentials: undefined, // FIDO2 credentials are handled separately and remain encrypted
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,4 +33,17 @@ describe("PasswordHistoryView", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("toSdkPasswordHistoryView", () => {
|
||||
it("should return a SdkPasswordHistoryView", () => {
|
||||
const passwordHistoryView = new PasswordHistoryView();
|
||||
passwordHistoryView.password = "password";
|
||||
passwordHistoryView.lastUsedDate = new Date("2023-10-01T00:00:00.000Z");
|
||||
|
||||
expect(passwordHistoryView.toSdkPasswordHistoryView()).toMatchObject({
|
||||
password: "password",
|
||||
lastUsedDate: "2023-10-01T00:00:00.000Z",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,4 +41,14 @@ export class PasswordHistoryView implements View {
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the PasswordHistoryView to an SDK PasswordHistoryView.
|
||||
*/
|
||||
toSdkPasswordHistoryView(): SdkPasswordHistoryView {
|
||||
return {
|
||||
password: this.password ?? "",
|
||||
lastUsedDate: this.lastUsedDate.toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { SecureNote } from "../domain/secure-note";
|
||||
|
||||
import { ItemView } from "./item.view";
|
||||
|
||||
export class SecureNoteView extends ItemView {
|
||||
export class SecureNoteView extends ItemView implements SdkSecureNoteView {
|
||||
type: SecureNoteType = null;
|
||||
|
||||
constructor(n?: SecureNote) {
|
||||
@@ -42,4 +42,12 @@ export class SecureNoteView extends ItemView {
|
||||
|
||||
return secureNoteView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the SecureNoteView to an SDK SecureNoteView.
|
||||
* The view implements the SdkView so we can safely return `this`
|
||||
*/
|
||||
toSdkSecureNoteView(): SdkSecureNoteView {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,4 +63,15 @@ export class SshKeyView extends ItemView {
|
||||
|
||||
return sshKeyView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the SshKeyView to an SDK SshKeyView.
|
||||
*/
|
||||
toSdkSshKeyView(): SdkSshKeyView {
|
||||
return {
|
||||
privateKey: this.privateKey,
|
||||
publicKey: this.publicKey,
|
||||
fingerprint: this.keyFingerprint,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user