From 3b9ef68f4b01036c84321d6bb4c3c14288bf137b Mon Sep 17 00:00:00 2001 From: Daniel James Smith Date: Wed, 6 Apr 2022 17:33:43 +0200 Subject: [PATCH] [EC-142] Fix error during import of 1pux containing new email field format (#758) * Add support for complex email field type * Ensure complex email field type gets imported on identities --- .../importers/onepassword1PuxImporter.spec.ts | 62 +++++++++++ .../testData/onePassword1Pux/Emailfield.ts | 91 ++++++++++++++++ .../onePassword1Pux/EmailfieldOnIdentity.ts | 87 +++++++++++++++ .../EmailfieldOnIdentity_Prefilled.ts | 103 ++++++++++++++++++ .../onePassword1Pux/SanitizedExport.ts | 25 ++++- .../onePassword1Pux/SoftwareLicense.ts | 10 +- .../onepassword1PuxImporter.ts | 32 +++++- .../types/onepassword1PuxImporterTypes.ts | 8 +- 8 files changed, 405 insertions(+), 13 deletions(-) create mode 100644 common/spec/importers/testData/onePassword1Pux/Emailfield.ts create mode 100644 common/spec/importers/testData/onePassword1Pux/EmailfieldOnIdentity.ts create mode 100644 common/spec/importers/testData/onePassword1Pux/EmailfieldOnIdentity_Prefilled.ts diff --git a/common/spec/importers/onepassword1PuxImporter.spec.ts b/common/spec/importers/onepassword1PuxImporter.spec.ts index fcbc9326eb..a56e080a96 100644 --- a/common/spec/importers/onepassword1PuxImporter.spec.ts +++ b/common/spec/importers/onepassword1PuxImporter.spec.ts @@ -11,6 +11,9 @@ import { CreditCardData } from "./testData/onePassword1Pux/CreditCard"; import { DatabaseData } from "./testData/onePassword1Pux/Database"; import { DriversLicenseData } from "./testData/onePassword1Pux/DriversLicense"; import { EmailAccountData } from "./testData/onePassword1Pux/EmailAccount"; +import { EmailFieldData } from "./testData/onePassword1Pux/Emailfield"; +import { EmailFieldOnIdentityData } from "./testData/onePassword1Pux/EmailfieldOnIdentity"; +import { EmailFieldOnIdentityPrefilledData } from "./testData/onePassword1Pux/EmailfieldOnIdentity_Prefilled"; import { IdentityData } from "./testData/onePassword1Pux/IdentityData"; import { LoginData } from "./testData/onePassword1Pux/LoginData"; import { MedicalRecordData } from "./testData/onePassword1Pux/MedicalRecord"; @@ -102,6 +105,25 @@ describe("1Password 1Pux Importer", () => { expect(cipher.fields[1].type).toBe(FieldType.Boolean); }); + it("should add fields of type email as custom fields", async () => { + const importer = new Importer(); + const EmailFieldDataJson = JSON.stringify(EmailFieldData); + const result = await importer.parse(EmailFieldDataJson); + expect(result != null).toBe(true); + + const ciphers = result.ciphers; + expect(ciphers.length).toEqual(1); + const cipher = ciphers.shift(); + + expect(cipher.fields[0].name).toEqual("reg_email"); + expect(cipher.fields[0].value).toEqual("kriddler@nullvalue.test"); + expect(cipher.fields[0].type).toBe(FieldType.Text); + + expect(cipher.fields[1].name).toEqual("provider"); + expect(cipher.fields[1].value).toEqual("myEmailProvider"); + expect(cipher.fields[1].type).toBe(FieldType.Text); + }); + it('should create concealed field as "hidden" type', async () => { const importer = new Importer(); const result = await importer.parse(OnePuxExampleFileJson); @@ -205,6 +227,46 @@ describe("1Password 1Pux Importer", () => { validateCustomField(cipher.fields, "forumsig", "super cool guy"); }); + it("emails fields on identity types should be added to the identity email field", async () => { + const importer = new Importer(); + const EmailFieldOnIdentityDataJson = JSON.stringify(EmailFieldOnIdentityData); + const result = await importer.parse(EmailFieldOnIdentityDataJson); + expect(result != null).toBe(true); + + const ciphers = result.ciphers; + expect(ciphers.length).toEqual(1); + const cipher = ciphers.shift(); + + const identity = cipher.identity; + expect(identity.email).toEqual("gengels@nullvalue.test"); + + expect(cipher.fields[0].name).toEqual("provider"); + expect(cipher.fields[0].value).toEqual("myEmailProvider"); + expect(cipher.fields[0].type).toBe(FieldType.Text); + }); + + it("emails fields on identity types should be added to custom fields if identity.email has been filled", async () => { + const importer = new Importer(); + const EmailFieldOnIdentityPrefilledDataJson = JSON.stringify(EmailFieldOnIdentityPrefilledData); + const result = await importer.parse(EmailFieldOnIdentityPrefilledDataJson); + expect(result != null).toBe(true); + + const ciphers = result.ciphers; + expect(ciphers.length).toEqual(1); + const cipher = ciphers.shift(); + + const identity = cipher.identity; + expect(identity.email).toEqual("gengels@nullvalue.test"); + + expect(cipher.fields[0].name).toEqual("2nd_email"); + expect(cipher.fields[0].value).toEqual("kriddler@nullvalue.test"); + expect(cipher.fields[0].type).toBe(FieldType.Text); + + expect(cipher.fields[1].name).toEqual("provider"); + expect(cipher.fields[1].value).toEqual("myEmailProvider"); + expect(cipher.fields[1].type).toBe(FieldType.Text); + }); + it("should parse category 005 - Password (Legacy)", async () => { const importer = new Importer(); const jsonString = JSON.stringify(PasswordData); diff --git a/common/spec/importers/testData/onePassword1Pux/Emailfield.ts b/common/spec/importers/testData/onePassword1Pux/Emailfield.ts new file mode 100644 index 0000000000..ae2723f397 --- /dev/null +++ b/common/spec/importers/testData/onePassword1Pux/Emailfield.ts @@ -0,0 +1,91 @@ +import { ExportData } from "jslib-common/importers/onepasswordImporters/types/onepassword1PuxImporterTypes"; + +export const EmailFieldData: ExportData = { + accounts: [ + { + attrs: { + accountName: "1Password Customer", + name: "1Password Customer", + avatar: "", + email: "username123123123@gmail.com", + uuid: "TRIZ3XV4JJFRXJ3BARILLTUA6E", + domain: "https://my.1password.com/", + }, + vaults: [ + { + attrs: { + uuid: "pqcgbqjxr4tng2hsqt5ffrgwju", + desc: "Just test entries", + avatar: "ke7i5rxnjrh3tj6uesstcosspu.png", + name: "T's Test Vault", + type: "U", + }, + items: [ + { + uuid: "47hvppiuwbanbza7bq6jpdjfxu", + favIndex: 1, + createdAt: 1619467985, + updatedAt: 1619468230, + trashed: false, + categoryUuid: "100", + details: { + loginFields: [], + notesPlain: "My Software License", + sections: [ + { + title: "", + fields: [], + }, + { + title: "Customer", + name: "customer", + fields: [ + { + title: "registered email", + id: "reg_email", + value: { + email: { + email_address: "kriddler@nullvalue.test", + provider: "myEmailProvider", + }, + }, + indexAtSource: 1, + guarded: false, + multiline: false, + dontGenerate: false, + inputTraits: { + keyboard: "emailAddress", + correction: "default", + capitalization: "default", + }, + }, + ], + }, + { + title: "Publisher", + name: "publisher", + fields: [], + }, + { + title: "Order", + name: "order", + fields: [], + }, + ], + passwordHistory: [], + }, + overview: { + subtitle: "5.10.1000", + title: "Limux Product Key", + url: "", + ps: 0, + pbe: 0.0, + pgrng: false, + }, + }, + ], + }, + ], + }, + ], +}; diff --git a/common/spec/importers/testData/onePassword1Pux/EmailfieldOnIdentity.ts b/common/spec/importers/testData/onePassword1Pux/EmailfieldOnIdentity.ts new file mode 100644 index 0000000000..e33404e384 --- /dev/null +++ b/common/spec/importers/testData/onePassword1Pux/EmailfieldOnIdentity.ts @@ -0,0 +1,87 @@ +import { ExportData } from "jslib-common/importers/onepasswordImporters/types/onepassword1PuxImporterTypes"; + +export const EmailFieldOnIdentityData: ExportData = { + accounts: [ + { + attrs: { + accountName: "1Password Customer", + name: "1Password Customer", + avatar: "", + email: "username123123123@gmail.com", + uuid: "TRIZ3XV4JJFRXJ3BARILLTUA6E", + domain: "https://my.1password.com/", + }, + vaults: [ + { + attrs: { + uuid: "pqcgbqjxr4tng2hsqt5ffrgwju", + desc: "Just test entries", + avatar: "ke7i5rxnjrh3tj6uesstcosspu.png", + name: "T's Test Vault", + type: "U", + }, + items: [ + { + uuid: "45mjttbbq3owgij2uis55pfrlq", + favIndex: 0, + createdAt: 1619465450, + updatedAt: 1619465789, + trashed: false, + categoryUuid: "004", + details: { + loginFields: [], + notesPlain: "", + sections: [ + { + title: "Identification", + name: "name", + fields: [], + }, + { + title: "Address", + name: "address", + fields: [], + }, + { + title: "Internet Details", + name: "internet", + fields: [ + { + title: "E-mail", + id: "E-mail", + value: { + email: { + email_address: "gengels@nullvalue.test", + provider: "myEmailProvider", + }, + }, + indexAtSource: 4, + guarded: false, + multiline: false, + dontGenerate: false, + inputTraits: { + keyboard: "emailAddress", + correction: "default", + capitalization: "default", + }, + }, + ], + }, + ], + passwordHistory: [], + }, + overview: { + subtitle: "George Engels", + title: "George Engels", + url: "", + ps: 0, + pbe: 0.0, + pgrng: false, + }, + }, + ], + }, + ], + }, + ], +}; diff --git a/common/spec/importers/testData/onePassword1Pux/EmailfieldOnIdentity_Prefilled.ts b/common/spec/importers/testData/onePassword1Pux/EmailfieldOnIdentity_Prefilled.ts new file mode 100644 index 0000000000..c383ebc825 --- /dev/null +++ b/common/spec/importers/testData/onePassword1Pux/EmailfieldOnIdentity_Prefilled.ts @@ -0,0 +1,103 @@ +import { ExportData } from "jslib-common/importers/onepasswordImporters/types/onepassword1PuxImporterTypes"; + +export const EmailFieldOnIdentityPrefilledData: ExportData = { + accounts: [ + { + attrs: { + accountName: "1Password Customer", + name: "1Password Customer", + avatar: "", + email: "username123123123@gmail.com", + uuid: "TRIZ3XV4JJFRXJ3BARILLTUA6E", + domain: "https://my.1password.com/", + }, + vaults: [ + { + attrs: { + uuid: "pqcgbqjxr4tng2hsqt5ffrgwju", + desc: "Just test entries", + avatar: "ke7i5rxnjrh3tj6uesstcosspu.png", + name: "T's Test Vault", + type: "U", + }, + items: [ + { + uuid: "45mjttbbq3owgij2uis55pfrlq", + favIndex: 0, + createdAt: 1619465450, + updatedAt: 1619465789, + trashed: false, + categoryUuid: "004", + details: { + loginFields: [], + notesPlain: "", + sections: [ + { + title: "Identification", + name: "name", + fields: [], + }, + { + title: "Address", + name: "address", + fields: [], + }, + { + title: "Internet Details", + name: "internet", + fields: [ + { + title: "email", + id: "email", + value: { + string: "gengels@nullvalue.test", + }, + indexAtSource: 4, + guarded: false, + multiline: false, + dontGenerate: false, + inputTraits: { + keyboard: "emailAddress", + correction: "default", + capitalization: "default", + }, + }, + { + title: "2nd email", + id: "2nd_email", + value: { + email: { + email_address: "kriddler@nullvalue.test", + provider: "myEmailProvider", + }, + }, + indexAtSource: 1, + guarded: false, + multiline: false, + dontGenerate: false, + inputTraits: { + keyboard: "emailAddress", + correction: "default", + capitalization: "default", + }, + }, + ], + }, + ], + passwordHistory: [], + }, + overview: { + subtitle: "George Engels", + title: "George Engels", + url: "", + ps: 0, + pbe: 0.0, + pgrng: false, + }, + }, + ], + }, + ], + }, + ], +}; diff --git a/common/spec/importers/testData/onePassword1Pux/SanitizedExport.ts b/common/spec/importers/testData/onePassword1Pux/SanitizedExport.ts index b11b8eae6a..657b26b557 100644 --- a/common/spec/importers/testData/onePassword1Pux/SanitizedExport.ts +++ b/common/spec/importers/testData/onePassword1Pux/SanitizedExport.ts @@ -344,7 +344,10 @@ export const SanitizedExport: ExportData = { title: "", id: "irpvnshg5kjpkmj5jwy4xxkfom", value: { - email: "plexuser@nullvalue.test", + email: { + email_address: "plexuser@nullvalue.test", + provider: null, + }, }, indexAtSource: 0, guarded: false, @@ -1434,7 +1437,10 @@ export const SanitizedExport: ExportData = { title: "registered email", id: "reg_email", value: { - email: "kriddler@nullvalue.test", + email: { + email_address: "kriddler@nullvalue.test", + provider: null, + }, }, indexAtSource: 1, guarded: false, @@ -1536,7 +1542,10 @@ export const SanitizedExport: ExportData = { title: "support email", id: "support_email", value: { - email: "support@nullvalue.test", + email: { + email_address: "support@nullvalue.test", + provider: null, + }, }, indexAtSource: 4, guarded: false, @@ -4014,7 +4023,10 @@ export const SanitizedExport: ExportData = { title: "registered email", id: "reg_email", value: { - email: "", + email: { + email_address: "", + provider: null, + }, }, indexAtSource: 1, guarded: false, @@ -4116,7 +4128,10 @@ export const SanitizedExport: ExportData = { title: "support email", id: "support_email", value: { - email: "", + email: { + email_address: "", + provider: null, + }, }, indexAtSource: 4, guarded: false, diff --git a/common/spec/importers/testData/onePassword1Pux/SoftwareLicense.ts b/common/spec/importers/testData/onePassword1Pux/SoftwareLicense.ts index d6531cfda5..769aa088f4 100644 --- a/common/spec/importers/testData/onePassword1Pux/SoftwareLicense.ts +++ b/common/spec/importers/testData/onePassword1Pux/SoftwareLicense.ts @@ -93,7 +93,10 @@ export const SoftwareLicenseData: ExportData = { title: "registered email", id: "reg_email", value: { - email: "kriddler@nullvalue.test", + email: { + email_address: "kriddler@nullvalue.test", + provider: null, + }, }, indexAtSource: 1, guarded: false, @@ -195,7 +198,10 @@ export const SoftwareLicenseData: ExportData = { title: "support email", id: "support_email", value: { - email: "support@nullvalue.test", + email: { + email_address: "support@nullvalue.test", + provider: null, + }, }, indexAtSource: 4, guarded: false, diff --git a/common/src/importers/onepasswordImporters/onepassword1PuxImporter.ts b/common/src/importers/onepasswordImporters/onepassword1PuxImporter.ts index 23ca3e09c9..67f4bb2590 100644 --- a/common/src/importers/onepasswordImporters/onepassword1PuxImporter.ts +++ b/common/src/importers/onepasswordImporters/onepassword1PuxImporter.ts @@ -258,7 +258,7 @@ export class OnePassword1PuxImporter extends BaseImporter implements Importer { } } } else if (cipher.type === CipherType.Identity) { - if (this.fillIdentity(field, fieldValue, cipher)) { + if (this.fillIdentity(field, fieldValue, cipher, valueKey)) { return; } if (valueKey === "address") { @@ -312,6 +312,14 @@ export class OnePassword1PuxImporter extends BaseImporter implements Importer { } } + if (valueKey === "email") { + // fieldValue is an object casted into a string, so access the plain value instead + const { email_address, provider } = field.value.email; + this.processKvp(cipher, fieldName, email_address, FieldType.Text); + this.processKvp(cipher, "provider", provider, FieldType.Text); + return; + } + // Do not include a password field if it's already in the history if ( field.title === "password" && @@ -440,7 +448,12 @@ export class OnePassword1PuxImporter extends BaseImporter implements Importer { return false; } - private fillIdentity(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean { + private fillIdentity( + field: FieldsEntity, + fieldValue: string, + cipher: CipherView, + valueKey: string + ): boolean { if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "firstname") { cipher.identity.firstName = fieldValue; return true; @@ -466,9 +479,18 @@ export class OnePassword1PuxImporter extends BaseImporter implements Importer { return true; } - if (this.isNullOrWhitespace(cipher.identity.email) && field.id === "email") { - cipher.identity.email = fieldValue; - return true; + if (this.isNullOrWhitespace(cipher.identity.email)) { + if (valueKey === "email") { + const { email_address, provider } = field.value.email; + cipher.identity.email = this.getValueOrDefault(email_address); + this.processKvp(cipher, "provider", provider, FieldType.Text); + return true; + } + + if (field.id === "email") { + cipher.identity.email = fieldValue; + return true; + } } if (this.isNullOrWhitespace(cipher.identity.username) && field.id === "username") { diff --git a/common/src/importers/onepasswordImporters/types/onepassword1PuxImporterTypes.ts b/common/src/importers/onepasswordImporters/types/onepassword1PuxImporterTypes.ts index 278fcb75dd..46ee06197b 100644 --- a/common/src/importers/onepasswordImporters/types/onepassword1PuxImporterTypes.ts +++ b/common/src/importers/onepasswordImporters/types/onepassword1PuxImporterTypes.ts @@ -106,7 +106,7 @@ export interface Value { date?: number | null; string?: string | null; concealed?: string | null; - email?: string | null; + email?: Email | null; phone?: string | null; menu?: string | null; gender?: string | null; @@ -117,6 +117,12 @@ export interface Value { creditCardNumber?: string | null; reference?: string | null; } + +export interface Email { + email_address: string; + provider: string; +} + export interface Address { street: string; city: string;