mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 21:33:27 +00:00
[PM-22440] [PM-22114] Parse RfFieldsV2 Roboform Fields (#15099)
* add support for RfFieldsV2 * add unit tests for totp and custom fields * update empty-folders data for new unit tests * ignore User ID$, Password$ and Script$ * refactor: extract parsing logic for Rf_fields and RfFieldsV2 into separate methods and don't ignore User ID$, Password$ or Script$ * Fixed linting issue by executing npm run prettier --------- Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,`;
|
||||
|
||||
Reference in New Issue
Block a user