diff --git a/libs/importer/src/importers/roboform-csv-importer.spec.ts b/libs/importer/src/importers/roboform-csv-importer.spec.ts index 23604042a02..65221669154 100644 --- a/libs/importer/src/importers/roboform-csv-importer.spec.ts +++ b/libs/importer/src/importers/roboform-csv-importer.spec.ts @@ -16,7 +16,7 @@ describe("Roboform CSV Importer", () => { expect(result != null).toBe(true); expect(result.folders.length).toBe(0); - expect(result.ciphers.length).toBe(5); + expect(result.ciphers.length).toBe(4); expect(result.ciphers[0].name).toBe("Bitwarden"); expect(result.ciphers[0].login.username).toBe("user@bitwarden.com"); expect(result.ciphers[0].login.password).toBe("password"); @@ -31,13 +31,32 @@ describe("Roboform CSV Importer", () => { expect(result.ciphers.length).toBe(5); }); + it("should parse CSV data totp", async () => { + const importer = new RoboFormCsvImporter(); + const result = await importer.parse(dataNoFolder); + expect(result != null).toBe(true); + + expect(result.ciphers[2].login.totp).toBe("totpKeyValue"); + }); + + it("should parse CSV data custom fields", async () => { + const importer = new RoboFormCsvImporter(); + const result = await importer.parse(dataNoFolder); + expect(result != null).toBe(true); + + expect(result.ciphers[1].fields[0].name).toBe("Custom Field 1"); + expect(result.ciphers[1].fields[0].value).toBe("Custom Field 1 Value"); + expect(result.ciphers[1].fields[1].name).toBe("Custom Field 2"); + expect(result.ciphers[1].fields[1].value).toBe("Custom Field 2 Value"); + }); + it("should parse CSV data secure note", async () => { const importer = new RoboFormCsvImporter(); const result = await importer.parse(dataNoFolder); expect(result != null).toBe(true); - expect(result.ciphers[4].type).toBe(CipherType.SecureNote); - expect(result.ciphers[4].notes).toBe("This is a safe note"); - expect(result.ciphers[4].name).toBe("note - 2023-03-31"); + expect(result.ciphers[3].type).toBe(CipherType.SecureNote); + expect(result.ciphers[3].notes).toBe("This is a safe note"); + expect(result.ciphers[3].name).toBe("note - 2023-03-31"); }); it("should parse CSV data with folder hierarchy", async () => { diff --git a/libs/importer/src/importers/roboform-csv-importer.ts b/libs/importer/src/importers/roboform-csv-importer.ts index 50e899bebfd..eb8a1ceac6a 100644 --- a/libs/importer/src/importers/roboform-csv-importer.ts +++ b/libs/importer/src/importers/roboform-csv-importer.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { FieldType } from "@bitwarden/common/vault/enums"; + import { ImportResult } from "../models/import-result"; import { BaseImporter } from "./base-importer"; @@ -31,19 +33,9 @@ export class RoboFormCsvImporter extends BaseImporter implements Importer { cipher.login.uris = this.makeUriArray(value.Url); if (!this.isNullOrWhitespace(value.Rf_fields)) { - let fields: string[] = [value.Rf_fields]; - if (value.__parsed_extra != null && value.__parsed_extra.length > 0) { - fields = fields.concat(value.__parsed_extra); - } - fields.forEach((field: string) => { - const parts = field.split(":"); - if (parts.length < 3) { - return; - } - const key = parts[0] === "-no-name-" ? null : parts[0]; - const val = parts.length === 4 && parts[2] === "rck" ? parts[1] : parts[2]; - this.processKvp(cipher, key, val); - }); + this.parseRfFields(cipher, value); + } else if (!this.isNullOrWhitespace(value.RfFieldsV2)) { + this.parseRfFieldsV2(cipher, value); } this.convertToNoteIfNeeded(cipher); @@ -68,4 +60,66 @@ export class RoboFormCsvImporter extends BaseImporter implements Importer { result.success = true; return Promise.resolve(result); } + + private parseRfFields(cipher: any, value: any): void { + let fields: string[] = [value.Rf_fields]; + + if (value.__parsed_extra != null && value.__parsed_extra.length > 0) { + fields = fields.concat(value.__parsed_extra); + } + + fields.forEach((field: string) => { + const parts = field.split(":"); + if (parts.length < 3) { + return; + } + const key = parts[0] === "-no-name-" ? null : parts[0]; + const val = parts.length === 4 && parts[2] === "rck" ? parts[1] : parts[2]; + this.processKvp(cipher, key, val); + }); + } + + private parseRfFieldsV2(cipher: any, value: any): void { + let fields: string[] = [value.RfFieldsV2]; + if (value.__parsed_extra != null && value.__parsed_extra.length > 0) { + fields = fields.concat(value.__parsed_extra); + } + + let userIdCount = 1; + let passwordCount = 1; + + fields.forEach((field: string) => { + const parts = field.split(","); + if (parts.length < 5) { + return; + } + + const key = parts[0] === "-no-name-" ? null : parts[0]; + const type = parts[3] === "pwd" ? FieldType.Hidden : FieldType.Text; + const val = parts[4]; + + if (key === "TOTP KEY$") { + cipher.login.totp = val; + return; + } + + // Skip if value matches login fields + if (key === "User ID$" && val === cipher.login.username) { + return; + } + if (key === "Password$" && val === cipher.login.password) { + return; + } + + // Index any extra User IDs or Passwords + let displayKey = key; + if (key === "User ID$") { + displayKey = `Alternate User ID ${userIdCount++}`; + } else if (key === "Password$") { + displayKey = `Alternate Password ${passwordCount++}`; + } + + this.processKvp(cipher, displayKey, val, type); + }); + } } diff --git a/libs/importer/src/importers/spec-data/roboform-csv/empty-folders.ts b/libs/importer/src/importers/spec-data/roboform-csv/empty-folders.ts index 0c90f68470d..17ddac4ac8b 100644 --- a/libs/importer/src/importers/spec-data/roboform-csv/empty-folders.ts +++ b/libs/importer/src/importers/spec-data/roboform-csv/empty-folders.ts @@ -1,6 +1,5 @@ export const data = `Name,Url,MatchUrl,Login,Pwd,Note,Folder,RfFieldsV2 Bitwarden,https://bitwarden.com,https://bitwarden.com,user@bitwarden.com,password,,,"User ID$,,,txt,user@bitwarden.com","Password$,,,pwd,password" -Test,https://www.test.com/,https://www.test.com/,test@gmail.com,:testPassword,test,,"User ID$,,,txt,test@gmail.com","Password$,,,pwd,:testPassword" -LoginWebsite,https://login.Website.com/,https://login.Website.com/,test@outlook.com,123password,,,"User ID$,,,txt,test@outlook.com","Password$,,,pwd,123password" -Website,https://signin.website.com/,https://signin.website.com/,user@bitwarden.com,password123,Website ,,"User ID$,,,txt,user@bitwarden.com","Password$,,,pwd,password123" +Customfields,https://www.customfields.com,https://www.customfields.com,customfields@gmail.com,customfieldsPassword,,,"User ID$,,,txt,customfields@gmail.com","Password$,,,pwd,customfieldsPassword","Custom Field 1,,,txt,Custom Field 1 Value","Custom Field 2,,,txt,Custom Field 2 Value" +Totpwebsite,https://www.totpwebsite.com,https://www.totpwebsite.com,totp@gmail.com,totpPassword,,,"User ID$,,,txt,totp@gmail.com","Password$,,,pwd,totpPassword","TOTP KEY$,,,txt,totpKeyValue" note - 2023-03-31,,,,,This is a safe note,`;