From 06e412a095cb4eab41652a4f70ed2788ccec3f2b Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 11 Jul 2018 10:18:25 -0400 Subject: [PATCH] one password importers --- src/importers/baseImporter.ts | 2 +- src/importers/onepassword1PifImporter.ts | 111 +++++++++++++++++++++ src/importers/onepasswordWinCsvImporter.ts | 107 ++++++++++++++++++++ 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 src/importers/onepassword1PifImporter.ts create mode 100644 src/importers/onepasswordWinCsvImporter.ts diff --git a/src/importers/baseImporter.ts b/src/importers/baseImporter.ts index cdef5ef338..21778c240b 100644 --- a/src/importers/baseImporter.ts +++ b/src/importers/baseImporter.ts @@ -275,7 +275,7 @@ export abstract class BaseImporter { if (cipher.notes == null) { cipher.notes = ''; } - cipher.notes += (key + ': ' + value + '\n'); + cipher.notes += (key + ': ' + value.split(this.newLineRegex).join('\n') + '\n'); } else { if (cipher.fields == null) { cipher.fields = []; diff --git a/src/importers/onepassword1PifImporter.ts b/src/importers/onepassword1PifImporter.ts new file mode 100644 index 0000000000..fadf535de1 --- /dev/null +++ b/src/importers/onepassword1PifImporter.ts @@ -0,0 +1,111 @@ +import { BaseImporter } from './baseImporter'; +import { Importer } from './importer'; + +import { ImportResult } from '../models/domain/importResult'; + +import { CardView } from '../models/view/cardView'; +import { CipherView } from '../models/view/cipherView'; +import { SecureNoteView } from '../models/view/secureNoteView'; + +import { CipherType } from '../enums/cipherType'; +import { SecureNoteType } from '../enums/secureNoteType'; + +export class OnePassword1PifImporter extends BaseImporter implements Importer { + result = new ImportResult(); + + parse(data: string): ImportResult { + data.split(this.newLineRegex).forEach((line) => { + if (this.isNullOrWhitespace(line) || line[0] !== '{') { + return; + } + const item = JSON.parse(line); + const cipher = this.initLoginCipher(); + cipher.favorite = item.openContents && item.openContents.faveIndex ? true : false; + cipher.name = this.getValueOrDefault(item.title, '--'); + + if (item.typeName === 'securenotes.SecureNote') { + cipher.type = CipherType.SecureNote; + cipher.secureNote = new SecureNoteView(); + cipher.secureNote.type = SecureNoteType.Generic; + } else if (item.typeName === 'wallet.financial.CreditCard') { + cipher.type = CipherType.Card; + cipher.card = new CardView(); + } else { + cipher.login.uris = this.makeUriArray(item.location); + } + + if (item.secureContents != null) { + if (!this.isNullOrWhitespace(item.secureContents.notesPlain)) { + cipher.notes = item.secureContents.notesPlain.split(this.newLineRegex).join('\n') + '\n'; + } + if (item.secureContents.fields != null) { + this.parseFields(item.secureContents.fields, cipher, 'designation', 'value', 'name'); + } + if (item.secureContents.sections != null) { + item.secureContents.sections.forEach((section) => { + if (section.fields != null) { + this.parseFields(section.fields, cipher, 'n', 'v', 't'); + } + }); + } + } + + this.cleanupCipher(cipher); + this.result.ciphers.push(cipher); + }); + + this.result.success = true; + return this.result; + } + + private parseFields(fields: any[], cipher: CipherView, designationKey: string, valueKey: string, nameKey: string) { + fields.forEach((field: any) => { + if (field[valueKey] == null || field[valueKey].toString().trim() === '') { + return; + } + + const fieldValue = field[valueKey].toString(); + const fieldDesignation = field[designationKey] != null ? field[designationKey].toString() : null; + + if (cipher.type === CipherType.Login) { + if (this.isNullOrWhitespace(cipher.login.username) && fieldDesignation === 'username') { + cipher.login.username = fieldValue; + return; + } else if (this.isNullOrWhitespace(cipher.login.password) && fieldDesignation === 'password') { + cipher.login.password = fieldValue; + return; + } else if (this.isNullOrWhitespace(cipher.login.totp) && fieldDesignation != null && + fieldDesignation.startsWith('TOTP_')) { + cipher.login.totp = fieldValue; + return; + } + } else if (cipher.type === CipherType.Card) { + if (this.isNullOrWhitespace(cipher.card.number) && fieldDesignation === 'ccnum') { + cipher.card.number = fieldValue; + cipher.card.brand = this.getCardBrand(fieldValue); + return; + } else if (this.isNullOrWhitespace(cipher.card.code) && fieldDesignation === 'cvv') { + cipher.card.code = fieldValue; + return; + } else if (this.isNullOrWhitespace(cipher.card.cardholderName) && fieldDesignation === 'cardholder') { + cipher.card.cardholderName = fieldValue; + return; + } else if (this.isNullOrWhitespace(cipher.card.expiration) && fieldDesignation === 'expiry' && + fieldValue.length === 6) { + cipher.card.expMonth = (fieldValue as string).substr(4, 2); + if (cipher.card.expMonth[0] === '0') { + cipher.card.expMonth = cipher.card.expMonth.substr(1, 1); + } + cipher.card.expYear = (fieldValue as string).substr(0, 4); + return; + } else if (fieldDesignation === 'type') { + // Skip since brand was determined from number above + return; + } + } + + const fieldName = this.isNullOrWhitespace(field[nameKey]) ? 'no_name' : field[nameKey]; + this.processKvp(cipher, fieldName, fieldValue); + }); + } +} diff --git a/src/importers/onepasswordWinCsvImporter.ts b/src/importers/onepasswordWinCsvImporter.ts new file mode 100644 index 0000000000..eaed7dd83a --- /dev/null +++ b/src/importers/onepasswordWinCsvImporter.ts @@ -0,0 +1,107 @@ +import { BaseImporter } from './baseImporter'; +import { Importer } from './importer'; + +import { ImportResult } from '../models/domain/importResult'; + +import { SecureNoteView } from '../models/view/secureNoteView'; + +import { CipherType } from '../enums/cipherType'; +import { SecureNoteType } from '../enums/secureNoteType'; +import { CardView } from '../models/view'; + +const IgnoredProperties = ['ainfo', 'autosubmit', 'notesPlain', 'ps', 'scope', 'tags', 'title', 'uuid']; + +export class OnePasswordWinCsvImporter extends BaseImporter implements Importer { + parse(data: string): ImportResult { + const result = new ImportResult(); + const results = this.parseCsv(data, true); + if (results == null) { + result.success = false; + return result; + } + + results.forEach((value) => { + if (this.isNullOrWhitespace(value.title)) { + return; + } + + const cipher = this.initLoginCipher(); + cipher.name = this.getValueOrDefault(value.title, '--'); + cipher.notes = this.getValueOrDefault(value.notesPlain, '') + '\n'; + + if (!this.isNullOrWhitespace(value.number) && !this.isNullOrWhitespace(value['expiry date'])) { + cipher.type = CipherType.Card; + cipher.card = new CardView(); + } + + let altUsername: string = null; + for (const property in value) { + if (!value.hasOwnProperty(property) || this.isNullOrWhitespace(value[property])) { + continue; + } + + if (cipher.type === CipherType.Login) { + if (this.isNullOrWhitespace(cipher.login.password) && property === 'password') { + cipher.login.password = value[property]; + continue; + } else if (this.isNullOrWhitespace(cipher.login.username) && property === 'username') { + cipher.login.username = value[property]; + continue; + } else if ((cipher.login.uris == null || cipher.login.uri.length === 0) && property === 'urls') { + const urls = value[property].split(this.newLineRegex); + cipher.login.uris = this.makeUriArray(urls); + continue; + } + } else if (cipher.type === CipherType.Card) { + if (this.isNullOrWhitespace(cipher.card.number) && property === 'number') { + cipher.card.number = value[property]; + cipher.card.brand = this.getCardBrand(value.number); + continue; + } else if (this.isNullOrWhitespace(cipher.card.code) && property === 'verification number') { + cipher.card.code = value[property]; + continue; + } else if (this.isNullOrWhitespace(cipher.card.cardholderName) && property === 'cardholder name') { + cipher.card.cardholderName = value[property]; + continue; + } else if (this.isNullOrWhitespace(cipher.card.expiration) && property === 'expiry date' && + value[property].length === 6) { + cipher.card.expMonth = (value[property] as string).substr(4, 2); + if (cipher.card.expMonth[0] === '0') { + cipher.card.expMonth = cipher.card.expMonth.substr(1, 1); + } + cipher.card.expYear = (value[property] as string).substr(0, 4); + continue; + } else if (property === 'type') { + // Skip since brand was determined from number above + continue; + } + } + + if (IgnoredProperties.indexOf(property) === -1 && !property.startsWith('section:')) { + if (altUsername == null && property === 'email') { + altUsername = value[property]; + } + this.processKvp(cipher, property, value[property]); + } + } + + if (cipher.type === CipherType.Login && !this.isNullOrWhitespace(altUsername) && + this.isNullOrWhitespace(cipher.login.username) && altUsername.indexOf('://') === -1) { + cipher.login.username = altUsername; + } + if (cipher.type === CipherType.Login && this.isNullOrWhitespace(cipher.login.username) && + this.isNullOrWhitespace(cipher.login.password) && + (cipher.login.uris == null || cipher.login.uris.length === 0)) { + cipher.type = CipherType.SecureNote; + cipher.secureNote = new SecureNoteView(); + cipher.secureNote.type = SecureNoteType.Generic; + } + + this.cleanupCipher(cipher); + result.ciphers.push(cipher); + }); + + result.success = true; + return result; + } +}