From eecd774b13106634220c3934207021894199b4f6 Mon Sep 17 00:00:00 2001 From: Reese <3253971+figadore@users.noreply.github.com> Date: Thu, 6 Feb 2020 08:24:18 -0800 Subject: [PATCH] fix lastpass import credit card expiration (#65) * Fix import of expiration date from LastPass Signed-off-by: Felipe Santos * handle empty cc exp from lastpass, add test * check for month/year null/whitespace * check for empty expiration from lp import Co-authored-by: Felipe Santos --- .../importers/lastpassCsvImporter.spec.ts | 136 ++++++++++++++++++ src/importers/lastpassCsvImporter.ts | 22 +++ 2 files changed, 158 insertions(+) create mode 100644 spec/common/importers/lastpassCsvImporter.spec.ts diff --git a/spec/common/importers/lastpassCsvImporter.spec.ts b/spec/common/importers/lastpassCsvImporter.spec.ts new file mode 100644 index 00000000000..d573f0140a6 --- /dev/null +++ b/spec/common/importers/lastpassCsvImporter.spec.ts @@ -0,0 +1,136 @@ +import { LastPassCsvImporter as Importer } from '../../../src/importers/lastpassCsvImporter'; +import { CipherView } from '../../../src/models/view/cipherView'; + +import { Utils } from '../../../src/misc/utils'; + +if (Utils.isNode) { + // Polyfills + // tslint:disable-next-line + const jsdom: any = require('jsdom'); + (global as any).DOMParser = new jsdom.JSDOM().window.DOMParser; +} + +const CipherData = [ + { + title: 'should parse expiration date', + csv: `url,username,password,extra,name,grouping,fav +http://sn,,,"NoteType:Credit Card +Name on Card:John Doe +Type: +Number:1234567812345678 +Security Code:123 +Start Date:October,2017 +Expiration Date:June,2020 +Notes:some text +",Credit-card,,0`, + expected: Object.assign(new CipherView(), { + id: null, + organizationId: null, + folderId: null, + name: 'Credit-card', + notes: 'Start Date: October,2017\nsome text\n', + type: 3, + card: { + cardholderName: 'John Doe', + number: '1234567812345678', + code: '123', + expYear: '2020', + expMonth: '6', + }, + }), + }, + { + title: 'should parse blank card note', + csv: `url,username,password,extra,name,grouping,fav +http://sn,,,"NoteType:Credit Card +Name on Card: +Type: +Number: +Security Code: +Start Date:, +Expiration Date:, +Notes:",empty,,0`, + expected: Object.assign(new CipherView(), { + id: null, + organizationId: null, + folderId: null, + name: 'empty', + notes: `Start Date: ,`, + type: 3, + card: {}, + }), + }, + { + title: 'should parse card expiration date w/ no exp year', + csv: `url,username,password,extra,name,grouping,fav +http://sn,,,"NoteType:Credit Card +Name on Card:John Doe +Type:Visa +Number:1234567887654321 +Security Code:321 +Start Date:, +Expiration Date:January, +Notes:",noyear,,0`, + expected: Object.assign(new CipherView(), { + id: null, + organizationId: null, + folderId: null, + name: 'noyear', + notes: `Type: Visa +Start Date: ,`, + type: 3, + card: { + cardholderName: 'John Doe', + number: '1234567887654321', + code: '321', + expMonth: '1', + }, + }), + }, + { + title: 'should parse card expiration date w/ no month', + csv: `url,username,password,extra,name,grouping,fav +http://sn,,,"NoteType:Credit Card +Name on Card:John Doe +Type:Mastercard +Number:8765432112345678 +Security Code:987 +Start Date:, +Expiration Date:,2020 +Notes:",nomonth,,0`, + expected: Object.assign(new CipherView(), { + id: null, + organizationId: null, + folderId: null, + name: 'nomonth', + notes: `Type: Mastercard +Start Date: ,`, + type: 3, + card: { + cardholderName: 'John Doe', + number: '8765432112345678', + code: '987', + expYear: '2020', + }, + }), + }, +]; + +describe('Lastpass CSV Importer', () => { + CipherData.forEach((data) => { + it(data.title, async () => { + const importer = new Importer(); + const result = importer.parse(data.csv); + expect(result != null).toBe(true); + expect(result.ciphers.length).toBeGreaterThan(0); + + const cipher = result.ciphers.shift(); + for (const property in data.expected) { + if (data.expected.hasOwnProperty(property)) { + expect(cipher.hasOwnProperty(property)).toBe(true); + expect(cipher[property]).toEqual(data.expected[property]); + } + } + }); + }); +}); diff --git a/src/importers/lastpassCsvImporter.ts b/src/importers/lastpassCsvImporter.ts index a068d6349a8..e9c5aac3b55 100644 --- a/src/importers/lastpassCsvImporter.ts +++ b/src/importers/lastpassCsvImporter.ts @@ -170,7 +170,29 @@ export class LastPassCsvImporter extends BaseImporter implements Importer { 'Number': 'number', 'Name on Card': 'cardholderName', 'Security Code': 'code', + // LP provides date in a format like 'June,2020' + // Store in expMonth, then parse and modify + 'Expiration Date': 'expMonth', }); + + const exp = mappedData[0].expMonth; + if (exp === ',' || this.isNullOrWhitespace(exp)) { + // No expiration data + delete mappedData[0].expMonth; + } else { + const [monthString, year] = exp.split(','); + // Parse month name into number + if (!this.isNullOrWhitespace(monthString)) { + const month = new Date(Date.parse(monthString + '1, 2012')).getMonth() + 1; + mappedData[0].expMonth = month.toString(); + } else { + delete mappedData[0].expMonth; + } + if (!this.isNullOrWhitespace(year)) { + mappedData[0].expYear = year; + } + } + cipher.type = CipherType.Card; cipher.card = mappedData[0]; cipher.notes = mappedData[1];