1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

BEEEP: Add importer for Keeper in json format (#608)

* Add testdata, create types for keeperjson import

* Create keeperjson importer and tests

* Register, Create instance of keeperjson importer

* Move keeperCsvImporter to keeperImporters folder

* Fixed import of BaseImporter

* Removed unnecessary check for key

* Move instantiation of importer into beforeEach

* Fixed the second import with a wrong path

* Adjust types based on new test export

* Add test case for empty notes and custom fields

* Implement logic for failed test case

* Removed test expectation
This commit is contained in:
Daniel James Smith
2022-01-26 23:04:55 +01:00
committed by GitHub
parent 4722a287ec
commit 5353cf03b5
6 changed files with 319 additions and 5 deletions

View File

@@ -1,9 +1,9 @@
import { BaseImporter } from "./baseImporter";
import { Importer } from "./importer";
import { BaseImporter } from "../baseImporter";
import { Importer } from "../importer";
import { ImportResult } from "../models/domain/importResult";
import { ImportResult } from "../../models/domain/importResult";
import { FolderView } from "../models/view/folderView";
import { FolderView } from "../../models/view/folderView";
export class KeeperCsvImporter extends BaseImporter implements Importer {
parse(data: string): Promise<ImportResult> {

View File

@@ -0,0 +1,70 @@
import { BaseImporter } from "../baseImporter";
import { Importer } from "../importer";
import { ImportResult } from "../../models/domain/importResult";
import { KeeperJsonExport, RecordsEntity } from "./types/keeperJsonTypes";
export class KeeperJsonImporter extends BaseImporter implements Importer {
parse(data: string): Promise<ImportResult> {
const result = new ImportResult();
const keeperExport: KeeperJsonExport = JSON.parse(data);
if (keeperExport == null || keeperExport.records == null || keeperExport.records.length === 0) {
result.success = false;
return Promise.resolve(result);
}
keeperExport.records.forEach((record) => {
this.parseFolders(result, record);
const cipher = this.initLoginCipher();
cipher.name = record.title;
cipher.login.username = record.login;
cipher.login.password = record.password;
cipher.login.uris = this.makeUriArray(record.login_url);
cipher.notes = record.notes;
if (record.custom_fields != null) {
let customfieldKeys = Object.keys(record.custom_fields);
if (record.custom_fields["TFC:Keeper"] != null) {
customfieldKeys = customfieldKeys.filter((item) => item !== "TFC:Keeper");
cipher.login.totp = record.custom_fields["TFC:Keeper"];
}
customfieldKeys.forEach((key) => {
this.processKvp(cipher, key, record.custom_fields[key]);
});
}
this.convertToNoteIfNeeded(cipher);
this.cleanupCipher(cipher);
result.ciphers.push(cipher);
});
if (this.organization) {
this.moveFoldersToCollections(result);
}
result.success = true;
return Promise.resolve(result);
}
private parseFolders(result: ImportResult, record: RecordsEntity) {
if (record.folders == null || record.folders.length === 0) {
return;
}
record.folders.forEach((item) => {
if (item.folder != null) {
this.processFolder(result, item.folder);
return;
}
if (item.shared_folder != null) {
this.processFolder(result, item.shared_folder);
return;
}
});
}
}

View File

@@ -0,0 +1,41 @@
export interface KeeperJsonExport {
shared_folders?: SharedFoldersEntity[] | null;
records?: RecordsEntity[] | null;
}
export interface SharedFoldersEntity {
path: string;
manage_users: boolean;
manage_records: boolean;
can_edit: boolean;
can_share: boolean;
permissions?: PermissionsEntity[] | null;
}
export interface PermissionsEntity {
uid?: string | null;
manage_users: boolean;
manage_records: boolean;
name?: string | null;
}
export interface RecordsEntity {
title: string;
login: string;
password: string;
login_url: string;
notes?: string;
custom_fields?: CustomFields;
folders?: FoldersEntity[] | null;
}
export type CustomFields = {
[key: string]: string | null;
};
export interface FoldersEntity {
folder?: string | null;
shared_folder?: string | null;
can_edit?: boolean | null;
can_share?: boolean | null;
}

View File

@@ -49,7 +49,8 @@ import { Importer } from "../importers/importer";
import { KasperskyTxtImporter } from "../importers/kasperskyTxtImporter";
import { KeePass2XmlImporter } from "../importers/keepass2XmlImporter";
import { KeePassXCsvImporter } from "../importers/keepassxCsvImporter";
import { KeeperCsvImporter } from "../importers/keeperCsvImporter";
import { KeeperCsvImporter } from "../importers/keeperImporters/keeperCsvImporter";
import { KeeperJsonImporter } from "../importers/keeperImporters/keeperJsonImporter";
import { LastPassCsvImporter } from "../importers/lastpassCsvImporter";
import { LogMeOnceCsvImporter } from "../importers/logMeOnceCsvImporter";
import { MeldiumCsvImporter } from "../importers/meldiumCsvImporter";
@@ -100,6 +101,7 @@ export class ImportService implements ImportServiceAbstraction {
{ id: "1passwordmaccsv", name: "1Password 6 and 7 Mac (csv)" },
{ id: "roboformcsv", name: "RoboForm (csv)" },
{ id: "keepercsv", name: "Keeper (csv)" },
{ id: "keeperjson", name: "Keeper (json)" },
{ id: "enpasscsv", name: "Enpass (csv)" },
{ id: "enpassjson", name: "Enpass (json)" },
{ id: "safeincloudxml", name: "SafeInCloud (xml)" },
@@ -251,6 +253,8 @@ export class ImportService implements ImportServiceAbstraction {
return new OnePasswordMacCsvImporter();
case "keepercsv":
return new KeeperCsvImporter();
case "keeperjson":
return new KeeperJsonImporter();
case "passworddragonxml":
return new PasswordDragonXmlImporter();
case "enpasscsv":