mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 09:43:23 +00:00
Fix 1password importer (#217)
* Fix import of 1password csv * 1password is using '\' as a quote escape character. * 1password's csv headers are sometimes capitalized. We want to identify them case insensitively * Change cipher type based on csv type header * Translate 1password data to correct fields * Test identity and credit card import * linter fixes * Do not use node 'fs' module Karma is being used for automated tests so node modules are not available Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
This commit is contained in:
@@ -65,19 +65,21 @@ export abstract class BaseImporter {
|
||||
'ort', 'adresse',
|
||||
];
|
||||
|
||||
protected parseCsvOptions = {
|
||||
encoding: 'UTF-8',
|
||||
skipEmptyLines: false,
|
||||
}
|
||||
|
||||
protected parseXml(data: string): Document {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(data, 'application/xml');
|
||||
return doc != null && doc.querySelector('parsererror') == null ? doc : null;
|
||||
}
|
||||
|
||||
protected parseCsv(data: string, header: boolean): any[] {
|
||||
protected parseCsv(data: string, header: boolean, options: any = {}): any[] {
|
||||
const parseOptions = Object.assign({ header: header }, this.parseCsvOptions, options);
|
||||
data = this.splitNewLine(data).join('\n').trim();
|
||||
const result = papa.parse(data, {
|
||||
header: header,
|
||||
encoding: 'UTF-8',
|
||||
skipEmptyLines: false,
|
||||
});
|
||||
const result = papa.parse(data, parseOptions);
|
||||
if (result.errors != null && result.errors.length > 0) {
|
||||
result.errors.forEach((e) => {
|
||||
if (e.row != null) {
|
||||
|
||||
@@ -4,14 +4,17 @@ import { Importer } from './importer';
|
||||
import { ImportResult } from '../models/domain/importResult';
|
||||
|
||||
import { CipherType } from '../enums/cipherType';
|
||||
import { CardView } from '../models/view';
|
||||
import { CardView, IdentityView } from '../models/view';
|
||||
|
||||
const IgnoredProperties = ['ainfo', 'autosubmit', 'notesplain', 'ps', 'scope', 'tags', 'title', 'uuid'];
|
||||
const IgnoredProperties = ['ainfo', 'autosubmit', 'notesplain', 'ps', 'scope', 'tags', 'title', 'uuid', 'notes'];
|
||||
|
||||
export class OnePasswordWinCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): ImportResult {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, true);
|
||||
const results = this.parseCsv(data, true, {
|
||||
quoteChar: '"',
|
||||
escapeChar: '\\',
|
||||
});
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return result;
|
||||
@@ -24,7 +27,29 @@ export class OnePasswordWinCsvImporter extends BaseImporter implements Importer
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(this.getProp(value, 'title'), '--');
|
||||
cipher.notes = this.getValueOrDefault(this.getProp(value, 'notesPlain'), '') + '\n';
|
||||
|
||||
cipher.notes = this.getValueOrDefault(this.getProp(value, 'notesPlain'), '') + '\n' +
|
||||
this.getValueOrDefault(this.getProp(value, 'notes'), '') + '\n';
|
||||
cipher.notes.trim();
|
||||
|
||||
const onePassType = this.getValueOrDefault(this.getProp(value, 'type'), 'Login')
|
||||
switch (onePassType) {
|
||||
case 'Credit Card':
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
IgnoredProperties.push('type');
|
||||
break;
|
||||
case 'Identity':
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
IgnoredProperties.push('type');
|
||||
break;
|
||||
case 'Login':
|
||||
case 'Secure Note':
|
||||
IgnoredProperties.push('type');
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!this.isNullOrWhitespace(this.getProp(value, 'number')) &&
|
||||
!this.isNullOrWhitespace(this.getProp(value, 'expiry date'))) {
|
||||
@@ -50,30 +75,59 @@ export class OnePasswordWinCsvImporter extends BaseImporter implements Importer
|
||||
const urls = value[property].split(this.newLineRegex);
|
||||
cipher.login.uris = this.makeUriArray(urls);
|
||||
continue;
|
||||
} else if ((lowerProp === 'url')) {
|
||||
if (cipher.login.uris == null) {
|
||||
cipher.login.uris = [];
|
||||
}
|
||||
cipher.login.uris.concat(this.makeUriArray(value[property]));
|
||||
continue;
|
||||
}
|
||||
} else if (cipher.type === CipherType.Card) {
|
||||
if (this.isNullOrWhitespace(cipher.card.number) && lowerProp === 'number') {
|
||||
if (this.isNullOrWhitespace(cipher.card.number) && lowerProp.includes('number')) {
|
||||
cipher.card.number = value[property];
|
||||
cipher.card.brand = this.getCardBrand(this.getProp(value, 'number'));
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.card.code) && lowerProp === 'verification number') {
|
||||
} else if (this.isNullOrWhitespace(cipher.card.code) && lowerProp.includes('verification number')) {
|
||||
cipher.card.code = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.card.cardholderName) && lowerProp === 'cardholder name') {
|
||||
} else if (this.isNullOrWhitespace(cipher.card.cardholderName) && lowerProp.includes('cardholder name')) {
|
||||
cipher.card.cardholderName = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.card.expiration) && lowerProp === 'expiry date' &&
|
||||
value[property].length === 6) {
|
||||
cipher.card.expMonth = (value[property] as string).substr(4, 2);
|
||||
} else if (this.isNullOrWhitespace(cipher.card.expiration) && lowerProp.includes('expiry date') &&
|
||||
value[property].length === 7) {
|
||||
cipher.card.expMonth = (value[property] as string).substr(0, 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);
|
||||
cipher.card.expYear = (value[property] as string).substr(3, 4);
|
||||
continue;
|
||||
} else if (lowerProp === 'type') {
|
||||
} else if (lowerProp === 'type' || lowerProp === 'type(type)') {
|
||||
// Skip since brand was determined from number above
|
||||
continue;
|
||||
}
|
||||
} else if (cipher.type === CipherType.Identity) {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && lowerProp.includes('first name')) {
|
||||
cipher.identity.firstName = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.identity.middleName) && lowerProp.includes('initial')) {
|
||||
cipher.identity.middleName = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.identity.lastName) && lowerProp.includes('last name')) {
|
||||
cipher.identity.lastName = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.identity.username) && lowerProp.includes('username')) {
|
||||
cipher.identity.username = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.identity.company) && lowerProp.includes('company')) {
|
||||
cipher.identity.company = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.identity.phone) && lowerProp.includes('default phone')) {
|
||||
cipher.identity.phone = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.identity.email) && lowerProp.includes('email')) {
|
||||
cipher.identity.email = value[property];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (IgnoredProperties.indexOf(lowerProp) === -1 && !lowerProp.startsWith('section:') &&
|
||||
@@ -81,6 +135,11 @@ export class OnePasswordWinCsvImporter extends BaseImporter implements Importer
|
||||
if (altUsername == null && lowerProp === 'email') {
|
||||
altUsername = value[property];
|
||||
}
|
||||
else if (lowerProp === 'created date' || lowerProp === 'modified date') {
|
||||
const readableDate = new Date(parseInt(value[property], 10) * 1000).toUTCString();
|
||||
this.processKvp(cipher, '1Password ' + property, readableDate);
|
||||
continue;
|
||||
}
|
||||
this.processKvp(cipher, property, value[property]);
|
||||
}
|
||||
}
|
||||
@@ -100,6 +159,10 @@ export class OnePasswordWinCsvImporter extends BaseImporter implements Importer
|
||||
}
|
||||
|
||||
private getProp(obj: any, name: string): any {
|
||||
return obj[name] || obj[name.toUpperCase()];
|
||||
const lowerObj = Object.entries(obj).reduce((agg: any, entry: [string, any]) => {
|
||||
agg[entry[0].toLowerCase()] = entry[1];
|
||||
return agg;
|
||||
}, {});
|
||||
return lowerObj[name.toLowerCase()];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user