mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-29 12:55:21 +01:00
[PM-10814] Fix nordpass importer (#10491)
* import additional_urls from nordpass csv * use type column of nordpass csv to get type of record * fixed wrong naming of nordpass csv type * impot custom fields from nordpass csv * fix parse nordpass custom_fields * fixed parsing of additional_urls in nordpass import * update nordpass csv importer tests * Capitalize type names * Add test for OrgImport/CollectionCreation and fix Org-import * Add test to verify success equals false when parsing fails. * use "Text" as default FieldType of nordpass custom fields * implemented seperated test for additional fields --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
parent
3a31eb2f10
commit
34943ed1fb
@ -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 { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { IdentityView } from "@bitwarden/common/vault/models/view/identity.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 creditCardData } from "./test-data/nordpass-csv/nordpass.card.csv";
|
||||||
import { data as identityData } from "./test-data/nordpass-csv/nordpass.identity.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 loginData } from "./test-data/nordpass-csv/nordpass.login.csv";
|
||||||
import { data as secureNoteData } from "./test-data/nordpass-csv/nordpass.secure-note.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.name).toBe("SomeVaultItemName");
|
||||||
expect(cipher.notes).toBe("Some note for the VaultItem");
|
expect(cipher.notes).toBe("Some note for the VaultItem");
|
||||||
expect(cipher.login.uri).toBe("https://example.com");
|
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.username).toBe("hello@bitwarden.com");
|
||||||
expect(cipher.login.password).toBe("someStrongPassword");
|
expect(cipher.login.password).toBe("someStrongPassword");
|
||||||
}
|
}
|
||||||
@ -107,12 +124,29 @@ function expectSecureNote(cipher: CipherView) {
|
|||||||
expect(cipher.notes).toBe("MySuperSecureNote");
|
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", () => {
|
describe("NordPass CSV Importer", () => {
|
||||||
let importer: NordPassCsvImporter;
|
let importer: NordPassCsvImporter;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
importer = new NordPassCsvImporter();
|
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 () => {
|
it("should parse login records", async () => {
|
||||||
const result = await importer.parse(loginData);
|
const result = await importer.parse(loginData);
|
||||||
|
|
||||||
@ -121,6 +155,17 @@ describe("NordPass CSV Importer", () => {
|
|||||||
expect(result.ciphers.length).toBe(1);
|
expect(result.ciphers.length).toBe(1);
|
||||||
const cipher = result.ciphers[0];
|
const cipher = result.ciphers[0];
|
||||||
expectLogin(cipher);
|
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 () => {
|
it("should parse credit card records", async () => {
|
||||||
@ -178,4 +223,15 @@ describe("NordPass CSV Importer", () => {
|
|||||||
const folder = result.folders[0];
|
const folder = result.folders[0];
|
||||||
expect(folder.name).toBe("notesFolder");
|
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");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -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
|
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,,,,,,,,,`;
|
SomeVisa,,,,,,SomeHolder,4024007103939509,123,01 / 22,12345,,,,,,,,,,credit_card,`;
|
||||||
|
@ -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
|
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`;
|
SomeTitle,,,,,SomeNoteToMyIdentity,,,,,123456,,#fullName,123456789,hello@bitwarden.com,Test street 123,additional addressinfo,Cologne,Germany,North-Rhine-Westphalia,identity,`;
|
||||||
|
@ -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,`;
|
@ -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
|
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,,,,,,,,`;
|
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""}]"`;
|
||||||
|
@ -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
|
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,,,,,,,,,,,,,,,,,,
|
notesFolder,,,,,,,,,,,,,,,,,,,,,
|
||||||
MySuperSecureNoteTitle,,,,MySuperSecureNote,,,,,,notesFolder,,,,,,,,`;
|
MySuperSecureNoteTitle,,,,,MySuperSecureNote,,,,,,notesFolder,,,,,,,,,note,`;
|
||||||
|
@ -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 { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { LoginView } from "@bitwarden/common/vault/models/view/login.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 { BaseImporter } from "./base-importer";
|
||||||
import { Importer } from "./importer";
|
import { Importer } from "./importer";
|
||||||
|
|
||||||
type nodePassCsvParsed = {
|
type NordPassCsvParsed = {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
additional_urls: string;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
note: string;
|
note: string;
|
||||||
@ -28,12 +29,20 @@ type nodePassCsvParsed = {
|
|||||||
city: string;
|
city: string;
|
||||||
country: string;
|
country: string;
|
||||||
state: string;
|
state: string;
|
||||||
|
type: string;
|
||||||
|
custom_fields: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NordPassCustomField = {
|
||||||
|
label: string;
|
||||||
|
type: string;
|
||||||
|
value: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class NordPassCsvImporter extends BaseImporter implements Importer {
|
export class NordPassCsvImporter extends BaseImporter implements Importer {
|
||||||
parse(data: string): Promise<ImportResult> {
|
parse(data: string): Promise<ImportResult> {
|
||||||
const result = new ImportResult();
|
const result = new ImportResult();
|
||||||
const results: nodePassCsvParsed[] = this.parseCsv(data, true);
|
const results: NordPassCsvParsed[] = this.parseCsv(data, true);
|
||||||
if (results == null) {
|
if (results == null) {
|
||||||
result.success = false;
|
result.success = false;
|
||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
@ -45,21 +54,40 @@ export class NordPassCsvImporter extends BaseImporter implements Importer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.organization) {
|
|
||||||
this.processFolder(result, record.folder);
|
this.processFolder(result, record.folder);
|
||||||
}
|
|
||||||
|
|
||||||
const cipher = new CipherView();
|
const cipher = new CipherView();
|
||||||
cipher.name = this.getValueOrDefault(record.name, "--");
|
cipher.name = this.getValueOrDefault(record.name, "--");
|
||||||
cipher.notes = this.getValueOrDefault(record.note);
|
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) {
|
switch (recordType) {
|
||||||
case CipherType.Login:
|
case CipherType.Login:
|
||||||
cipher.type = CipherType.Login;
|
cipher.type = CipherType.Login;
|
||||||
cipher.login = new LoginView();
|
cipher.login = new LoginView();
|
||||||
cipher.login.username = this.getValueOrDefault(record.username);
|
cipher.login.username = this.getValueOrDefault(record.username);
|
||||||
cipher.login.password = this.getValueOrDefault(record.password);
|
cipher.login.password = this.getValueOrDefault(record.password);
|
||||||
|
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);
|
cipher.login.uris = this.makeUriArray(record.url);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case CipherType.Card:
|
case CipherType.Card:
|
||||||
cipher.type = CipherType.Card;
|
cipher.type = CipherType.Card;
|
||||||
@ -106,21 +134,16 @@ export class NordPassCsvImporter extends BaseImporter implements Importer {
|
|||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private evaluateType(record: nodePassCsvParsed): CipherType {
|
private evaluateType(record: NordPassCsvParsed): CipherType {
|
||||||
if (!this.isNullOrWhitespace(record.username)) {
|
switch (record.type) {
|
||||||
|
case "password":
|
||||||
return CipherType.Login;
|
return CipherType.Login;
|
||||||
}
|
case "credit_card":
|
||||||
|
|
||||||
if (!this.isNullOrWhitespace(record.cardnumber)) {
|
|
||||||
return CipherType.Card;
|
return CipherType.Card;
|
||||||
}
|
case "note":
|
||||||
|
|
||||||
if (!this.isNullOrWhitespace(record.full_name)) {
|
|
||||||
return CipherType.Identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isNullOrWhitespace(record.note)) {
|
|
||||||
return CipherType.SecureNote;
|
return CipherType.SecureNote;
|
||||||
|
case "identity":
|
||||||
|
return CipherType.Identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
Loading…
Reference in New Issue
Block a user