mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-14 10:26:19 +01:00
[PM-8506] Create importer for csv-export from Netwrix Password Secure (#9446)
* Create an importer for csv-export from Netwrix Password Secure * Wire the new importer into the clients * Add instructions to export from Netwrix Password Secure * Mark method as private * Remove line which disables linting * Add docs to importer --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> Co-authored-by: Matt Bishop <mbishop@bitwarden.com>
This commit is contained in:
parent
cab1156d63
commit
ac0e008e3c
@ -0,0 +1,91 @@
|
|||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
|
||||||
|
import { NetwrixPasswordSecureCsvImporter } from "../src/importers";
|
||||||
|
|
||||||
|
import { credentialsData } from "./test-data/netwrix-csv/login-export.csv";
|
||||||
|
|
||||||
|
describe("Netwrix Password Secure CSV Importer", () => {
|
||||||
|
let importer: NetwrixPasswordSecureCsvImporter;
|
||||||
|
beforeEach(() => {
|
||||||
|
importer = new NetwrixPasswordSecureCsvImporter();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passing invalid data returns false", async () => {
|
||||||
|
const result = await importer.parse("");
|
||||||
|
expect(result != null).toBe(true);
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse login records", async () => {
|
||||||
|
const result = await importer.parse(credentialsData);
|
||||||
|
expect(result != null).toBe(true);
|
||||||
|
|
||||||
|
let cipher = result.ciphers.shift();
|
||||||
|
expect(cipher.name).toEqual("Test Entry 1");
|
||||||
|
expect(cipher.login.username).toEqual("someUser");
|
||||||
|
expect(cipher.login.password).toEqual("somePassword");
|
||||||
|
expect(cipher.login.totp).toEqual("someTOTPSeed");
|
||||||
|
expect(cipher.login.uris.length).toEqual(1);
|
||||||
|
let uriView = cipher.login.uris.shift();
|
||||||
|
expect(uriView.uri).toEqual("https://www.example.com");
|
||||||
|
expect(cipher.notes).toEqual("some note for example.com");
|
||||||
|
|
||||||
|
cipher = result.ciphers.shift();
|
||||||
|
expect(cipher.name).toEqual("Test Entry 2");
|
||||||
|
expect(cipher.login.username).toEqual("jdoe");
|
||||||
|
expect(cipher.login.password).toEqual("})9+Kg2fz_O#W1§H1-<ox>0Zio");
|
||||||
|
expect(cipher.login.totp).toEqual("anotherTOTP");
|
||||||
|
expect(cipher.login.uris.length).toEqual(1);
|
||||||
|
uriView = cipher.login.uris.shift();
|
||||||
|
expect(uriView.uri).toEqual("http://www.123.com");
|
||||||
|
expect(cipher.notes).toEqual("Description123");
|
||||||
|
|
||||||
|
cipher = result.ciphers.shift();
|
||||||
|
expect(cipher.name).toEqual("Test Entry 3");
|
||||||
|
expect(cipher.login.username).toEqual("username");
|
||||||
|
expect(cipher.login.password).toEqual("password");
|
||||||
|
expect(cipher.login.totp).toBeNull();
|
||||||
|
expect(cipher.login.uris.length).toEqual(1);
|
||||||
|
uriView = cipher.login.uris.shift();
|
||||||
|
expect(uriView.uri).toEqual("http://www.internetsite.com");
|
||||||
|
expect(cipher.notes).toEqual("Information");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add any unmapped fields as custom fields", async () => {
|
||||||
|
const result = await importer.parse(credentialsData);
|
||||||
|
expect(result != null).toBe(true);
|
||||||
|
|
||||||
|
const cipher = result.ciphers.shift();
|
||||||
|
expect(cipher.fields.length).toBe(1);
|
||||||
|
const field = cipher.fields.shift();
|
||||||
|
expect(field.name).toEqual("DataTags");
|
||||||
|
expect(field.value).toEqual("tag1, tag2, tag3");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse an item and create a folder", async () => {
|
||||||
|
const result = await importer.parse(credentialsData);
|
||||||
|
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.folders.length).toBe(2);
|
||||||
|
expect(result.folders[0].name).toBe("folderOrCollection1");
|
||||||
|
expect(result.folders[1].name).toBe("folderOrCollection2");
|
||||||
|
expect(result.folderRelationships[0]).toEqual([0, 0]);
|
||||||
|
expect(result.folderRelationships[1]).toEqual([1, 1]);
|
||||||
|
expect(result.folderRelationships[2]).toEqual([2, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse an item and create a collection when importing into an organization", async () => {
|
||||||
|
importer.organizationId = Utils.newGuid();
|
||||||
|
const result = await importer.parse(credentialsData);
|
||||||
|
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.collections.length).toBe(2);
|
||||||
|
expect(result.collections[0].name).toBe("folderOrCollection1");
|
||||||
|
expect(result.collections[1].name).toBe("folderOrCollection2");
|
||||||
|
expect(result.collectionRelationships[0]).toEqual([0, 0]);
|
||||||
|
expect(result.collectionRelationships[1]).toEqual([1, 1]);
|
||||||
|
expect(result.collectionRelationships[2]).toEqual([2, 0]);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,4 @@
|
|||||||
|
export const credentialsData = `"Organisationseinheit";"DataTags";"Beschreibung";"Benutzername";"Passwort";"Internetseite";"Informationen";"One-Time Passwort"
|
||||||
|
"folderOrCollection1";"tag1, tag2, tag3";"Test Entry 1";"someUser";"somePassword";"https://www.example.com";"some note for example.com";"someTOTPSeed"
|
||||||
|
"folderOrCollection2";"tag2";"Test Entry 2";"jdoe";"})9+Kg2fz_O#W1§H1-<ox>0Zio";"www.123.com";"Description123";"anotherTOTP"
|
||||||
|
"folderOrCollection1";"someTag";"Test Entry 3";"username";"password";"www.internetsite.com";"Information";""`;
|
@ -380,6 +380,10 @@
|
|||||||
In the ProtonPass browser extension, go to Settings > Export. Export without PGP
|
In the ProtonPass browser extension, go to Settings > Export. Export without PGP
|
||||||
encryption and save the zip file.
|
encryption and save the zip file.
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<ng-container *ngIf="format === 'netwrixpasswordsecure'">
|
||||||
|
Open the FullClient, go to the Main Menu and select Export. Start the export passwords
|
||||||
|
wizard and follow the instructions to export a CSV file.
|
||||||
|
</ng-container>
|
||||||
</bit-callout>
|
</bit-callout>
|
||||||
<import-lastpass
|
<import-lastpass
|
||||||
*ngIf="showLastPassOptions"
|
*ngIf="showLastPassOptions"
|
||||||
|
@ -27,6 +27,7 @@ export { LogMeOnceCsvImporter } from "./logmeonce-csv-importer";
|
|||||||
export { MeldiumCsvImporter } from "./meldium-csv-importer";
|
export { MeldiumCsvImporter } from "./meldium-csv-importer";
|
||||||
export { MSecureCsvImporter } from "./msecure-csv-importer";
|
export { MSecureCsvImporter } from "./msecure-csv-importer";
|
||||||
export { MykiCsvImporter } from "./myki-csv-importer";
|
export { MykiCsvImporter } from "./myki-csv-importer";
|
||||||
|
export { NetwrixPasswordSecureCsvImporter } from "./netwrix";
|
||||||
export { NordPassCsvImporter } from "./nordpass-csv-importer";
|
export { NordPassCsvImporter } from "./nordpass-csv-importer";
|
||||||
export {
|
export {
|
||||||
OnePassword1PifImporter,
|
OnePassword1PifImporter,
|
||||||
|
1
libs/importer/src/importers/netwrix/index.ts
Normal file
1
libs/importer/src/importers/netwrix/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { NetwrixPasswordSecureCsvImporter } from "./netwrix-passwordsecure-csv-importer";
|
@ -0,0 +1,69 @@
|
|||||||
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
|
import { ImportResult } from "../../models/import-result";
|
||||||
|
import { BaseImporter } from "../base-importer";
|
||||||
|
import { Importer } from "../importer";
|
||||||
|
|
||||||
|
import { LoginRecord } from "./netwrix-passwordsecure-csv-types";
|
||||||
|
|
||||||
|
const _mappedColumns = new Set([
|
||||||
|
"Organisationseinheit",
|
||||||
|
"Informationen",
|
||||||
|
"Beschreibung",
|
||||||
|
"Benutzername",
|
||||||
|
"Passwort",
|
||||||
|
"Internetseite",
|
||||||
|
"One-Time Passwort",
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importer for Netwrix Password Secure CSV files.
|
||||||
|
* @see https://www.netwrix.com/enterprise_password_management_software.html
|
||||||
|
*/
|
||||||
|
export class NetwrixPasswordSecureCsvImporter extends BaseImporter implements Importer {
|
||||||
|
parse(data: string): Promise<ImportResult> {
|
||||||
|
const result = new ImportResult();
|
||||||
|
const results = this.parseCsv(data, true);
|
||||||
|
if (results == null) {
|
||||||
|
result.success = false;
|
||||||
|
return Promise.resolve(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
results.forEach((row: LoginRecord) => {
|
||||||
|
this.processFolder(result, row.Organisationseinheit);
|
||||||
|
const cipher = this.initLoginCipher();
|
||||||
|
|
||||||
|
const notes = this.getValueOrDefault(row.Informationen);
|
||||||
|
if (notes) {
|
||||||
|
cipher.notes = `${notes}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher.name = this.getValueOrDefault(row.Beschreibung, "--");
|
||||||
|
cipher.login.username = this.getValueOrDefault(row.Benutzername);
|
||||||
|
cipher.login.password = this.getValueOrDefault(row.Passwort);
|
||||||
|
cipher.login.uris = this.makeUriArray(row.Internetseite);
|
||||||
|
|
||||||
|
cipher.login.totp = this.getValueOrDefault(row["One-Time Passwort"]);
|
||||||
|
|
||||||
|
this.importUnmappedFields(cipher, row, _mappedColumns);
|
||||||
|
|
||||||
|
this.cleanupCipher(cipher);
|
||||||
|
result.ciphers.push(cipher);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.organization) {
|
||||||
|
this.moveFoldersToCollections(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.success = true;
|
||||||
|
return Promise.resolve(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private importUnmappedFields(cipher: CipherView, row: any, mappedValues: Set<string>) {
|
||||||
|
const unmappedFields = Object.keys(row).filter((x) => !mappedValues.has(x));
|
||||||
|
unmappedFields.forEach((key) => {
|
||||||
|
const item = row as any;
|
||||||
|
this.processKvp(cipher, key, item[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
export class LoginRecord {
|
||||||
|
/** Organization unit / folder / collection */
|
||||||
|
Organisationseinheit: string;
|
||||||
|
/** Tags? */
|
||||||
|
DataTags: string;
|
||||||
|
/** Description/title */
|
||||||
|
Beschreibung: string;
|
||||||
|
/** Username */
|
||||||
|
Benutzername: string;
|
||||||
|
/** Password */
|
||||||
|
Passwort: string;
|
||||||
|
/** URL */
|
||||||
|
Internetseite: string;
|
||||||
|
/** Notes/additional information */
|
||||||
|
Informationen: string;
|
||||||
|
/** TOTP */
|
||||||
|
"One-Time Passwort": string;
|
||||||
|
}
|
@ -70,6 +70,7 @@ export const regularImportOptions = [
|
|||||||
{ id: "nordpasscsv", name: "Nordpass (csv)" },
|
{ id: "nordpasscsv", name: "Nordpass (csv)" },
|
||||||
{ id: "psonojson", name: "Psono (json)" },
|
{ id: "psonojson", name: "Psono (json)" },
|
||||||
{ id: "passkyjson", name: "Passky (json)" },
|
{ id: "passkyjson", name: "Passky (json)" },
|
||||||
|
{ id: "netwrixpasswordsecure", name: "Netwrix Password Secure (csv)" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type ImportType =
|
export type ImportType =
|
||||||
|
@ -54,6 +54,7 @@ import {
|
|||||||
MSecureCsvImporter,
|
MSecureCsvImporter,
|
||||||
MeldiumCsvImporter,
|
MeldiumCsvImporter,
|
||||||
MykiCsvImporter,
|
MykiCsvImporter,
|
||||||
|
NetwrixPasswordSecureCsvImporter,
|
||||||
NordPassCsvImporter,
|
NordPassCsvImporter,
|
||||||
OnePassword1PifImporter,
|
OnePassword1PifImporter,
|
||||||
OnePassword1PuxImporter,
|
OnePassword1PuxImporter,
|
||||||
@ -335,6 +336,8 @@ export class ImportService implements ImportServiceAbstraction {
|
|||||||
return new PasskyJsonImporter();
|
return new PasskyJsonImporter();
|
||||||
case "protonpass":
|
case "protonpass":
|
||||||
return new ProtonPassJsonImporter(this.i18nService);
|
return new ProtonPassJsonImporter(this.i18nService);
|
||||||
|
case "netwrixpasswordsecure":
|
||||||
|
return new NetwrixPasswordSecureCsvImporter();
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user