From 2bd47a19dfbfeb3a66cc3b24bb285725e90335c0 Mon Sep 17 00:00:00 2001 From: Robert Wachs Date: Sun, 24 Mar 2019 03:21:43 +0100 Subject: [PATCH] 1password 1pif importer: create identity records (#34) * 1password 1pif importer: create identity records * importer: do not store empty strings replace them with null instead --- .../importers/onepassword1PifImporter.spec.ts | 301 ++++++++++++++++++ src/importers/onepassword1PifImporter.ts | 34 ++ 2 files changed, 335 insertions(+) diff --git a/spec/common/importers/onepassword1PifImporter.spec.ts b/spec/common/importers/onepassword1PifImporter.spec.ts index 724be90c5e..1a2d5d7a6f 100644 --- a/spec/common/importers/onepassword1PifImporter.spec.ts +++ b/spec/common/importers/onepassword1PifImporter.spec.ts @@ -61,6 +61,284 @@ const TestData: string = '***aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee***\n' + typeName: 'webforms.WebForm', }); +const IdentityTestData = JSON.stringify({ + uuid: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + updatedAt: 1553365894, + securityLevel: 'SL5', + contentsHash: 'eeeeeeee', + title: 'Test Identity', + secureContents: { + lastname: 'Fritzenberger', + zip: '223344', + birthdate_dd: '11', + homephone: '+49 333 222 111', + company: 'Web Inc.', + firstname: 'Frank', + birthdate_mm: '3', + country: 'de', + sex: 'male', + sections: [ + { + fields: [ + { + k: 'string', + inputTraits: { + autocapitalization: 'Words', + }, + n: 'firstname', + v: 'Frank', + a: { + guarded: 'yes', + }, + t: 'first name', + }, + { + k: 'string', + inputTraits: { + autocapitalization: 'Words', + }, + n: 'initial', + v: 'MD', + a: { + guarded: 'yes', + }, + t: 'initial', + }, + { + k: 'string', + inputTraits: { + autocapitalization: 'Words', + }, + n: 'lastname', + v: 'Fritzenberger', + a: { + guarded: 'yes', + }, + t: 'last name', + }, + { + k: 'menu', + v: 'male', + n: 'sex', + a: { + guarded: 'yes', + }, + t: 'sex', + }, + { + k: 'date', + v: 1552305660, + n: 'birthdate', + a: { + guarded: 'yes', + }, + t: 'birth date', + }, + { + k: 'string', + inputTraits: { + autocapitalization: 'Words', + }, + n: 'occupation', + v: 'Engineer', + a: { + guarded: 'yes', + }, + t: 'occupation', + }, + { + k: 'string', + inputTraits: { + autocapitalization: 'Words', + }, + n: 'company', + v: 'Web Inc.', + a: { + guarded: 'yes', + }, + t: 'company', + }, + { + k: 'string', + inputTraits: { + autocapitalization: 'Words', + }, + n: 'department', + v: 'IT', + a: { + guarded: 'yes', + }, + t: 'department', + }, + { + k: 'string', + inputTraits: { + autocapitalization: 'Words', + }, + n: 'jobtitle', + v: 'Developer', + a: { + guarded: 'yes', + }, + t: 'job title', + }, + ], + title: 'Identification', + name: 'name', + }, + { + fields: [ + { + k: 'address', + inputTraits: { + autocapitalization: 'Sentences', + }, + n: 'address', + v: { + street: 'Mainstreet 1', + city: 'Berlin', + country: 'de', + zip: '223344', + }, + a: { + guarded: 'yes', + }, + t: 'address', + }, + { + k: 'phone', + v: '+49 001 222 333 44', + n: 'defphone', + a: { + guarded: 'yes', + }, + t: 'default phone', + }, + { + k: 'phone', + v: '+49 333 222 111', + n: 'homephone', + a: { + guarded: 'yes', + }, + t: 'home', + }, + { + k: 'phone', + n: 'cellphone', + a: { + guarded: 'yes', + }, + t: 'mobile', + }, + { + k: 'phone', + n: 'busphone', + a: { + guarded: 'yes', + }, + t: 'business', + }, + ], + title: 'Address', + name: 'address', + }, + { + fields: [ + { + k: 'string', + n: 'username', + a: { + guarded: 'yes', + }, + t: 'username', + }, + { + k: 'string', + n: 'reminderq', + t: 'reminder question', + }, + { + k: 'string', + n: 'remindera', + t: 'reminder answer', + }, + { + k: 'string', + inputTraits: { + keyboard: 'EmailAddress', + }, + n: 'email', + v: 'test@web.de', + a: { + guarded: 'yes', + }, + t: 'email', + }, + { + k: 'string', + n: 'website', + inputTraits: { + keyboard: 'URL', + }, + t: 'website', + }, + { + k: 'string', + n: 'icq', + t: 'ICQ', + }, + { + k: 'string', + n: 'skype', + t: 'skype', + }, + { + k: 'string', + n: 'aim', + t: 'AOL/AIM', + }, + { + k: 'string', + n: 'yahoo', + t: 'Yahoo', + }, + { + k: 'string', + n: 'msn', + t: 'MSN', + }, + { + k: 'string', + n: 'forumsig', + t: 'forum signature', + }, + ], + title: 'Internet Details', + name: 'internet', + }, + { + title: 'Related Items', + name: 'linked items', + }, + ], + initial: 'MD', + address1: 'Mainstreet 1', + city: 'Berlin', + jobtitle: 'Developer', + occupation: 'Engineer', + department: 'IT', + email: 'test@web.de', + birthdate_yy: '2019', + homephone_local: '+49 333 222 111', + defphone_local: '+49 001 222 333 44', + defphone: '+49 001 222 333 44', + }, + txTimestamp: 1553365894, + createdAt: 1553364679, + typeName: 'identities.Identity', +}); + describe('1Password 1Pif Importer', () => { it('should parse data', async () => { const importer = new Importer(); @@ -92,4 +370,27 @@ describe('1Password 1Pif Importer', () => { expect(field.value).toEqual('console-password-123'); expect(field.type).toEqual(FieldType.Hidden); }); + + it('should create identity records', async () => { + const importer = new Importer(); + const result = importer.parse(IdentityTestData); + expect(result != null).toBe(true); + const cipher = result.ciphers.shift(); + expect(cipher.name).toEqual('Test Identity'); + + const identity = cipher.identity; + expect(identity.firstName).toEqual('Frank'); + expect(identity.middleName).toEqual('MD'); + expect(identity.lastName).toEqual('Fritzenberger'); + expect(identity.company).toEqual('Web Inc.'); + expect(identity.address1).toEqual('Mainstreet 1'); + expect(identity.country).toEqual('de'); + expect(identity.city).toEqual('Berlin'); + expect(identity.postalCode).toEqual('223344'); + expect(identity.phone).toEqual('+49 001 222 333 44'); + expect(identity.email).toEqual('test@web.de'); + + // remaining fields as custom fields + expect(cipher.fields.length).toEqual(6); + }); }); diff --git a/src/importers/onepassword1PifImporter.ts b/src/importers/onepassword1PifImporter.ts index 6c2b4e73a4..e7ed3041e1 100644 --- a/src/importers/onepassword1PifImporter.ts +++ b/src/importers/onepassword1PifImporter.ts @@ -5,6 +5,7 @@ import { ImportResult } from '../models/domain/importResult'; import { CardView } from '../models/view/cardView'; import { CipherView } from '../models/view/cipherView'; +import { IdentityView } from '../models/view/identityView'; import { SecureNoteView } from '../models/view/secureNoteView'; import { CipherType } from '../enums/cipherType'; @@ -86,6 +87,9 @@ export class OnePassword1PifImporter extends BaseImporter implements Importer { } else if (item.typeName === 'wallet.financial.CreditCard') { cipher.type = CipherType.Card; cipher.card = new CardView(); + } else if (item.typeName === 'identities.Identity') { + cipher.type = CipherType.Identity; + cipher.identity = new IdentityView(); } else { cipher.login.uris = this.makeUriArray(item.location); } @@ -167,6 +171,36 @@ export class OnePassword1PifImporter extends BaseImporter implements Importer { // Skip since brand was determined from number above return; } + } else if (cipher.type === CipherType.Identity) { + const identity = cipher.identity; + + if (this.isNullOrWhitespace(identity.firstName) && fieldDesignation === 'firstname') { + identity.firstName = fieldValue; + return; + } else if (this.isNullOrWhitespace(identity.lastName) && fieldDesignation === 'lastname') { + identity.lastName = fieldValue; + return; + } else if (this.isNullOrWhitespace(identity.middleName) && fieldDesignation === 'initial') { + identity.middleName = fieldValue; + return; + } else if (this.isNullOrWhitespace(identity.phone) && fieldDesignation === 'defphone') { + identity.phone = fieldValue; + return; + } else if (this.isNullOrWhitespace(identity.company) && fieldDesignation === 'company') { + identity.company = fieldValue; + return; + } else if (this.isNullOrWhitespace(identity.email) && fieldDesignation === 'email') { + identity.email = fieldValue; + return; + } else if (fieldDesignation === 'address') { + // fieldValue is an object casted into a string, so access the plain value instead + const { street, city, country, zip } = field[valueKey]; + identity.address1 = this.getValueOrDefault(street); + identity.city = this.getValueOrDefault(city); + identity.country = this.getValueOrDefault(country); // lower case iso code + identity.postalCode = this.getValueOrDefault(zip); + return; + } } const fieldType = field.k === 'concealed' ? FieldType.Hidden : FieldType.Text;