From 8ed27eeeeca4250a984d8c8fe6f70744bf1f0da6 Mon Sep 17 00:00:00 2001 From: Robert Wachs Date: Sun, 24 Mar 2019 15:50:49 +0100 Subject: [PATCH] 1password 1pif: import password history (#33) * 1password 1pif import password history * 1password 1pif importer: process windows password history * linter fix --- .../importers/onepassword1PifImporter.spec.ts | 102 ++++++++++++++++++ spec/common/misc/throttle.spec.ts | 2 +- src/importers/onepassword1PifImporter.ts | 17 +++ 3 files changed, 120 insertions(+), 1 deletion(-) diff --git a/spec/common/importers/onepassword1PifImporter.spec.ts b/spec/common/importers/onepassword1PifImporter.spec.ts index 6584ac3769..971e82e6a2 100644 --- a/spec/common/importers/onepassword1PifImporter.spec.ts +++ b/spec/common/importers/onepassword1PifImporter.spec.ts @@ -49,6 +49,12 @@ const TestData: string = '***aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee***\n' + name: 'admin_console', }, ], + passwordHistory: [ + { + value: 'old-password', + time: 1447791421, + }, + ], }, URLs: [ { @@ -61,6 +67,76 @@ const TestData: string = '***aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee***\n' + typeName: 'webforms.WebForm', }); +const WindowsTestData = JSON.stringify({ + category: '001', + created: 1544823719, + hmac: 'NtyBmTTPOb88HV3JUKPx1xl/vcMhac9kvCfe/NtszY0=', + k: 'XC/z20QveYCoV8xQ4tCJZZp/uum77xLkMSMEhlUULndryXgSmkG+VBtkW7AfuerfKc8Rtu43a4Sd078j7XfZTcwUCEKtBECUTDNbEgv4+4hoFVk1EzZgEUy/0bW1Ap+jNLmmdSU9h74+REu6pdxsvQ==', + tx: 1553395669, + updated: 1553395669, + uuid: '528AB076FB5F4FBF960884B8E01619AC', + overview: { + title: 'Google', + URLs: [ + { + u: 'google.com', + }, + ], + url: 'google.com', + ps: 26, + ainfo: 'googluser', + }, + details: { + passwordHistory: [ + { + value: 'oldpass2', + time: 1553394449, + }, + { + value: 'oldpass1', + time: 1553394457, + }, + ], + fields: [ + { + type: 'T', + id: 'username', + name: 'username', + value: 'googluser', + designation: 'username', + }, + { + type: 'P', + id: 'password', + name: 'password', + value: '12345678901', + designation: 'password', + }, + ], + notesPlain: 'This is a note\r\n\r\nline1\r\nline2', + sections: [ + { + title: 'test', + name: '1214FD88CD30405D9EED14BEB4D61B60', + fields: [ + { + k: 'string', + n: '6CC3BD77482D4559A4B8BB2D360F821B', + v: 'fgfg', + t: 'fgggf', + }, + { + k: 'concealed', + n: '5CFE7BCAA1DF4578BBF7EB508959BFF3', + v: 'dfgdfgfdg', + t: 'pwfield', + }, + ], + }, + ], + }, +}); + const IdentityTestData = JSON.stringify({ uuid: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', updatedAt: 1553365894, @@ -393,4 +469,30 @@ describe('1Password 1Pif Importer', () => { // remaining fields as custom fields expect(cipher.fields.length).toEqual(6); }); + + it('should create password history', async () => { + const importer = new Importer(); + const result = importer.parse(TestData); + const cipher = result.ciphers.shift(); + + expect(cipher.passwordHistory.length).toEqual(1); + const ph = cipher.passwordHistory.shift(); + expect(ph.password).toEqual('old-password'); + expect(ph.lastUsedDate.toISOString()).toEqual('2015-11-17T20:17:01.000Z'); + }); + + it('should create password history from windows 1pif format', async () => { + const importer = new Importer(); + const result = importer.parse(WindowsTestData); + const cipher = result.ciphers.shift(); + + expect(cipher.passwordHistory.length).toEqual(2); + let ph = cipher.passwordHistory.shift(); + expect(ph.password).toEqual('oldpass2'); + expect(ph.lastUsedDate.toISOString()).toEqual('2019-03-24T02:27:29.000Z'); + + ph = cipher.passwordHistory.shift(); + expect(ph.password).toEqual('oldpass1'); + expect(ph.lastUsedDate.toISOString()).toEqual('2019-03-24T02:27:37.000Z'); + }); }); diff --git a/spec/common/misc/throttle.spec.ts b/spec/common/misc/throttle.spec.ts index 3e5a988341..f7a273a6f9 100644 --- a/spec/common/misc/throttle.spec.ts +++ b/spec/common/misc/throttle.spec.ts @@ -1,5 +1,5 @@ -import { throttle } from '../../../src/misc/throttle'; import { sequentialize } from '../../../src/misc/sequentialize'; +import { throttle } from '../../../src/misc/throttle'; describe('throttle decorator', () => { it('should call the function once at a time', async () => { diff --git a/src/importers/onepassword1PifImporter.ts b/src/importers/onepassword1PifImporter.ts index c607f5a709..ced4c9104d 100644 --- a/src/importers/onepassword1PifImporter.ts +++ b/src/importers/onepassword1PifImporter.ts @@ -6,6 +6,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 { PasswordHistoryView } from '../models/view/passwordHistoryView'; import { SecureNoteView } from '../models/view/secureNoteView'; import { CipherType } from '../enums/cipherType'; @@ -77,9 +78,22 @@ export class OnePassword1PifImporter extends BaseImporter implements Importer { } }); } + if (item.details.passwordHistory != null) { + this.processPasswordHistory(item.details.passwordHistory, cipher); + } } } + private processPasswordHistory(items: any[], cipher: CipherView) { + cipher.passwordHistory = cipher.passwordHistory || []; + items.forEach((entry: any) => { + const phv = new PasswordHistoryView(); + phv.password = entry.value; + phv.lastUsedDate = new Date(entry.time * 1000); + cipher.passwordHistory.push(phv); + }); + } + private processStandardItem(item: any, cipher: CipherView) { cipher.favorite = item.openContents && item.openContents.faveIndex ? true : false; cipher.name = this.getValueOrDefault(item.title); @@ -128,6 +142,9 @@ export class OnePassword1PifImporter extends BaseImporter implements Importer { } }); } + if (item.secureContents.passwordHistory != null) { + this.processPasswordHistory(item.secureContents.passwordHistory, cipher); + } } }