From a6308042b6fff28dff97cfde2d8eca081ad6f376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hermann=20K=C3=A4ser?= Date: Wed, 18 Jan 2023 04:19:46 -0500 Subject: [PATCH] [PS-2263] Keeper CSV import: import TOTP to correct field (#4478) * Keeper CSV import: import TOTP to correct field * Fix small issue with notes import Notes field can be null, the ` + "\n"` coerces those to `"null"`. * Adds unit tests --- .../importers/keeper-csv-importer.spec.ts | 77 +++++++++++++++++++ .../test-data/keeper-csv/testdata.csv.ts | 4 + .../importers/keeper/keeper-csv-importer.ts | 13 +++- 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 libs/common/spec/importers/keeper-csv-importer.spec.ts create mode 100644 libs/common/spec/importers/test-data/keeper-csv/testdata.csv.ts diff --git a/libs/common/spec/importers/keeper-csv-importer.spec.ts b/libs/common/spec/importers/keeper-csv-importer.spec.ts new file mode 100644 index 0000000000..13935c8e97 --- /dev/null +++ b/libs/common/spec/importers/keeper-csv-importer.spec.ts @@ -0,0 +1,77 @@ +import { KeeperCsvImporter as Importer } from "@bitwarden/common/importers/keeper/keeper-csv-importer"; + +import { testData as TestData } from "./test-data/keeper-csv/testdata.csv"; + +describe("Keeper CSV Importer", () => { + let importer: Importer; + beforeEach(() => { + importer = new Importer(); + }); + + it("should parse login data", async () => { + const result = await importer.parse(TestData); + expect(result != null).toBe(true); + + const cipher = result.ciphers.shift(); + expect(cipher.name).toEqual("Bar"); + expect(cipher.login.username).toEqual("john.doe@example.com"); + expect(cipher.login.password).toEqual("1234567890abcdef"); + expect(cipher.login.uris.length).toEqual(1); + const uriView = cipher.login.uris.shift(); + expect(uriView.uri).toEqual("https://example.com/"); + expect(cipher.notes).toEqual("These are some notes."); + + const cipher2 = result.ciphers.shift(); + expect(cipher2.name).toEqual("Bar 1"); + expect(cipher2.login.username).toEqual("john.doe1@example.com"); + expect(cipher2.login.password).toEqual("234567890abcdef1"); + expect(cipher2.login.uris.length).toEqual(1); + const uriView2 = cipher2.login.uris.shift(); + expect(uriView2.uri).toEqual("https://an.example.com/"); + expect(cipher2.notes).toBeNull(); + + const cipher3 = result.ciphers.shift(); + expect(cipher3.name).toEqual("Bar 2"); + expect(cipher3.login.username).toEqual("john.doe2@example.com"); + expect(cipher3.login.password).toEqual("34567890abcdef12"); + expect(cipher3.notes).toBeNull(); + expect(cipher3.login.uris.length).toEqual(1); + const uriView3 = cipher3.login.uris.shift(); + expect(uriView3.uri).toEqual("https://another.example.com/"); + }); + + it("should import TOTP when present", async () => { + const result = await importer.parse(TestData); + expect(result != null).toBe(true); + + const cipher = result.ciphers.shift(); + expect(cipher.login.totp).toBeNull(); + + const cipher2 = result.ciphers.shift(); + expect(cipher2.login.totp).toBeNull(); + + const cipher3 = result.ciphers.shift(); + expect(cipher3.login.totp).toEqual( + "otpauth://totp/Amazon:me@company.com?secret=JBSWY3DPEHPK3PXP&issuer=Amazon&algorithm=SHA1&digits=6&period=30" + ); + }); + + it("should parse custom fields", async () => { + const result = await importer.parse(TestData); + expect(result != null).toBe(true); + + const cipher = result.ciphers.shift(); + expect(cipher.fields).toBeNull(); + + const cipher2 = result.ciphers.shift(); + expect(cipher2.fields.length).toBe(2); + expect(cipher2.fields[0].name).toEqual("Account ID"); + expect(cipher2.fields[0].value).toEqual("12345"); + expect(cipher2.fields[1].name).toEqual("Org ID"); + expect(cipher2.fields[1].value).toEqual("54321"); + + const cipher3 = result.ciphers.shift(); + expect(cipher3.fields[0].name).toEqual("Account ID"); + expect(cipher3.fields[0].value).toEqual("23456"); + }); +}); diff --git a/libs/common/spec/importers/test-data/keeper-csv/testdata.csv.ts b/libs/common/spec/importers/test-data/keeper-csv/testdata.csv.ts new file mode 100644 index 0000000000..a40e97ff3f --- /dev/null +++ b/libs/common/spec/importers/test-data/keeper-csv/testdata.csv.ts @@ -0,0 +1,4 @@ +export const testData = `"Foo","Bar","john.doe@example.com","1234567890abcdef","https://example.com/","These are some notes.","" +"Foo","Bar 1","john.doe1@example.com","234567890abcdef1","https://an.example.com/","","","Account ID","12345","Org ID","54321" +"Foo\\Baz","Bar 2","john.doe2@example.com","34567890abcdef12","https://another.example.com/","","","Account ID","23456","TFC:Keeper","otpauth://totp/Amazon:me@company.com?secret=JBSWY3DPEHPK3PXP&issuer=Amazon&algorithm=SHA1&digits=6&period=30" +`; diff --git a/libs/common/src/importers/keeper/keeper-csv-importer.ts b/libs/common/src/importers/keeper/keeper-csv-importer.ts index 54ff4a3d4d..ea082b9909 100644 --- a/libs/common/src/importers/keeper/keeper-csv-importer.ts +++ b/libs/common/src/importers/keeper/keeper-csv-importer.ts @@ -18,7 +18,12 @@ export class KeeperCsvImporter extends BaseImporter implements Importer { this.processFolder(result, value[0]); const cipher = this.initLoginCipher(); - cipher.notes = this.getValueOrDefault(value[5]) + "\n"; + + const notes = this.getValueOrDefault(value[5]); + if (notes) { + cipher.notes = `${notes}\n`; + } + cipher.name = this.getValueOrDefault(value[1], "--"); cipher.login.username = this.getValueOrDefault(value[2]); cipher.login.password = this.getValueOrDefault(value[3]); @@ -27,7 +32,11 @@ export class KeeperCsvImporter extends BaseImporter implements Importer { if (value.length > 7) { // we have some custom fields. for (let i = 7; i < value.length; i = i + 2) { - this.processKvp(cipher, value[i], value[i + 1]); + if (value[i] == "TFC:Keeper") { + cipher.login.totp = value[i + 1]; + } else { + this.processKvp(cipher, value[i], value[i + 1]); + } } }