mirror of
https://github.com/bitwarden/browser.git
synced 2024-09-20 03:01:46 +02:00
[PS-2108] Enpass importer add support for androidurl's (#4314)
* Move and rename importers ater new naming convention Create a subfolder to hold all enpass-importers Change names to new naming convention Fix imports Remove entries from whitelist * Added types for exported enpass json file * Add unit tests to verify for current behaviour * Prefer types over enums * Replace `any` types with defined Enpass types * Add support for parsing Android urls Fixes #2831 Added test-file with several combinations Wrote test cases to verify
This commit is contained in:
parent
3976271d61
commit
ae3edcc34d
133
libs/common/spec/importers/enpass/enpass-json-importer.spec.ts
Normal file
133
libs/common/spec/importers/enpass/enpass-json-importer.spec.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import { CipherType } from "@bitwarden/common/enums/cipherType";
|
||||||
|
import { EnpassJsonImporter as Importer } from "@bitwarden/common/importers/enpass/enpass-json-importer";
|
||||||
|
import { FieldView } from "@bitwarden/common/models/view/field.view";
|
||||||
|
|
||||||
|
import { creditCard } from "./test-data/json/credit-card";
|
||||||
|
import { folders } from "./test-data/json/folders";
|
||||||
|
import { login } from "./test-data/json/login";
|
||||||
|
import { loginAndroidUrl } from "./test-data/json/login-android-url";
|
||||||
|
import { note } from "./test-data/json/note";
|
||||||
|
|
||||||
|
function validateCustomField(fields: FieldView[], fieldName: string, expectedValue: any) {
|
||||||
|
expect(fields).toBeDefined();
|
||||||
|
const customField = fields.find((f) => f.name === fieldName);
|
||||||
|
expect(customField).toBeDefined();
|
||||||
|
|
||||||
|
expect(customField.value).toEqual(expectedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Enpass JSON Importer", () => {
|
||||||
|
it("should create folders/ nested folder and assignment", async () => {
|
||||||
|
const importer = new Importer();
|
||||||
|
const testDataString = JSON.stringify(folders);
|
||||||
|
const result = await importer.parse(testDataString);
|
||||||
|
expect(result != null).toBe(true);
|
||||||
|
|
||||||
|
expect(result.folders.length).toEqual(2);
|
||||||
|
const folder1 = result.folders.shift();
|
||||||
|
expect(folder1.name).toEqual("Social");
|
||||||
|
|
||||||
|
// Created 2 folders and Twitter is child of Social
|
||||||
|
const folder2 = result.folders.shift();
|
||||||
|
expect(folder2.name).toEqual("Social/Twitter");
|
||||||
|
|
||||||
|
// Expect that the single cipher item is assigned to sub-folder "Social/Twitter"
|
||||||
|
const folderRelationship = result.folderRelationships.shift();
|
||||||
|
expect(folderRelationship).toEqual([0, 1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse login items", async () => {
|
||||||
|
const importer = new Importer();
|
||||||
|
const testDataString = JSON.stringify(login);
|
||||||
|
const result = await importer.parse(testDataString);
|
||||||
|
expect(result != null).toBe(true);
|
||||||
|
|
||||||
|
const cipher = result.ciphers.shift();
|
||||||
|
expect(cipher.type).toEqual(CipherType.Login);
|
||||||
|
expect(cipher.name).toEqual("Amazon");
|
||||||
|
expect(cipher.subTitle).toEqual("emily@enpass.io");
|
||||||
|
expect(cipher.favorite).toBe(true);
|
||||||
|
expect(cipher.notes).toEqual("some notes on the login item");
|
||||||
|
|
||||||
|
expect(cipher.login.username).toEqual("emily@enpass.io");
|
||||||
|
expect(cipher.login.password).toEqual("$&W:v@}4\\iRpUXVbjPdPKDGbD<xK>");
|
||||||
|
expect(cipher.login.totp).toEqual("TOTP_SEED_VALUE");
|
||||||
|
expect(cipher.login.uris.length).toEqual(1);
|
||||||
|
const uriView = cipher.login.uris.shift();
|
||||||
|
expect(uriView.uri).toEqual("https://www.amazon.com");
|
||||||
|
|
||||||
|
// remaining fields as custom fields
|
||||||
|
expect(cipher.fields.length).toEqual(3);
|
||||||
|
validateCustomField(cipher.fields, "Phone number", "12345678");
|
||||||
|
validateCustomField(cipher.fields, "Security question", "SECURITY_QUESTION");
|
||||||
|
validateCustomField(cipher.fields, "Security answer", "SECURITY_ANSWER");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse login items with Android Autofill information", async () => {
|
||||||
|
const importer = new Importer();
|
||||||
|
const testDataString = JSON.stringify(loginAndroidUrl);
|
||||||
|
const result = await importer.parse(testDataString);
|
||||||
|
expect(result != null).toBe(true);
|
||||||
|
|
||||||
|
const cipher = result.ciphers.shift();
|
||||||
|
expect(cipher.type).toEqual(CipherType.Login);
|
||||||
|
expect(cipher.name).toEqual("Amazon");
|
||||||
|
|
||||||
|
expect(cipher.login.uris.length).toEqual(5);
|
||||||
|
expect(cipher.login.uris[0].uri).toEqual("https://www.amazon.com");
|
||||||
|
expect(cipher.login.uris[1].uri).toEqual("androidapp://com.amazon.0");
|
||||||
|
expect(cipher.login.uris[2].uri).toEqual("androidapp://com.amazon.1");
|
||||||
|
expect(cipher.login.uris[3].uri).toEqual("androidapp://com.amazon.2");
|
||||||
|
expect(cipher.login.uris[4].uri).toEqual("androidapp://com.amazon.3");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse credit card items", async () => {
|
||||||
|
const importer = new Importer();
|
||||||
|
const testDataString = JSON.stringify(creditCard);
|
||||||
|
const result = await importer.parse(testDataString);
|
||||||
|
expect(result != null).toBe(true);
|
||||||
|
|
||||||
|
const cipher = result.ciphers.shift();
|
||||||
|
expect(cipher.type).toEqual(CipherType.Card);
|
||||||
|
expect(cipher.name).toEqual("Emily Sample Credit Card");
|
||||||
|
expect(cipher.subTitle).toEqual("Amex, *10005");
|
||||||
|
expect(cipher.favorite).toBe(true);
|
||||||
|
expect(cipher.notes).toEqual("some notes on the credit card");
|
||||||
|
|
||||||
|
expect(cipher.card.cardholderName).toEqual("Emily Sample");
|
||||||
|
expect(cipher.card.number).toEqual("3782 822463 10005");
|
||||||
|
expect(cipher.card.brand).toEqual("Amex");
|
||||||
|
expect(cipher.card.code).toEqual("1234");
|
||||||
|
expect(cipher.card.expMonth).toEqual("3");
|
||||||
|
expect(cipher.card.expYear).toEqual("23");
|
||||||
|
|
||||||
|
// remaining fields as custom fields
|
||||||
|
expect(cipher.fields.length).toEqual(9);
|
||||||
|
validateCustomField(cipher.fields, "PIN", "9874");
|
||||||
|
validateCustomField(cipher.fields, "Username", "Emily_ENP");
|
||||||
|
validateCustomField(
|
||||||
|
cipher.fields,
|
||||||
|
"Login password",
|
||||||
|
"nnn tug shoot selfish bon liars convent dusty minnow uncheck"
|
||||||
|
);
|
||||||
|
validateCustomField(cipher.fields, "Website", "http://global.americanexpress.com/");
|
||||||
|
validateCustomField(cipher.fields, "Issuing bank", "American Express");
|
||||||
|
validateCustomField(cipher.fields, "Credit limit", "100000");
|
||||||
|
validateCustomField(cipher.fields, "Withdrawal limit", "50000");
|
||||||
|
validateCustomField(cipher.fields, "Interest rate", "1.5");
|
||||||
|
validateCustomField(cipher.fields, "If lost, call", "12345678");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse notes", async () => {
|
||||||
|
const importer = new Importer();
|
||||||
|
const testDataString = JSON.stringify(note);
|
||||||
|
const result = await importer.parse(testDataString);
|
||||||
|
expect(result != null).toBe(true);
|
||||||
|
|
||||||
|
const cipher = result.ciphers.shift();
|
||||||
|
expect(cipher.type).toEqual(CipherType.SecureNote);
|
||||||
|
expect(cipher.name).toEqual("some secure note title");
|
||||||
|
expect(cipher.favorite).toBe(false);
|
||||||
|
expect(cipher.notes).toEqual("some secure note content");
|
||||||
|
});
|
||||||
|
});
|
274
libs/common/spec/importers/enpass/test-data/json/credit-card.ts
Normal file
274
libs/common/spec/importers/enpass/test-data/json/credit-card.ts
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type";
|
||||||
|
|
||||||
|
export const creditCard: EnpassJsonFile = {
|
||||||
|
folders: [],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
archived: 0,
|
||||||
|
auto_submit: 1,
|
||||||
|
category: "creditcard",
|
||||||
|
createdAt: 1666449561,
|
||||||
|
favorite: 1,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
history: [
|
||||||
|
{
|
||||||
|
updated_at: 1534490234,
|
||||||
|
value: "Wendy Apple Seed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
updated_at: 1535521811,
|
||||||
|
value: "Emma",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
updated_at: 1535522090,
|
||||||
|
value: "Emily",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: "Cardholder",
|
||||||
|
order: 1,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "ccName",
|
||||||
|
uid: 0,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "Emily Sample",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Type",
|
||||||
|
order: 2,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "ccType",
|
||||||
|
uid: 17,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "American Express",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
history: [
|
||||||
|
{
|
||||||
|
updated_at: 1534490234,
|
||||||
|
value: "1234 1234 5678 0000",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: "Number",
|
||||||
|
order: 3,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "ccNumber",
|
||||||
|
uid: 1,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "3782 822463 10005",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "CVC",
|
||||||
|
order: 4,
|
||||||
|
sensitive: 1,
|
||||||
|
type: "ccCvc",
|
||||||
|
uid: 2,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "1234",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "PIN",
|
||||||
|
order: 5,
|
||||||
|
sensitive: 1,
|
||||||
|
type: "ccPin",
|
||||||
|
uid: 3,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "9874",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Expiry date",
|
||||||
|
order: 6,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "ccExpiry",
|
||||||
|
uid: 4,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "03/23",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "INTERNET BANKING",
|
||||||
|
order: 7,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "section",
|
||||||
|
uid: 103,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
history: [
|
||||||
|
{
|
||||||
|
updated_at: 1534490234,
|
||||||
|
value: "WendySeed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
updated_at: 1535521811,
|
||||||
|
value: "Emma1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
updated_at: 1535522182,
|
||||||
|
value: "Emily1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: "Username",
|
||||||
|
order: 8,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "username",
|
||||||
|
uid: 15,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "Emily_ENP",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Login password",
|
||||||
|
order: 9,
|
||||||
|
sensitive: 1,
|
||||||
|
type: "password",
|
||||||
|
uid: 16,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "nnn tug shoot selfish bon liars convent dusty minnow uncheck",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Transaction password",
|
||||||
|
order: 10,
|
||||||
|
sensitive: 1,
|
||||||
|
type: "ccTxnpassword",
|
||||||
|
uid: 9,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Website",
|
||||||
|
order: 11,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "url",
|
||||||
|
uid: 14,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "http://global.americanexpress.com/",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "ADDITIONAL DETAILS",
|
||||||
|
order: 12,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "section",
|
||||||
|
uid: 104,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Issuing bank",
|
||||||
|
order: 13,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "ccBankname",
|
||||||
|
uid: 6,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "American Express",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Issued on",
|
||||||
|
order: 14,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "date",
|
||||||
|
uid: 7,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Valid from",
|
||||||
|
order: 15,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "ccValidfrom",
|
||||||
|
uid: 18,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Credit limit",
|
||||||
|
order: 16,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "numeric",
|
||||||
|
uid: 10,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "100000",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Withdrawal limit",
|
||||||
|
order: 17,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "numeric",
|
||||||
|
uid: 11,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "50000",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Interest rate",
|
||||||
|
order: 18,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "numeric",
|
||||||
|
uid: 12,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "1.5",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "If lost, call",
|
||||||
|
order: 19,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "phone",
|
||||||
|
uid: 8,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "12345678",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
icon: {
|
||||||
|
fav: "global.americanexpress.com",
|
||||||
|
image: {
|
||||||
|
file: "cc/american_express",
|
||||||
|
},
|
||||||
|
type: 2,
|
||||||
|
uuid: "",
|
||||||
|
},
|
||||||
|
note: "some notes on the credit card",
|
||||||
|
subtitle: "***** 0000",
|
||||||
|
template_type: "creditcard.default",
|
||||||
|
title: "Emily Sample Credit Card",
|
||||||
|
trashed: 0,
|
||||||
|
updated_at: 1666554351,
|
||||||
|
uuid: "dbbc741b-81d6-491a-9660-92995fd8958c",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
45
libs/common/spec/importers/enpass/test-data/json/folders.ts
Normal file
45
libs/common/spec/importers/enpass/test-data/json/folders.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type";
|
||||||
|
|
||||||
|
export const folders: EnpassJsonFile = {
|
||||||
|
folders: [
|
||||||
|
{
|
||||||
|
icon: "1008",
|
||||||
|
parent_uuid: "",
|
||||||
|
title: "Social",
|
||||||
|
updated_at: 1666449561,
|
||||||
|
uuid: "7b2ed0da-8cd9-445f-9a1a-490ca2b9ffbc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "1008",
|
||||||
|
parent_uuid: "7b2ed0da-8cd9-445f-9a1a-490ca2b9ffbc",
|
||||||
|
title: "Twitter",
|
||||||
|
updated_at: 1666450857,
|
||||||
|
uuid: "7fe8a8bc-b848-4f9f-9870-c2936317e74d",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
archived: 0,
|
||||||
|
auto_submit: 1,
|
||||||
|
category: "note",
|
||||||
|
createdAt: 1666554621,
|
||||||
|
favorite: 0,
|
||||||
|
folders: ["7fe8a8bc-b848-4f9f-9870-c2936317e74d"],
|
||||||
|
icon: {
|
||||||
|
fav: "",
|
||||||
|
image: {
|
||||||
|
file: "misc/secure_note",
|
||||||
|
},
|
||||||
|
type: 1,
|
||||||
|
uuid: "",
|
||||||
|
},
|
||||||
|
note: "some secure note content",
|
||||||
|
subtitle: "",
|
||||||
|
template_type: "note.default",
|
||||||
|
title: "some secure note title",
|
||||||
|
trashed: 0,
|
||||||
|
updated_at: 1666554621,
|
||||||
|
uuid: "8b5ea2f6-f62b-4fec-a235-4a40946026b6",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
@ -0,0 +1,79 @@
|
|||||||
|
import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type";
|
||||||
|
|
||||||
|
import { login } from "./login";
|
||||||
|
|
||||||
|
export const loginAndroidUrl: EnpassJsonFile = {
|
||||||
|
folders: [],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
archived: 0,
|
||||||
|
auto_submit: 1,
|
||||||
|
category: "login",
|
||||||
|
createdAt: 1666449561,
|
||||||
|
favorite: 1,
|
||||||
|
fields: [
|
||||||
|
...login.items[0].fields,
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Autofill Info",
|
||||||
|
order: 9,
|
||||||
|
sensitive: 0,
|
||||||
|
type: ".Android#",
|
||||||
|
uid: 7696,
|
||||||
|
updated_at: 1666551057,
|
||||||
|
value: "com.amazon.0",
|
||||||
|
value_updated_at: 1666551057,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Autofill Info 1",
|
||||||
|
order: 9,
|
||||||
|
sensitive: 0,
|
||||||
|
type: ".Android#",
|
||||||
|
uid: 7696,
|
||||||
|
updated_at: 1666551057,
|
||||||
|
value:
|
||||||
|
"android://pMUhLBalOhcc3yK-84sMiGc2U856FVVUhm8PZveoRfNFT3ocT1KWZlciAkF2ED--B5i_fMuNlC6JfPxcHk1AQg==@com.amazon.1",
|
||||||
|
value_updated_at: 1666551057,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Autofill Info2 ",
|
||||||
|
order: 9,
|
||||||
|
sensitive: 0,
|
||||||
|
type: ".Android#",
|
||||||
|
uid: 7696,
|
||||||
|
updated_at: 1666551057,
|
||||||
|
value: "android://com.amazon.2",
|
||||||
|
value_updated_at: 1666551057,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Autofill Info 3",
|
||||||
|
order: 9,
|
||||||
|
sensitive: 0,
|
||||||
|
type: ".Android#",
|
||||||
|
uid: 7696,
|
||||||
|
updated_at: 1666551057,
|
||||||
|
value: "androidapp://com.amazon.3",
|
||||||
|
value_updated_at: 1666551057,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
icon: {
|
||||||
|
fav: "www.amazon.com",
|
||||||
|
image: {
|
||||||
|
file: "web/amazon.com",
|
||||||
|
},
|
||||||
|
type: 1,
|
||||||
|
uuid: "",
|
||||||
|
},
|
||||||
|
note: "some notes on the login item",
|
||||||
|
subtitle: "emily@enpass.io",
|
||||||
|
template_type: "login.default",
|
||||||
|
title: "Amazon",
|
||||||
|
trashed: 0,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
uuid: "f717cb7c-6cce-4b24-b023-ec8a429cc992",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
130
libs/common/spec/importers/enpass/test-data/json/login.ts
Normal file
130
libs/common/spec/importers/enpass/test-data/json/login.ts
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type";
|
||||||
|
|
||||||
|
export const login: EnpassJsonFile = {
|
||||||
|
folders: [],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
archived: 0,
|
||||||
|
auto_submit: 1,
|
||||||
|
category: "login",
|
||||||
|
createdAt: 1666449561,
|
||||||
|
favorite: 1,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Username",
|
||||||
|
order: 1,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "username",
|
||||||
|
uid: 10,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "E-mail",
|
||||||
|
order: 2,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "email",
|
||||||
|
uid: 12,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "emily@enpass.io",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Password",
|
||||||
|
order: 3,
|
||||||
|
sensitive: 1,
|
||||||
|
type: "password",
|
||||||
|
uid: 11,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "$&W:v@}4\\iRpUXVbjPdPKDGbD<xK>",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Website",
|
||||||
|
order: 4,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "url",
|
||||||
|
uid: 13,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "https://www.amazon.com",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "ADDITIONAL DETAILS",
|
||||||
|
order: 5,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "section",
|
||||||
|
uid: 101,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Phone number",
|
||||||
|
order: 6,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "phone",
|
||||||
|
uid: 14,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "12345678",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "TOTP",
|
||||||
|
order: 7,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "totp",
|
||||||
|
uid: 102,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "TOTP_SEED_VALUE",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Security question",
|
||||||
|
order: 8,
|
||||||
|
sensitive: 0,
|
||||||
|
type: "text",
|
||||||
|
uid: 15,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "SECURITY_QUESTION",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: 0,
|
||||||
|
label: "Security answer",
|
||||||
|
order: 9,
|
||||||
|
sensitive: 1,
|
||||||
|
type: "text",
|
||||||
|
uid: 16,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
value: "SECURITY_ANSWER",
|
||||||
|
value_updated_at: 1666449561,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
icon: {
|
||||||
|
fav: "www.amazon.com",
|
||||||
|
image: {
|
||||||
|
file: "web/amazon.com",
|
||||||
|
},
|
||||||
|
type: 1,
|
||||||
|
uuid: "",
|
||||||
|
},
|
||||||
|
note: "some notes on the login item",
|
||||||
|
subtitle: "emily@enpass.io",
|
||||||
|
template_type: "login.default",
|
||||||
|
title: "Amazon",
|
||||||
|
trashed: 0,
|
||||||
|
updated_at: 1666449561,
|
||||||
|
uuid: "f717cb7c-6cce-4b24-b023-ec8a429cc992",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
29
libs/common/spec/importers/enpass/test-data/json/note.ts
Normal file
29
libs/common/spec/importers/enpass/test-data/json/note.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type";
|
||||||
|
|
||||||
|
export const note: EnpassJsonFile = {
|
||||||
|
folders: [],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
archived: 0,
|
||||||
|
auto_submit: 1,
|
||||||
|
category: "note",
|
||||||
|
createdAt: 1666554621,
|
||||||
|
favorite: 0,
|
||||||
|
icon: {
|
||||||
|
fav: "",
|
||||||
|
image: {
|
||||||
|
file: "misc/secure_note",
|
||||||
|
},
|
||||||
|
type: 1,
|
||||||
|
uuid: "",
|
||||||
|
},
|
||||||
|
note: "some secure note content",
|
||||||
|
subtitle: "",
|
||||||
|
template_type: "note.default",
|
||||||
|
title: "some secure note title",
|
||||||
|
trashed: 0,
|
||||||
|
updated_at: 1666554621,
|
||||||
|
uuid: "8b5ea2f6-f62b-4fec-a235-4a40946026b6",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
@ -1,11 +1,10 @@
|
|||||||
import { CipherType } from "../enums/cipherType";
|
import { CipherType } from "../../enums/cipherType";
|
||||||
import { SecureNoteType } from "../enums/secureNoteType";
|
import { SecureNoteType } from "../../enums/secureNoteType";
|
||||||
import { ImportResult } from "../models/domain/import-result";
|
import { ImportResult } from "../../models/domain/import-result";
|
||||||
import { CardView } from "../models/view/card.view";
|
import { CardView } from "../../models/view/card.view";
|
||||||
import { SecureNoteView } from "../models/view/secure-note.view";
|
import { SecureNoteView } from "../../models/view/secure-note.view";
|
||||||
|
import { BaseImporter } from "../base-importer";
|
||||||
import { BaseImporter } from "./base-importer";
|
import { Importer } from "../importer";
|
||||||
import { Importer } from "./importer";
|
|
||||||
|
|
||||||
export class EnpassCsvImporter extends BaseImporter implements Importer {
|
export class EnpassCsvImporter extends BaseImporter implements Importer {
|
||||||
parse(data: string): Promise<ImportResult> {
|
parse(data: string): Promise<ImportResult> {
|
@ -1,17 +1,21 @@
|
|||||||
import { CipherType } from "../enums/cipherType";
|
import { CipherType } from "../../enums/cipherType";
|
||||||
import { FieldType } from "../enums/fieldType";
|
import { FieldType } from "../../enums/fieldType";
|
||||||
import { ImportResult } from "../models/domain/import-result";
|
import { ImportResult } from "../../models/domain/import-result";
|
||||||
import { CardView } from "../models/view/card.view";
|
import { CardView } from "../../models/view/card.view";
|
||||||
import { CipherView } from "../models/view/cipher.view";
|
import { CipherView } from "../../models/view/cipher.view";
|
||||||
import { FolderView } from "../models/view/folder.view";
|
import { FolderView } from "../../models/view/folder.view";
|
||||||
|
import { BaseImporter } from "../base-importer";
|
||||||
|
import { Importer } from "../importer";
|
||||||
|
|
||||||
import { BaseImporter } from "./base-importer";
|
import { EnpassJsonFile, EnpassFolder, EnpassField } from "./types/enpass-json-type";
|
||||||
import { Importer } from "./importer";
|
|
||||||
|
type EnpassFolderTreeItem = EnpassFolder & { children: EnpassFolderTreeItem[] };
|
||||||
|
const androidUrlRegex = new RegExp("androidapp://.*==@", "g");
|
||||||
|
|
||||||
export class EnpassJsonImporter extends BaseImporter implements Importer {
|
export class EnpassJsonImporter extends BaseImporter implements Importer {
|
||||||
parse(data: string): Promise<ImportResult> {
|
parse(data: string): Promise<ImportResult> {
|
||||||
const result = new ImportResult();
|
const result = new ImportResult();
|
||||||
const results = JSON.parse(data);
|
const results: EnpassJsonFile = JSON.parse(data);
|
||||||
if (results == null || results.items == null || results.items.length === 0) {
|
if (results == null || results.items == null || results.items.length === 0) {
|
||||||
result.success = false;
|
result.success = false;
|
||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
@ -28,7 +32,7 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
|
|||||||
result.folders.push(f);
|
result.folders.push(f);
|
||||||
});
|
});
|
||||||
|
|
||||||
results.items.forEach((item: any) => {
|
results.items.forEach((item) => {
|
||||||
if (item.folders != null && item.folders.length > 0 && foldersIndexMap.has(item.folders[0])) {
|
if (item.folders != null && item.folders.length > 0 && foldersIndexMap.has(item.folders[0])) {
|
||||||
result.folderRelationships.push([
|
result.folderRelationships.push([
|
||||||
result.ciphers.length,
|
result.ciphers.length,
|
||||||
@ -50,7 +54,7 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
|
|||||||
this.processCard(cipher, item.fields);
|
this.processCard(cipher, item.fields);
|
||||||
} else if (
|
} else if (
|
||||||
item.template_type.indexOf("identity.") < 0 &&
|
item.template_type.indexOf("identity.") < 0 &&
|
||||||
item.fields.some((f: any) => f.type === "password" && !this.isNullOrWhitespace(f.value))
|
item.fields.some((f) => f.type === "password" && !this.isNullOrWhitespace(f.value))
|
||||||
) {
|
) {
|
||||||
this.processLogin(cipher, item.fields);
|
this.processLogin(cipher, item.fields);
|
||||||
} else {
|
} else {
|
||||||
@ -68,9 +72,9 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
|
|||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private processLogin(cipher: CipherView, fields: any[]) {
|
private processLogin(cipher: CipherView, fields: EnpassField[]) {
|
||||||
const urls: string[] = [];
|
const urls: string[] = [];
|
||||||
fields.forEach((field: any) => {
|
fields.forEach((field) => {
|
||||||
if (this.isNullOrWhitespace(field.value) || field.type === "section") {
|
if (this.isNullOrWhitespace(field.value) || field.type === "section") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -86,6 +90,13 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
|
|||||||
cipher.login.totp = field.value;
|
cipher.login.totp = field.value;
|
||||||
} else if (field.type === "url") {
|
} else if (field.type === "url") {
|
||||||
urls.push(field.value);
|
urls.push(field.value);
|
||||||
|
} else if (field.type === ".Android#") {
|
||||||
|
let cleanedValue = field.value.startsWith("androidapp://")
|
||||||
|
? field.value
|
||||||
|
: "androidapp://" + field.value;
|
||||||
|
cleanedValue = cleanedValue.replace("android://", "");
|
||||||
|
cleanedValue = cleanedValue.replace(androidUrlRegex, "androidapp://");
|
||||||
|
urls.push(cleanedValue);
|
||||||
} else {
|
} else {
|
||||||
this.processKvp(
|
this.processKvp(
|
||||||
cipher,
|
cipher,
|
||||||
@ -98,10 +109,10 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
|
|||||||
cipher.login.uris = this.makeUriArray(urls);
|
cipher.login.uris = this.makeUriArray(urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
private processCard(cipher: CipherView, fields: any[]) {
|
private processCard(cipher: CipherView, fields: EnpassField[]) {
|
||||||
cipher.card = new CardView();
|
cipher.card = new CardView();
|
||||||
cipher.type = CipherType.Card;
|
cipher.type = CipherType.Card;
|
||||||
fields.forEach((field: any) => {
|
fields.forEach((field) => {
|
||||||
if (
|
if (
|
||||||
this.isNullOrWhitespace(field.value) ||
|
this.isNullOrWhitespace(field.value) ||
|
||||||
field.type === "section" ||
|
field.type === "section" ||
|
||||||
@ -137,8 +148,8 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private processNote(cipher: CipherView, fields: any[]) {
|
private processNote(cipher: CipherView, fields: EnpassField[]) {
|
||||||
fields.forEach((field: any) => {
|
fields.forEach((field) => {
|
||||||
if (this.isNullOrWhitespace(field.value) || field.type === "section") {
|
if (this.isNullOrWhitespace(field.value) || field.type === "section") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -151,17 +162,17 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildFolderTree(folders: any[]): any[] {
|
private buildFolderTree(folders: EnpassFolder[]): EnpassFolderTreeItem[] {
|
||||||
if (folders == null) {
|
if (folders == null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const folderTree: any[] = [];
|
const folderTree: EnpassFolderTreeItem[] = [];
|
||||||
const map = new Map<string, any>([]);
|
const map = new Map<string, EnpassFolderTreeItem>([]);
|
||||||
folders.forEach((obj: any) => {
|
folders.forEach((obj: EnpassFolderTreeItem) => {
|
||||||
map.set(obj.uuid, obj);
|
map.set(obj.uuid, obj);
|
||||||
obj.children = [];
|
obj.children = [];
|
||||||
});
|
});
|
||||||
folders.forEach((obj: any) => {
|
folders.forEach((obj: EnpassFolderTreeItem) => {
|
||||||
if (obj.parent_uuid != null && obj.parent_uuid !== "" && map.has(obj.parent_uuid)) {
|
if (obj.parent_uuid != null && obj.parent_uuid !== "" && map.has(obj.parent_uuid)) {
|
||||||
map.get(obj.parent_uuid).children.push(obj);
|
map.get(obj.parent_uuid).children.push(obj);
|
||||||
} else {
|
} else {
|
||||||
@ -171,11 +182,15 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
|
|||||||
return folderTree;
|
return folderTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
private flattenFolderTree(titlePrefix: string, tree: any[], map: Map<string, string>) {
|
private flattenFolderTree(
|
||||||
|
titlePrefix: string,
|
||||||
|
tree: EnpassFolderTreeItem[],
|
||||||
|
map: Map<string, string>
|
||||||
|
) {
|
||||||
if (tree == null) {
|
if (tree == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tree.forEach((f: any) => {
|
tree.forEach((f) => {
|
||||||
if (f.title != null && f.title.trim() !== "") {
|
if (f.title != null && f.title.trim() !== "") {
|
||||||
let title = f.title.trim();
|
let title = f.title.trim();
|
||||||
if (titlePrefix != null && titlePrefix.trim() !== "") {
|
if (titlePrefix != null && titlePrefix.trim() !== "") {
|
@ -0,0 +1,79 @@
|
|||||||
|
type Login = "login.default";
|
||||||
|
|
||||||
|
type CreditCard = "creditcard.default";
|
||||||
|
|
||||||
|
type Identity = "identity.default";
|
||||||
|
|
||||||
|
type Note = "note.default";
|
||||||
|
|
||||||
|
type Password = "password.default";
|
||||||
|
|
||||||
|
type Finance =
|
||||||
|
| "finance.stock"
|
||||||
|
| "finance.bankaccount"
|
||||||
|
| "finance.loan"
|
||||||
|
| "finance.mutualfund"
|
||||||
|
| "finance.insurance"
|
||||||
|
| "finance.other";
|
||||||
|
|
||||||
|
type License = "license.driving" | "license.hunting" | "license.software" | "license.other";
|
||||||
|
|
||||||
|
type Travel =
|
||||||
|
| "travel.passport"
|
||||||
|
| "travel.flightdetails"
|
||||||
|
| "travel.hotelreservation"
|
||||||
|
| "travel.visa"
|
||||||
|
| "travel.freqflyer"
|
||||||
|
| "travel.other";
|
||||||
|
|
||||||
|
type Computer =
|
||||||
|
| "computer.database"
|
||||||
|
| "computer.emailaccount"
|
||||||
|
| "computer.ftp"
|
||||||
|
| "computer.messaging"
|
||||||
|
| "computer.internetprovider"
|
||||||
|
| "computer.server"
|
||||||
|
| "computer.wifi"
|
||||||
|
| "computer.hosting"
|
||||||
|
| "computer.other";
|
||||||
|
|
||||||
|
type Misc =
|
||||||
|
| "misc.Aadhar"
|
||||||
|
| "misc.address"
|
||||||
|
| "misc.library"
|
||||||
|
| "misc.rewardprogram"
|
||||||
|
| "misc.lens"
|
||||||
|
| "misc.service"
|
||||||
|
| "misc.vehicleinfo"
|
||||||
|
| "misc.itic"
|
||||||
|
| "misc.itz"
|
||||||
|
| "misc.propertyinfo"
|
||||||
|
| "misc.clothsize"
|
||||||
|
| "misc.contact"
|
||||||
|
| "misc.membership"
|
||||||
|
| "misc.cellphone"
|
||||||
|
| "misc.emergencyno"
|
||||||
|
| "misc.pan"
|
||||||
|
| "misc.identity"
|
||||||
|
| "misc.regcode"
|
||||||
|
| "misc.prescription"
|
||||||
|
| "misc.serial"
|
||||||
|
| "misc.socialsecurityno"
|
||||||
|
| "misc.isic"
|
||||||
|
| "misc.calling"
|
||||||
|
| "misc.voicemail"
|
||||||
|
| "misc.voter"
|
||||||
|
| "misc.combilock"
|
||||||
|
| "misc.other";
|
||||||
|
|
||||||
|
export type EnpassItemTemplate =
|
||||||
|
| Login
|
||||||
|
| CreditCard
|
||||||
|
| Identity
|
||||||
|
| Note
|
||||||
|
| Password
|
||||||
|
| Finance
|
||||||
|
| License
|
||||||
|
| Travel
|
||||||
|
| Computer
|
||||||
|
| Misc;
|
85
libs/common/src/importers/enpass/types/enpass-json-type.ts
Normal file
85
libs/common/src/importers/enpass/types/enpass-json-type.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { EnpassItemTemplate } from "./enpass-item-templates";
|
||||||
|
|
||||||
|
export type EnpassJsonFile = {
|
||||||
|
folders: EnpassFolder[];
|
||||||
|
items: EnpassItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EnpassFolder = {
|
||||||
|
icon: string;
|
||||||
|
parent_uuid: string;
|
||||||
|
title: string;
|
||||||
|
updated_at: number;
|
||||||
|
uuid: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EnpassItem = {
|
||||||
|
archived: number;
|
||||||
|
auto_submit: number;
|
||||||
|
category: string;
|
||||||
|
createdAt: number;
|
||||||
|
favorite: number;
|
||||||
|
fields?: EnpassField[];
|
||||||
|
icon: Icon;
|
||||||
|
note: string;
|
||||||
|
subtitle: string;
|
||||||
|
template_type: EnpassItemTemplate;
|
||||||
|
title: string;
|
||||||
|
trashed: number;
|
||||||
|
updated_at: number;
|
||||||
|
uuid: string;
|
||||||
|
folders?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EnpassFieldType =
|
||||||
|
| "text"
|
||||||
|
| "password"
|
||||||
|
| "pin"
|
||||||
|
| "numeric"
|
||||||
|
| "date"
|
||||||
|
| "email"
|
||||||
|
| "url"
|
||||||
|
| "phone"
|
||||||
|
| "username"
|
||||||
|
| "totp"
|
||||||
|
| "multiline"
|
||||||
|
| "ccName"
|
||||||
|
| "ccNumber"
|
||||||
|
| "ccCvc"
|
||||||
|
| "ccPin"
|
||||||
|
| "ccExpiry"
|
||||||
|
| "ccBankname"
|
||||||
|
| "ccTxnpassword"
|
||||||
|
| "ccType"
|
||||||
|
| "ccValidfrom"
|
||||||
|
| "section"
|
||||||
|
| ".Android#";
|
||||||
|
|
||||||
|
export type EnpassField = {
|
||||||
|
deleted: number;
|
||||||
|
history?: History[];
|
||||||
|
label: string;
|
||||||
|
order: number;
|
||||||
|
sensitive: number;
|
||||||
|
type: EnpassFieldType;
|
||||||
|
uid: number;
|
||||||
|
updated_at: number;
|
||||||
|
value: string;
|
||||||
|
value_updated_at: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type History = {
|
||||||
|
updated_at: number;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Icon = {
|
||||||
|
fav: string;
|
||||||
|
image: Image;
|
||||||
|
type: number;
|
||||||
|
uuid: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Image = {
|
||||||
|
file: string;
|
||||||
|
};
|
@ -28,8 +28,8 @@ import { CodebookCsvImporter } from "../importers/codebook-csv-importer";
|
|||||||
import { DashlaneCsvImporter } from "../importers/dashlane/dashlane-csv-importer";
|
import { DashlaneCsvImporter } from "../importers/dashlane/dashlane-csv-importer";
|
||||||
import { DashlaneJsonImporter } from "../importers/dashlane/dashlane-json-importer";
|
import { DashlaneJsonImporter } from "../importers/dashlane/dashlane-json-importer";
|
||||||
import { EncryptrCsvImporter } from "../importers/encryptr-csv-importer";
|
import { EncryptrCsvImporter } from "../importers/encryptr-csv-importer";
|
||||||
import { EnpassCsvImporter } from "../importers/enpass-csv-importer";
|
import { EnpassCsvImporter } from "../importers/enpass/enpass-csv-importer";
|
||||||
import { EnpassJsonImporter } from "../importers/enpass-json-importer";
|
import { EnpassJsonImporter } from "../importers/enpass/enpass-json-importer";
|
||||||
import { FirefoxCsvImporter } from "../importers/firefox-csv-importer";
|
import { FirefoxCsvImporter } from "../importers/firefox-csv-importer";
|
||||||
import { FSecureFskImporter } from "../importers/fsecure/fsecure-fsk-importer";
|
import { FSecureFskImporter } from "../importers/fsecure/fsecure-fsk-importer";
|
||||||
import { GnomeJsonImporter } from "../importers/gnome-json-importer";
|
import { GnomeJsonImporter } from "../importers/gnome-json-importer";
|
||||||
|
Loading…
Reference in New Issue
Block a user