diff --git a/libs/importer/spec/nordpass-csv-importer.spec.ts b/libs/importer/spec/nordpass-csv-importer.spec.ts index 963e4e847c..a7bbf0fc79 100644 --- a/libs/importer/spec/nordpass-csv-importer.spec.ts +++ b/libs/importer/spec/nordpass-csv-importer.spec.ts @@ -1,4 +1,5 @@ -import { SecureNoteType, CipherType } from "@bitwarden/common/vault/enums"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SecureNoteType, CipherType, FieldType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; @@ -6,6 +7,7 @@ import { NordPassCsvImporter } from "../src/importers"; import { data as creditCardData } from "./test-data/nordpass-csv/nordpass.card.csv"; import { data as identityData } from "./test-data/nordpass-csv/nordpass.identity.csv"; +import { data as loginDataWithAdditionalUrls } from "./test-data/nordpass-csv/nordpass.login-with-additinal-urls.csv"; import { data as loginData } from "./test-data/nordpass-csv/nordpass.login.csv"; import { data as secureNoteData } from "./test-data/nordpass-csv/nordpass.secure-note.csv"; @@ -63,6 +65,21 @@ function expectLogin(cipher: CipherView) { expect(cipher.name).toBe("SomeVaultItemName"); expect(cipher.notes).toBe("Some note for the VaultItem"); expect(cipher.login.uri).toBe("https://example.com"); + expect(cipher.login.uris.length).toBe(1); + expect(cipher.login.uris[0].uri).toBe("https://example.com"); + expect(cipher.login.username).toBe("hello@bitwarden.com"); + expect(cipher.login.password).toBe("someStrongPassword"); +} + +function expectLoginWithAdditionalUrls(cipher: CipherView) { + expect(cipher.type).toBe(CipherType.Login); + + expect(cipher.name).toBe("SomeVaultItemName"); + expect(cipher.notes).toBe("Some note for the VaultItem"); + expect(cipher.login.uri).toBe("https://example.com"); + expect(cipher.login.uris.length).toBe(2); + expect(cipher.login.uris[0].uri).toBe("https://example.com"); + expect(cipher.login.uris[1].uri).toBe("https://example.net"); expect(cipher.login.username).toBe("hello@bitwarden.com"); expect(cipher.login.password).toBe("someStrongPassword"); } @@ -107,12 +124,29 @@ function expectSecureNote(cipher: CipherView) { expect(cipher.notes).toBe("MySuperSecureNote"); } +function expectFields(cipher: CipherView) { + expect(cipher.fields.length).toBe(2); + expect(cipher.fields[0].name).toBe("textLabel"); + expect(cipher.fields[0].value).toBe("text value"); + expect(cipher.fields[0].type).toBe(FieldType.Text); + expect(cipher.fields[1].name).toBe("hiddenLabel"); + expect(cipher.fields[1].value).toBe("hidden value"); + expect(cipher.fields[1].type).toBe(FieldType.Hidden); +} + describe("NordPass CSV Importer", () => { let importer: NordPassCsvImporter; beforeEach(() => { importer = new NordPassCsvImporter(); }); + it("should return false when not able to parse data", async () => { + const result = await importer.parse(""); + + expect(result).not.toBeNull(); + expect(result.success).toBe(false); + }); + it("should parse login records", async () => { const result = await importer.parse(loginData); @@ -121,6 +155,17 @@ describe("NordPass CSV Importer", () => { expect(result.ciphers.length).toBe(1); const cipher = result.ciphers[0]; expectLogin(cipher); + expectFields(cipher); //for custom fields + }); + + it("should parse login records with additinal urls", async () => { + const result = await importer.parse(loginDataWithAdditionalUrls); + + expect(result).not.toBeNull(); + expect(result.success).toBe(true); + expect(result.ciphers.length).toBe(1); + const cipher = result.ciphers[0]; + expectLoginWithAdditionalUrls(cipher); }); it("should parse credit card records", async () => { @@ -178,4 +223,15 @@ describe("NordPass CSV Importer", () => { const folder = result.folders[0]; expect(folder.name).toBe("notesFolder"); }); + + it("should parse an item and create a collection if organizationId is set", async () => { + importer.organizationId = Utils.newGuid(); + const result = await importer.parse(secureNoteData); + + expect(result).not.toBeNull(); + expect(result.success).toBe(true); + expect(result.collections.length).toBe(1); + const collection = result.collections[0]; + expect(collection.name).toBe("notesFolder"); + }); }); diff --git a/libs/importer/spec/test-data/nordpass-csv/nordpass.card.csv.ts b/libs/importer/spec/test-data/nordpass-csv/nordpass.card.csv.ts index 8d79d2b1bf..0141260ce0 100644 --- a/libs/importer/spec/test-data/nordpass-csv/nordpass.card.csv.ts +++ b/libs/importer/spec/test-data/nordpass-csv/nordpass.card.csv.ts @@ -1,2 +1,2 @@ -export const data = `name,url,username,password,note,cardholdername,cardnumber,cvc,expirydate,zipcode,folder,full_name,phone_number,email,address1,address2,city,country,state -SomeVisa,,,,,SomeHolder,4024007103939509,123,01 / 22,12345,,,,,,,,,`; +export const data = `name,url,additional_urls,username,password,note,cardholdername,cardnumber,cvc,expirydate,zipcode,folder,full_name,phone_number,email,address1,address2,city,country,state,type,custom_fields +SomeVisa,,,,,,SomeHolder,4024007103939509,123,01 / 22,12345,,,,,,,,,,credit_card,`; diff --git a/libs/importer/spec/test-data/nordpass-csv/nordpass.identity.csv.ts b/libs/importer/spec/test-data/nordpass-csv/nordpass.identity.csv.ts index 4dde389c72..d1bb481dfb 100644 --- a/libs/importer/spec/test-data/nordpass-csv/nordpass.identity.csv.ts +++ b/libs/importer/spec/test-data/nordpass-csv/nordpass.identity.csv.ts @@ -1,2 +1,2 @@ -export const data = `name,url,username,password,note,cardholdername,cardnumber,cvc,expirydate,zipcode,folder,full_name,phone_number,email,address1,address2,city,country,state -SomeTitle,,,,SomeNoteToMyIdentity,,,,,123456,,#fullName,123456789,hello@bitwarden.com,Test street 123,additional addressinfo,Cologne,Germany,North-Rhine-Westphalia`; +export const data = `name,url,additional_urls,username,password,note,cardholdername,cardnumber,cvc,expirydate,zipcode,folder,full_name,phone_number,email,address1,address2,city,country,state,type,custom_fields +SomeTitle,,,,,SomeNoteToMyIdentity,,,,,123456,,#fullName,123456789,hello@bitwarden.com,Test street 123,additional addressinfo,Cologne,Germany,North-Rhine-Westphalia,identity,`; diff --git a/libs/importer/spec/test-data/nordpass-csv/nordpass.login-with-additinal-urls.csv.ts b/libs/importer/spec/test-data/nordpass-csv/nordpass.login-with-additinal-urls.csv.ts new file mode 100644 index 0000000000..a1052a0fbb --- /dev/null +++ b/libs/importer/spec/test-data/nordpass-csv/nordpass.login-with-additinal-urls.csv.ts @@ -0,0 +1,2 @@ +export const data = `name,url,additional_urls,username,password,note,cardholdername,cardnumber,cvc,expirydate,zipcode,folder,full_name,phone_number,email,address1,address2,city,country,state,type,custom_fields +SomeVaultItemName,https://example.com,"[""https://example.net""]",hello@bitwarden.com,someStrongPassword,Some note for the VaultItem,,,,,,SomeFolderForVaultItem,,,,,,,,,password,`; diff --git a/libs/importer/spec/test-data/nordpass-csv/nordpass.login.csv.ts b/libs/importer/spec/test-data/nordpass-csv/nordpass.login.csv.ts index e2af76b0fb..d6c9f682a1 100644 --- a/libs/importer/spec/test-data/nordpass-csv/nordpass.login.csv.ts +++ b/libs/importer/spec/test-data/nordpass-csv/nordpass.login.csv.ts @@ -1,2 +1,2 @@ -export const data = `name,url,username,password,note,cardholdername,cardnumber,cvc,expirydate,zipcode,folder,full_name,phone_number,email,address1,address2,city,country,state -SomeVaultItemName,https://example.com,hello@bitwarden.com,someStrongPassword,Some note for the VaultItem,,,,,,SomeFolderForVaultItem,,,,,,,,`; +export const data = `name,url,additional_urls,username,password,note,cardholdername,cardnumber,cvc,expirydate,zipcode,folder,full_name,phone_number,email,address1,address2,city,country,state,type,custom_fields +SomeVaultItemName,https://example.com,,hello@bitwarden.com,someStrongPassword,Some note for the VaultItem,,,,,,SomeFolderForVaultItem,,,,,,,,,password,"[{""label"":""textLabel"",""type"":""text"",""value"":""text value""},{""label"":""hiddenLabel"",""type"":""hidden"",""value"":""hidden value""}]"`; diff --git a/libs/importer/spec/test-data/nordpass-csv/nordpass.secure-note.csv.ts b/libs/importer/spec/test-data/nordpass-csv/nordpass.secure-note.csv.ts index 0e4dcb2ef8..1e8866525c 100644 --- a/libs/importer/spec/test-data/nordpass-csv/nordpass.secure-note.csv.ts +++ b/libs/importer/spec/test-data/nordpass-csv/nordpass.secure-note.csv.ts @@ -1,3 +1,3 @@ -export const data = `name,url,username,password,note,cardholdername,cardnumber,cvc,expirydate,zipcode,folder,full_name,phone_number,email,address1,address2,city,country,state -notesFolder,,,,,,,,,,,,,,,,,, -MySuperSecureNoteTitle,,,,MySuperSecureNote,,,,,,notesFolder,,,,,,,,`; +export const data = `name,url,additional_urls,username,password,note,cardholdername,cardnumber,cvc,expirydate,zipcode,folder,full_name,phone_number,email,address1,address2,city,country,state,type,custom_fields +notesFolder,,,,,,,,,,,,,,,,,,,,, +MySuperSecureNoteTitle,,,,,MySuperSecureNote,,,,,,notesFolder,,,,,,,,,note,`; diff --git a/libs/importer/src/importers/nordpass-csv-importer.ts b/libs/importer/src/importers/nordpass-csv-importer.ts index 39685df3d4..a2a3905e41 100644 --- a/libs/importer/src/importers/nordpass-csv-importer.ts +++ b/libs/importer/src/importers/nordpass-csv-importer.ts @@ -1,4 +1,4 @@ -import { SecureNoteType, CipherType } from "@bitwarden/common/vault/enums"; +import { SecureNoteType, CipherType, FieldType } from "@bitwarden/common/vault/enums"; import { CardView } from "@bitwarden/common/vault/models/view/card.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; @@ -8,9 +8,10 @@ import { ImportResult } from "../models/import-result"; import { BaseImporter } from "./base-importer"; import { Importer } from "./importer"; -type nodePassCsvParsed = { +type NordPassCsvParsed = { name: string; url: string; + additional_urls: string; username: string; password: string; note: string; @@ -28,12 +29,20 @@ type nodePassCsvParsed = { city: string; country: string; state: string; + type: string; + custom_fields: string; +}; + +type NordPassCustomField = { + label: string; + type: string; + value: string; }; export class NordPassCsvImporter extends BaseImporter implements Importer { parse(data: string): Promise { const result = new ImportResult(); - const results: nodePassCsvParsed[] = this.parseCsv(data, true); + const results: NordPassCsvParsed[] = this.parseCsv(data, true); if (results == null) { result.success = false; return Promise.resolve(result); @@ -45,21 +54,40 @@ export class NordPassCsvImporter extends BaseImporter implements Importer { return; } - if (!this.organization) { - this.processFolder(result, record.folder); - } + this.processFolder(result, record.folder); const cipher = new CipherView(); cipher.name = this.getValueOrDefault(record.name, "--"); cipher.notes = this.getValueOrDefault(record.note); + if (record.custom_fields) { + const customFieldsParsed: NordPassCustomField[] = JSON.parse(record.custom_fields); + if (customFieldsParsed && customFieldsParsed.length > 0) { + customFieldsParsed.forEach((field) => { + let fieldType = FieldType.Text; + + if (field.type == "hidden") { + fieldType = FieldType.Hidden; + } + + this.processKvp(cipher, field.label, field.value, fieldType); + }); + } + } + switch (recordType) { case CipherType.Login: cipher.type = CipherType.Login; cipher.login = new LoginView(); cipher.login.username = this.getValueOrDefault(record.username); cipher.login.password = this.getValueOrDefault(record.password); - cipher.login.uris = this.makeUriArray(record.url); + if (record.additional_urls) { + const additionalUrlsParsed: string[] = JSON.parse(record.additional_urls); + const uris = [record.url, ...additionalUrlsParsed]; + cipher.login.uris = this.makeUriArray(uris); + } else { + cipher.login.uris = this.makeUriArray(record.url); + } break; case CipherType.Card: cipher.type = CipherType.Card; @@ -106,21 +134,16 @@ export class NordPassCsvImporter extends BaseImporter implements Importer { return Promise.resolve(result); } - private evaluateType(record: nodePassCsvParsed): CipherType { - if (!this.isNullOrWhitespace(record.username)) { - return CipherType.Login; - } - - if (!this.isNullOrWhitespace(record.cardnumber)) { - return CipherType.Card; - } - - if (!this.isNullOrWhitespace(record.full_name)) { - return CipherType.Identity; - } - - if (!this.isNullOrWhitespace(record.note)) { - return CipherType.SecureNote; + private evaluateType(record: NordPassCsvParsed): CipherType { + switch (record.type) { + case "password": + return CipherType.Login; + case "credit_card": + return CipherType.Card; + case "note": + return CipherType.SecureNote; + case "identity": + return CipherType.Identity; } return undefined;