mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
[PM-328] Move common/importer to libs/importer (tools-migration) (#5060)
* Create and register new libs/importer Create package.json Create tsconfig Create jest.config Extend shared and root tsconfig and jest.configs Register with eslint * Move importer-related files to libs/importer * Move importer-spec-related files to libs/importer Move import.service.spec * Update package-lock.json * Set CODEOWNERS for new libs/importer * Register libs/importer with cli and fix imports * Register libs/importer with web and fix imports * Move importOption into models Rename importOptions to import-options * Fix linting issues after updating prettier * Only expose necessary files from libs/importer Fix tsconfig files - Removes the trailing /index on imports in web/cli As the spec-files no longer can access the internals via @bitwarden/importer they import by path (../src/importers) * Add barrel files to vendors with more than one importer
This commit is contained in:
committed by
GitHub
parent
7cfabf053c
commit
a5a12a6723
@@ -1,10 +0,0 @@
|
||||
import { ImportCiphersRequest } from "../../models/request/import-ciphers.request";
|
||||
import { ImportOrganizationCiphersRequest } from "../../models/request/import-organization-ciphers.request";
|
||||
|
||||
export abstract class ImportApiServiceAbstraction {
|
||||
postImportCiphers: (request: ImportCiphersRequest) => Promise<any>;
|
||||
postImportOrganizationCiphers: (
|
||||
organizationId: string,
|
||||
request: ImportOrganizationCiphersRequest
|
||||
) => Promise<any>;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { ImportOption, ImportType } from "../../enums/importOptions";
|
||||
import { ImportError } from "../../importers/import-error";
|
||||
import { Importer } from "../../importers/importer";
|
||||
|
||||
export abstract class ImportService {
|
||||
featuredImportOptions: readonly ImportOption[];
|
||||
regularImportOptions: readonly ImportOption[];
|
||||
getImportOptions: () => ImportOption[];
|
||||
import: (
|
||||
importer: Importer,
|
||||
fileContents: string,
|
||||
organizationId?: string
|
||||
) => Promise<ImportError>;
|
||||
getImporter: (
|
||||
format: ImportType | "bitwardenpasswordprotected",
|
||||
organizationId: string,
|
||||
password?: string
|
||||
) => Importer;
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
export interface ImportOption {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const featuredImportOptions = [
|
||||
{ id: "bitwardenjson", name: "Bitwarden (json)" },
|
||||
{ id: "bitwardencsv", name: "Bitwarden (csv)" },
|
||||
{ id: "chromecsv", name: "Chrome (csv)" },
|
||||
{ id: "dashlanecsv", name: "Dashlane (csv)" },
|
||||
{ id: "firefoxcsv", name: "Firefox (csv)" },
|
||||
{ id: "keepass2xml", name: "KeePass 2 (xml)" },
|
||||
{ id: "lastpasscsv", name: "LastPass (csv)" },
|
||||
{ id: "safaricsv", name: "Safari and macOS (csv)" },
|
||||
{ id: "1password1pux", name: "1Password (1pux)" },
|
||||
] as const;
|
||||
|
||||
export const regularImportOptions = [
|
||||
{ id: "keepassxcsv", name: "KeePassX (csv)" },
|
||||
{ id: "1password1pif", name: "1Password (1pif)" },
|
||||
{ id: "1passwordwincsv", name: "1Password 6 and 7 Windows (csv)" },
|
||||
{ id: "1passwordmaccsv", name: "1Password 6 and 7 Mac (csv)" },
|
||||
{ id: "dashlanejson", name: "Dashlane (json)" },
|
||||
{ id: "roboformcsv", name: "RoboForm (csv)" },
|
||||
{ id: "keepercsv", name: "Keeper (csv)" },
|
||||
// Temporarily remove this option for the Feb release
|
||||
// { id: "keeperjson", name: "Keeper (json)" },
|
||||
{ id: "enpasscsv", name: "Enpass (csv)" },
|
||||
{ id: "enpassjson", name: "Enpass (json)" },
|
||||
{ id: "safeincloudxml", name: "SafeInCloud (xml)" },
|
||||
{ id: "pwsafexml", name: "Password Safe (xml)" },
|
||||
{ id: "stickypasswordxml", name: "Sticky Password (xml)" },
|
||||
{ id: "msecurecsv", name: "mSecure (csv)" },
|
||||
{ id: "truekeycsv", name: "True Key (csv)" },
|
||||
{ id: "passwordbossjson", name: "Password Boss (json)" },
|
||||
{ id: "zohovaultcsv", name: "Zoho Vault (csv)" },
|
||||
{ id: "splashidcsv", name: "SplashID (csv)" },
|
||||
{ id: "passworddragonxml", name: "Password Dragon (xml)" },
|
||||
{ id: "padlockcsv", name: "Padlock (csv)" },
|
||||
{ id: "passboltcsv", name: "Passbolt (csv)" },
|
||||
{ id: "clipperzhtml", name: "Clipperz (html)" },
|
||||
{ id: "aviracsv", name: "Avira (csv)" },
|
||||
{ id: "saferpasscsv", name: "SaferPass (csv)" },
|
||||
{ id: "upmcsv", name: "Universal Password Manager (csv)" },
|
||||
{ id: "ascendocsv", name: "Ascendo DataVault (csv)" },
|
||||
{ id: "meldiumcsv", name: "Meldium (csv)" },
|
||||
{ id: "passkeepcsv", name: "PassKeep (csv)" },
|
||||
{ id: "operacsv", name: "Opera (csv)" },
|
||||
{ id: "vivaldicsv", name: "Vivaldi (csv)" },
|
||||
{ id: "gnomejson", name: "GNOME Passwords and Keys/Seahorse (json)" },
|
||||
{ id: "blurcsv", name: "Blur (csv)" },
|
||||
{ id: "passwordagentcsv", name: "Password Agent (csv)" },
|
||||
{ id: "passpackcsv", name: "Passpack (csv)" },
|
||||
{ id: "passmanjson", name: "Passman (json)" },
|
||||
{ id: "avastcsv", name: "Avast Passwords (csv)" },
|
||||
{ id: "avastjson", name: "Avast Passwords (json)" },
|
||||
{ id: "fsecurefsk", name: "F-Secure KEY (fsk)" },
|
||||
{ id: "kasperskytxt", name: "Kaspersky Password Manager (txt)" },
|
||||
{ id: "remembearcsv", name: "RememBear (csv)" },
|
||||
{ id: "passwordwallettxt", name: "PasswordWallet (txt)" },
|
||||
{ id: "mykicsv", name: "Myki (csv)" },
|
||||
{ id: "securesafecsv", name: "SecureSafe (csv)" },
|
||||
{ id: "logmeoncecsv", name: "LogMeOnce (csv)" },
|
||||
{ id: "blackberrycsv", name: "BlackBerry Password Keeper (csv)" },
|
||||
{ id: "buttercupcsv", name: "Buttercup (csv)" },
|
||||
{ id: "codebookcsv", name: "Codebook (csv)" },
|
||||
{ id: "encryptrcsv", name: "Encryptr (csv)" },
|
||||
{ id: "yoticsv", name: "Yoti (csv)" },
|
||||
{ id: "nordpasscsv", name: "Nordpass (csv)" },
|
||||
{ id: "psonojson", name: "Psono (json)" },
|
||||
{ id: "passkyjson", name: "Passky (json)" },
|
||||
] as const;
|
||||
|
||||
export type ImportType =
|
||||
| (typeof featuredImportOptions)[number]["id"]
|
||||
| (typeof regularImportOptions)[number]["id"];
|
||||
@@ -1,59 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class AscendoCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, false);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((value) => {
|
||||
if (value.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.notes = this.getValueOrDefault(value[value.length - 1]);
|
||||
cipher.name = this.getValueOrDefault(value[0], "--");
|
||||
|
||||
if (value.length > 2 && value.length % 2 === 0) {
|
||||
for (let i = 0; i < value.length - 2; i += 2) {
|
||||
const val: string = value[i + 2];
|
||||
const field: string = value[i + 1];
|
||||
if (this.isNullOrWhitespace(val) || this.isNullOrWhitespace(field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fieldLower = field.toLowerCase();
|
||||
if (cipher.login.password == null && this.passwordFieldNames.indexOf(fieldLower) > -1) {
|
||||
cipher.login.password = this.getValueOrDefault(val);
|
||||
} else if (
|
||||
cipher.login.username == null &&
|
||||
this.usernameFieldNames.indexOf(fieldLower) > -1
|
||||
) {
|
||||
cipher.login.username = this.getValueOrDefault(val);
|
||||
} else if (
|
||||
(cipher.login.uris == null || cipher.login.uris.length === 0) &&
|
||||
this.uriFieldNames.indexOf(fieldLower) > -1
|
||||
) {
|
||||
cipher.login.uris = this.makeUriArray(val);
|
||||
} else {
|
||||
this.processKvp(cipher, field, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class AvastCsvImporter 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((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value.name);
|
||||
cipher.login.uris = this.makeUriArray(value.web);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
cipher.login.username = this.getValueOrDefault(value.login);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherType } from "../vault/enums/cipher-type";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class AvastJsonImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = JSON.parse(data);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
if (results.logins != null) {
|
||||
results.logins.forEach((value: any) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value.custName);
|
||||
cipher.notes = this.getValueOrDefault(value.note);
|
||||
cipher.login.uris = this.makeUriArray(value.url);
|
||||
cipher.login.password = this.getValueOrDefault(value.pwd);
|
||||
cipher.login.username = this.getValueOrDefault(value.loginName);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
}
|
||||
|
||||
if (results.notes != null) {
|
||||
results.notes.forEach((value: any) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
cipher.name = this.getValueOrDefault(value.label);
|
||||
cipher.notes = this.getValueOrDefault(value.text);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
}
|
||||
|
||||
if (results.cards != null) {
|
||||
results.cards.forEach((value: any) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.name = this.getValueOrDefault(value.custName);
|
||||
cipher.notes = this.getValueOrDefault(value.note);
|
||||
cipher.card.cardholderName = this.getValueOrDefault(value.holderName);
|
||||
cipher.card.number = this.getValueOrDefault(value.cardNumber);
|
||||
cipher.card.code = this.getValueOrDefault(value.cvv);
|
||||
cipher.card.brand = this.getCardBrand(cipher.card.number);
|
||||
if (value.expirationDate != null) {
|
||||
if (value.expirationDate.month != null) {
|
||||
cipher.card.expMonth = value.expirationDate.month + "";
|
||||
}
|
||||
if (value.expirationDate.year != null) {
|
||||
cipher.card.expYear = value.expirationDate.year + "";
|
||||
}
|
||||
}
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class AviraCsvImporter 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((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(
|
||||
value.name,
|
||||
this.getValueOrDefault(this.nameFromUrl(value.website), "--")
|
||||
);
|
||||
cipher.login.uris = this.makeUriArray(value.website);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
|
||||
if (
|
||||
this.isNullOrWhitespace(value.username) &&
|
||||
!this.isNullOrWhitespace(value.secondary_username)
|
||||
) {
|
||||
cipher.login.username = value.secondary_username;
|
||||
} else {
|
||||
cipher.login.username = this.getValueOrDefault(value.username);
|
||||
cipher.notes = this.getValueOrDefault(value.secondary_username);
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,468 +0,0 @@
|
||||
import * as papa from "papaparse";
|
||||
|
||||
import { LogService } from "../abstractions/log.service";
|
||||
import { CollectionView } from "../admin-console/models/view/collection.view";
|
||||
import { FieldType } from "../enums/fieldType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { Utils } from "../misc/utils";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { ConsoleLogService } from "../services/consoleLog.service";
|
||||
import { CipherType } from "../vault/enums/cipher-type";
|
||||
import { CipherView } from "../vault/models/view/cipher.view";
|
||||
import { FieldView } from "../vault/models/view/field.view";
|
||||
import { FolderView } from "../vault/models/view/folder.view";
|
||||
import { LoginUriView } from "../vault/models/view/login-uri.view";
|
||||
import { LoginView } from "../vault/models/view/login.view";
|
||||
import { SecureNoteView } from "../vault/models/view/secure-note.view";
|
||||
|
||||
export abstract class BaseImporter {
|
||||
organizationId: string = null;
|
||||
|
||||
protected logService: LogService = new ConsoleLogService(false);
|
||||
|
||||
protected newLineRegex = /(?:\r\n|\r|\n)/;
|
||||
|
||||
protected passwordFieldNames = [
|
||||
"password",
|
||||
"pass word",
|
||||
"passphrase",
|
||||
"pass phrase",
|
||||
"pass",
|
||||
"code",
|
||||
"code word",
|
||||
"codeword",
|
||||
"secret",
|
||||
"secret word",
|
||||
"personpwd",
|
||||
"key",
|
||||
"keyword",
|
||||
"key word",
|
||||
"keyphrase",
|
||||
"key phrase",
|
||||
"form_pw",
|
||||
"wppassword",
|
||||
"pin",
|
||||
"pwd",
|
||||
"pw",
|
||||
"pword",
|
||||
"passwd",
|
||||
"p",
|
||||
"serial",
|
||||
"serial#",
|
||||
"license key",
|
||||
"reg #",
|
||||
|
||||
// Non-English names
|
||||
"passwort",
|
||||
];
|
||||
|
||||
protected usernameFieldNames = [
|
||||
"user",
|
||||
"name",
|
||||
"user name",
|
||||
"username",
|
||||
"login name",
|
||||
"email",
|
||||
"e-mail",
|
||||
"id",
|
||||
"userid",
|
||||
"user id",
|
||||
"login",
|
||||
"form_loginname",
|
||||
"wpname",
|
||||
"mail",
|
||||
"loginid",
|
||||
"login id",
|
||||
"log",
|
||||
"personlogin",
|
||||
"first name",
|
||||
"last name",
|
||||
"card#",
|
||||
"account #",
|
||||
"member",
|
||||
"member #",
|
||||
|
||||
// Non-English names
|
||||
"nom",
|
||||
"benutzername",
|
||||
];
|
||||
|
||||
protected notesFieldNames = [
|
||||
"note",
|
||||
"notes",
|
||||
"comment",
|
||||
"comments",
|
||||
"memo",
|
||||
"description",
|
||||
"free form",
|
||||
"freeform",
|
||||
"free text",
|
||||
"freetext",
|
||||
"free",
|
||||
|
||||
// Non-English names
|
||||
"kommentar",
|
||||
];
|
||||
|
||||
protected uriFieldNames: string[] = [
|
||||
"url",
|
||||
"hyper link",
|
||||
"hyperlink",
|
||||
"link",
|
||||
"host",
|
||||
"hostname",
|
||||
"host name",
|
||||
"server",
|
||||
"address",
|
||||
"hyper ref",
|
||||
"href",
|
||||
"web",
|
||||
"website",
|
||||
"web site",
|
||||
"site",
|
||||
"web-site",
|
||||
"uri",
|
||||
|
||||
// Non-English names
|
||||
"ort",
|
||||
"adresse",
|
||||
];
|
||||
|
||||
protected parseCsvOptions = {
|
||||
encoding: "UTF-8",
|
||||
skipEmptyLines: false,
|
||||
};
|
||||
|
||||
protected get organization() {
|
||||
return this.organizationId != null;
|
||||
}
|
||||
|
||||
protected parseXml(data: string): Document {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(data, "application/xml");
|
||||
return doc != null && doc.querySelector("parsererror") == null ? doc : null;
|
||||
}
|
||||
|
||||
protected parseCsv(data: string, header: boolean, options: any = {}): any[] {
|
||||
const parseOptions: papa.ParseConfig<string> = Object.assign(
|
||||
{ header: header },
|
||||
this.parseCsvOptions,
|
||||
options
|
||||
);
|
||||
data = this.splitNewLine(data).join("\n").trim();
|
||||
const result = papa.parse(data, parseOptions);
|
||||
if (result.errors != null && result.errors.length > 0) {
|
||||
result.errors.forEach((e) => {
|
||||
if (e.row != null) {
|
||||
this.logService.warning("Error parsing row " + e.row + ": " + e.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result.data && result.data.length > 0 ? result.data : null;
|
||||
}
|
||||
|
||||
protected parseSingleRowCsv(rowData: string) {
|
||||
if (this.isNullOrWhitespace(rowData)) {
|
||||
return null;
|
||||
}
|
||||
const parsedRow = this.parseCsv(rowData, false);
|
||||
if (parsedRow != null && parsedRow.length > 0 && parsedRow[0].length > 0) {
|
||||
return parsedRow[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected makeUriArray(uri: string | string[]): LoginUriView[] {
|
||||
if (uri == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof uri === "string") {
|
||||
const loginUri = new LoginUriView();
|
||||
loginUri.uri = this.fixUri(uri);
|
||||
if (this.isNullOrWhitespace(loginUri.uri)) {
|
||||
return null;
|
||||
}
|
||||
loginUri.match = null;
|
||||
return [loginUri];
|
||||
}
|
||||
|
||||
if (uri.length > 0) {
|
||||
const returnArr: LoginUriView[] = [];
|
||||
uri.forEach((u) => {
|
||||
const loginUri = new LoginUriView();
|
||||
loginUri.uri = this.fixUri(u);
|
||||
if (this.isNullOrWhitespace(loginUri.uri)) {
|
||||
return;
|
||||
}
|
||||
loginUri.match = null;
|
||||
returnArr.push(loginUri);
|
||||
});
|
||||
return returnArr.length === 0 ? null : returnArr;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected fixUri(uri: string) {
|
||||
if (uri == null) {
|
||||
return null;
|
||||
}
|
||||
uri = uri.trim();
|
||||
if (uri.indexOf("://") === -1 && uri.indexOf(".") >= 0) {
|
||||
uri = "http://" + uri;
|
||||
}
|
||||
if (uri.length > 1000) {
|
||||
return uri.substring(0, 1000);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
protected nameFromUrl(url: string) {
|
||||
const hostname = Utils.getHostname(url);
|
||||
if (this.isNullOrWhitespace(hostname)) {
|
||||
return null;
|
||||
}
|
||||
return hostname.startsWith("www.") ? hostname.replace("www.", "") : hostname;
|
||||
}
|
||||
|
||||
protected isNullOrWhitespace(str: string): boolean {
|
||||
return Utils.isNullOrWhitespace(str);
|
||||
}
|
||||
|
||||
protected getValueOrDefault(str: string, defaultValue: string = null): string {
|
||||
if (this.isNullOrWhitespace(str)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
protected splitNewLine(str: string): string[] {
|
||||
return str.split(this.newLineRegex);
|
||||
}
|
||||
|
||||
// ref https://stackoverflow.com/a/5911300
|
||||
protected getCardBrand(cardNum: string) {
|
||||
if (this.isNullOrWhitespace(cardNum)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Visa
|
||||
let re = new RegExp("^4");
|
||||
if (cardNum.match(re) != null) {
|
||||
return "Visa";
|
||||
}
|
||||
|
||||
// Mastercard
|
||||
// Updated for Mastercard 2017 BINs expansion
|
||||
if (
|
||||
/^(5[1-5][0-9]{14}|2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12}))$/.test(
|
||||
cardNum
|
||||
)
|
||||
) {
|
||||
return "Mastercard";
|
||||
}
|
||||
|
||||
// AMEX
|
||||
re = new RegExp("^3[47]");
|
||||
if (cardNum.match(re) != null) {
|
||||
return "Amex";
|
||||
}
|
||||
|
||||
// Discover
|
||||
re = new RegExp(
|
||||
"^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)"
|
||||
);
|
||||
if (cardNum.match(re) != null) {
|
||||
return "Discover";
|
||||
}
|
||||
|
||||
// Diners
|
||||
re = new RegExp("^36");
|
||||
if (cardNum.match(re) != null) {
|
||||
return "Diners Club";
|
||||
}
|
||||
|
||||
// Diners - Carte Blanche
|
||||
re = new RegExp("^30[0-5]");
|
||||
if (cardNum.match(re) != null) {
|
||||
return "Diners Club";
|
||||
}
|
||||
|
||||
// JCB
|
||||
re = new RegExp("^35(2[89]|[3-8][0-9])");
|
||||
if (cardNum.match(re) != null) {
|
||||
return "JCB";
|
||||
}
|
||||
|
||||
// Visa Electron
|
||||
re = new RegExp("^(4026|417500|4508|4844|491(3|7))");
|
||||
if (cardNum.match(re) != null) {
|
||||
return "Visa";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected setCardExpiration(cipher: CipherView, expiration: string): boolean {
|
||||
if (!this.isNullOrWhitespace(expiration)) {
|
||||
expiration = expiration.replace(/\s/g, "");
|
||||
const parts = expiration.split("/");
|
||||
if (parts.length === 2) {
|
||||
let month: string = null;
|
||||
let year: string = null;
|
||||
if (parts[0].length === 1 || parts[0].length === 2) {
|
||||
month = parts[0];
|
||||
if (month.length === 2 && month[0] === "0") {
|
||||
month = month.substr(1, 1);
|
||||
}
|
||||
}
|
||||
if (parts[1].length === 2 || parts[1].length === 4) {
|
||||
year = month.length === 2 ? "20" + parts[1] : parts[1];
|
||||
}
|
||||
if (month != null && year != null) {
|
||||
cipher.card.expMonth = month;
|
||||
cipher.card.expYear = year;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected moveFoldersToCollections(result: ImportResult) {
|
||||
result.folderRelationships.forEach((r) => result.collectionRelationships.push(r));
|
||||
result.collections = result.folders.map((f) => {
|
||||
const collection = new CollectionView();
|
||||
collection.name = f.name;
|
||||
return collection;
|
||||
});
|
||||
result.folderRelationships = [];
|
||||
result.folders = [];
|
||||
}
|
||||
|
||||
protected querySelectorDirectChild(parentEl: Element, query: string) {
|
||||
const els = this.querySelectorAllDirectChild(parentEl, query);
|
||||
return els.length === 0 ? null : els[0];
|
||||
}
|
||||
|
||||
protected querySelectorAllDirectChild(parentEl: Element, query: string) {
|
||||
return Array.from(parentEl.querySelectorAll(query)).filter((el) => el.parentNode === parentEl);
|
||||
}
|
||||
|
||||
protected initLoginCipher() {
|
||||
const cipher = new CipherView();
|
||||
cipher.favorite = false;
|
||||
cipher.notes = "";
|
||||
cipher.fields = [];
|
||||
cipher.login = new LoginView();
|
||||
cipher.type = CipherType.Login;
|
||||
return cipher;
|
||||
}
|
||||
|
||||
protected cleanupCipher(cipher: CipherView) {
|
||||
if (cipher == null) {
|
||||
return;
|
||||
}
|
||||
if (cipher.type !== CipherType.Login) {
|
||||
cipher.login = null;
|
||||
}
|
||||
if (this.isNullOrWhitespace(cipher.name)) {
|
||||
cipher.name = "--";
|
||||
}
|
||||
if (this.isNullOrWhitespace(cipher.notes)) {
|
||||
cipher.notes = null;
|
||||
} else {
|
||||
cipher.notes = cipher.notes.trim();
|
||||
}
|
||||
if (cipher.fields != null && cipher.fields.length === 0) {
|
||||
cipher.fields = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected processKvp(
|
||||
cipher: CipherView,
|
||||
key: string,
|
||||
value: string,
|
||||
type: FieldType = FieldType.Text
|
||||
) {
|
||||
if (this.isNullOrWhitespace(value)) {
|
||||
return;
|
||||
}
|
||||
if (this.isNullOrWhitespace(key)) {
|
||||
key = "";
|
||||
}
|
||||
if (value.length > 200 || value.trim().search(this.newLineRegex) > -1) {
|
||||
if (cipher.notes == null) {
|
||||
cipher.notes = "";
|
||||
}
|
||||
cipher.notes += key + ": " + this.splitNewLine(value).join("\n") + "\n";
|
||||
} else {
|
||||
if (cipher.fields == null) {
|
||||
cipher.fields = [];
|
||||
}
|
||||
const field = new FieldView();
|
||||
field.type = type;
|
||||
field.name = key;
|
||||
field.value = value;
|
||||
cipher.fields.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
protected processFolder(result: ImportResult, folderName: string) {
|
||||
let folderIndex = result.folders.length;
|
||||
const hasFolder = !this.isNullOrWhitespace(folderName);
|
||||
// Replace backslashes with forward slashes, ensuring we create sub-folders
|
||||
folderName = folderName.replace("\\", "/");
|
||||
let addFolder = hasFolder;
|
||||
|
||||
if (hasFolder) {
|
||||
for (let i = 0; i < result.folders.length; i++) {
|
||||
if (result.folders[i].name === folderName) {
|
||||
addFolder = false;
|
||||
folderIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (addFolder) {
|
||||
const f = new FolderView();
|
||||
f.name = folderName;
|
||||
result.folders.push(f);
|
||||
}
|
||||
if (hasFolder) {
|
||||
result.folderRelationships.push([result.ciphers.length, folderIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
protected convertToNoteIfNeeded(cipher: CipherView) {
|
||||
if (
|
||||
cipher.type === CipherType.Login &&
|
||||
this.isNullOrWhitespace(cipher.login.username) &&
|
||||
this.isNullOrWhitespace(cipher.login.password) &&
|
||||
(cipher.login.uris == null || cipher.login.uris.length === 0)
|
||||
) {
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
}
|
||||
}
|
||||
|
||||
protected processFullName(cipher: CipherView, fullName: string) {
|
||||
if (this.isNullOrWhitespace(fullName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nameParts = fullName.split(" ");
|
||||
if (nameParts.length > 0) {
|
||||
cipher.identity.firstName = this.getValueOrDefault(nameParts[0]);
|
||||
}
|
||||
if (nameParts.length === 2) {
|
||||
cipher.identity.lastName = this.getValueOrDefault(nameParts[1]);
|
||||
} else if (nameParts.length >= 3) {
|
||||
cipher.identity.middleName = this.getValueOrDefault(nameParts[1]);
|
||||
cipher.identity.lastName = nameParts.slice(2, nameParts.length).join(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
import { CollectionView } from "../admin-console/models/view/collection.view";
|
||||
import { FieldType } from "../enums/fieldType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherRepromptType } from "../vault/enums/cipher-reprompt-type";
|
||||
import { CipherType } from "../vault/enums/cipher-type";
|
||||
import { CipherView } from "../vault/models/view/cipher.view";
|
||||
import { FieldView } from "../vault/models/view/field.view";
|
||||
import { LoginView } from "../vault/models/view/login.view";
|
||||
import { SecureNoteView } from "../vault/models/view/secure-note.view";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class BitwardenCsvImporter 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((value) => {
|
||||
if (this.organization && !this.isNullOrWhitespace(value.collections)) {
|
||||
const collections = (value.collections as string).split(",");
|
||||
collections.forEach((col) => {
|
||||
let addCollection = true;
|
||||
let collectionIndex = result.collections.length;
|
||||
|
||||
for (let i = 0; i < result.collections.length; i++) {
|
||||
if (result.collections[i].name === col) {
|
||||
addCollection = false;
|
||||
collectionIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (addCollection) {
|
||||
const collection = new CollectionView();
|
||||
collection.name = col;
|
||||
result.collections.push(collection);
|
||||
}
|
||||
|
||||
result.collectionRelationships.push([result.ciphers.length, collectionIndex]);
|
||||
});
|
||||
} else if (!this.organization) {
|
||||
this.processFolder(result, value.folder);
|
||||
}
|
||||
|
||||
const cipher = new CipherView();
|
||||
cipher.favorite =
|
||||
!this.organization && this.getValueOrDefault(value.favorite, "0") !== "0" ? true : false;
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.notes = this.getValueOrDefault(value.notes);
|
||||
cipher.name = this.getValueOrDefault(value.name, "--");
|
||||
try {
|
||||
cipher.reprompt = parseInt(
|
||||
this.getValueOrDefault(value.reprompt, CipherRepromptType.None.toString()),
|
||||
10
|
||||
);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line
|
||||
console.error("Unable to parse reprompt value", e);
|
||||
cipher.reprompt = CipherRepromptType.None;
|
||||
}
|
||||
|
||||
if (!this.isNullOrWhitespace(value.fields)) {
|
||||
const fields = this.splitNewLine(value.fields);
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
if (this.isNullOrWhitespace(fields[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const delimPosition = fields[i].lastIndexOf(": ");
|
||||
if (delimPosition === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cipher.fields == null) {
|
||||
cipher.fields = [];
|
||||
}
|
||||
|
||||
const field = new FieldView();
|
||||
field.name = fields[i].substr(0, delimPosition);
|
||||
field.value = null;
|
||||
field.type = FieldType.Text;
|
||||
if (fields[i].length > delimPosition + 2) {
|
||||
field.value = fields[i].substr(delimPosition + 2);
|
||||
}
|
||||
cipher.fields.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
const valueType = value.type != null ? value.type.toLowerCase() : null;
|
||||
switch (valueType) {
|
||||
case "note":
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
break;
|
||||
default: {
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.login = new LoginView();
|
||||
cipher.login.totp = this.getValueOrDefault(value.login_totp || value.totp);
|
||||
cipher.login.username = this.getValueOrDefault(value.login_username || value.username);
|
||||
cipher.login.password = this.getValueOrDefault(value.login_password || value.password);
|
||||
const uris = this.parseSingleRowCsv(value.login_uri || value.uri);
|
||||
cipher.login.uris = this.makeUriArray(uris);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
import { EncString } from "../models/domain/enc-string";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherWithIdExport } from "../models/export/cipher-with-ids.export";
|
||||
import { CollectionWithIdExport } from "../models/export/collection-with-id.export";
|
||||
import { FolderWithIdExport } from "../models/export/folder-with-id.export";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||
private results: any;
|
||||
private result: ImportResult;
|
||||
|
||||
constructor(protected cryptoService: CryptoService, protected i18nService: I18nService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async parse(data: string): Promise<ImportResult> {
|
||||
this.result = new ImportResult();
|
||||
this.results = JSON.parse(data);
|
||||
if (this.results == null || this.results.items == null) {
|
||||
if (this.results?.passwordProtected) {
|
||||
this.result.success = false;
|
||||
this.result.missingPassword = true;
|
||||
this.result.errorMessage = this.i18nService.t("importPasswordRequired");
|
||||
return this.result;
|
||||
}
|
||||
|
||||
this.result.success = false;
|
||||
return this.result;
|
||||
}
|
||||
|
||||
if (this.results.encrypted) {
|
||||
await this.parseEncrypted();
|
||||
} else {
|
||||
this.parseDecrypted();
|
||||
}
|
||||
|
||||
return this.result;
|
||||
}
|
||||
|
||||
private async parseEncrypted() {
|
||||
if (this.results.encKeyValidation_DO_NOT_EDIT != null) {
|
||||
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
|
||||
const encKeyValidation = new EncString(this.results.encKeyValidation_DO_NOT_EDIT);
|
||||
const encKeyValidationDecrypt = await this.cryptoService.decryptToUtf8(
|
||||
encKeyValidation,
|
||||
orgKey
|
||||
);
|
||||
if (encKeyValidationDecrypt === null) {
|
||||
this.result.success = false;
|
||||
this.result.errorMessage = this.i18nService.t("importEncKeyError");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const groupingsMap = new Map<string, number>();
|
||||
|
||||
if (this.organization && this.results.collections != null) {
|
||||
for (const c of this.results.collections as CollectionWithIdExport[]) {
|
||||
const collection = CollectionWithIdExport.toDomain(c);
|
||||
if (collection != null) {
|
||||
collection.id = null;
|
||||
collection.organizationId = this.organizationId;
|
||||
const view = await collection.decrypt();
|
||||
groupingsMap.set(c.id, this.result.collections.length);
|
||||
this.result.collections.push(view);
|
||||
}
|
||||
}
|
||||
} else if (!this.organization && this.results.folders != null) {
|
||||
for (const f of this.results.folders as FolderWithIdExport[]) {
|
||||
const folder = FolderWithIdExport.toDomain(f);
|
||||
if (folder != null) {
|
||||
folder.id = null;
|
||||
const view = await folder.decrypt();
|
||||
groupingsMap.set(f.id, this.result.folders.length);
|
||||
this.result.folders.push(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const c of this.results.items as CipherWithIdExport[]) {
|
||||
const cipher = CipherWithIdExport.toDomain(c);
|
||||
// reset ids incase they were set for some reason
|
||||
cipher.id = null;
|
||||
cipher.folderId = null;
|
||||
cipher.organizationId = this.organizationId;
|
||||
cipher.collectionIds = null;
|
||||
|
||||
// make sure password history is limited
|
||||
if (cipher.passwordHistory != null && cipher.passwordHistory.length > 5) {
|
||||
cipher.passwordHistory = cipher.passwordHistory.slice(0, 5);
|
||||
}
|
||||
|
||||
if (!this.organization && c.folderId != null && groupingsMap.has(c.folderId)) {
|
||||
this.result.folderRelationships.push([
|
||||
this.result.ciphers.length,
|
||||
groupingsMap.get(c.folderId),
|
||||
]);
|
||||
} else if (this.organization && c.collectionIds != null) {
|
||||
c.collectionIds.forEach((cId) => {
|
||||
if (groupingsMap.has(cId)) {
|
||||
this.result.collectionRelationships.push([
|
||||
this.result.ciphers.length,
|
||||
groupingsMap.get(cId),
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const view = await cipher.decrypt();
|
||||
this.cleanupCipher(view);
|
||||
this.result.ciphers.push(view);
|
||||
}
|
||||
|
||||
this.result.success = true;
|
||||
}
|
||||
|
||||
private parseDecrypted() {
|
||||
const groupingsMap = new Map<string, number>();
|
||||
if (this.organization && this.results.collections != null) {
|
||||
this.results.collections.forEach((c: CollectionWithIdExport) => {
|
||||
const collection = CollectionWithIdExport.toView(c);
|
||||
if (collection != null) {
|
||||
collection.id = null;
|
||||
collection.organizationId = null;
|
||||
groupingsMap.set(c.id, this.result.collections.length);
|
||||
this.result.collections.push(collection);
|
||||
}
|
||||
});
|
||||
} else if (!this.organization && this.results.folders != null) {
|
||||
this.results.folders.forEach((f: FolderWithIdExport) => {
|
||||
const folder = FolderWithIdExport.toView(f);
|
||||
if (folder != null) {
|
||||
folder.id = null;
|
||||
groupingsMap.set(f.id, this.result.folders.length);
|
||||
this.result.folders.push(folder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.results.items.forEach((c: CipherWithIdExport) => {
|
||||
const cipher = CipherWithIdExport.toView(c);
|
||||
// reset ids incase they were set for some reason
|
||||
cipher.id = null;
|
||||
cipher.folderId = null;
|
||||
cipher.organizationId = null;
|
||||
cipher.collectionIds = null;
|
||||
|
||||
// make sure password history is limited
|
||||
if (cipher.passwordHistory != null && cipher.passwordHistory.length > 5) {
|
||||
cipher.passwordHistory = cipher.passwordHistory.slice(0, 5);
|
||||
}
|
||||
|
||||
if (!this.organization && c.folderId != null && groupingsMap.has(c.folderId)) {
|
||||
this.result.folderRelationships.push([
|
||||
this.result.ciphers.length,
|
||||
groupingsMap.get(c.folderId),
|
||||
]);
|
||||
} else if (this.organization && c.collectionIds != null) {
|
||||
c.collectionIds.forEach((cId) => {
|
||||
if (groupingsMap.has(cId)) {
|
||||
this.result.collectionRelationships.push([
|
||||
this.result.ciphers.length,
|
||||
groupingsMap.get(cId),
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
this.result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
this.result.success = true;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
import { KdfConfig } from "../auth/models/domain/kdf-config";
|
||||
import { KdfType } from "../enums/kdfType";
|
||||
import { EncString } from "../models/domain/enc-string";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||
|
||||
import { BitwardenJsonImporter } from "./bitwarden-json-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
interface BitwardenPasswordProtectedFileFormat {
|
||||
encrypted: boolean;
|
||||
passwordProtected: boolean;
|
||||
salt: string;
|
||||
kdfIterations: number;
|
||||
kdfType: number;
|
||||
encKeyValidation_DO_NOT_EDIT: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter implements Importer {
|
||||
private key: SymmetricCryptoKey;
|
||||
|
||||
constructor(cryptoService: CryptoService, i18nService: I18nService, private password: string) {
|
||||
super(cryptoService, i18nService);
|
||||
}
|
||||
|
||||
async parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const parsedData = JSON.parse(data);
|
||||
if (this.cannotParseFile(parsedData)) {
|
||||
result.success = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!(await this.checkPassword(parsedData))) {
|
||||
result.success = false;
|
||||
result.errorMessage = this.i18nService.t("invalidFilePassword");
|
||||
return result;
|
||||
}
|
||||
|
||||
const encData = new EncString(parsedData.data);
|
||||
const clearTextData = await this.cryptoService.decryptToUtf8(encData, this.key);
|
||||
return await super.parse(clearTextData);
|
||||
}
|
||||
|
||||
private async checkPassword(jdoc: BitwardenPasswordProtectedFileFormat): Promise<boolean> {
|
||||
this.key = await this.cryptoService.makePinKey(
|
||||
this.password,
|
||||
jdoc.salt,
|
||||
KdfType.PBKDF2_SHA256,
|
||||
new KdfConfig(jdoc.kdfIterations)
|
||||
);
|
||||
|
||||
const encKeyValidation = new EncString(jdoc.encKeyValidation_DO_NOT_EDIT);
|
||||
|
||||
const encKeyValidationDecrypt = await this.cryptoService.decryptToUtf8(
|
||||
encKeyValidation,
|
||||
this.key
|
||||
);
|
||||
if (encKeyValidationDecrypt === null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private cannotParseFile(jdoc: BitwardenPasswordProtectedFileFormat): boolean {
|
||||
return (
|
||||
!jdoc ||
|
||||
!jdoc.encrypted ||
|
||||
!jdoc.passwordProtected ||
|
||||
!jdoc.salt ||
|
||||
!jdoc.kdfIterations ||
|
||||
typeof jdoc.kdfIterations !== "number" ||
|
||||
jdoc.kdfType == null ||
|
||||
KdfType[jdoc.kdfType] == null ||
|
||||
!jdoc.encKeyValidation_DO_NOT_EDIT ||
|
||||
!jdoc.data
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class BlackBerryCsvImporter 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((value) => {
|
||||
if (value.grouping === "list") {
|
||||
return;
|
||||
}
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.favorite = value.fav === "1";
|
||||
cipher.name = this.getValueOrDefault(value.name);
|
||||
cipher.notes = this.getValueOrDefault(value.extra);
|
||||
if (value.grouping !== "note") {
|
||||
cipher.login.uris = this.makeUriArray(value.url);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
cipher.login.username = this.getValueOrDefault(value.username);
|
||||
}
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class BlurCsvImporter 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((value) => {
|
||||
if (value.label === "null") {
|
||||
value.label = null;
|
||||
}
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(
|
||||
value.label,
|
||||
this.getValueOrDefault(this.nameFromUrl(value.domain), "--")
|
||||
);
|
||||
cipher.login.uris = this.makeUriArray(value.domain);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
|
||||
if (this.isNullOrWhitespace(value.email) && !this.isNullOrWhitespace(value.username)) {
|
||||
cipher.login.username = value.username;
|
||||
} else {
|
||||
cipher.login.username = this.getValueOrDefault(value.email);
|
||||
cipher.notes = this.getValueOrDefault(value.username);
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
const OfficialProps = ["!group_id", "!group_name", "title", "username", "password", "URL", "id"];
|
||||
|
||||
export class ButtercupCsvImporter 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((value) => {
|
||||
this.processFolder(result, this.getValueOrDefault(value["!group_name"]));
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value.title, "--");
|
||||
cipher.login.username = this.getValueOrDefault(value.username);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
cipher.login.uris = this.makeUriArray(value.URL);
|
||||
|
||||
let processingCustomFields = false;
|
||||
for (const prop in value) {
|
||||
// eslint-disable-next-line
|
||||
if (value.hasOwnProperty(prop)) {
|
||||
if (!processingCustomFields && OfficialProps.indexOf(prop) === -1) {
|
||||
processingCustomFields = true;
|
||||
}
|
||||
if (processingCustomFields) {
|
||||
this.processKvp(cipher, prop, value[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class ChromeCsvImporter extends BaseImporter implements Importer {
|
||||
private androidPatternRegex = new RegExp("^android:\\/\\/.*(?<=@)(.*)(?=\\/)");
|
||||
|
||||
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((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
let name = value.name;
|
||||
if (!name && this.androidPatternRegex.test(value.url)) {
|
||||
name = value.url.match(this.androidPatternRegex)[1];
|
||||
}
|
||||
cipher.name = this.getValueOrDefault(name, "--");
|
||||
cipher.login.username = this.getValueOrDefault(value.username);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
cipher.login.uris = this.makeUriArray(value.url);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class ClipperzHtmlImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const doc = this.parseXml(data);
|
||||
if (doc == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
const textarea = doc.querySelector("textarea");
|
||||
if (textarea == null || this.isNullOrWhitespace(textarea.textContent)) {
|
||||
result.errorMessage = "Missing textarea.";
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
const entries = JSON.parse(textarea.textContent);
|
||||
entries.forEach((entry: any) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
if (!this.isNullOrWhitespace(entry.label)) {
|
||||
cipher.name = entry.label.split(" ")[0];
|
||||
}
|
||||
if (entry.data != null && !this.isNullOrWhitespace(entry.data.notes)) {
|
||||
cipher.notes = entry.data.notes.split("\\n").join("\n");
|
||||
}
|
||||
|
||||
if (entry.currentVersion != null && entry.currentVersion.fields != null) {
|
||||
for (const property in entry.currentVersion.fields) {
|
||||
// eslint-disable-next-line
|
||||
if (!entry.currentVersion.fields.hasOwnProperty(property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const field = entry.currentVersion.fields[property];
|
||||
const actionType = field.actionType != null ? field.actionType.toLowerCase() : null;
|
||||
switch (actionType) {
|
||||
case "password":
|
||||
cipher.login.password = this.getValueOrDefault(field.value);
|
||||
break;
|
||||
case "email":
|
||||
case "username":
|
||||
case "user":
|
||||
case "name":
|
||||
cipher.login.username = this.getValueOrDefault(field.value);
|
||||
break;
|
||||
case "url":
|
||||
cipher.login.uris = this.makeUriArray(field.value);
|
||||
break;
|
||||
default: {
|
||||
const labelLower = field.label != null ? field.label.toLowerCase() : null;
|
||||
if (
|
||||
cipher.login.password == null &&
|
||||
this.passwordFieldNames.indexOf(labelLower) > -1
|
||||
) {
|
||||
cipher.login.password = this.getValueOrDefault(field.value);
|
||||
} else if (
|
||||
cipher.login.username == null &&
|
||||
this.usernameFieldNames.indexOf(labelLower) > -1
|
||||
) {
|
||||
cipher.login.username = this.getValueOrDefault(field.value);
|
||||
} else if (
|
||||
(cipher.login.uris == null || cipher.login.uris.length === 0) &&
|
||||
this.uriFieldNames.indexOf(labelLower) > -1
|
||||
) {
|
||||
cipher.login.uris = this.makeUriArray(field.value);
|
||||
} else {
|
||||
this.processKvp(cipher, field.label, field.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class CodebookCsvImporter 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((value) => {
|
||||
this.processFolder(result, this.getValueOrDefault(value.Category));
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.favorite = this.getValueOrDefault(value.Favorite) === "True";
|
||||
cipher.name = this.getValueOrDefault(value.Entry, "--");
|
||||
cipher.notes = this.getValueOrDefault(value.Note);
|
||||
cipher.login.username = this.getValueOrDefault(value.Username, value.Email);
|
||||
cipher.login.password = this.getValueOrDefault(value.Password);
|
||||
cipher.login.totp = this.getValueOrDefault(value.TOTP);
|
||||
cipher.login.uris = this.makeUriArray(value.Website);
|
||||
|
||||
if (!this.isNullOrWhitespace(value.Username)) {
|
||||
this.processKvp(cipher, "Email", value.Email);
|
||||
}
|
||||
this.processKvp(cipher, "Phone", value.Phone);
|
||||
this.processKvp(cipher, "PIN", value.PIN);
|
||||
this.processKvp(cipher, "Account", value.Account);
|
||||
this.processKvp(cipher, "Date", value.Date);
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
import { SecureNoteType } from "../../enums/secureNoteType";
|
||||
import { ImportResult } from "../../models/domain/import-result";
|
||||
import { CipherType } from "../../vault/enums/cipher-type";
|
||||
import { CardView } from "../../vault/models/view/card.view";
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
import { IdentityView } from "../../vault/models/view/identity.view";
|
||||
import { LoginView } from "../../vault/models/view/login.view";
|
||||
import { BaseImporter } from "../base-importer";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import {
|
||||
CredentialsRecord,
|
||||
IdRecord,
|
||||
PaymentsRecord,
|
||||
PersonalInformationRecord,
|
||||
SecureNoteRecord,
|
||||
} from "./types/dashlane-csv-types";
|
||||
|
||||
const _mappedCredentialsColums = new Set([
|
||||
"title",
|
||||
"note",
|
||||
"username",
|
||||
"password",
|
||||
"url",
|
||||
"otpSecret",
|
||||
"category",
|
||||
]);
|
||||
|
||||
const _mappedPersonalInfoAsIdentiyColumns = new Set([
|
||||
"type",
|
||||
"title",
|
||||
"first_name",
|
||||
"middle_name",
|
||||
"last_name",
|
||||
"login",
|
||||
"email",
|
||||
"phone_number",
|
||||
"address",
|
||||
"country",
|
||||
"state",
|
||||
"city",
|
||||
"zip",
|
||||
// Skip item_name as we already have set a combined name
|
||||
"item_name",
|
||||
]);
|
||||
|
||||
const _mappedSecureNoteColumns = new Set(["title", "note"]);
|
||||
|
||||
export class DashlaneCsvImporter 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);
|
||||
}
|
||||
|
||||
if (results[0].type != null && results[0].title != null) {
|
||||
const personalRecords = results as PersonalInformationRecord[];
|
||||
|
||||
// If personalRecords has only one "name" then create an Identity-Cipher
|
||||
if (personalRecords.filter((x) => x.type === "name").length === 1) {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
results.forEach((row) => {
|
||||
this.parsePersonalInformationRecordAsIdentity(cipher, row);
|
||||
});
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
|
||||
results.forEach((row) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
|
||||
const rowKeys = Object.keys(row);
|
||||
if (rowKeys[0] === "username") {
|
||||
this.processFolder(result, row.category);
|
||||
this.parseCredentialsRecord(cipher, row);
|
||||
}
|
||||
|
||||
if (rowKeys[0] === "type" && rowKeys[1] === "account_name") {
|
||||
this.parsePaymentRecord(cipher, row);
|
||||
}
|
||||
|
||||
if (rowKeys[0] === "type" && rowKeys[1] === "number") {
|
||||
this.parseIdRecord(cipher, row);
|
||||
}
|
||||
|
||||
if ((rowKeys[0] === "type") != null && rowKeys[1] === "title") {
|
||||
this.parsePersonalInformationRecord(cipher, row);
|
||||
}
|
||||
|
||||
if (rowKeys[0] === "title" && rowKeys[1] === "note") {
|
||||
this.parseSecureNoteRecords(cipher, row);
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
parseCredentialsRecord(cipher: CipherView, row: CredentialsRecord) {
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.login = new LoginView();
|
||||
|
||||
cipher.name = row.title;
|
||||
cipher.notes = row.note;
|
||||
cipher.login.username = row.username;
|
||||
cipher.login.password = row.password;
|
||||
cipher.login.totp = row.otpSecret;
|
||||
cipher.login.uris = this.makeUriArray(row.url);
|
||||
|
||||
this.importUnmappedFields(cipher, row, _mappedCredentialsColums);
|
||||
}
|
||||
|
||||
parsePaymentRecord(cipher: CipherView, row: PaymentsRecord) {
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
|
||||
cipher.name = row.account_name;
|
||||
let mappedValues: string[] = [];
|
||||
switch (row.type) {
|
||||
case "credit_card":
|
||||
cipher.card.cardholderName = row.account_name;
|
||||
cipher.card.number = row.cc_number;
|
||||
cipher.card.brand = this.getCardBrand(cipher.card.number);
|
||||
cipher.card.code = row.code;
|
||||
this.setCardExpiration(cipher, `${row.expiration_month}/${row.expiration_year}`);
|
||||
|
||||
// If you add more mapped fields please extend this
|
||||
mappedValues = [
|
||||
"account_name",
|
||||
"account_holder",
|
||||
"cc_number",
|
||||
"code",
|
||||
"expiration_month",
|
||||
"expiration_year",
|
||||
];
|
||||
break;
|
||||
case "bank":
|
||||
cipher.card.cardholderName = row.account_holder;
|
||||
cipher.card.number = row.account_number;
|
||||
|
||||
// If you add more mapped fields please extend this
|
||||
mappedValues = ["account_name", "account_holder", "account_number"];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.importUnmappedFields(cipher, row, new Set(mappedValues));
|
||||
}
|
||||
|
||||
parseIdRecord(cipher: CipherView, row: IdRecord) {
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
|
||||
const mappedValues: string[] = ["name", "number"];
|
||||
switch (row.type) {
|
||||
case "card":
|
||||
cipher.name = `${row.name} ${row.type}`;
|
||||
this.processFullName(cipher, row.name);
|
||||
cipher.identity.licenseNumber = row.number;
|
||||
break;
|
||||
case "passport":
|
||||
cipher.name = `${row.name} ${row.type}`;
|
||||
this.processFullName(cipher, row.name);
|
||||
cipher.identity.passportNumber = row.number;
|
||||
break;
|
||||
case "license":
|
||||
cipher.name = `${row.name} ${row.type}`;
|
||||
this.processFullName(cipher, row.name);
|
||||
cipher.identity.licenseNumber = row.number;
|
||||
cipher.identity.state = row.state;
|
||||
|
||||
mappedValues.push("state");
|
||||
break;
|
||||
case "social_security":
|
||||
cipher.name = `${row.name} ${row.type}`;
|
||||
this.processFullName(cipher, row.name);
|
||||
cipher.identity.ssn = row.number;
|
||||
break;
|
||||
case "tax_number":
|
||||
cipher.name = row.type;
|
||||
cipher.identity.licenseNumber = row.number;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// If you add more mapped fields please extend this
|
||||
this.importUnmappedFields(cipher, row, new Set(mappedValues));
|
||||
}
|
||||
|
||||
parsePersonalInformationRecord(cipher: CipherView, row: PersonalInformationRecord) {
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
if (row.type === "name") {
|
||||
cipher.name = `${row.title} ${row.first_name} ${row.middle_name} ${row.last_name}`
|
||||
.replace(" ", " ")
|
||||
.trim();
|
||||
} else {
|
||||
cipher.name = row.item_name;
|
||||
}
|
||||
|
||||
const dataRow = row as any;
|
||||
Object.keys(row).forEach((key) => {
|
||||
this.processKvp(cipher, key, dataRow[key]);
|
||||
});
|
||||
}
|
||||
|
||||
parsePersonalInformationRecordAsIdentity(cipher: CipherView, row: PersonalInformationRecord) {
|
||||
switch (row.type) {
|
||||
case "name":
|
||||
this.processFullName(cipher, `${row.first_name} ${row.middle_name} ${row.last_name}`);
|
||||
cipher.identity.title = row.title;
|
||||
cipher.name = cipher.identity.fullName;
|
||||
|
||||
cipher.identity.username = row.login;
|
||||
break;
|
||||
case "email":
|
||||
cipher.identity.email = row.email;
|
||||
break;
|
||||
case "number":
|
||||
cipher.identity.phone = row.phone_number;
|
||||
break;
|
||||
case "address":
|
||||
cipher.identity.address1 = row.address;
|
||||
cipher.identity.city = row.city;
|
||||
cipher.identity.postalCode = row.zip;
|
||||
cipher.identity.state = row.state;
|
||||
cipher.identity.country = row.country;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.importUnmappedFields(cipher, row, _mappedPersonalInfoAsIdentiyColumns);
|
||||
}
|
||||
|
||||
parseSecureNoteRecords(cipher: CipherView, row: SecureNoteRecord) {
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
cipher.name = row.title;
|
||||
cipher.notes = row.note;
|
||||
|
||||
this.importUnmappedFields(cipher, row, _mappedSecureNoteColumns);
|
||||
}
|
||||
|
||||
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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
import { SecureNoteType } from "../../enums/secureNoteType";
|
||||
import { ImportResult } from "../../models/domain/import-result";
|
||||
import { CipherType } from "../../vault/enums/cipher-type";
|
||||
import { CardView } from "../../vault/models/view/card.view";
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
import { IdentityView } from "../../vault/models/view/identity.view";
|
||||
import { SecureNoteView } from "../../vault/models/view/secure-note.view";
|
||||
import { BaseImporter } from "../base-importer";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
const HandledResults = new Set([
|
||||
"ADDRESS",
|
||||
"AUTHENTIFIANT",
|
||||
"BANKSTATEMENT",
|
||||
"IDCARD",
|
||||
"IDENTITY",
|
||||
"PAYMENTMEANS_CREDITCARD",
|
||||
"PAYMENTMEAN_PAYPAL",
|
||||
"EMAIL",
|
||||
]);
|
||||
|
||||
export class DashlaneJsonImporter extends BaseImporter implements Importer {
|
||||
private result: ImportResult;
|
||||
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
this.result = new ImportResult();
|
||||
const results = JSON.parse(data);
|
||||
if (results == null || results.length === 0) {
|
||||
this.result.success = false;
|
||||
return Promise.resolve(this.result);
|
||||
}
|
||||
|
||||
if (results.ADDRESS != null) {
|
||||
this.processAddress(results.ADDRESS);
|
||||
}
|
||||
if (results.AUTHENTIFIANT != null) {
|
||||
this.processAuth(results.AUTHENTIFIANT);
|
||||
}
|
||||
if (results.BANKSTATEMENT != null) {
|
||||
this.processNote(results.BANKSTATEMENT, "BankAccountName");
|
||||
}
|
||||
if (results.IDCARD != null) {
|
||||
this.processNote(results.IDCARD, "Fullname");
|
||||
}
|
||||
if (results.PAYMENTMEANS_CREDITCARD != null) {
|
||||
this.processCard(results.PAYMENTMEANS_CREDITCARD);
|
||||
}
|
||||
if (results.IDENTITY != null) {
|
||||
this.processIdentity(results.IDENTITY);
|
||||
}
|
||||
|
||||
for (const key in results) {
|
||||
// eslint-disable-next-line
|
||||
if (results.hasOwnProperty(key) && !HandledResults.has(key)) {
|
||||
this.processNote(results[key], null, "Generic Note");
|
||||
}
|
||||
}
|
||||
|
||||
this.result.success = true;
|
||||
return Promise.resolve(this.result);
|
||||
}
|
||||
|
||||
private processAuth(results: any[]) {
|
||||
results.forEach((credential: any) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(credential.title);
|
||||
|
||||
cipher.login.username = this.getValueOrDefault(
|
||||
credential.login,
|
||||
this.getValueOrDefault(credential.secondaryLogin)
|
||||
);
|
||||
if (this.isNullOrWhitespace(cipher.login.username)) {
|
||||
cipher.login.username = this.getValueOrDefault(credential.email);
|
||||
} else if (!this.isNullOrWhitespace(credential.email)) {
|
||||
cipher.notes = "Email: " + credential.email + "\n";
|
||||
}
|
||||
|
||||
cipher.login.password = this.getValueOrDefault(credential.password);
|
||||
cipher.login.uris = this.makeUriArray(credential.domain);
|
||||
cipher.notes += this.getValueOrDefault(credential.note, "");
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
this.result.ciphers.push(cipher);
|
||||
});
|
||||
}
|
||||
|
||||
private processIdentity(results: any[]) {
|
||||
results.forEach((obj: any) => {
|
||||
const cipher = new CipherView();
|
||||
cipher.identity = new IdentityView();
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.name = this.getValueOrDefault(obj.fullName, "");
|
||||
const nameParts = cipher.name.split(" ");
|
||||
if (nameParts.length > 0) {
|
||||
cipher.identity.firstName = this.getValueOrDefault(nameParts[0]);
|
||||
}
|
||||
if (nameParts.length === 2) {
|
||||
cipher.identity.lastName = this.getValueOrDefault(nameParts[1]);
|
||||
} else if (nameParts.length === 3) {
|
||||
cipher.identity.middleName = this.getValueOrDefault(nameParts[1]);
|
||||
cipher.identity.lastName = this.getValueOrDefault(nameParts[2]);
|
||||
}
|
||||
cipher.identity.username = this.getValueOrDefault(obj.pseudo);
|
||||
this.cleanupCipher(cipher);
|
||||
this.result.ciphers.push(cipher);
|
||||
});
|
||||
}
|
||||
|
||||
private processAddress(results: any[]) {
|
||||
results.forEach((obj: any) => {
|
||||
const cipher = new CipherView();
|
||||
cipher.identity = new IdentityView();
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.name = this.getValueOrDefault(obj.addressName);
|
||||
cipher.identity.address1 = this.getValueOrDefault(obj.addressFull);
|
||||
cipher.identity.city = this.getValueOrDefault(obj.city);
|
||||
cipher.identity.state = this.getValueOrDefault(obj.state);
|
||||
cipher.identity.postalCode = this.getValueOrDefault(obj.zipcode);
|
||||
cipher.identity.country = this.getValueOrDefault(obj.country);
|
||||
if (cipher.identity.country != null) {
|
||||
cipher.identity.country = cipher.identity.country.toUpperCase();
|
||||
}
|
||||
this.cleanupCipher(cipher);
|
||||
this.result.ciphers.push(cipher);
|
||||
});
|
||||
}
|
||||
|
||||
private processCard(results: any[]) {
|
||||
results.forEach((obj: any) => {
|
||||
const cipher = new CipherView();
|
||||
cipher.card = new CardView();
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.name = this.getValueOrDefault(obj.bank);
|
||||
cipher.card.number = this.getValueOrDefault(obj.cardNumber);
|
||||
cipher.card.brand = this.getCardBrand(cipher.card.number);
|
||||
cipher.card.cardholderName = this.getValueOrDefault(obj.owner);
|
||||
if (!this.isNullOrWhitespace(cipher.card.brand)) {
|
||||
if (this.isNullOrWhitespace(cipher.name)) {
|
||||
cipher.name = cipher.card.brand;
|
||||
} else {
|
||||
cipher.name += " - " + cipher.card.brand;
|
||||
}
|
||||
}
|
||||
this.cleanupCipher(cipher);
|
||||
this.result.ciphers.push(cipher);
|
||||
});
|
||||
}
|
||||
|
||||
private processNote(results: any[], nameProperty: string, name: string = null) {
|
||||
results.forEach((obj: any) => {
|
||||
const cipher = new CipherView();
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
if (name != null) {
|
||||
cipher.name = name;
|
||||
} else {
|
||||
cipher.name = this.getValueOrDefault(obj[nameProperty]);
|
||||
}
|
||||
for (const key in obj) {
|
||||
// eslint-disable-next-line
|
||||
if (obj.hasOwnProperty(key) && key !== nameProperty) {
|
||||
this.processKvp(cipher, key, obj[key].toString());
|
||||
}
|
||||
}
|
||||
this.cleanupCipher(cipher);
|
||||
this.result.ciphers.push(cipher);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
// tslint:disable
|
||||
export class CredentialsRecord {
|
||||
username: string;
|
||||
username2: string;
|
||||
username3: string;
|
||||
title: string;
|
||||
password: string;
|
||||
note: string;
|
||||
url: string;
|
||||
category: string;
|
||||
otpSecret: string;
|
||||
}
|
||||
|
||||
export class PaymentsRecord {
|
||||
type: string;
|
||||
account_name: string;
|
||||
account_holder: string;
|
||||
cc_number: string;
|
||||
code: string;
|
||||
expiration_month: string;
|
||||
expiration_year: string;
|
||||
routing_number: string;
|
||||
account_number: string;
|
||||
country: string;
|
||||
issuing_bank: string;
|
||||
}
|
||||
|
||||
export class IdRecord {
|
||||
type: string;
|
||||
number: string;
|
||||
name: string;
|
||||
issue_date: string;
|
||||
expiration_date: string;
|
||||
place_of_issue: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
export class PersonalInformationRecord {
|
||||
type: string;
|
||||
title: string;
|
||||
first_name: string;
|
||||
middle_name: string;
|
||||
last_name: string;
|
||||
login: string;
|
||||
date_of_birth: string;
|
||||
place_of_birth: string;
|
||||
email: string;
|
||||
email_type: string;
|
||||
item_name: string;
|
||||
phone_number: string;
|
||||
address: string;
|
||||
country: string;
|
||||
state: string;
|
||||
city: string;
|
||||
zip: string;
|
||||
address_recipient: string;
|
||||
address_building: string;
|
||||
address_apartment: string;
|
||||
address_floor: string;
|
||||
address_door_code: string;
|
||||
job_title: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class SecureNoteRecord {
|
||||
title: string;
|
||||
note: string;
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherType } from "../vault/enums/cipher-type";
|
||||
import { CardView } from "../vault/models/view/card.view";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class EncryptrCsvImporter 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((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value.Label, "--");
|
||||
cipher.notes = this.getValueOrDefault(value.Notes);
|
||||
const text = this.getValueOrDefault(value.Text);
|
||||
if (!this.isNullOrWhitespace(text)) {
|
||||
if (this.isNullOrWhitespace(cipher.notes)) {
|
||||
cipher.notes = text;
|
||||
} else {
|
||||
cipher.notes += "\n\n" + text;
|
||||
}
|
||||
}
|
||||
|
||||
const type = value["Entry Type"];
|
||||
if (type === "Password") {
|
||||
cipher.login.username = this.getValueOrDefault(value.Username);
|
||||
cipher.login.password = this.getValueOrDefault(value.Password);
|
||||
cipher.login.uris = this.makeUriArray(value["Site URL"]);
|
||||
} else if (type === "Credit Card") {
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
cipher.card.cardholderName = this.getValueOrDefault(value["Name on card"]);
|
||||
cipher.card.number = this.getValueOrDefault(value["Card Number"]);
|
||||
cipher.card.brand = this.getCardBrand(cipher.card.number);
|
||||
cipher.card.code = this.getValueOrDefault(value.CVV);
|
||||
const expiry = this.getValueOrDefault(value.Expiry);
|
||||
if (!this.isNullOrWhitespace(expiry)) {
|
||||
const expParts = expiry.split("/");
|
||||
if (expParts.length > 1) {
|
||||
cipher.card.expMonth = parseInt(expParts[0], null).toString();
|
||||
cipher.card.expYear = (2000 + parseInt(expParts[1], null)).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import { SecureNoteType } from "../../enums/secureNoteType";
|
||||
import { ImportResult } from "../../models/domain/import-result";
|
||||
import { CipherType } from "../../vault/enums/cipher-type";
|
||||
import { CardView } from "../../vault/models/view/card.view";
|
||||
import { SecureNoteView } from "../../vault/models/view/secure-note.view";
|
||||
import { BaseImporter } from "../base-importer";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
export class EnpassCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, false);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
let firstRow = true;
|
||||
results.forEach((value) => {
|
||||
if (value.length < 2 || (firstRow && (value[0] === "Title" || value[0] === "title"))) {
|
||||
firstRow = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.notes = this.getValueOrDefault(value[value.length - 1]);
|
||||
cipher.name = this.getValueOrDefault(value[0], "--");
|
||||
|
||||
if (
|
||||
value.length === 2 ||
|
||||
(!this.containsField(value, "username") &&
|
||||
!this.containsField(value, "password") &&
|
||||
!this.containsField(value, "email") &&
|
||||
!this.containsField(value, "url"))
|
||||
) {
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
}
|
||||
|
||||
if (
|
||||
this.containsField(value, "cardholder") &&
|
||||
this.containsField(value, "number") &&
|
||||
this.containsField(value, "expiry date")
|
||||
) {
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
}
|
||||
|
||||
if (value.length > 2 && value.length % 2 === 0) {
|
||||
for (let i = 0; i < value.length - 2; i += 2) {
|
||||
const fieldValue: string = value[i + 2];
|
||||
if (this.isNullOrWhitespace(fieldValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fieldName: string = value[i + 1];
|
||||
const fieldNameLower = fieldName.toLowerCase();
|
||||
|
||||
if (cipher.type === CipherType.Login) {
|
||||
if (
|
||||
fieldNameLower === "url" &&
|
||||
(cipher.login.uris == null || cipher.login.uris.length === 0)
|
||||
) {
|
||||
cipher.login.uris = this.makeUriArray(fieldValue);
|
||||
continue;
|
||||
} else if (
|
||||
(fieldNameLower === "username" || fieldNameLower === "email") &&
|
||||
this.isNullOrWhitespace(cipher.login.username)
|
||||
) {
|
||||
cipher.login.username = fieldValue;
|
||||
continue;
|
||||
} else if (
|
||||
fieldNameLower === "password" &&
|
||||
this.isNullOrWhitespace(cipher.login.password)
|
||||
) {
|
||||
cipher.login.password = fieldValue;
|
||||
continue;
|
||||
} else if (fieldNameLower === "totp" && this.isNullOrWhitespace(cipher.login.totp)) {
|
||||
cipher.login.totp = fieldValue;
|
||||
continue;
|
||||
}
|
||||
} else if (cipher.type === CipherType.Card) {
|
||||
if (
|
||||
fieldNameLower === "cardholder" &&
|
||||
this.isNullOrWhitespace(cipher.card.cardholderName)
|
||||
) {
|
||||
cipher.card.cardholderName = fieldValue;
|
||||
continue;
|
||||
} else if (fieldNameLower === "number" && this.isNullOrWhitespace(cipher.card.number)) {
|
||||
cipher.card.number = fieldValue;
|
||||
cipher.card.brand = this.getCardBrand(fieldValue);
|
||||
continue;
|
||||
} else if (fieldNameLower === "cvc" && this.isNullOrWhitespace(cipher.card.code)) {
|
||||
cipher.card.code = fieldValue;
|
||||
continue;
|
||||
} else if (
|
||||
fieldNameLower === "expiry date" &&
|
||||
this.isNullOrWhitespace(cipher.card.expMonth) &&
|
||||
this.isNullOrWhitespace(cipher.card.expYear)
|
||||
) {
|
||||
if (this.setCardExpiration(cipher, fieldValue)) {
|
||||
continue;
|
||||
}
|
||||
} else if (fieldNameLower === "type") {
|
||||
// Skip since brand was determined from number above
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
this.processKvp(cipher, fieldName, fieldValue);
|
||||
}
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private containsField(fields: any[], name: string) {
|
||||
if (fields == null || name == null) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
fields.filter((f) => !this.isNullOrWhitespace(f) && f.toLowerCase() === name.toLowerCase())
|
||||
.length > 0
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
import { FieldType } from "../../enums/fieldType";
|
||||
import { ImportResult } from "../../models/domain/import-result";
|
||||
import { CipherType } from "../../vault/enums/cipher-type";
|
||||
import { CardView } from "../../vault/models/view/card.view";
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
import { FolderView } from "../../vault/models/view/folder.view";
|
||||
import { BaseImporter } from "../base-importer";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { EnpassJsonFile, EnpassFolder, EnpassField } from "./types/enpass-json-type";
|
||||
|
||||
type EnpassFolderTreeItem = EnpassFolder & { children: EnpassFolderTreeItem[] };
|
||||
const androidUrlRegex = new RegExp("androidapp://.*==@", "g");
|
||||
|
||||
export class EnpassJsonImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results: EnpassJsonFile = JSON.parse(data);
|
||||
if (results == null || results.items == null || results.items.length === 0) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
const foldersMap = new Map<string, string>();
|
||||
const foldersIndexMap = new Map<string, number>();
|
||||
const folderTree = this.buildFolderTree(results.folders);
|
||||
this.flattenFolderTree(null, folderTree, foldersMap);
|
||||
foldersMap.forEach((val, key) => {
|
||||
foldersIndexMap.set(key, result.folders.length);
|
||||
const f = new FolderView();
|
||||
f.name = val;
|
||||
result.folders.push(f);
|
||||
});
|
||||
|
||||
results.items.forEach((item) => {
|
||||
if (item.folders != null && item.folders.length > 0 && foldersIndexMap.has(item.folders[0])) {
|
||||
result.folderRelationships.push([
|
||||
result.ciphers.length,
|
||||
foldersIndexMap.get(item.folders[0]),
|
||||
]);
|
||||
}
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(item.title);
|
||||
cipher.favorite = item.favorite > 0;
|
||||
|
||||
if (item.template_type != null && item.fields != null && item.fields.length > 0) {
|
||||
if (
|
||||
item.template_type.indexOf("login.") === 0 ||
|
||||
item.template_type.indexOf("password.") === 0
|
||||
) {
|
||||
this.processLogin(cipher, item.fields);
|
||||
} else if (item.template_type.indexOf("creditcard.") === 0) {
|
||||
this.processCard(cipher, item.fields);
|
||||
} else if (
|
||||
item.template_type.indexOf("identity.") < 0 &&
|
||||
item.fields.some((f) => f.type === "password" && !this.isNullOrWhitespace(f.value))
|
||||
) {
|
||||
this.processLogin(cipher, item.fields);
|
||||
} else {
|
||||
this.processNote(cipher, item.fields);
|
||||
}
|
||||
}
|
||||
|
||||
cipher.notes += "\n" + this.getValueOrDefault(item.note, "");
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private processLogin(cipher: CipherView, fields: EnpassField[]) {
|
||||
const urls: string[] = [];
|
||||
fields.forEach((field) => {
|
||||
if (this.isNullOrWhitespace(field.value) || field.type === "section") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(field.type === "username" || field.type === "email") &&
|
||||
this.isNullOrWhitespace(cipher.login.username)
|
||||
) {
|
||||
cipher.login.username = field.value;
|
||||
} else if (field.type === "password" && this.isNullOrWhitespace(cipher.login.password)) {
|
||||
cipher.login.password = field.value;
|
||||
} else if (field.type === "totp" && this.isNullOrWhitespace(cipher.login.totp)) {
|
||||
cipher.login.totp = field.value;
|
||||
} else if (field.type === "url") {
|
||||
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 {
|
||||
this.processKvp(
|
||||
cipher,
|
||||
field.label,
|
||||
field.value,
|
||||
field.sensitive === 1 ? FieldType.Hidden : FieldType.Text
|
||||
);
|
||||
}
|
||||
});
|
||||
cipher.login.uris = this.makeUriArray(urls);
|
||||
}
|
||||
|
||||
private processCard(cipher: CipherView, fields: EnpassField[]) {
|
||||
cipher.card = new CardView();
|
||||
cipher.type = CipherType.Card;
|
||||
fields.forEach((field) => {
|
||||
if (
|
||||
this.isNullOrWhitespace(field.value) ||
|
||||
field.type === "section" ||
|
||||
field.type === "ccType"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.type === "ccName" && this.isNullOrWhitespace(cipher.card.cardholderName)) {
|
||||
cipher.card.cardholderName = field.value;
|
||||
} else if (field.type === "ccNumber" && this.isNullOrWhitespace(cipher.card.number)) {
|
||||
cipher.card.number = field.value;
|
||||
cipher.card.brand = this.getCardBrand(cipher.card.number);
|
||||
} else if (field.type === "ccCvc" && this.isNullOrWhitespace(cipher.card.code)) {
|
||||
cipher.card.code = field.value;
|
||||
} else if (field.type === "ccExpiry" && this.isNullOrWhitespace(cipher.card.expYear)) {
|
||||
if (!this.setCardExpiration(cipher, field.value)) {
|
||||
this.processKvp(
|
||||
cipher,
|
||||
field.label,
|
||||
field.value,
|
||||
field.sensitive === 1 ? FieldType.Hidden : FieldType.Text
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.processKvp(
|
||||
cipher,
|
||||
field.label,
|
||||
field.value,
|
||||
field.sensitive === 1 ? FieldType.Hidden : FieldType.Text
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private processNote(cipher: CipherView, fields: EnpassField[]) {
|
||||
fields.forEach((field) => {
|
||||
if (this.isNullOrWhitespace(field.value) || field.type === "section") {
|
||||
return;
|
||||
}
|
||||
this.processKvp(
|
||||
cipher,
|
||||
field.label,
|
||||
field.value,
|
||||
field.sensitive === 1 ? FieldType.Hidden : FieldType.Text
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private buildFolderTree(folders: EnpassFolder[]): EnpassFolderTreeItem[] {
|
||||
if (folders == null) {
|
||||
return [];
|
||||
}
|
||||
const folderTree: EnpassFolderTreeItem[] = [];
|
||||
const map = new Map<string, EnpassFolderTreeItem>([]);
|
||||
folders.forEach((obj: EnpassFolderTreeItem) => {
|
||||
map.set(obj.uuid, obj);
|
||||
obj.children = [];
|
||||
});
|
||||
folders.forEach((obj: EnpassFolderTreeItem) => {
|
||||
if (obj.parent_uuid != null && obj.parent_uuid !== "" && map.has(obj.parent_uuid)) {
|
||||
map.get(obj.parent_uuid).children.push(obj);
|
||||
} else {
|
||||
folderTree.push(obj);
|
||||
}
|
||||
});
|
||||
return folderTree;
|
||||
}
|
||||
|
||||
private flattenFolderTree(
|
||||
titlePrefix: string,
|
||||
tree: EnpassFolderTreeItem[],
|
||||
map: Map<string, string>
|
||||
) {
|
||||
if (tree == null) {
|
||||
return;
|
||||
}
|
||||
tree.forEach((f) => {
|
||||
if (f.title != null && f.title.trim() !== "") {
|
||||
let title = f.title.trim();
|
||||
if (titlePrefix != null && titlePrefix.trim() !== "") {
|
||||
title = titlePrefix + "/" + title;
|
||||
}
|
||||
map.set(f.uuid, title);
|
||||
if (f.children != null && f.children.length !== 0) {
|
||||
this.flattenFolderTree(title, f.children, map);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
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;
|
||||
@@ -1,85 +0,0 @@
|
||||
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;
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class FirefoxCsvImporter 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
|
||||
.filter((value) => {
|
||||
return value.url !== "chrome://FirefoxAccounts";
|
||||
})
|
||||
.forEach((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
const url = this.getValueOrDefault(value.url, this.getValueOrDefault(value.hostname));
|
||||
cipher.name = this.getValueOrDefault(this.nameFromUrl(url), "--");
|
||||
cipher.login.username = this.getValueOrDefault(value.username);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
cipher.login.uris = this.makeUriArray(url);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { CipherType } from "../../vault/enums/cipher-type";
|
||||
|
||||
import { FSecureFskImporter as Importer } from "./fsecure-fsk-importer";
|
||||
import { CreditCardTestEntry, LoginTestEntry } from "./fsk-test-data";
|
||||
|
||||
describe("FSecure FSK Importer", () => {
|
||||
it("should import data of type login", async () => {
|
||||
const importer = new Importer();
|
||||
const LoginTestEntryStringified = JSON.stringify(LoginTestEntry);
|
||||
const result = await importer.parse(LoginTestEntryStringified);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
|
||||
expect(cipher.name).toEqual("example.com");
|
||||
expect(cipher.favorite).toBe(true);
|
||||
expect(cipher.notes).toEqual("some note for example.com");
|
||||
|
||||
expect(cipher.type).toBe(CipherType.Login);
|
||||
expect(cipher.login.username).toEqual("jdoe");
|
||||
expect(cipher.login.password).toEqual("somePassword");
|
||||
|
||||
expect(cipher.login.uris.length).toEqual(1);
|
||||
const uriView = cipher.login.uris.shift();
|
||||
expect(uriView.uri).toEqual("https://www.example.com");
|
||||
});
|
||||
|
||||
it("should import data of type creditCard", async () => {
|
||||
const importer = new Importer();
|
||||
const CreditCardTestEntryStringified = JSON.stringify(CreditCardTestEntry);
|
||||
const result = await importer.parse(CreditCardTestEntryStringified);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
|
||||
expect(cipher.name).toEqual("My credit card");
|
||||
expect(cipher.favorite).toBe(false);
|
||||
expect(cipher.notes).toEqual("some notes to my card");
|
||||
|
||||
expect(cipher.type).toBe(CipherType.Card);
|
||||
expect(cipher.card.cardholderName).toEqual("John Doe");
|
||||
expect(cipher.card.number).toEqual("4242424242424242");
|
||||
expect(cipher.card.code).toEqual("123");
|
||||
|
||||
expect(cipher.fields.length).toBe(2);
|
||||
expect(cipher.fields[0].name).toEqual("Expiration");
|
||||
expect(cipher.fields[0].value).toEqual("22.10.2026");
|
||||
|
||||
expect(cipher.fields[1].name).toEqual("PIN");
|
||||
expect(cipher.fields[1].value).toEqual("1234");
|
||||
});
|
||||
});
|
||||
@@ -1,79 +0,0 @@
|
||||
import { ImportResult } from "../../models/domain/import-result";
|
||||
import { CipherType } from "../../vault/enums/cipher-type";
|
||||
import { CardView } from "../../vault/models/view/card.view";
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
import { BaseImporter } from "../base-importer";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { FskEntry, FskEntryTypesEnum, FskFile } from "./fsecure-fsk-types";
|
||||
|
||||
export class FSecureFskImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results: FskFile = JSON.parse(data);
|
||||
if (results == null || results.data == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
for (const key in results.data) {
|
||||
// eslint-disable-next-line
|
||||
if (!results.data.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = results.data[key];
|
||||
const cipher = this.parseEntry(value);
|
||||
result.ciphers.push(cipher);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private parseEntry(entry: FskEntry): CipherView {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(entry.service);
|
||||
cipher.notes = this.getValueOrDefault(entry.notes);
|
||||
cipher.favorite = entry.favorite > 0;
|
||||
|
||||
switch (entry.type) {
|
||||
case FskEntryTypesEnum.Login:
|
||||
this.handleLoginEntry(entry, cipher);
|
||||
break;
|
||||
case FskEntryTypesEnum.CreditCard:
|
||||
this.handleCreditCardEntry(entry, cipher);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
private handleLoginEntry(entry: FskEntry, cipher: CipherView) {
|
||||
cipher.login.username = this.getValueOrDefault(entry.username);
|
||||
cipher.login.password = this.getValueOrDefault(entry.password);
|
||||
cipher.login.uris = this.makeUriArray(entry.url);
|
||||
}
|
||||
|
||||
private handleCreditCardEntry(entry: FskEntry, cipher: CipherView) {
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
cipher.card.cardholderName = this.getValueOrDefault(entry.username);
|
||||
cipher.card.number = this.getValueOrDefault(entry.creditNumber);
|
||||
cipher.card.brand = this.getCardBrand(cipher.card.number);
|
||||
cipher.card.code = this.getValueOrDefault(entry.creditCvv);
|
||||
if (!this.isNullOrWhitespace(entry.creditExpiry)) {
|
||||
if (!this.setCardExpiration(cipher, entry.creditExpiry)) {
|
||||
this.processKvp(cipher, "Expiration", entry.creditExpiry);
|
||||
}
|
||||
}
|
||||
if (!this.isNullOrWhitespace(entry.password)) {
|
||||
this.processKvp(cipher, "PIN", entry.password);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
export interface FskFile {
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
[key: string]: FskEntry;
|
||||
}
|
||||
|
||||
export enum FskEntryTypesEnum {
|
||||
Login = 1,
|
||||
CreditCard = 2,
|
||||
}
|
||||
|
||||
export interface FskEntry {
|
||||
color: string;
|
||||
creditCvv: string;
|
||||
creditExpiry: string;
|
||||
creditNumber: string;
|
||||
favorite: number; // UNIX timestamp
|
||||
notes: string;
|
||||
password: string;
|
||||
passwordList: PasswordList[];
|
||||
passwordModifiedDate: number; // UNIX timestamp
|
||||
rev: string | number;
|
||||
service: string;
|
||||
style: string;
|
||||
type: FskEntryTypesEnum;
|
||||
url: string;
|
||||
username: string;
|
||||
createdDate: number; // UNIX timestamp
|
||||
modifiedDate: number; // UNIX timestamp
|
||||
}
|
||||
|
||||
export interface PasswordList {
|
||||
changedate: string;
|
||||
password: string;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { FskFile } from "./fsecure-fsk-types";
|
||||
|
||||
export const LoginTestEntry: FskFile = {
|
||||
data: {
|
||||
"1c3a2e31dcaa8459edd70a9d895ce298": {
|
||||
color: "#00A34D",
|
||||
createdDate: 0,
|
||||
creditCvv: "",
|
||||
creditExpiry: "",
|
||||
creditNumber: "",
|
||||
favorite: 1666440874,
|
||||
modifiedDate: 0,
|
||||
notes: "some note for example.com",
|
||||
password: "somePassword",
|
||||
passwordList: [],
|
||||
passwordModifiedDate: 0,
|
||||
rev: 1,
|
||||
service: "example.com",
|
||||
style: "website",
|
||||
type: 1,
|
||||
url: "https://www.example.com",
|
||||
username: "jdoe",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const CreditCardTestEntry: FskFile = {
|
||||
data: {
|
||||
"156498a46a3254f16035cbbbd09c2b8f": {
|
||||
color: "#00baff",
|
||||
createdDate: 1666438977,
|
||||
creditCvv: "123",
|
||||
creditExpiry: "22.10.2026",
|
||||
creditNumber: "4242424242424242",
|
||||
favorite: 0,
|
||||
modifiedDate: 1666438977,
|
||||
notes: "some notes to my card",
|
||||
password: "1234",
|
||||
passwordList: [],
|
||||
passwordModifiedDate: 1666438977,
|
||||
rev: 1,
|
||||
service: "My credit card",
|
||||
style: "creditcard",
|
||||
type: 2,
|
||||
url: "mybank",
|
||||
username: "John Doe",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class GnomeJsonImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = JSON.parse(data);
|
||||
if (results == null || Object.keys(results).length === 0) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
for (const keyRing in results) {
|
||||
if (
|
||||
!results.hasOwnProperty(keyRing) || // eslint-disable-line
|
||||
this.isNullOrWhitespace(keyRing) ||
|
||||
results[keyRing].length === 0
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
results[keyRing].forEach((value: any) => {
|
||||
if (
|
||||
this.isNullOrWhitespace(value.display_name) ||
|
||||
value.display_name.indexOf("http") !== 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.processFolder(result, keyRing);
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = value.display_name.replace("http://", "").replace("https://", "");
|
||||
if (cipher.name.length > 30) {
|
||||
cipher.name = cipher.name.substring(0, 30);
|
||||
}
|
||||
cipher.login.password = this.getValueOrDefault(value.secret);
|
||||
cipher.login.uris = this.makeUriArray(value.display_name);
|
||||
|
||||
if (value.attributes != null) {
|
||||
cipher.login.username =
|
||||
value.attributes != null
|
||||
? this.getValueOrDefault(value.attributes.username_value)
|
||||
: null;
|
||||
for (const attr in value.attributes) {
|
||||
if (
|
||||
!value.attributes.hasOwnProperty(attr) || // eslint-disable-line
|
||||
attr === "username_value" ||
|
||||
attr === "xdg:schema"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
this.processKvp(cipher, attr, value.attributes[attr]);
|
||||
}
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export class ImportError extends Error {
|
||||
constructor(message?: string, public passwordRequired: boolean = false) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
export interface Importer {
|
||||
organizationId: string;
|
||||
parse(data: string): Promise<ImportResult>;
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
const NotesHeader = "Notes\n\n";
|
||||
const ApplicationsHeader = "Applications\n\n";
|
||||
const WebsitesHeader = "Websites\n\n";
|
||||
const Delimiter = "\n---\n";
|
||||
|
||||
export class KasperskyTxtImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
let notesData: string;
|
||||
let applicationsData: string;
|
||||
let websitesData: string;
|
||||
let workingData = this.splitNewLine(data).join("\n");
|
||||
|
||||
if (workingData.indexOf(NotesHeader) !== -1) {
|
||||
const parts = workingData.split(NotesHeader);
|
||||
if (parts.length > 1) {
|
||||
workingData = parts[0];
|
||||
notesData = parts[1];
|
||||
}
|
||||
}
|
||||
if (workingData.indexOf(ApplicationsHeader) !== -1) {
|
||||
const parts = workingData.split(ApplicationsHeader);
|
||||
if (parts.length > 1) {
|
||||
workingData = parts[0];
|
||||
applicationsData = parts[1];
|
||||
}
|
||||
}
|
||||
if (workingData.indexOf(WebsitesHeader) === 0) {
|
||||
const parts = workingData.split(WebsitesHeader);
|
||||
if (parts.length > 1) {
|
||||
workingData = parts[0];
|
||||
websitesData = parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
const notes = this.parseDataCategory(notesData);
|
||||
const applications = this.parseDataCategory(applicationsData);
|
||||
const websites = this.parseDataCategory(websitesData);
|
||||
|
||||
notes.forEach((n) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(n.get("Name"));
|
||||
cipher.notes = this.getValueOrDefault(n.get("Text"));
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
websites.concat(applications).forEach((w) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
const nameKey = w.has("Website name") ? "Website name" : "Application";
|
||||
cipher.name = this.getValueOrDefault(w.get(nameKey), "");
|
||||
if (!this.isNullOrWhitespace(w.get("Login name"))) {
|
||||
if (!this.isNullOrWhitespace(cipher.name)) {
|
||||
cipher.name += ": ";
|
||||
}
|
||||
cipher.name += w.get("Login name");
|
||||
}
|
||||
cipher.notes = this.getValueOrDefault(w.get("Comment"));
|
||||
if (w.has("Website URL")) {
|
||||
cipher.login.uris = this.makeUriArray(w.get("Website URL"));
|
||||
}
|
||||
cipher.login.username = this.getValueOrDefault(w.get("Login"));
|
||||
cipher.login.password = this.getValueOrDefault(w.get("Password"));
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private parseDataCategory(data: string): Map<string, string>[] {
|
||||
if (this.isNullOrWhitespace(data) || data.indexOf(Delimiter) === -1) {
|
||||
return [];
|
||||
}
|
||||
const items: Map<string, string>[] = [];
|
||||
data.split(Delimiter).forEach((p) => {
|
||||
if (p.indexOf("\n") === -1) {
|
||||
return;
|
||||
}
|
||||
const item = new Map<string, string>();
|
||||
let itemComment: string;
|
||||
let itemCommentKey: string;
|
||||
p.split("\n").forEach((l) => {
|
||||
if (itemComment != null) {
|
||||
itemComment += "\n" + l;
|
||||
return;
|
||||
}
|
||||
const colonIndex = l.indexOf(":");
|
||||
let key: string;
|
||||
let val: string;
|
||||
if (colonIndex === -1) {
|
||||
return;
|
||||
} else {
|
||||
key = l.substring(0, colonIndex);
|
||||
if (l.length > colonIndex + 1) {
|
||||
val = l.substring(colonIndex + 2);
|
||||
}
|
||||
}
|
||||
if (key != null) {
|
||||
item.set(key, val);
|
||||
}
|
||||
if (key === "Comment" || key === "Text") {
|
||||
itemComment = val;
|
||||
itemCommentKey = key;
|
||||
}
|
||||
});
|
||||
if (itemComment != null && itemCommentKey != null) {
|
||||
item.set(itemCommentKey, itemComment);
|
||||
}
|
||||
if (item.size === 0) {
|
||||
return;
|
||||
}
|
||||
items.push(item);
|
||||
});
|
||||
return items;
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import { FieldType } from "../enums/fieldType";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { FolderView } from "../vault/models/view/folder.view";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class KeePass2XmlImporter extends BaseImporter implements Importer {
|
||||
result = new ImportResult();
|
||||
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const doc = this.parseXml(data);
|
||||
if (doc == null) {
|
||||
this.result.success = false;
|
||||
return Promise.resolve(this.result);
|
||||
}
|
||||
|
||||
//Note: The doc.querySelector("KeePassFile > Root > Group") no longers works on node and we have to breakdown the query by nodes
|
||||
const KeePassFileNode = doc.querySelector("KeePassFile");
|
||||
|
||||
if (KeePassFileNode == null) {
|
||||
this.result.errorMessage = "Missing `KeePassFile` node.";
|
||||
this.result.success = false;
|
||||
return Promise.resolve(this.result);
|
||||
}
|
||||
|
||||
const RootNode = KeePassFileNode.querySelector("Root");
|
||||
if (RootNode == null) {
|
||||
this.result.errorMessage = "Missing `KeePassFile > Root` node.";
|
||||
this.result.success = false;
|
||||
return Promise.resolve(this.result);
|
||||
}
|
||||
|
||||
const rootGroup = RootNode.querySelector("Group");
|
||||
if (rootGroup == null) {
|
||||
this.result.errorMessage = "Missing `KeePassFile > Root > Group` node.";
|
||||
this.result.success = false;
|
||||
return Promise.resolve(this.result);
|
||||
}
|
||||
|
||||
this.traverse(rootGroup, true, "");
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(this.result);
|
||||
}
|
||||
|
||||
this.result.success = true;
|
||||
return Promise.resolve(this.result);
|
||||
}
|
||||
|
||||
traverse(node: Element, isRootNode: boolean, groupPrefixName: string) {
|
||||
const folderIndex = this.result.folders.length;
|
||||
let groupName = groupPrefixName;
|
||||
|
||||
if (!isRootNode) {
|
||||
if (groupName !== "") {
|
||||
groupName += "/";
|
||||
}
|
||||
const nameEl = this.querySelectorDirectChild(node, "Name");
|
||||
groupName += nameEl == null ? "-" : nameEl.textContent;
|
||||
const folder = new FolderView();
|
||||
folder.name = groupName;
|
||||
this.result.folders.push(folder);
|
||||
}
|
||||
|
||||
this.querySelectorAllDirectChild(node, "Entry").forEach((entry) => {
|
||||
const cipherIndex = this.result.ciphers.length;
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
this.querySelectorAllDirectChild(entry, "String").forEach((entryString) => {
|
||||
const valueEl = this.querySelectorDirectChild(entryString, "Value");
|
||||
const value = valueEl != null ? valueEl.textContent : null;
|
||||
if (this.isNullOrWhitespace(value)) {
|
||||
return;
|
||||
}
|
||||
const keyEl = this.querySelectorDirectChild(entryString, "Key");
|
||||
const key = keyEl != null ? keyEl.textContent : null;
|
||||
|
||||
if (key === "URL") {
|
||||
cipher.login.uris = this.makeUriArray(value);
|
||||
} else if (key === "UserName") {
|
||||
cipher.login.username = value;
|
||||
} else if (key === "Password") {
|
||||
cipher.login.password = value;
|
||||
} else if (key === "otp") {
|
||||
cipher.login.totp = value.replace("key=", "");
|
||||
} else if (key === "Title") {
|
||||
cipher.name = value;
|
||||
} else if (key === "Notes") {
|
||||
cipher.notes += value + "\n";
|
||||
} else {
|
||||
let type = FieldType.Text;
|
||||
const attrs = valueEl.attributes as any;
|
||||
if (
|
||||
attrs.length > 0 &&
|
||||
attrs.ProtectInMemory != null &&
|
||||
attrs.ProtectInMemory.value === "True"
|
||||
) {
|
||||
type = FieldType.Hidden;
|
||||
}
|
||||
this.processKvp(cipher, key, value, type);
|
||||
}
|
||||
});
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
this.result.ciphers.push(cipher);
|
||||
|
||||
if (!isRootNode) {
|
||||
this.result.folderRelationships.push([cipherIndex, folderIndex]);
|
||||
}
|
||||
});
|
||||
|
||||
this.querySelectorAllDirectChild(node, "Group").forEach((group) => {
|
||||
this.traverse(group, false, groupName);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class KeePassXCsvImporter 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((value) => {
|
||||
if (this.isNullOrWhitespace(value.Title)) {
|
||||
return;
|
||||
}
|
||||
|
||||
value.Group =
|
||||
!this.isNullOrWhitespace(value.Group) && value.Group.startsWith("Root/")
|
||||
? value.Group.replace("Root/", "")
|
||||
: value.Group;
|
||||
const groupName = !this.isNullOrWhitespace(value.Group) ? value.Group : null;
|
||||
this.processFolder(result, groupName);
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.notes = this.getValueOrDefault(value.Notes);
|
||||
cipher.name = this.getValueOrDefault(value.Title, "--");
|
||||
cipher.login.username = this.getValueOrDefault(value.Username);
|
||||
cipher.login.password = this.getValueOrDefault(value.Password);
|
||||
cipher.login.uris = this.makeUriArray(value.URL);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import { ImportResult } from "../../models/domain/import-result";
|
||||
import { BaseImporter } from "../base-importer";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
export class KeeperCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, false);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((value) => {
|
||||
if (value.length < 6) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.processFolder(result, value[0]);
|
||||
const cipher = this.initLoginCipher();
|
||||
|
||||
const notes = this.getValueOrDefault(value[5]);
|
||||
if (notes) {
|
||||
cipher.notes = `${notes}\n`;
|
||||
}
|
||||
|
||||
cipher.name = this.getValueOrDefault(value[1], "--");
|
||||
cipher.login.username = this.getValueOrDefault(value[2]);
|
||||
cipher.login.password = this.getValueOrDefault(value[3]);
|
||||
cipher.login.uris = this.makeUriArray(value[4]);
|
||||
|
||||
if (value.length > 7) {
|
||||
// we have some custom fields.
|
||||
for (let i = 7; i < value.length; i = i + 2) {
|
||||
if (value[i] == "TFC:Keeper") {
|
||||
cipher.login.totp = value[i + 1];
|
||||
} else {
|
||||
this.processKvp(cipher, value[i], value[i + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { ImportResult } from "../../models/domain/import-result";
|
||||
import { BaseImporter } from "../base-importer";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { KeeperJsonExport, RecordsEntity } from "./types/keeper-json-types";
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherType } from "../vault/enums/cipher-type";
|
||||
import { CardView } from "../vault/models/view/card.view";
|
||||
import { CipherView } from "../vault/models/view/cipher.view";
|
||||
import { FolderView } from "../vault/models/view/folder.view";
|
||||
import { IdentityView } from "../vault/models/view/identity.view";
|
||||
import { LoginView } from "../vault/models/view/login.view";
|
||||
import { SecureNoteView } from "../vault/models/view/secure-note.view";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class LastPassCsvImporter 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((value) => {
|
||||
const cipherIndex = result.ciphers.length;
|
||||
let folderIndex = result.folders.length;
|
||||
let grouping = value.grouping;
|
||||
if (grouping != null) {
|
||||
// eslint-disable-next-line
|
||||
grouping = grouping.replace(/\\/g, "/").replace(/[\x00-\x1F\x7F-\x9F]/g, "");
|
||||
}
|
||||
const hasFolder = this.getValueOrDefault(grouping, "(none)") !== "(none)";
|
||||
let addFolder = hasFolder;
|
||||
|
||||
if (hasFolder) {
|
||||
for (let i = 0; i < result.folders.length; i++) {
|
||||
if (result.folders[i].name === grouping) {
|
||||
addFolder = false;
|
||||
folderIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cipher = this.buildBaseCipher(value);
|
||||
if (cipher.type === CipherType.Login) {
|
||||
cipher.notes = this.getValueOrDefault(value.extra);
|
||||
cipher.login = new LoginView();
|
||||
cipher.login.uris = this.makeUriArray(value.url);
|
||||
cipher.login.username = this.getValueOrDefault(value.username);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
cipher.login.totp = this.getValueOrDefault(value.totp);
|
||||
} else if (cipher.type === CipherType.SecureNote) {
|
||||
this.parseSecureNote(value, cipher);
|
||||
} else if (cipher.type === CipherType.Card) {
|
||||
cipher.card = this.parseCard(value);
|
||||
cipher.notes = this.getValueOrDefault(value.notes);
|
||||
} else if (cipher.type === CipherType.Identity) {
|
||||
cipher.identity = this.parseIdentity(value);
|
||||
cipher.notes = this.getValueOrDefault(value.notes);
|
||||
if (!this.isNullOrWhitespace(value.ccnum)) {
|
||||
// there is a card on this identity too
|
||||
const cardCipher = this.buildBaseCipher(value);
|
||||
cardCipher.identity = null;
|
||||
cardCipher.type = CipherType.Card;
|
||||
cardCipher.card = this.parseCard(value);
|
||||
result.ciphers.push(cardCipher);
|
||||
}
|
||||
}
|
||||
|
||||
result.ciphers.push(cipher);
|
||||
|
||||
if (addFolder) {
|
||||
const f = new FolderView();
|
||||
f.name = grouping;
|
||||
result.folders.push(f);
|
||||
}
|
||||
if (hasFolder) {
|
||||
result.folderRelationships.push([cipherIndex, folderIndex]);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private buildBaseCipher(value: any) {
|
||||
const cipher = new CipherView();
|
||||
// eslint-disable-next-line
|
||||
if (value.hasOwnProperty("profilename") && value.hasOwnProperty("profilelanguage")) {
|
||||
// form fill
|
||||
cipher.favorite = false;
|
||||
cipher.name = this.getValueOrDefault(value.profilename, "--");
|
||||
cipher.type = CipherType.Card;
|
||||
|
||||
if (
|
||||
!this.isNullOrWhitespace(value.title) ||
|
||||
!this.isNullOrWhitespace(value.firstname) ||
|
||||
!this.isNullOrWhitespace(value.lastname) ||
|
||||
!this.isNullOrWhitespace(value.address1) ||
|
||||
!this.isNullOrWhitespace(value.phone) ||
|
||||
!this.isNullOrWhitespace(value.username) ||
|
||||
!this.isNullOrWhitespace(value.email)
|
||||
) {
|
||||
cipher.type = CipherType.Identity;
|
||||
}
|
||||
} else {
|
||||
// site or secure note
|
||||
cipher.favorite = !this.organization && this.getValueOrDefault(value.fav, "0") === "1";
|
||||
cipher.name = this.getValueOrDefault(value.name, "--");
|
||||
cipher.type = value.url === "http://sn" ? CipherType.SecureNote : CipherType.Login;
|
||||
}
|
||||
return cipher;
|
||||
}
|
||||
|
||||
private parseCard(value: any): CardView {
|
||||
const card = new CardView();
|
||||
card.cardholderName = this.getValueOrDefault(value.ccname);
|
||||
card.number = this.getValueOrDefault(value.ccnum);
|
||||
card.code = this.getValueOrDefault(value.cccsc);
|
||||
card.brand = this.getCardBrand(value.ccnum);
|
||||
|
||||
if (!this.isNullOrWhitespace(value.ccexp) && value.ccexp.indexOf("-") > -1) {
|
||||
const ccexpParts = (value.ccexp as string).split("-");
|
||||
if (ccexpParts.length > 1) {
|
||||
card.expYear = ccexpParts[0];
|
||||
card.expMonth = ccexpParts[1];
|
||||
if (card.expMonth.length === 2 && card.expMonth[0] === "0") {
|
||||
card.expMonth = card.expMonth[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private parseIdentity(value: any): IdentityView {
|
||||
const identity = new IdentityView();
|
||||
identity.title = this.getValueOrDefault(value.title);
|
||||
identity.firstName = this.getValueOrDefault(value.firstname);
|
||||
identity.middleName = this.getValueOrDefault(value.middlename);
|
||||
identity.lastName = this.getValueOrDefault(value.lastname);
|
||||
identity.username = this.getValueOrDefault(value.username);
|
||||
identity.company = this.getValueOrDefault(value.company);
|
||||
identity.ssn = this.getValueOrDefault(value.ssn);
|
||||
identity.address1 = this.getValueOrDefault(value.address1);
|
||||
identity.address2 = this.getValueOrDefault(value.address2);
|
||||
identity.address3 = this.getValueOrDefault(value.address3);
|
||||
identity.city = this.getValueOrDefault(value.city);
|
||||
identity.state = this.getValueOrDefault(value.state);
|
||||
identity.postalCode = this.getValueOrDefault(value.zip);
|
||||
identity.country = this.getValueOrDefault(value.country);
|
||||
identity.email = this.getValueOrDefault(value.email);
|
||||
identity.phone = this.getValueOrDefault(value.phone);
|
||||
|
||||
if (!this.isNullOrWhitespace(identity.title)) {
|
||||
identity.title = identity.title.charAt(0).toUpperCase() + identity.title.slice(1);
|
||||
}
|
||||
|
||||
return identity;
|
||||
}
|
||||
|
||||
private parseSecureNote(value: any, cipher: CipherView) {
|
||||
const extraParts = this.splitNewLine(value.extra);
|
||||
let processedNote = false;
|
||||
|
||||
if (extraParts.length) {
|
||||
const typeParts = extraParts[0].split(":");
|
||||
if (
|
||||
typeParts.length > 1 &&
|
||||
typeParts[0] === "NoteType" &&
|
||||
(typeParts[1] === "Credit Card" || typeParts[1] === "Address")
|
||||
) {
|
||||
if (typeParts[1] === "Credit Card") {
|
||||
const mappedData = this.parseSecureNoteMapping<CardView>(cipher, extraParts, {
|
||||
Number: "number",
|
||||
"Name on Card": "cardholderName",
|
||||
"Security Code": "code",
|
||||
// LP provides date in a format like 'June,2020'
|
||||
// Store in expMonth, then parse and modify
|
||||
"Expiration Date": "expMonth",
|
||||
});
|
||||
|
||||
if (this.isNullOrWhitespace(mappedData.expMonth) || mappedData.expMonth === ",") {
|
||||
// No expiration data
|
||||
mappedData.expMonth = undefined;
|
||||
} else {
|
||||
const [monthString, year] = mappedData.expMonth.split(",");
|
||||
// Parse month name into number
|
||||
if (!this.isNullOrWhitespace(monthString)) {
|
||||
const month = new Date(Date.parse(monthString.trim() + " 1, 2012")).getMonth() + 1;
|
||||
if (isNaN(month)) {
|
||||
mappedData.expMonth = undefined;
|
||||
} else {
|
||||
mappedData.expMonth = month.toString();
|
||||
}
|
||||
} else {
|
||||
mappedData.expMonth = undefined;
|
||||
}
|
||||
if (!this.isNullOrWhitespace(year)) {
|
||||
mappedData.expYear = year;
|
||||
}
|
||||
}
|
||||
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = mappedData;
|
||||
} else if (typeParts[1] === "Address") {
|
||||
const mappedData = this.parseSecureNoteMapping<IdentityView>(cipher, extraParts, {
|
||||
Title: "title",
|
||||
"First Name": "firstName",
|
||||
"Last Name": "lastName",
|
||||
"Middle Name": "middleName",
|
||||
Company: "company",
|
||||
"Address 1": "address1",
|
||||
"Address 2": "address2",
|
||||
"Address 3": "address3",
|
||||
"City / Town": "city",
|
||||
State: "state",
|
||||
"Zip / Postal Code": "postalCode",
|
||||
Country: "country",
|
||||
"Email Address": "email",
|
||||
Username: "username",
|
||||
});
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = mappedData;
|
||||
}
|
||||
processedNote = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!processedNote) {
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
cipher.notes = this.getValueOrDefault(value.extra);
|
||||
}
|
||||
}
|
||||
|
||||
private parseSecureNoteMapping<T>(cipher: CipherView, extraParts: string[], map: any): T {
|
||||
const dataObj: any = {};
|
||||
|
||||
let processingNotes = false;
|
||||
extraParts.forEach((extraPart) => {
|
||||
let key: string = null;
|
||||
let val: string = null;
|
||||
if (!processingNotes) {
|
||||
if (this.isNullOrWhitespace(extraPart)) {
|
||||
return;
|
||||
}
|
||||
const colonIndex = extraPart.indexOf(":");
|
||||
if (colonIndex === -1) {
|
||||
key = extraPart;
|
||||
} else {
|
||||
key = extraPart.substring(0, colonIndex);
|
||||
if (extraPart.length > colonIndex) {
|
||||
val = extraPart.substring(colonIndex + 1);
|
||||
}
|
||||
}
|
||||
if (this.isNullOrWhitespace(key) || this.isNullOrWhitespace(val) || key === "NoteType") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (processingNotes) {
|
||||
cipher.notes += "\n" + extraPart;
|
||||
} else if (key === "Notes") {
|
||||
if (!this.isNullOrWhitespace(cipher.notes)) {
|
||||
cipher.notes += "\n" + val;
|
||||
} else {
|
||||
cipher.notes = val;
|
||||
}
|
||||
processingNotes = true;
|
||||
// eslint-disable-next-line
|
||||
} else if (map.hasOwnProperty(key)) {
|
||||
dataObj[map[key]] = val;
|
||||
} else {
|
||||
this.processKvp(cipher, key, val);
|
||||
}
|
||||
});
|
||||
|
||||
return dataObj;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class LogMeOnceCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, false);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((value) => {
|
||||
if (value.length < 4) {
|
||||
return;
|
||||
}
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value[0], "--");
|
||||
cipher.login.username = this.getValueOrDefault(value[2]);
|
||||
cipher.login.password = this.getValueOrDefault(value[3]);
|
||||
cipher.login.uris = this.makeUriArray(value[1]);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class MeldiumCsvImporter 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((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value.DisplayName, "--");
|
||||
cipher.notes = this.getValueOrDefault(value.Notes);
|
||||
cipher.login.username = this.getValueOrDefault(value.UserName);
|
||||
cipher.login.password = this.getValueOrDefault(value.Password);
|
||||
cipher.login.uris = this.makeUriArray(value.Url);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherType } from "../vault/enums/cipher-type";
|
||||
import { SecureNoteView } from "../vault/models/view/secure-note.view";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class MSecureCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, false);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((value) => {
|
||||
if (value.length < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
const folderName =
|
||||
this.getValueOrDefault(value[0], "Unassigned") !== "Unassigned" ? value[0] : null;
|
||||
this.processFolder(result, folderName);
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value[2], "--");
|
||||
|
||||
if (value[1] === "Web Logins" || value[1] === "Login") {
|
||||
cipher.login.uris = this.makeUriArray(value[4]);
|
||||
cipher.login.username = this.getValueOrDefault(value[5]);
|
||||
cipher.login.password = this.getValueOrDefault(value[6]);
|
||||
cipher.notes = !this.isNullOrWhitespace(value[3]) ? value[3].split("\\n").join("\n") : null;
|
||||
} else if (value.length > 3) {
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
for (let i = 3; i < value.length; i++) {
|
||||
if (!this.isNullOrWhitespace(value[i])) {
|
||||
cipher.notes += value[i] + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.isNullOrWhitespace(value[1]) && cipher.type !== CipherType.Login) {
|
||||
cipher.name = value[1] + ": " + cipher.name;
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherType } from "../vault/enums/cipher-type";
|
||||
import { CardView } from "../vault/models/view/card.view";
|
||||
import { CipherView } from "../vault/models/view/cipher.view";
|
||||
import { IdentityView } from "../vault/models/view/identity.view";
|
||||
import { SecureNoteView } from "../vault/models/view/secure-note.view";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
const mappedBaseColumns = ["nickname", "additionalInfo"];
|
||||
const _mappedUserAccountColumns = new Set(
|
||||
mappedBaseColumns.concat(["url", "username", "password", "twofaSecret"])
|
||||
);
|
||||
const _mappedCreditCardColumns = new Set(
|
||||
mappedBaseColumns.concat(["cardNumber", "cardName", "exp_month", "exp_year", "cvv"])
|
||||
);
|
||||
|
||||
const _mappedIdentityColumns = new Set(
|
||||
mappedBaseColumns.concat([
|
||||
"title",
|
||||
"firstName",
|
||||
"middleName",
|
||||
"lastName",
|
||||
"email",
|
||||
"firstAddressLine",
|
||||
"secondAddressLine",
|
||||
"city",
|
||||
"country",
|
||||
"zipCode",
|
||||
])
|
||||
);
|
||||
|
||||
const _mappedIdCardColumns = new Set(mappedBaseColumns.concat(["idName", "idNumber", "idCountry"]));
|
||||
|
||||
const _mappedTwoFaColumns = new Set(mappedBaseColumns.concat(["authToken"]));
|
||||
|
||||
const _mappedUserNoteColumns = new Set(mappedBaseColumns.concat(["content"]));
|
||||
|
||||
export class MykiCsvImporter 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((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value.nickname, "--");
|
||||
cipher.notes = this.getValueOrDefault(value.additionalInfo);
|
||||
|
||||
if (value.url !== undefined) {
|
||||
// Accounts
|
||||
cipher.login.uris = this.makeUriArray(value.url);
|
||||
cipher.login.username = this.getValueOrDefault(value.username);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
cipher.login.totp = this.getValueOrDefault(value.twofaSecret);
|
||||
|
||||
this.importUnmappedFields(cipher, value, _mappedUserAccountColumns);
|
||||
} else if (value.authToken !== undefined) {
|
||||
// TwoFA
|
||||
cipher.login.totp = this.getValueOrDefault(value.authToken);
|
||||
|
||||
this.importUnmappedFields(cipher, value, _mappedTwoFaColumns);
|
||||
} else if (value.cardNumber !== undefined) {
|
||||
// Cards
|
||||
cipher.card = new CardView();
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card.cardholderName = this.getValueOrDefault(value.cardName);
|
||||
cipher.card.number = this.getValueOrDefault(value.cardNumber);
|
||||
cipher.card.brand = this.getCardBrand(cipher.card.number);
|
||||
cipher.card.expMonth = this.getValueOrDefault(value.exp_month);
|
||||
cipher.card.expYear = this.getValueOrDefault(value.exp_year);
|
||||
cipher.card.code = this.getValueOrDefault(value.cvv);
|
||||
|
||||
this.importUnmappedFields(cipher, value, _mappedCreditCardColumns);
|
||||
} else if (value.firstName !== undefined) {
|
||||
// Identities
|
||||
cipher.identity = new IdentityView();
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity.title = this.getValueOrDefault(value.title);
|
||||
cipher.identity.firstName = this.getValueOrDefault(value.firstName);
|
||||
cipher.identity.middleName = this.getValueOrDefault(value.middleName);
|
||||
cipher.identity.lastName = this.getValueOrDefault(value.lastName);
|
||||
cipher.identity.phone = this.getValueOrDefault(value.number);
|
||||
cipher.identity.email = this.getValueOrDefault(value.email);
|
||||
cipher.identity.address1 = this.getValueOrDefault(value.firstAddressLine);
|
||||
cipher.identity.address2 = this.getValueOrDefault(value.secondAddressLine);
|
||||
cipher.identity.city = this.getValueOrDefault(value.city);
|
||||
cipher.identity.country = this.getValueOrDefault(value.country);
|
||||
cipher.identity.postalCode = this.getValueOrDefault(value.zipCode);
|
||||
|
||||
this.importUnmappedFields(cipher, value, _mappedIdentityColumns);
|
||||
} else if (value.idType !== undefined) {
|
||||
// IdCards
|
||||
|
||||
cipher.identity = new IdentityView();
|
||||
cipher.type = CipherType.Identity;
|
||||
this.processFullName(cipher, value.idName);
|
||||
cipher.identity.country = this.getValueOrDefault(value.idCountry);
|
||||
|
||||
switch (value.idType) {
|
||||
// case "Driver's License":
|
||||
// case "ID Card":
|
||||
// case "Outdoor License":
|
||||
// case "Software License":
|
||||
// case "Tax Number":
|
||||
// case "Bank Account":
|
||||
// case "Insurance Card":
|
||||
// case "Health Card":
|
||||
// case "Membership":
|
||||
// case "Database":
|
||||
// case "Reward Program":
|
||||
// case "Tour Visa":
|
||||
case "Passport":
|
||||
cipher.identity.passportNumber = value.idNumber;
|
||||
break;
|
||||
case "Social Security":
|
||||
cipher.identity.ssn = value.idNumber;
|
||||
break;
|
||||
default:
|
||||
cipher.identity.licenseNumber = value.idNumber;
|
||||
break;
|
||||
}
|
||||
|
||||
this.importUnmappedFields(cipher, value, _mappedIdCardColumns);
|
||||
} else if (value.content !== undefined) {
|
||||
// Notes
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
cipher.notes = this.getValueOrDefault(value.content);
|
||||
|
||||
this.importUnmappedFields(cipher, value, _mappedUserNoteColumns);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherType } from "../vault/enums/cipher-type";
|
||||
import { CipherView } from "../vault/models/view/cipher.view";
|
||||
import { LoginView } from "../vault/models/view/login.view";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
type nodePassCsvParsed = {
|
||||
name: string;
|
||||
url: string;
|
||||
username: string;
|
||||
password: string;
|
||||
note: string;
|
||||
cardholdername: string;
|
||||
cardnumber: string;
|
||||
cvc: string;
|
||||
expirydate: string;
|
||||
zipcode: string;
|
||||
folder: string;
|
||||
full_name: string;
|
||||
phone_number: string;
|
||||
email: string;
|
||||
address1: string;
|
||||
address2: string;
|
||||
city: string;
|
||||
country: string;
|
||||
state: string;
|
||||
};
|
||||
|
||||
export class NordPassCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results: nodePassCsvParsed[] = this.parseCsv(data, true);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((record) => {
|
||||
const recordType = this.evaluateType(record);
|
||||
if (recordType === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.organization) {
|
||||
this.processFolder(result, record.folder);
|
||||
}
|
||||
|
||||
const cipher = new CipherView();
|
||||
cipher.name = this.getValueOrDefault(record.name, "--");
|
||||
cipher.notes = this.getValueOrDefault(record.note);
|
||||
|
||||
switch (recordType) {
|
||||
case CipherType.Login:
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.login = new LoginView();
|
||||
cipher.login.username = this.getValueOrDefault(record.username);
|
||||
cipher.login.password = this.getValueOrDefault(record.password);
|
||||
cipher.login.uris = this.makeUriArray(record.url);
|
||||
break;
|
||||
case CipherType.Card:
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card.cardholderName = this.getValueOrDefault(record.cardholdername);
|
||||
cipher.card.number = this.getValueOrDefault(record.cardnumber);
|
||||
cipher.card.code = this.getValueOrDefault(record.cvc);
|
||||
cipher.card.brand = this.getCardBrand(cipher.card.number);
|
||||
this.setCardExpiration(cipher, record.expirydate);
|
||||
break;
|
||||
|
||||
case CipherType.Identity:
|
||||
cipher.type = CipherType.Identity;
|
||||
|
||||
this.processFullName(cipher, this.getValueOrDefault(record.full_name));
|
||||
cipher.identity.address1 = this.getValueOrDefault(record.address1);
|
||||
cipher.identity.address2 = this.getValueOrDefault(record.address2);
|
||||
cipher.identity.city = this.getValueOrDefault(record.city);
|
||||
cipher.identity.state = this.getValueOrDefault(record.state);
|
||||
cipher.identity.postalCode = this.getValueOrDefault(record.zipcode);
|
||||
cipher.identity.country = this.getValueOrDefault(record.country);
|
||||
if (cipher.identity.country != null) {
|
||||
cipher.identity.country = cipher.identity.country.toUpperCase();
|
||||
}
|
||||
cipher.identity.email = this.getValueOrDefault(record.email);
|
||||
cipher.identity.phone = this.getValueOrDefault(record.phone_number);
|
||||
break;
|
||||
case CipherType.SecureNote:
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private evaluateType(record: nodePassCsvParsed): CipherType {
|
||||
if (!this.isNullOrWhitespace(record.username)) {
|
||||
return CipherType.Login;
|
||||
}
|
||||
|
||||
if (!this.isNullOrWhitespace(record.cardnumber)) {
|
||||
return CipherType.Card;
|
||||
}
|
||||
|
||||
if (!this.isNullOrWhitespace(record.full_name)) {
|
||||
return CipherType.Identity;
|
||||
}
|
||||
|
||||
if (!this.isNullOrWhitespace(record.note)) {
|
||||
return CipherType.SecureNote;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
|
||||
export class CipherImportContext {
|
||||
lowerProperty: string;
|
||||
constructor(public importRecord: any, public property: string, public cipher: CipherView) {
|
||||
this.lowerProperty = property.toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
import { FieldType } from "../../enums/fieldType";
|
||||
import { SecureNoteType } from "../../enums/secureNoteType";
|
||||
import { ImportResult } from "../../models/domain/import-result";
|
||||
import { CipherType } from "../../vault/enums/cipher-type";
|
||||
import { CardView } from "../../vault/models/view/card.view";
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
import { IdentityView } from "../../vault/models/view/identity.view";
|
||||
import { PasswordHistoryView } from "../../vault/models/view/password-history.view";
|
||||
import { SecureNoteView } from "../../vault/models/view/secure-note.view";
|
||||
import { BaseImporter } from "../base-importer";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
export class OnePassword1PifImporter extends BaseImporter implements Importer {
|
||||
result = new ImportResult();
|
||||
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
data.split(this.newLineRegex).forEach((line) => {
|
||||
if (this.isNullOrWhitespace(line) || line[0] !== "{") {
|
||||
return;
|
||||
}
|
||||
const item = JSON.parse(line);
|
||||
if (item.trashed === true) {
|
||||
return;
|
||||
}
|
||||
const cipher = this.initLoginCipher();
|
||||
|
||||
if (this.isNullOrWhitespace(item.hmac)) {
|
||||
this.processStandardItem(item, cipher);
|
||||
} else {
|
||||
this.processWinOpVaultItem(item, cipher);
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
this.result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
this.result.success = true;
|
||||
return Promise.resolve(this.result);
|
||||
}
|
||||
|
||||
private processWinOpVaultItem(item: any, cipher: CipherView) {
|
||||
if (item.overview != null) {
|
||||
cipher.name = this.getValueOrDefault(item.overview.title);
|
||||
if (item.overview.URLs != null) {
|
||||
const urls: string[] = [];
|
||||
item.overview.URLs.forEach((url: any) => {
|
||||
if (!this.isNullOrWhitespace(url.u)) {
|
||||
urls.push(url.u);
|
||||
}
|
||||
});
|
||||
cipher.login.uris = this.makeUriArray(urls);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.details != null) {
|
||||
if (item.details.passwordHistory != null) {
|
||||
this.parsePasswordHistory(item.details.passwordHistory, cipher);
|
||||
}
|
||||
if (
|
||||
!this.isNullOrWhitespace(item.details.ccnum) ||
|
||||
!this.isNullOrWhitespace(item.details.cvv)
|
||||
) {
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
} else if (
|
||||
!this.isNullOrWhitespace(item.details.firstname) ||
|
||||
!this.isNullOrWhitespace(item.details.address1)
|
||||
) {
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
}
|
||||
if (cipher.type === CipherType.Login && !this.isNullOrWhitespace(item.details.password)) {
|
||||
cipher.login.password = item.details.password;
|
||||
}
|
||||
if (!this.isNullOrWhitespace(item.details.notesPlain)) {
|
||||
cipher.notes = item.details.notesPlain.split(this.newLineRegex).join("\n") + "\n";
|
||||
}
|
||||
if (item.details.fields != null) {
|
||||
this.parseFields(item.details.fields, cipher, "designation", "value", "name");
|
||||
}
|
||||
if (item.details.sections != null) {
|
||||
item.details.sections.forEach((section: any) => {
|
||||
if (section.fields != null) {
|
||||
this.parseFields(section.fields, cipher, "n", "v", "t");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private processStandardItem(item: any, cipher: CipherView) {
|
||||
cipher.favorite = item.openContents && item.openContents.faveIndex ? true : false;
|
||||
cipher.name = this.getValueOrDefault(item.title);
|
||||
|
||||
if (item.typeName === "securenotes.SecureNote") {
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
} else if (item.typeName === "wallet.financial.CreditCard") {
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
} else if (item.typeName === "identities.Identity") {
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
} else {
|
||||
cipher.login.uris = this.makeUriArray(item.location);
|
||||
}
|
||||
|
||||
if (item.secureContents != null) {
|
||||
if (item.secureContents.passwordHistory != null) {
|
||||
this.parsePasswordHistory(item.secureContents.passwordHistory, cipher);
|
||||
}
|
||||
if (!this.isNullOrWhitespace(item.secureContents.notesPlain)) {
|
||||
cipher.notes = item.secureContents.notesPlain.split(this.newLineRegex).join("\n") + "\n";
|
||||
}
|
||||
if (cipher.type === CipherType.Login) {
|
||||
if (!this.isNullOrWhitespace(item.secureContents.password)) {
|
||||
cipher.login.password = item.secureContents.password;
|
||||
}
|
||||
if (item.secureContents.URLs != null) {
|
||||
const urls: string[] = [];
|
||||
item.secureContents.URLs.forEach((u: any) => {
|
||||
if (!this.isNullOrWhitespace(u.url)) {
|
||||
urls.push(u.url);
|
||||
}
|
||||
});
|
||||
if (urls.length > 0) {
|
||||
cipher.login.uris = this.makeUriArray(urls);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item.secureContents.fields != null) {
|
||||
this.parseFields(item.secureContents.fields, cipher, "designation", "value", "name");
|
||||
}
|
||||
if (item.secureContents.sections != null) {
|
||||
item.secureContents.sections.forEach((section: any) => {
|
||||
if (section.fields != null) {
|
||||
this.parseFields(section.fields, cipher, "n", "v", "t");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private parsePasswordHistory(items: any[], cipher: CipherView) {
|
||||
const maxSize = items.length > 5 ? 5 : items.length;
|
||||
cipher.passwordHistory = items
|
||||
.filter((h: any) => !this.isNullOrWhitespace(h.value) && h.time != null)
|
||||
.sort((a, b) => b.time - a.time)
|
||||
.slice(0, maxSize)
|
||||
.map((h: any) => {
|
||||
const ph = new PasswordHistoryView();
|
||||
ph.password = h.value;
|
||||
ph.lastUsedDate = new Date(("" + h.time).length >= 13 ? h.time : h.time * 1000);
|
||||
return ph;
|
||||
});
|
||||
}
|
||||
|
||||
private parseFields(
|
||||
fields: any[],
|
||||
cipher: CipherView,
|
||||
designationKey: string,
|
||||
valueKey: string,
|
||||
nameKey: string
|
||||
) {
|
||||
fields.forEach((field: any) => {
|
||||
if (field[valueKey] == null || field[valueKey].toString().trim() === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: when date FieldType exists, store this as a date field type instead of formatted Text if k is 'date'
|
||||
const fieldValue =
|
||||
field.k === "date"
|
||||
? new Date(field[valueKey] * 1000).toUTCString()
|
||||
: field[valueKey].toString();
|
||||
const fieldDesignation =
|
||||
field[designationKey] != null ? field[designationKey].toString() : null;
|
||||
|
||||
if (cipher.type === CipherType.Login) {
|
||||
if (this.isNullOrWhitespace(cipher.login.username) && fieldDesignation === "username") {
|
||||
cipher.login.username = fieldValue;
|
||||
return;
|
||||
} else if (
|
||||
this.isNullOrWhitespace(cipher.login.password) &&
|
||||
fieldDesignation === "password"
|
||||
) {
|
||||
cipher.login.password = fieldValue;
|
||||
return;
|
||||
} else if (
|
||||
this.isNullOrWhitespace(cipher.login.totp) &&
|
||||
fieldDesignation != null &&
|
||||
fieldDesignation.startsWith("TOTP_")
|
||||
) {
|
||||
cipher.login.totp = fieldValue;
|
||||
return;
|
||||
}
|
||||
} else if (cipher.type === CipherType.Card) {
|
||||
if (this.isNullOrWhitespace(cipher.card.number) && fieldDesignation === "ccnum") {
|
||||
cipher.card.number = fieldValue;
|
||||
cipher.card.brand = this.getCardBrand(fieldValue);
|
||||
return;
|
||||
} else if (this.isNullOrWhitespace(cipher.card.code) && fieldDesignation === "cvv") {
|
||||
cipher.card.code = fieldValue;
|
||||
return;
|
||||
} else if (
|
||||
this.isNullOrWhitespace(cipher.card.cardholderName) &&
|
||||
fieldDesignation === "cardholder"
|
||||
) {
|
||||
cipher.card.cardholderName = fieldValue;
|
||||
return;
|
||||
} else if (
|
||||
this.isNullOrWhitespace(cipher.card.expiration) &&
|
||||
fieldDesignation === "expiry" &&
|
||||
fieldValue.length === 6
|
||||
) {
|
||||
cipher.card.expMonth = (fieldValue as string).substr(4, 2);
|
||||
if (cipher.card.expMonth[0] === "0") {
|
||||
cipher.card.expMonth = cipher.card.expMonth.substr(1, 1);
|
||||
}
|
||||
cipher.card.expYear = (fieldValue as string).substr(0, 4);
|
||||
return;
|
||||
} else if (fieldDesignation === "type") {
|
||||
// Skip since brand was determined from number above
|
||||
return;
|
||||
}
|
||||
} else if (cipher.type === CipherType.Identity) {
|
||||
const identity = cipher.identity;
|
||||
if (this.isNullOrWhitespace(identity.firstName) && fieldDesignation === "firstname") {
|
||||
identity.firstName = fieldValue;
|
||||
return;
|
||||
} else if (this.isNullOrWhitespace(identity.lastName) && fieldDesignation === "lastname") {
|
||||
identity.lastName = fieldValue;
|
||||
return;
|
||||
} else if (this.isNullOrWhitespace(identity.middleName) && fieldDesignation === "initial") {
|
||||
identity.middleName = fieldValue;
|
||||
return;
|
||||
} else if (this.isNullOrWhitespace(identity.phone) && fieldDesignation === "defphone") {
|
||||
identity.phone = fieldValue;
|
||||
return;
|
||||
} else if (this.isNullOrWhitespace(identity.company) && fieldDesignation === "company") {
|
||||
identity.company = fieldValue;
|
||||
return;
|
||||
} else if (this.isNullOrWhitespace(identity.email) && fieldDesignation === "email") {
|
||||
identity.email = fieldValue;
|
||||
return;
|
||||
} else if (this.isNullOrWhitespace(identity.username) && fieldDesignation === "username") {
|
||||
identity.username = fieldValue;
|
||||
return;
|
||||
} else if (fieldDesignation === "address") {
|
||||
// fieldValue is an object casted into a string, so access the plain value instead
|
||||
const { street, city, country, zip } = field[valueKey];
|
||||
identity.address1 = this.getValueOrDefault(street);
|
||||
identity.city = this.getValueOrDefault(city);
|
||||
if (!this.isNullOrWhitespace(country)) {
|
||||
identity.country = country.toUpperCase();
|
||||
}
|
||||
identity.postalCode = this.getValueOrDefault(zip);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const fieldName = this.isNullOrWhitespace(field[nameKey]) ? "no_name" : field[nameKey];
|
||||
if (
|
||||
fieldName === "password" &&
|
||||
cipher.passwordHistory != null &&
|
||||
cipher.passwordHistory.some((h) => h.password === fieldValue)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldType = field.k === "concealed" ? FieldType.Hidden : FieldType.Text;
|
||||
this.processKvp(cipher, fieldName, fieldValue, fieldType);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,637 +0,0 @@
|
||||
import { FieldType } from "../../enums/fieldType";
|
||||
import { SecureNoteType } from "../../enums/secureNoteType";
|
||||
import { ImportResult } from "../../models/domain/import-result";
|
||||
import { CipherRepromptType } from "../../vault/enums/cipher-reprompt-type";
|
||||
import { CipherType } from "../../vault/enums/cipher-type";
|
||||
import { CardView } from "../../vault/models/view/card.view";
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
import { IdentityView } from "../../vault/models/view/identity.view";
|
||||
import { LoginView } from "../../vault/models/view/login.view";
|
||||
import { PasswordHistoryView } from "../../vault/models/view/password-history.view";
|
||||
import { SecureNoteView } from "../../vault/models/view/secure-note.view";
|
||||
import { BaseImporter } from "../base-importer";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import {
|
||||
CategoryEnum,
|
||||
Details,
|
||||
ExportData,
|
||||
FieldsEntity,
|
||||
Item,
|
||||
LoginFieldTypeEnum,
|
||||
Overview,
|
||||
PasswordHistoryEntity,
|
||||
SectionsEntity,
|
||||
UrlsEntity,
|
||||
Value,
|
||||
VaultsEntity,
|
||||
} from "./types/onepassword-1pux-importer-types";
|
||||
|
||||
export class OnePassword1PuxImporter extends BaseImporter implements Importer {
|
||||
result = new ImportResult();
|
||||
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const exportData: ExportData = JSON.parse(data);
|
||||
|
||||
const account = exportData.accounts[0];
|
||||
// TODO Add handling of multiple vaults
|
||||
// const personalVaults = account.vaults[0].filter((v) => v.attrs.type === VaultAttributeTypeEnum.Personal);
|
||||
account.vaults.forEach((vault: VaultsEntity) => {
|
||||
vault.items.forEach((item: Item) => {
|
||||
if (item.trashed === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
|
||||
const category = item.categoryUuid as CategoryEnum;
|
||||
switch (category) {
|
||||
case CategoryEnum.Login:
|
||||
case CategoryEnum.Database:
|
||||
case CategoryEnum.Password:
|
||||
case CategoryEnum.WirelessRouter:
|
||||
case CategoryEnum.Server:
|
||||
case CategoryEnum.API_Credential:
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.login = new LoginView();
|
||||
break;
|
||||
case CategoryEnum.CreditCard:
|
||||
case CategoryEnum.BankAccount:
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
break;
|
||||
case CategoryEnum.SecureNote:
|
||||
case CategoryEnum.SoftwareLicense:
|
||||
case CategoryEnum.EmailAccount:
|
||||
case CategoryEnum.MedicalRecord:
|
||||
// case CategoryEnum.Document:
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
break;
|
||||
case CategoryEnum.Identity:
|
||||
case CategoryEnum.DriversLicense:
|
||||
case CategoryEnum.OutdoorLicense:
|
||||
case CategoryEnum.Membership:
|
||||
case CategoryEnum.Passport:
|
||||
case CategoryEnum.RewardsProgram:
|
||||
case CategoryEnum.SocialSecurityNumber:
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
cipher.favorite = item.favIndex === 1 ? true : false;
|
||||
|
||||
this.processOverview(item.overview, cipher);
|
||||
|
||||
this.processLoginFields(item, cipher);
|
||||
|
||||
this.processDetails(category, item.details, cipher);
|
||||
|
||||
this.parsePasswordHistory(item.details.passwordHistory, cipher);
|
||||
|
||||
this.processSections(category, item.details.sections, cipher);
|
||||
|
||||
if (!this.isNullOrWhitespace(item.details.notesPlain)) {
|
||||
cipher.notes = item.details.notesPlain.split(this.newLineRegex).join("\n") + "\n";
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
this.result.ciphers.push(cipher);
|
||||
});
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(this.result);
|
||||
}
|
||||
|
||||
this.result.success = true;
|
||||
return Promise.resolve(this.result);
|
||||
}
|
||||
|
||||
private processOverview(overview: Overview, cipher: CipherView) {
|
||||
if (overview == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
cipher.name = this.getValueOrDefault(overview.title);
|
||||
|
||||
if (overview.urls != null) {
|
||||
const urls: string[] = [];
|
||||
overview.urls.forEach((url: UrlsEntity) => {
|
||||
if (!this.isNullOrWhitespace(url.url)) {
|
||||
urls.push(url.url);
|
||||
}
|
||||
});
|
||||
cipher.login.uris = this.makeUriArray(urls);
|
||||
}
|
||||
|
||||
if (overview.tags != null && overview.tags.length > 0) {
|
||||
const folderName = this.capitalize(overview.tags[0]);
|
||||
this.processFolder(this.result, folderName);
|
||||
}
|
||||
}
|
||||
|
||||
private capitalize(inputString: string): string {
|
||||
return inputString.trim().replace(/\w\S*/g, (w) => w.replace(/^\w/, (c) => c.toUpperCase()));
|
||||
}
|
||||
|
||||
private processLoginFields(item: Item, cipher: CipherView) {
|
||||
if (item.details == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.details.loginFields == null || item.details.loginFields.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.details.loginFields.forEach((loginField) => {
|
||||
if (loginField.designation === "username" && loginField.value !== "") {
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.login.username = loginField.value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (loginField.designation === "password" && loginField.value !== "") {
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.login.password = loginField.value;
|
||||
return;
|
||||
}
|
||||
|
||||
let fieldValue = loginField.value;
|
||||
let fieldType: FieldType = FieldType.Text;
|
||||
switch (loginField.fieldType) {
|
||||
case LoginFieldTypeEnum.Password:
|
||||
fieldType = FieldType.Hidden;
|
||||
break;
|
||||
case LoginFieldTypeEnum.CheckBox:
|
||||
fieldValue = loginField.value !== "" ? "true" : "false";
|
||||
fieldType = FieldType.Boolean;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.processKvp(cipher, loginField.name, fieldValue, fieldType);
|
||||
});
|
||||
}
|
||||
|
||||
private processDetails(category: CategoryEnum, details: Details, cipher: CipherView) {
|
||||
if (category !== CategoryEnum.Password) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (details == null) {
|
||||
return;
|
||||
}
|
||||
cipher.login.password = details.password;
|
||||
}
|
||||
|
||||
private processSections(category: CategoryEnum, sections: SectionsEntity[], cipher: CipherView) {
|
||||
if (sections == null || sections.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
sections.forEach((section: SectionsEntity) => {
|
||||
if (section.fields == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.parseSectionFields(category, section.fields, cipher);
|
||||
});
|
||||
}
|
||||
|
||||
private parseSectionFields(category: CategoryEnum, fields: FieldsEntity[], cipher: CipherView) {
|
||||
fields.forEach((field: FieldsEntity) => {
|
||||
const valueKey = Object.keys(field.value)[0];
|
||||
const anyField = field as any;
|
||||
|
||||
if (
|
||||
anyField.value == null ||
|
||||
anyField.value[valueKey] == null ||
|
||||
anyField.value[valueKey] === ""
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldName = this.getFieldName(field.id, field.title);
|
||||
const fieldValue = this.extractValue(field.value, valueKey);
|
||||
|
||||
if (cipher.type === CipherType.Login) {
|
||||
if (this.fillLogin(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (category) {
|
||||
case CategoryEnum.Login:
|
||||
case CategoryEnum.Database:
|
||||
case CategoryEnum.EmailAccount:
|
||||
case CategoryEnum.WirelessRouter:
|
||||
break;
|
||||
|
||||
case CategoryEnum.Server:
|
||||
if (this.isNullOrWhitespace(cipher.login.uri) && field.id === "url") {
|
||||
cipher.login.uris = this.makeUriArray(fieldValue);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case CategoryEnum.API_Credential:
|
||||
if (this.fillApiCredentials(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (cipher.type === CipherType.Card) {
|
||||
if (this.fillCreditCard(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (category === CategoryEnum.BankAccount) {
|
||||
if (this.fillBankAccount(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (cipher.type === CipherType.Identity) {
|
||||
if (this.fillIdentity(field, fieldValue, cipher, valueKey)) {
|
||||
return;
|
||||
}
|
||||
if (valueKey === "address") {
|
||||
// fieldValue is an object casted into a string, so access the plain value instead
|
||||
const { street, city, country, zip, state } = field.value.address;
|
||||
cipher.identity.address1 = this.getValueOrDefault(street);
|
||||
cipher.identity.city = this.getValueOrDefault(city);
|
||||
if (!this.isNullOrWhitespace(country)) {
|
||||
cipher.identity.country = country.toUpperCase();
|
||||
}
|
||||
cipher.identity.postalCode = this.getValueOrDefault(zip);
|
||||
cipher.identity.state = this.getValueOrDefault(state);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (category) {
|
||||
case CategoryEnum.Identity:
|
||||
break;
|
||||
case CategoryEnum.DriversLicense:
|
||||
if (this.fillDriversLicense(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CategoryEnum.OutdoorLicense:
|
||||
if (this.fillOutdoorLicense(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CategoryEnum.Membership:
|
||||
if (this.fillMembership(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CategoryEnum.Passport:
|
||||
if (this.fillPassport(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CategoryEnum.RewardsProgram:
|
||||
if (this.fillRewardsProgram(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CategoryEnum.SocialSecurityNumber:
|
||||
if (this.fillSSN(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (valueKey === "email") {
|
||||
// fieldValue is an object casted into a string, so access the plain value instead
|
||||
const { email_address, provider } = field.value.email;
|
||||
this.processKvp(cipher, fieldName, email_address, FieldType.Text);
|
||||
this.processKvp(cipher, "provider", provider, FieldType.Text);
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not include a password field if it's already in the history
|
||||
if (
|
||||
field.title === "password" &&
|
||||
cipher.passwordHistory != null &&
|
||||
cipher.passwordHistory.some((h) => h.password === fieldValue)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO ?? If one of the fields is marked as guarded, then activate Password-Reprompt for the entire item
|
||||
if (field.guarded && cipher.reprompt === CipherRepromptType.None) {
|
||||
cipher.reprompt = CipherRepromptType.Password;
|
||||
}
|
||||
|
||||
const fieldType = valueKey === "concealed" ? FieldType.Hidden : FieldType.Text;
|
||||
this.processKvp(cipher, fieldName, fieldValue, fieldType);
|
||||
});
|
||||
}
|
||||
|
||||
private getFieldName(id: string, title: string): string {
|
||||
if (this.isNullOrWhitespace(title)) {
|
||||
return id;
|
||||
}
|
||||
|
||||
// Naive approach of checking if the fields id is usable
|
||||
if (id.length > 25 && RegExp(/[0-9]{2}[A-Z]{2}/, "i").test(id)) {
|
||||
return title;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private extractValue(value: Value, valueKey: string): string {
|
||||
if (valueKey === "date") {
|
||||
return new Date(value.date * 1000).toUTCString();
|
||||
}
|
||||
|
||||
if (valueKey === "monthYear") {
|
||||
return value.monthYear.toString();
|
||||
}
|
||||
|
||||
return (value as any)[valueKey];
|
||||
}
|
||||
|
||||
private fillLogin(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
const fieldName = this.getFieldName(field.id, field.title);
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.login.username) && fieldName === "username") {
|
||||
cipher.login.username = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.login.password) && fieldName === "password") {
|
||||
cipher.login.password = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
this.isNullOrWhitespace(cipher.login.totp) &&
|
||||
field.id != null &&
|
||||
field.id.startsWith("TOTP_")
|
||||
) {
|
||||
cipher.login.totp = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillApiCredentials(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
const fieldName = this.getFieldName(field.id, field.title);
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.login.password) && fieldName === "credential") {
|
||||
cipher.login.password = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.login.uri) && fieldName === "hostname") {
|
||||
cipher.login.uris = this.makeUriArray(fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillCreditCard(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.card.number) && field.id === "ccnum") {
|
||||
cipher.card.number = fieldValue;
|
||||
cipher.card.brand = this.getCardBrand(fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.card.code) && field.id === "cvv") {
|
||||
cipher.card.code = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.card.cardholderName) && field.id === "cardholder") {
|
||||
cipher.card.cardholderName = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.card.expiration) && field.id === "expiry") {
|
||||
const monthYear: string = fieldValue.toString().trim();
|
||||
cipher.card.expMonth = monthYear.substring(4, 6);
|
||||
if (cipher.card.expMonth[0] === "0") {
|
||||
cipher.card.expMonth = cipher.card.expMonth.substring(1, 2);
|
||||
}
|
||||
cipher.card.expYear = monthYear.substring(0, 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (field.id === "type") {
|
||||
// Skip since brand was determined from number above
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillBankAccount(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.card.cardholderName) && field.id === "owner") {
|
||||
cipher.card.cardholderName = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillIdentity(
|
||||
field: FieldsEntity,
|
||||
fieldValue: string,
|
||||
cipher: CipherView,
|
||||
valueKey: string
|
||||
): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "firstname") {
|
||||
cipher.identity.firstName = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.lastName) && field.id === "lastname") {
|
||||
cipher.identity.lastName = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.middleName) && field.id === "initial") {
|
||||
cipher.identity.middleName = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.phone) && field.id === "defphone") {
|
||||
cipher.identity.phone = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.company) && field.id === "company") {
|
||||
cipher.identity.company = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.email)) {
|
||||
if (valueKey === "email") {
|
||||
const { email_address, provider } = field.value.email;
|
||||
cipher.identity.email = this.getValueOrDefault(email_address);
|
||||
this.processKvp(cipher, "provider", provider, FieldType.Text);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (field.id === "email") {
|
||||
cipher.identity.email = fieldValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.username) && field.id === "username") {
|
||||
cipher.identity.username = fieldValue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillDriversLicense(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "fullname") {
|
||||
this.processFullName(cipher, fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.address1) && field.id === "address") {
|
||||
cipher.identity.address1 = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO ISO code
|
||||
if (this.isNullOrWhitespace(cipher.identity.country) && field.id === "country") {
|
||||
cipher.identity.country = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.state) && field.id === "state") {
|
||||
cipher.identity.state = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.licenseNumber) && field.id === "number") {
|
||||
cipher.identity.licenseNumber = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillOutdoorLicense(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "name") {
|
||||
this.processFullName(cipher, fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO ISO code
|
||||
if (this.isNullOrWhitespace(cipher.identity.country) && field.id === "country") {
|
||||
cipher.identity.country = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.state) && field.id === "state") {
|
||||
cipher.identity.state = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillMembership(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "member_name") {
|
||||
this.processFullName(cipher, fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.company) && field.id === "org_name") {
|
||||
cipher.identity.company = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.phone) && field.id === "phone") {
|
||||
cipher.identity.phone = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillPassport(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "fullname") {
|
||||
this.processFullName(cipher, fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO Iso
|
||||
if (this.isNullOrWhitespace(cipher.identity.country) && field.id === "issuing_country") {
|
||||
cipher.identity.country = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.passportNumber) && field.id === "number") {
|
||||
cipher.identity.passportNumber = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillRewardsProgram(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "member_name") {
|
||||
this.processFullName(cipher, fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.company) && field.id === "company_name") {
|
||||
cipher.identity.company = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillSSN(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "name") {
|
||||
this.processFullName(cipher, fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.ssn) && field.id === "number") {
|
||||
cipher.identity.ssn = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private parsePasswordHistory(historyItems: PasswordHistoryEntity[], cipher: CipherView) {
|
||||
if (historyItems == null || historyItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxSize = historyItems.length > 5 ? 5 : historyItems.length;
|
||||
cipher.passwordHistory = historyItems
|
||||
.filter((h: any) => !this.isNullOrWhitespace(h.value) && h.time != null)
|
||||
.sort((a, b) => b.time - a.time)
|
||||
.slice(0, maxSize)
|
||||
.map((h: any) => {
|
||||
const ph = new PasswordHistoryView();
|
||||
ph.password = h.value;
|
||||
ph.lastUsedDate = new Date(("" + h.time).length >= 13 ? h.time : h.time * 1000);
|
||||
return ph;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,383 +0,0 @@
|
||||
import { FieldType } from "../../enums/fieldType";
|
||||
import { ImportResult } from "../../models/domain/import-result";
|
||||
import { CipherType } from "../../vault/enums/cipher-type";
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
import { BaseImporter } from "../base-importer";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { CipherImportContext } from "./cipher-import-context";
|
||||
|
||||
export const IgnoredProperties = [
|
||||
"ainfo",
|
||||
"autosubmit",
|
||||
"notesplain",
|
||||
"ps",
|
||||
"scope",
|
||||
"tags",
|
||||
"title",
|
||||
"uuid",
|
||||
"notes",
|
||||
];
|
||||
|
||||
export abstract class OnePasswordCsvImporter extends BaseImporter implements Importer {
|
||||
protected loginPropertyParsers = [
|
||||
this.setLoginUsername,
|
||||
this.setLoginPassword,
|
||||
this.setLoginUris,
|
||||
];
|
||||
protected creditCardPropertyParsers = [
|
||||
this.setCreditCardNumber,
|
||||
this.setCreditCardVerification,
|
||||
this.setCreditCardCardholderName,
|
||||
this.setCreditCardExpiry,
|
||||
];
|
||||
protected identityPropertyParsers = [
|
||||
this.setIdentityFirstName,
|
||||
this.setIdentityInitial,
|
||||
this.setIdentityLastName,
|
||||
this.setIdentityUserName,
|
||||
this.setIdentityEmail,
|
||||
this.setIdentityPhone,
|
||||
this.setIdentityCompany,
|
||||
];
|
||||
|
||||
abstract setCipherType(value: any, cipher: CipherView): void;
|
||||
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, true, {
|
||||
quoteChar: '"',
|
||||
escapeChar: "\\",
|
||||
});
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((value) => {
|
||||
if (this.isNullOrWhitespace(this.getProp(value, "title"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(this.getProp(value, "title"), "--");
|
||||
|
||||
this.setNotes(value, cipher);
|
||||
|
||||
this.setCipherType(value, cipher);
|
||||
|
||||
let altUsername: string = null;
|
||||
for (const property in value) {
|
||||
// eslint-disable-next-line
|
||||
if (!value.hasOwnProperty(property) || this.isNullOrWhitespace(value[property])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const context = new CipherImportContext(value, property, cipher);
|
||||
if (cipher.type === CipherType.Login && this.setKnownLoginValue(context)) {
|
||||
continue;
|
||||
} else if (cipher.type === CipherType.Card && this.setKnownCreditCardValue(context)) {
|
||||
continue;
|
||||
} else if (cipher.type === CipherType.Identity && this.setKnownIdentityValue(context)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
altUsername = this.setUnknownValue(context, altUsername);
|
||||
}
|
||||
|
||||
if (
|
||||
cipher.type === CipherType.Login &&
|
||||
!this.isNullOrWhitespace(altUsername) &&
|
||||
this.isNullOrWhitespace(cipher.login.username) &&
|
||||
altUsername.indexOf("://") === -1
|
||||
) {
|
||||
cipher.login.username = altUsername;
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
protected getProp(obj: any, name: string): any {
|
||||
const lowerObj = Object.entries(obj).reduce((agg: any, entry: [string, any]) => {
|
||||
agg[entry[0].toLowerCase()] = entry[1];
|
||||
return agg;
|
||||
}, {});
|
||||
return lowerObj[name.toLowerCase()];
|
||||
}
|
||||
|
||||
protected getPropByRegexp(obj: any, regexp: RegExp): any {
|
||||
const matchingKeys = Object.keys(obj).reduce((agg: string[], key: string) => {
|
||||
if (key.match(regexp)) {
|
||||
agg.push(key);
|
||||
}
|
||||
return agg;
|
||||
}, []);
|
||||
if (matchingKeys.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
return obj[matchingKeys[0]];
|
||||
}
|
||||
}
|
||||
|
||||
protected getPropIncluding(obj: any, name: string): any {
|
||||
const includesMap = Object.keys(obj).reduce((agg: string[], entry: string) => {
|
||||
if (entry.toLowerCase().includes(name.toLowerCase())) {
|
||||
agg.push(entry);
|
||||
}
|
||||
return agg;
|
||||
}, []);
|
||||
if (includesMap.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
return obj[includesMap[0]];
|
||||
}
|
||||
}
|
||||
|
||||
protected setNotes(importRecord: any, cipher: CipherView) {
|
||||
cipher.notes =
|
||||
this.getValueOrDefault(this.getProp(importRecord, "notesPlain"), "") +
|
||||
"\n" +
|
||||
this.getValueOrDefault(this.getProp(importRecord, "notes"), "") +
|
||||
"\n";
|
||||
cipher.notes.trim();
|
||||
}
|
||||
|
||||
protected setKnownLoginValue(context: CipherImportContext): boolean {
|
||||
return this.loginPropertyParsers.reduce((agg: boolean, func) => {
|
||||
if (!agg) {
|
||||
agg = func.bind(this)(context);
|
||||
}
|
||||
return agg;
|
||||
}, false);
|
||||
}
|
||||
|
||||
protected setKnownCreditCardValue(context: CipherImportContext): boolean {
|
||||
return this.creditCardPropertyParsers.reduce((agg: boolean, func) => {
|
||||
if (!agg) {
|
||||
agg = func.bind(this)(context);
|
||||
}
|
||||
return agg;
|
||||
}, false);
|
||||
}
|
||||
|
||||
protected setKnownIdentityValue(context: CipherImportContext): boolean {
|
||||
return this.identityPropertyParsers.reduce((agg: boolean, func) => {
|
||||
if (!agg) {
|
||||
agg = func.bind(this)(context);
|
||||
}
|
||||
return agg;
|
||||
}, false);
|
||||
}
|
||||
|
||||
protected setUnknownValue(context: CipherImportContext, altUsername: string): string {
|
||||
if (
|
||||
IgnoredProperties.indexOf(context.lowerProperty) === -1 &&
|
||||
!context.lowerProperty.startsWith("section:") &&
|
||||
!context.lowerProperty.startsWith("section ")
|
||||
) {
|
||||
if (altUsername == null && context.lowerProperty === "email") {
|
||||
return context.importRecord[context.property];
|
||||
} else if (
|
||||
context.lowerProperty === "created date" ||
|
||||
context.lowerProperty === "modified date"
|
||||
) {
|
||||
const readableDate = new Date(
|
||||
parseInt(context.importRecord[context.property], 10) * 1000
|
||||
).toUTCString();
|
||||
this.processKvp(context.cipher, "1Password " + context.property, readableDate);
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
context.lowerProperty.includes("password") ||
|
||||
context.lowerProperty.includes("key") ||
|
||||
context.lowerProperty.includes("secret")
|
||||
) {
|
||||
this.processKvp(
|
||||
context.cipher,
|
||||
context.property,
|
||||
context.importRecord[context.property],
|
||||
FieldType.Hidden
|
||||
);
|
||||
} else {
|
||||
this.processKvp(context.cipher, context.property, context.importRecord[context.property]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected setIdentityFirstName(context: CipherImportContext) {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.identity.firstName) &&
|
||||
context.lowerProperty.includes("first name")
|
||||
) {
|
||||
context.cipher.identity.firstName = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setIdentityInitial(context: CipherImportContext) {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.identity.middleName) &&
|
||||
context.lowerProperty.includes("initial")
|
||||
) {
|
||||
context.cipher.identity.middleName = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setIdentityLastName(context: CipherImportContext) {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.identity.lastName) &&
|
||||
context.lowerProperty.includes("last name")
|
||||
) {
|
||||
context.cipher.identity.lastName = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setIdentityUserName(context: CipherImportContext) {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.identity.username) &&
|
||||
context.lowerProperty.includes("username")
|
||||
) {
|
||||
context.cipher.identity.username = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setIdentityCompany(context: CipherImportContext) {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.identity.company) &&
|
||||
context.lowerProperty.includes("company")
|
||||
) {
|
||||
context.cipher.identity.company = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setIdentityPhone(context: CipherImportContext) {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.identity.phone) &&
|
||||
context.lowerProperty.includes("default phone")
|
||||
) {
|
||||
context.cipher.identity.phone = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setIdentityEmail(context: CipherImportContext) {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.identity.email) &&
|
||||
context.lowerProperty.includes("email")
|
||||
) {
|
||||
context.cipher.identity.email = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setCreditCardNumber(context: CipherImportContext): boolean {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.card.number) &&
|
||||
context.lowerProperty.includes("number")
|
||||
) {
|
||||
context.cipher.card.number = context.importRecord[context.property];
|
||||
context.cipher.card.brand = this.getCardBrand(context.cipher.card.number);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setCreditCardVerification(context: CipherImportContext) {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.card.code) &&
|
||||
context.lowerProperty.includes("verification number")
|
||||
) {
|
||||
context.cipher.card.code = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setCreditCardCardholderName(context: CipherImportContext) {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.card.cardholderName) &&
|
||||
context.lowerProperty.includes("cardholder name")
|
||||
) {
|
||||
context.cipher.card.cardholderName = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setCreditCardExpiry(context: CipherImportContext) {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.card.expiration) &&
|
||||
context.lowerProperty.includes("expiry date") &&
|
||||
context.importRecord[context.property].length === 7
|
||||
) {
|
||||
context.cipher.card.expMonth = (context.importRecord[context.property] as string).substr(
|
||||
0,
|
||||
2
|
||||
);
|
||||
if (context.cipher.card.expMonth[0] === "0") {
|
||||
context.cipher.card.expMonth = context.cipher.card.expMonth.substr(1, 1);
|
||||
}
|
||||
context.cipher.card.expYear = (context.importRecord[context.property] as string).substr(3, 4);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setLoginPassword(context: CipherImportContext) {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.login.password) &&
|
||||
context.lowerProperty === "password"
|
||||
) {
|
||||
context.cipher.login.password = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setLoginUsername(context: CipherImportContext) {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.login.username) &&
|
||||
context.lowerProperty === "username"
|
||||
) {
|
||||
context.cipher.login.username = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setLoginUris(context: CipherImportContext) {
|
||||
if (
|
||||
(context.cipher.login.uris == null || context.cipher.login.uris.length === 0) &&
|
||||
context.lowerProperty === "urls"
|
||||
) {
|
||||
const urls = context.importRecord[context.property].split(this.newLineRegex);
|
||||
context.cipher.login.uris = this.makeUriArray(urls);
|
||||
return true;
|
||||
} else if (context.lowerProperty === "url") {
|
||||
if (context.cipher.login.uris == null) {
|
||||
context.cipher.login.uris = [];
|
||||
}
|
||||
context.cipher.login.uris.concat(this.makeUriArray(context.importRecord[context.property]));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { CipherType } from "../../vault/enums/cipher-type";
|
||||
import { CardView } from "../../vault/models/view/card.view";
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
import { IdentityView } from "../../vault/models/view/identity.view";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { IgnoredProperties, OnePasswordCsvImporter } from "./onepassword-csv-importer";
|
||||
|
||||
export class OnePasswordMacCsvImporter extends OnePasswordCsvImporter implements Importer {
|
||||
setCipherType(value: any, cipher: CipherView) {
|
||||
const onePassType = this.getValueOrDefault(this.getProp(value, "type"), "Login");
|
||||
switch (onePassType) {
|
||||
case "Credit Card":
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
IgnoredProperties.push("type");
|
||||
break;
|
||||
case "Identity":
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
IgnoredProperties.push("type");
|
||||
break;
|
||||
case "Login":
|
||||
case "Secure Note":
|
||||
IgnoredProperties.push("type");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { CipherType } from "../../vault/enums/cipher-type";
|
||||
import { CardView } from "../../vault/models/view/card.view";
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
import { IdentityView } from "../../vault/models/view/identity.view";
|
||||
import { LoginView } from "../../vault/models/view/login.view";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { CipherImportContext } from "./cipher-import-context";
|
||||
import { OnePasswordCsvImporter } from "./onepassword-csv-importer";
|
||||
|
||||
export class OnePasswordWinCsvImporter extends OnePasswordCsvImporter implements Importer {
|
||||
constructor() {
|
||||
super();
|
||||
this.identityPropertyParsers.push(this.setIdentityAddress);
|
||||
}
|
||||
|
||||
setCipherType(value: any, cipher: CipherView) {
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.login = new LoginView();
|
||||
|
||||
if (
|
||||
!this.isNullOrWhitespace(this.getPropByRegexp(value, /\d+: number/i)) &&
|
||||
!this.isNullOrWhitespace(this.getPropByRegexp(value, /\d+: expiry date/i))
|
||||
) {
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
}
|
||||
|
||||
if (
|
||||
!this.isNullOrWhitespace(this.getPropByRegexp(value, /name \d+: first name/i)) ||
|
||||
!this.isNullOrWhitespace(this.getPropByRegexp(value, /name \d+: initial/i)) ||
|
||||
!this.isNullOrWhitespace(this.getPropByRegexp(value, /name \d+: last name/i)) ||
|
||||
!this.isNullOrWhitespace(this.getPropByRegexp(value, /internet \d+: email/i))
|
||||
) {
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
}
|
||||
}
|
||||
|
||||
setIdentityAddress(context: CipherImportContext) {
|
||||
if (context.lowerProperty.match(/address \d+: address/i)) {
|
||||
this.processKvp(context.cipher, "address", context.importRecord[context.property]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setCreditCardExpiry(context: CipherImportContext) {
|
||||
if (
|
||||
this.isNullOrWhitespace(context.cipher.card.expiration) &&
|
||||
context.lowerProperty.includes("expiry date")
|
||||
) {
|
||||
const expSplit = (context.importRecord[context.property] as string).split("/");
|
||||
context.cipher.card.expMonth = expSplit[0];
|
||||
if (context.cipher.card.expMonth[0] === "0" && context.cipher.card.expMonth.length === 2) {
|
||||
context.cipher.card.expMonth = context.cipher.card.expMonth.substr(1, 1);
|
||||
}
|
||||
context.cipher.card.expYear = expSplit[2].length > 4 ? expSplit[2].substr(0, 4) : expSplit[2];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
export interface ExportData {
|
||||
accounts?: AccountsEntity[] | null;
|
||||
}
|
||||
export interface AccountsEntity {
|
||||
attrs: AccountAttributes;
|
||||
vaults?: VaultsEntity[] | null;
|
||||
}
|
||||
export interface AccountAttributes {
|
||||
accountName: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
email: string;
|
||||
uuid: string;
|
||||
domain: string;
|
||||
}
|
||||
export interface VaultsEntity {
|
||||
attrs: VaultAttributes;
|
||||
items?: Item[] | null;
|
||||
}
|
||||
export interface VaultAttributes {
|
||||
uuid: string;
|
||||
desc: string;
|
||||
avatar: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export enum CategoryEnum {
|
||||
Login = "001",
|
||||
CreditCard = "002",
|
||||
SecureNote = "003",
|
||||
Identity = "004",
|
||||
Password = "005",
|
||||
Document = "006",
|
||||
SoftwareLicense = "100",
|
||||
BankAccount = "101",
|
||||
Database = "102",
|
||||
DriversLicense = "103",
|
||||
OutdoorLicense = "104",
|
||||
Membership = "105",
|
||||
Passport = "106",
|
||||
RewardsProgram = "107",
|
||||
SocialSecurityNumber = "108",
|
||||
WirelessRouter = "109",
|
||||
Server = "110",
|
||||
EmailAccount = "111",
|
||||
API_Credential = "112",
|
||||
MedicalRecord = "113",
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
uuid: string;
|
||||
favIndex: number;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
trashed?: boolean;
|
||||
categoryUuid: string;
|
||||
details: Details;
|
||||
overview: Overview;
|
||||
}
|
||||
export interface Details {
|
||||
loginFields?: (LoginFieldsEntity | null)[] | null;
|
||||
notesPlain?: string | null;
|
||||
sections?: (SectionsEntity | null)[] | null;
|
||||
passwordHistory?: (PasswordHistoryEntity | null)[] | null;
|
||||
documentAttributes?: DocumentAttributes | null;
|
||||
password?: string | null;
|
||||
}
|
||||
|
||||
export enum LoginFieldTypeEnum {
|
||||
TextOrHtml = "T",
|
||||
EmailAddress = "E",
|
||||
URL = "U",
|
||||
Number = "N",
|
||||
Password = "P",
|
||||
TextArea = "A",
|
||||
PhoneNumber = "T",
|
||||
CheckBox = "C",
|
||||
}
|
||||
export interface LoginFieldsEntity {
|
||||
value: string;
|
||||
id: string;
|
||||
name: string;
|
||||
fieldType: LoginFieldTypeEnum | string;
|
||||
designation?: string | null;
|
||||
}
|
||||
export interface SectionsEntity {
|
||||
title: string;
|
||||
name?: string | null;
|
||||
fields?: FieldsEntity[] | null;
|
||||
}
|
||||
export interface FieldsEntity {
|
||||
title: string;
|
||||
id: string;
|
||||
value: Value;
|
||||
indexAtSource: number;
|
||||
guarded: boolean;
|
||||
multiline: boolean;
|
||||
dontGenerate: boolean;
|
||||
placeholder?: string;
|
||||
inputTraits: InputTraits;
|
||||
clipboardFilter?: string | null;
|
||||
}
|
||||
export interface Value {
|
||||
totp?: string | null;
|
||||
date?: number | null;
|
||||
string?: string | null;
|
||||
concealed?: string | null;
|
||||
email?: Email | null;
|
||||
phone?: string | null;
|
||||
menu?: string | null;
|
||||
gender?: string | null;
|
||||
monthYear?: number | null;
|
||||
url?: string | null;
|
||||
address?: Address | null;
|
||||
creditCardType?: string | null;
|
||||
creditCardNumber?: string | null;
|
||||
reference?: string | null;
|
||||
}
|
||||
|
||||
export interface Email {
|
||||
email_address: string;
|
||||
provider: string;
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
street: string;
|
||||
city: string;
|
||||
country: string;
|
||||
zip: string;
|
||||
state: string;
|
||||
}
|
||||
export interface InputTraits {
|
||||
keyboard: string;
|
||||
correction: string;
|
||||
capitalization: string;
|
||||
}
|
||||
export interface PasswordHistoryEntity {
|
||||
value: string;
|
||||
time: number;
|
||||
}
|
||||
export interface DocumentAttributes {
|
||||
fileName: string;
|
||||
documentId: string;
|
||||
decryptedSize: number;
|
||||
}
|
||||
export interface Overview {
|
||||
subtitle: string;
|
||||
title: string;
|
||||
url: string;
|
||||
urls?: UrlsEntity[] | null;
|
||||
ps?: number | null;
|
||||
pbe?: number | null;
|
||||
pgrng?: boolean | null;
|
||||
tags?: string[] | null;
|
||||
}
|
||||
export interface UrlsEntity {
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import { CollectionView } from "../admin-console/models/view/collection.view";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class PadlockCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, false);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
let headers: string[] = null;
|
||||
results.forEach((value) => {
|
||||
if (headers == null) {
|
||||
headers = value.map((v: string) => v);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.length < 2 || value.length !== headers.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isNullOrWhitespace(value[1])) {
|
||||
if (this.organization) {
|
||||
const tags = (value[1] as string).split(",");
|
||||
tags.forEach((tag) => {
|
||||
tag = tag.trim();
|
||||
let addCollection = true;
|
||||
let collectionIndex = result.collections.length;
|
||||
|
||||
for (let i = 0; i < result.collections.length; i++) {
|
||||
if (result.collections[i].name === tag) {
|
||||
addCollection = false;
|
||||
collectionIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (addCollection) {
|
||||
const collection = new CollectionView();
|
||||
collection.name = tag;
|
||||
result.collections.push(collection);
|
||||
}
|
||||
|
||||
result.collectionRelationships.push([result.ciphers.length, collectionIndex]);
|
||||
});
|
||||
} else {
|
||||
const tags = (value[1] as string).split(",");
|
||||
const tag = tags.length > 0 ? tags[0].trim() : null;
|
||||
this.processFolder(result, tag);
|
||||
}
|
||||
}
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value[0], "--");
|
||||
|
||||
for (let i = 2; i < value.length; i++) {
|
||||
const header = headers[i].trim().toLowerCase();
|
||||
if (this.isNullOrWhitespace(value[i]) || this.isNullOrWhitespace(header)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.usernameFieldNames.indexOf(header) > -1) {
|
||||
cipher.login.username = value[i];
|
||||
} else if (this.passwordFieldNames.indexOf(header) > -1) {
|
||||
cipher.login.password = value[i];
|
||||
} else if (this.uriFieldNames.indexOf(header) > -1) {
|
||||
cipher.login.uris = this.makeUriArray(value[i]);
|
||||
} else {
|
||||
this.processKvp(cipher, headers[i], value[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class PassKeepCsvImporter 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((value) => {
|
||||
this.processFolder(result, this.getValue("category", value));
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.notes = this.getValue("description", value);
|
||||
cipher.name = this.getValueOrDefault(this.getValue("title", value), "--");
|
||||
cipher.login.username = this.getValue("username", value);
|
||||
cipher.login.password = this.getValue("password", value);
|
||||
cipher.login.uris = this.makeUriArray(this.getValue("site", value));
|
||||
this.processKvp(cipher, "Password 2", this.getValue("password2", value));
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private getValue(key: string, value: any) {
|
||||
return this.getValueOrDefault(value[key], this.getValueOrDefault(value[" " + key]));
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { ImportResult } from "../../models/domain/import-result";
|
||||
import { BaseImporter } from "../base-importer";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { PasskyJsonExport } from "./passky-json-types";
|
||||
|
||||
export class PasskyJsonImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const passkyExport: PasskyJsonExport = JSON.parse(data);
|
||||
if (
|
||||
passkyExport == null ||
|
||||
passkyExport.passwords == null ||
|
||||
passkyExport.passwords.length === 0
|
||||
) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
if (passkyExport.encrypted == true) {
|
||||
result.success = false;
|
||||
result.errorMessage = "Unable to import an encrypted passky backup.";
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
passkyExport.passwords.forEach((record) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = record.website;
|
||||
cipher.login.username = record.username;
|
||||
cipher.login.password = record.password;
|
||||
|
||||
cipher.login.uris = this.makeUriArray(record.website);
|
||||
cipher.notes = record.message;
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
export interface PasskyJsonExport {
|
||||
encrypted: boolean;
|
||||
passwords: LoginEntry[];
|
||||
}
|
||||
|
||||
export interface LoginEntry {
|
||||
website: string;
|
||||
username: string;
|
||||
password: string;
|
||||
message: string;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class PassmanJsonImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = JSON.parse(data);
|
||||
if (results == null || results.length === 0) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((credential: any) => {
|
||||
if (credential.tags != null && credential.tags.length > 0) {
|
||||
const folderName = credential.tags[0].text;
|
||||
this.processFolder(result, folderName);
|
||||
}
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = credential.label;
|
||||
|
||||
cipher.login.username = this.getValueOrDefault(credential.username);
|
||||
if (this.isNullOrWhitespace(cipher.login.username)) {
|
||||
cipher.login.username = this.getValueOrDefault(credential.email);
|
||||
} else if (!this.isNullOrWhitespace(credential.email)) {
|
||||
cipher.notes = "Email: " + credential.email + "\n";
|
||||
}
|
||||
|
||||
cipher.login.password = this.getValueOrDefault(credential.password);
|
||||
cipher.login.uris = this.makeUriArray(credential.url);
|
||||
cipher.notes += this.getValueOrDefault(credential.description, "");
|
||||
if (credential.otp != null) {
|
||||
cipher.login.totp = this.getValueOrDefault(credential.otp.secret);
|
||||
}
|
||||
|
||||
if (credential.custom_fields != null) {
|
||||
credential.custom_fields.forEach((customField: any) => {
|
||||
switch (customField.field_type) {
|
||||
case "text":
|
||||
case "password":
|
||||
this.processKvp(cipher, customField.label, customField.value);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import { CollectionView } from "../admin-console/models/view/collection.view";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class PasspackCsvImporter 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((value) => {
|
||||
const tagsJson = !this.isNullOrWhitespace(value.Tags) ? JSON.parse(value.Tags) : null;
|
||||
const tags: string[] =
|
||||
tagsJson != null && tagsJson.tags != null && tagsJson.tags.length > 0
|
||||
? tagsJson.tags
|
||||
.map((tagJson: string) => {
|
||||
try {
|
||||
const t = JSON.parse(tagJson);
|
||||
return this.getValueOrDefault(t.tag);
|
||||
} catch {
|
||||
// Ignore error
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((t: string) => !this.isNullOrWhitespace(t))
|
||||
: null;
|
||||
|
||||
if (this.organization && tags != null && tags.length > 0) {
|
||||
tags.forEach((tag) => {
|
||||
let addCollection = true;
|
||||
let collectionIndex = result.collections.length;
|
||||
|
||||
for (let i = 0; i < result.collections.length; i++) {
|
||||
if (result.collections[i].name === tag) {
|
||||
addCollection = false;
|
||||
collectionIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (addCollection) {
|
||||
const collection = new CollectionView();
|
||||
collection.name = tag;
|
||||
result.collections.push(collection);
|
||||
}
|
||||
|
||||
result.collectionRelationships.push([result.ciphers.length, collectionIndex]);
|
||||
});
|
||||
} else if (!this.organization && tags != null && tags.length > 0) {
|
||||
this.processFolder(result, tags[0]);
|
||||
}
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.notes = this.getValueOrDefault(value.Notes, "");
|
||||
cipher.notes += "\n\n" + this.getValueOrDefault(value["Shared Notes"], "") + "\n";
|
||||
cipher.name = this.getValueOrDefault(value["Entry Name"], "--");
|
||||
cipher.login.username = this.getValueOrDefault(value["User ID"]);
|
||||
cipher.login.password = this.getValueOrDefault(value.Password);
|
||||
cipher.login.uris = this.makeUriArray(value.URL);
|
||||
|
||||
if (value.__parsed_extra != null && value.__parsed_extra.length > 0) {
|
||||
value.__parsed_extra.forEach((extra: string) => {
|
||||
if (!this.isNullOrWhitespace(extra)) {
|
||||
cipher.notes += "\n" + extra;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const fieldsJson = !this.isNullOrWhitespace(value["Extra Fields"])
|
||||
? JSON.parse(value["Extra Fields"])
|
||||
: null;
|
||||
const fields =
|
||||
fieldsJson != null && fieldsJson.extraFields != null && fieldsJson.extraFields.length > 0
|
||||
? fieldsJson.extraFields.map((fieldJson: string) => {
|
||||
try {
|
||||
return JSON.parse(fieldJson);
|
||||
} catch {
|
||||
// Ignore error
|
||||
}
|
||||
return null;
|
||||
})
|
||||
: null;
|
||||
if (fields != null) {
|
||||
fields.forEach((f: any) => {
|
||||
if (f != null) {
|
||||
this.processKvp(cipher, f.name, f.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class PasswordAgentCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, false);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
let newVersion = true;
|
||||
results.forEach((value) => {
|
||||
if (value.length !== 5 && value.length < 9) {
|
||||
return;
|
||||
}
|
||||
const altFormat = value.length === 10 && value[0] === "0";
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value[altFormat ? 1 : 0], "--");
|
||||
cipher.login.username = this.getValueOrDefault(value[altFormat ? 2 : 1]);
|
||||
cipher.login.password = this.getValueOrDefault(value[altFormat ? 3 : 2]);
|
||||
if (value.length === 5) {
|
||||
newVersion = false;
|
||||
cipher.notes = this.getValueOrDefault(value[4]);
|
||||
cipher.login.uris = this.makeUriArray(value[3]);
|
||||
} else {
|
||||
const folder = this.getValueOrDefault(value[altFormat ? 9 : 8], "(None)");
|
||||
let folderName = folder !== "(None)" ? folder.split("\\").join("/") : null;
|
||||
if (folderName != null) {
|
||||
folderName = folder.split(" > ").join("/");
|
||||
folderName = folder.split(">").join("/");
|
||||
}
|
||||
this.processFolder(result, folderName);
|
||||
cipher.notes = this.getValueOrDefault(value[altFormat ? 5 : 3]);
|
||||
cipher.login.uris = this.makeUriArray(value[4]);
|
||||
}
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (newVersion && this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherType } from "../vault/enums/cipher-type";
|
||||
import { CardView } from "../vault/models/view/card.view";
|
||||
import { FolderView } from "../vault/models/view/folder.view";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class PasswordBossJsonImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = JSON.parse(data);
|
||||
if (results == null || results.items == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
const foldersMap = new Map<string, string>();
|
||||
results.folders.forEach((value: any) => {
|
||||
foldersMap.set(value.id, value.name);
|
||||
});
|
||||
const foldersIndexMap = new Map<string, number>();
|
||||
foldersMap.forEach((val, key) => {
|
||||
foldersIndexMap.set(key, result.folders.length);
|
||||
const f = new FolderView();
|
||||
f.name = val;
|
||||
result.folders.push(f);
|
||||
});
|
||||
|
||||
results.items.forEach((value: any) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value.name, "--");
|
||||
cipher.login.uris = this.makeUriArray(value.login_url);
|
||||
|
||||
if (value.folder != null && foldersIndexMap.has(value.folder)) {
|
||||
result.folderRelationships.push([result.ciphers.length, foldersIndexMap.get(value.folder)]);
|
||||
}
|
||||
|
||||
if (value.identifiers == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isNullOrWhitespace(value.identifiers.notes)) {
|
||||
cipher.notes = value.identifiers.notes.split("\\r\\n").join("\n").split("\\n").join("\n");
|
||||
}
|
||||
|
||||
if (value.type === "CreditCard") {
|
||||
cipher.card = new CardView();
|
||||
cipher.type = CipherType.Card;
|
||||
}
|
||||
|
||||
for (const property in value.identifiers) {
|
||||
// eslint-disable-next-line
|
||||
if (!value.identifiers.hasOwnProperty(property)) {
|
||||
continue;
|
||||
}
|
||||
const valObj = value.identifiers[property];
|
||||
const val = valObj != null ? valObj.toString() : null;
|
||||
if (
|
||||
this.isNullOrWhitespace(val) ||
|
||||
property === "notes" ||
|
||||
property === "ignoreItemInSecurityScore"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (property === "custom_fields") {
|
||||
valObj.forEach((cf: any) => {
|
||||
this.processKvp(cipher, cf.name, cf.value);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cipher.type === CipherType.Card) {
|
||||
if (property === "cardNumber") {
|
||||
cipher.card.number = val;
|
||||
cipher.card.brand = this.getCardBrand(val);
|
||||
continue;
|
||||
} else if (property === "nameOnCard") {
|
||||
cipher.card.cardholderName = val;
|
||||
continue;
|
||||
} else if (property === "security_code") {
|
||||
cipher.card.code = val;
|
||||
continue;
|
||||
} else if (property === "expires") {
|
||||
try {
|
||||
const expDate = new Date(val);
|
||||
cipher.card.expYear = expDate.getFullYear().toString();
|
||||
cipher.card.expMonth = (expDate.getMonth() + 1).toString();
|
||||
} catch {
|
||||
// Ignore error
|
||||
}
|
||||
continue;
|
||||
} else if (property === "cardType") {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
(property === "username" || property === "email") &&
|
||||
this.isNullOrWhitespace(cipher.login.username)
|
||||
) {
|
||||
cipher.login.username = val;
|
||||
continue;
|
||||
} else if (property === "password") {
|
||||
cipher.login.password = val;
|
||||
continue;
|
||||
} else if (property === "totp") {
|
||||
cipher.login.totp = val;
|
||||
continue;
|
||||
} else if (
|
||||
(cipher.login.uris == null || cipher.login.uris.length === 0) &&
|
||||
this.uriFieldNames.indexOf(property) > -1
|
||||
) {
|
||||
cipher.login.uris = this.makeUriArray(val);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
this.processKvp(cipher, property, val);
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class PasswordDragonXmlImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const doc = this.parseXml(data);
|
||||
if (doc == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
const records = doc.querySelectorAll("PasswordManager > record");
|
||||
Array.from(records).forEach((record) => {
|
||||
const category = this.querySelectorDirectChild(record, "Category");
|
||||
const categoryText =
|
||||
category != null &&
|
||||
!this.isNullOrWhitespace(category.textContent) &&
|
||||
category.textContent !== "Unfiled"
|
||||
? category.textContent
|
||||
: null;
|
||||
this.processFolder(result, categoryText);
|
||||
|
||||
const accountName = this.querySelectorDirectChild(record, "Account-Name");
|
||||
const userId = this.querySelectorDirectChild(record, "User-Id");
|
||||
const password = this.querySelectorDirectChild(record, "Password");
|
||||
const url = this.querySelectorDirectChild(record, "URL");
|
||||
const notes = this.querySelectorDirectChild(record, "Notes");
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name =
|
||||
accountName != null ? this.getValueOrDefault(accountName.textContent, "--") : "--";
|
||||
cipher.notes = notes != null ? this.getValueOrDefault(notes.textContent) : "";
|
||||
cipher.login.username = userId != null ? this.getValueOrDefault(userId.textContent) : null;
|
||||
cipher.login.password =
|
||||
password != null ? this.getValueOrDefault(password.textContent) : null;
|
||||
cipher.login.uris = url != null ? this.makeUriArray(url.textContent) : null;
|
||||
|
||||
const attributes: string[] = [];
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
attributes.push("Attribute-" + i);
|
||||
}
|
||||
|
||||
this.querySelectorAllDirectChild(record, attributes.join(",")).forEach((attr) => {
|
||||
if (this.isNullOrWhitespace(attr.textContent) || attr.textContent === "null") {
|
||||
return;
|
||||
}
|
||||
this.processKvp(cipher, attr.tagName, attr.textContent);
|
||||
});
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class PasswordSafeXmlImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const doc = this.parseXml(data);
|
||||
if (doc == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
const passwordSafe = doc.querySelector("passwordsafe");
|
||||
if (passwordSafe == null) {
|
||||
result.errorMessage = "Missing `passwordsafe` node.";
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
const notesDelimiter = passwordSafe.getAttribute("delimiter");
|
||||
const entries = doc.querySelectorAll("passwordsafe > entry");
|
||||
Array.from(entries).forEach((entry) => {
|
||||
const group = this.querySelectorDirectChild(entry, "group");
|
||||
const groupText =
|
||||
group != null && !this.isNullOrWhitespace(group.textContent)
|
||||
? group.textContent.split(".").join("/")
|
||||
: null;
|
||||
this.processFolder(result, groupText);
|
||||
|
||||
const title = this.querySelectorDirectChild(entry, "title");
|
||||
const username = this.querySelectorDirectChild(entry, "username");
|
||||
const email = this.querySelectorDirectChild(entry, "email");
|
||||
const password = this.querySelectorDirectChild(entry, "password");
|
||||
const url = this.querySelectorDirectChild(entry, "url");
|
||||
const notes = this.querySelectorDirectChild(entry, "notes");
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = title != null ? this.getValueOrDefault(title.textContent, "--") : "--";
|
||||
cipher.notes =
|
||||
notes != null
|
||||
? this.getValueOrDefault(notes.textContent, "").split(notesDelimiter).join("\n")
|
||||
: null;
|
||||
cipher.login.username =
|
||||
username != null ? this.getValueOrDefault(username.textContent) : null;
|
||||
cipher.login.password =
|
||||
password != null ? this.getValueOrDefault(password.textContent) : null;
|
||||
cipher.login.uris = url != null ? this.makeUriArray(url.textContent) : null;
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.login.username) && email != null) {
|
||||
cipher.login.username = this.getValueOrDefault(email.textContent);
|
||||
} else if (email != null && !this.isNullOrWhitespace(email.textContent)) {
|
||||
cipher.notes = this.isNullOrWhitespace(cipher.notes)
|
||||
? "Email: " + email.textContent
|
||||
: cipher.notes + "\n" + "Email: " + email.textContent;
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class PasswordWalletTxtImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, false);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((value) => {
|
||||
if (value.length < 1) {
|
||||
return;
|
||||
}
|
||||
if (value.length > 5) {
|
||||
this.processFolder(result, value[5]);
|
||||
}
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value[0], "--");
|
||||
if (value.length > 4) {
|
||||
cipher.notes = this.getValueOrDefault(value[4], "").split("¬").join("\n");
|
||||
}
|
||||
if (value.length > 2) {
|
||||
cipher.login.username = this.getValueOrDefault(value[2]);
|
||||
}
|
||||
if (value.length > 3) {
|
||||
cipher.login.password = this.getValueOrDefault(value[3]);
|
||||
}
|
||||
if (value.length > 1) {
|
||||
cipher.login.uris = this.makeUriArray(value[1]);
|
||||
}
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
import { FieldType } from "../../enums/fieldType";
|
||||
import { SecureNoteType } from "../../enums/secureNoteType";
|
||||
import { ImportResult } from "../../models/domain/import-result";
|
||||
import { CipherType } from "../../vault/enums/cipher-type";
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
import { SecureNoteView } from "../../vault/models/view/secure-note.view";
|
||||
import { BaseImporter } from "../base-importer";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import {
|
||||
AppPasswordEntry,
|
||||
BookmarkEntry,
|
||||
EnvironmentVariablesEntry,
|
||||
FoldersEntity,
|
||||
GPGEntry,
|
||||
NotesEntry,
|
||||
PsonoItemTypes,
|
||||
PsonoJsonExport,
|
||||
TOTPEntry,
|
||||
WebsitePasswordEntry,
|
||||
} from "./psono-json-types";
|
||||
|
||||
export class PsonoJsonImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const psonoExport: PsonoJsonExport = JSON.parse(data);
|
||||
if (psonoExport == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
this.parseFolders(result, psonoExport.folders);
|
||||
this.handleItemParsing(result, psonoExport.items);
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private parseFolders(result: ImportResult, folders: FoldersEntity[]) {
|
||||
if (folders == null || folders.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
folders.forEach((folder) => {
|
||||
if (folder.items == null || folder.items.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.processFolder(result, folder.name);
|
||||
|
||||
this.handleItemParsing(result, folder.items);
|
||||
});
|
||||
}
|
||||
|
||||
private handleItemParsing(result: ImportResult, items?: PsonoItemTypes[]) {
|
||||
if (items == null || items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
items.forEach((record) => {
|
||||
const cipher = this.parsePsonoItem(record);
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
}
|
||||
|
||||
private parsePsonoItem(item: PsonoItemTypes): CipherView {
|
||||
const cipher = this.initLoginCipher();
|
||||
|
||||
switch (item.type) {
|
||||
case "website_password":
|
||||
this.parseWebsiteLogins(item, cipher);
|
||||
break;
|
||||
case "application_password":
|
||||
this.parseApplicationPasswords(item, cipher);
|
||||
break;
|
||||
case "environment_variables":
|
||||
this.parseEnvironmentVariables(item, cipher);
|
||||
break;
|
||||
case "totp":
|
||||
this.parseTOTP(item, cipher);
|
||||
break;
|
||||
case "bookmark":
|
||||
this.parseBookmarks(item, cipher);
|
||||
break;
|
||||
// Skipping this until we can save GPG into notes/custom fields
|
||||
// case "mail_gpg_own_key":
|
||||
// this.parseGPG(item, cipher);
|
||||
// break;
|
||||
case "note":
|
||||
this.parseNotes(item, cipher);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return cipher;
|
||||
}
|
||||
|
||||
readonly WEBSITE_mappedValues = new Set([
|
||||
"type",
|
||||
"name",
|
||||
"website_password_title",
|
||||
"website_password_notes",
|
||||
"website_password_username",
|
||||
"website_password_password",
|
||||
"website_password_url",
|
||||
"autosubmit",
|
||||
"website_password_auto_submit",
|
||||
"urlfilter",
|
||||
"website_password_url_filter",
|
||||
]);
|
||||
private parseWebsiteLogins(entry: WebsitePasswordEntry, cipher: CipherView) {
|
||||
if (entry == null || entry.type != "website_password") {
|
||||
return;
|
||||
}
|
||||
|
||||
cipher.name = entry.website_password_title;
|
||||
cipher.notes = entry.website_password_notes;
|
||||
|
||||
cipher.login.username = entry.website_password_username;
|
||||
cipher.login.password = entry.website_password_password;
|
||||
|
||||
cipher.login.uris = this.makeUriArray(entry.website_password_url);
|
||||
|
||||
this.processKvp(
|
||||
cipher,
|
||||
"website_password_auto_submit",
|
||||
entry.website_password_auto_submit.toString(),
|
||||
FieldType.Boolean
|
||||
);
|
||||
|
||||
this.processKvp(cipher, "website_password_url_filter", entry.website_password_url_filter);
|
||||
|
||||
this.importUnmappedFields(cipher, entry, this.WEBSITE_mappedValues);
|
||||
}
|
||||
|
||||
readonly APP_PWD_mappedValues = new Set([
|
||||
"type",
|
||||
"name",
|
||||
"application_password_title",
|
||||
"application_password_notes",
|
||||
"application_password_username",
|
||||
"application_password_password",
|
||||
]);
|
||||
private parseApplicationPasswords(entry: AppPasswordEntry, cipher: CipherView) {
|
||||
if (entry == null || entry.type != "application_password") {
|
||||
return;
|
||||
}
|
||||
|
||||
cipher.name = entry.application_password_title;
|
||||
cipher.notes = entry.application_password_notes;
|
||||
|
||||
cipher.login.username = entry.application_password_username;
|
||||
cipher.login.password = entry.application_password_password;
|
||||
|
||||
this.importUnmappedFields(cipher, entry, this.APP_PWD_mappedValues);
|
||||
}
|
||||
|
||||
readonly BOOKMARK_mappedValues = new Set([
|
||||
"type",
|
||||
"name",
|
||||
"bookmark_title",
|
||||
"bookmark_notes",
|
||||
"bookmark_url",
|
||||
]);
|
||||
private parseBookmarks(entry: BookmarkEntry, cipher: CipherView) {
|
||||
if (entry == null || entry.type != "bookmark") {
|
||||
return;
|
||||
}
|
||||
|
||||
cipher.name = entry.bookmark_title;
|
||||
cipher.notes = entry.bookmark_notes;
|
||||
|
||||
cipher.login.uris = this.makeUriArray(entry.bookmark_url);
|
||||
|
||||
this.importUnmappedFields(cipher, entry, this.BOOKMARK_mappedValues);
|
||||
}
|
||||
|
||||
readonly NOTES_mappedValues = new Set(["type", "name", "note_title", "note_notes"]);
|
||||
private parseNotes(entry: NotesEntry, cipher: CipherView) {
|
||||
if (entry == null || entry.type != "note") {
|
||||
return;
|
||||
}
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
cipher.name = entry.note_title;
|
||||
cipher.notes = entry.note_notes;
|
||||
|
||||
this.importUnmappedFields(cipher, entry, this.NOTES_mappedValues);
|
||||
}
|
||||
|
||||
readonly TOTP_mappedValues = new Set(["type", "name", "totp_title", "totp_notes", "totp_code"]);
|
||||
private parseTOTP(entry: TOTPEntry, cipher: CipherView) {
|
||||
if (entry == null || entry.type != "totp") {
|
||||
return;
|
||||
}
|
||||
|
||||
cipher.name = entry.totp_title;
|
||||
cipher.notes = entry.totp_notes;
|
||||
|
||||
cipher.login.totp = entry.totp_code;
|
||||
|
||||
this.importUnmappedFields(cipher, entry, this.TOTP_mappedValues);
|
||||
}
|
||||
|
||||
readonly ENV_VARIABLES_mappedValues = new Set([
|
||||
"type",
|
||||
"name",
|
||||
"environment_variables_title",
|
||||
"environment_variables_notes",
|
||||
"environment_variables_variables",
|
||||
]);
|
||||
private parseEnvironmentVariables(entry: EnvironmentVariablesEntry, cipher: CipherView) {
|
||||
if (entry == null || entry.type != "environment_variables") {
|
||||
return;
|
||||
}
|
||||
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
cipher.name = entry.environment_variables_title;
|
||||
cipher.notes = entry.environment_variables_notes;
|
||||
|
||||
entry.environment_variables_variables.forEach((KvPair) => {
|
||||
this.processKvp(cipher, KvPair.key, KvPair.value);
|
||||
});
|
||||
|
||||
this.importUnmappedFields(cipher, entry, this.ENV_VARIABLES_mappedValues);
|
||||
}
|
||||
|
||||
readonly GPG_mappedValues = new Set([
|
||||
"type",
|
||||
"name",
|
||||
"mail_gpg_own_key_title",
|
||||
"mail_gpg_own_key_public",
|
||||
"mail_gpg_own_key_name",
|
||||
"mail_gpg_own_key_email",
|
||||
"mail_gpg_own_key_private",
|
||||
]);
|
||||
private parseGPG(entry: GPGEntry, cipher: CipherView) {
|
||||
if (entry == null || entry.type != "mail_gpg_own_key") {
|
||||
return;
|
||||
}
|
||||
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
cipher.name = entry.mail_gpg_own_key_title;
|
||||
cipher.notes = entry.mail_gpg_own_key_public;
|
||||
|
||||
this.processKvp(cipher, "mail_gpg_own_key_name", entry.mail_gpg_own_key_name);
|
||||
this.processKvp(cipher, "mail_gpg_own_key_email", entry.mail_gpg_own_key_email);
|
||||
this.processKvp(
|
||||
cipher,
|
||||
"mail_gpg_own_key_private",
|
||||
entry.mail_gpg_own_key_private,
|
||||
FieldType.Hidden
|
||||
);
|
||||
|
||||
this.importUnmappedFields(cipher, entry, this.GPG_mappedValues);
|
||||
}
|
||||
|
||||
private importUnmappedFields(
|
||||
cipher: CipherView,
|
||||
entry: PsonoItemTypes,
|
||||
mappedValues: Set<string>
|
||||
) {
|
||||
const unmappedFields = Object.keys(entry).filter((x) => !mappedValues.has(x));
|
||||
unmappedFields.forEach((key) => {
|
||||
const item = entry as any;
|
||||
this.processKvp(cipher, key, item[key].toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
export type PsonoItemTypes =
|
||||
| WebsitePasswordEntry
|
||||
| AppPasswordEntry
|
||||
| TOTPEntry
|
||||
| NotesEntry
|
||||
| EnvironmentVariablesEntry
|
||||
| GPGEntry
|
||||
| BookmarkEntry;
|
||||
|
||||
export interface PsonoJsonExport {
|
||||
folders?: FoldersEntity[];
|
||||
items?: PsonoItemTypes[];
|
||||
}
|
||||
|
||||
export interface FoldersEntity {
|
||||
name: string;
|
||||
items: PsonoItemTypes[] | null;
|
||||
}
|
||||
|
||||
export interface RecordBase {
|
||||
type: PsonoEntryTypes;
|
||||
name: string;
|
||||
create_date: string;
|
||||
write_date: string;
|
||||
callback_url: string;
|
||||
callback_user: string;
|
||||
callback_pass: string;
|
||||
}
|
||||
|
||||
export type PsonoEntryTypes =
|
||||
| "website_password"
|
||||
| "bookmark"
|
||||
| "mail_gpg_own_key"
|
||||
| "environment_variables"
|
||||
| "note"
|
||||
| "application_password"
|
||||
| "totp";
|
||||
|
||||
export interface WebsitePasswordEntry extends RecordBase {
|
||||
type: "website_password";
|
||||
autosubmit: boolean;
|
||||
urlfilter: string;
|
||||
website_password_title: string;
|
||||
website_password_url: string;
|
||||
website_password_username: string;
|
||||
website_password_password: string;
|
||||
website_password_notes: string;
|
||||
website_password_auto_submit: boolean;
|
||||
website_password_url_filter: string;
|
||||
}
|
||||
|
||||
export interface PsonoEntry {
|
||||
type: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface BookmarkEntry extends RecordBase {
|
||||
type: "bookmark";
|
||||
urlfilter: string;
|
||||
bookmark_title: string;
|
||||
bookmark_url: string;
|
||||
bookmark_notes: string;
|
||||
bookmark_url_filter: string;
|
||||
}
|
||||
|
||||
export interface GPGEntry extends RecordBase {
|
||||
type: "mail_gpg_own_key";
|
||||
mail_gpg_own_key_title: string;
|
||||
mail_gpg_own_key_email: string;
|
||||
mail_gpg_own_key_name: string;
|
||||
mail_gpg_own_key_public: string;
|
||||
mail_gpg_own_key_private: string;
|
||||
}
|
||||
|
||||
export interface EnvironmentVariablesEntry extends RecordBase {
|
||||
type: "environment_variables";
|
||||
environment_variables_title: string;
|
||||
environment_variables_variables: EnvironmentVariables_KVPair[];
|
||||
environment_variables_notes: string;
|
||||
}
|
||||
|
||||
export interface EnvironmentVariables_KVPair {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface AppPasswordEntry extends RecordBase {
|
||||
type: "application_password";
|
||||
application_password_title: string;
|
||||
application_password_username: string;
|
||||
application_password_password: string;
|
||||
application_password_notes: string;
|
||||
}
|
||||
|
||||
export interface TOTPEntry extends RecordBase {
|
||||
type: "totp";
|
||||
totp_title: string;
|
||||
totp_period: number;
|
||||
totp_algorithm: "SHA1";
|
||||
totp_digits: number;
|
||||
totp_code: string;
|
||||
totp_notes: string;
|
||||
}
|
||||
|
||||
export interface NotesEntry extends RecordBase {
|
||||
type: "note";
|
||||
note_title: string;
|
||||
note_notes: string;
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherType } from "../vault/enums/cipher-type";
|
||||
import { CardView } from "../vault/models/view/card.view";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class RememBearCsvImporter 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((value) => {
|
||||
if (value.trash === "true") {
|
||||
return;
|
||||
}
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value.name);
|
||||
cipher.notes = this.getValueOrDefault(value.notes);
|
||||
if (value.type === "LoginItem") {
|
||||
cipher.login.uris = this.makeUriArray(value.website);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
cipher.login.username = this.getValueOrDefault(value.username);
|
||||
} else if (value.type === "CreditCardItem") {
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
cipher.card.cardholderName = this.getValueOrDefault(value.cardholder);
|
||||
cipher.card.number = this.getValueOrDefault(value.number);
|
||||
cipher.card.brand = this.getCardBrand(cipher.card.number);
|
||||
cipher.card.code = this.getValueOrDefault(value.verification);
|
||||
|
||||
try {
|
||||
const expMonth = this.getValueOrDefault(value.expiryMonth);
|
||||
if (expMonth != null) {
|
||||
const expMonthNumber = parseInt(expMonth, null);
|
||||
if (expMonthNumber != null && expMonthNumber >= 1 && expMonthNumber <= 12) {
|
||||
cipher.card.expMonth = expMonthNumber.toString();
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore error
|
||||
}
|
||||
try {
|
||||
const expYear = this.getValueOrDefault(value.expiryYear);
|
||||
if (expYear != null) {
|
||||
const expYearNumber = parseInt(expYear, null);
|
||||
if (expYearNumber != null) {
|
||||
cipher.card.expYear = expYearNumber.toString();
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore error
|
||||
}
|
||||
|
||||
const pin = this.getValueOrDefault(value.pin);
|
||||
if (pin != null) {
|
||||
this.processKvp(cipher, "PIN", pin);
|
||||
}
|
||||
const zip = this.getValueOrDefault(value.zipCode);
|
||||
if (zip != null) {
|
||||
this.processKvp(cipher, "Zip Code", zip);
|
||||
}
|
||||
}
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class RoboFormCsvImporter 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);
|
||||
}
|
||||
|
||||
let i = 1;
|
||||
results.forEach((value) => {
|
||||
const folder =
|
||||
!this.isNullOrWhitespace(value.Folder) && value.Folder.startsWith("/")
|
||||
? value.Folder.replace("/", "")
|
||||
: value.Folder;
|
||||
const folderName = !this.isNullOrWhitespace(folder) ? folder : null;
|
||||
this.processFolder(result, folderName);
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.notes = this.getValueOrDefault(value.Note);
|
||||
cipher.name = this.getValueOrDefault(value.Name, "--");
|
||||
cipher.login.username = this.getValueOrDefault(value.Login);
|
||||
cipher.login.password = this.getValueOrDefault(value.Pwd);
|
||||
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.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
|
||||
if (
|
||||
i === results.length &&
|
||||
cipher.name === "--" &&
|
||||
this.isNullOrWhitespace(cipher.login.password)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.ciphers.push(cipher);
|
||||
i++;
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class SafariCsvImporter 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((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value.Title, "--");
|
||||
cipher.login.username = this.getValueOrDefault(value.Username);
|
||||
cipher.login.password = this.getValueOrDefault(value.Password);
|
||||
cipher.login.uris = this.makeUriArray(value.Url ?? value.URL);
|
||||
cipher.login.totp = this.getValueOrDefault(value.OTPAuth);
|
||||
cipher.notes = this.getValueOrDefault(value.Notes);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import { FieldType } from "../enums/fieldType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherType } from "../vault/enums/cipher-type";
|
||||
import { CipherView } from "../vault/models/view/cipher.view";
|
||||
import { FieldView } from "../vault/models/view/field.view";
|
||||
import { FolderView } from "../vault/models/view/folder.view";
|
||||
import { SecureNoteView } from "../vault/models/view/secure-note.view";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class SafeInCloudXmlImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const doc = this.parseXml(data);
|
||||
if (doc == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
const db = doc.querySelector("database");
|
||||
if (db == null) {
|
||||
result.errorMessage = "Missing `database` node.";
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
const foldersMap = new Map<string, number>();
|
||||
|
||||
Array.from(doc.querySelectorAll("database > label")).forEach((labelEl) => {
|
||||
const name = labelEl.getAttribute("name");
|
||||
const id = labelEl.getAttribute("id");
|
||||
if (!this.isNullOrWhitespace(name) && !this.isNullOrWhitespace(id)) {
|
||||
foldersMap.set(id, result.folders.length);
|
||||
const folder = new FolderView();
|
||||
folder.name = name;
|
||||
result.folders.push(folder);
|
||||
}
|
||||
});
|
||||
|
||||
Array.from(doc.querySelectorAll("database > card")).forEach((cardEl) => {
|
||||
if (cardEl.getAttribute("template") === "true" || cardEl.getAttribute("deleted") === "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
const labelIdEl = this.querySelectorDirectChild(cardEl, "label_id");
|
||||
if (labelIdEl != null) {
|
||||
const labelId = labelIdEl.textContent;
|
||||
if (!this.isNullOrWhitespace(labelId) && foldersMap.has(labelId)) {
|
||||
result.folderRelationships.push([result.ciphers.length, foldersMap.get(labelId)]);
|
||||
}
|
||||
}
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(cardEl.getAttribute("title"), "--");
|
||||
|
||||
if (cardEl.getAttribute("star") === "true") {
|
||||
cipher.favorite = true;
|
||||
}
|
||||
|
||||
const cardType = cardEl.getAttribute("type");
|
||||
if (cardType === "note") {
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
} else {
|
||||
Array.from(this.querySelectorAllDirectChild(cardEl, "field")).forEach((fieldEl) => {
|
||||
const text = fieldEl.textContent;
|
||||
if (this.isNullOrWhitespace(text)) {
|
||||
return;
|
||||
}
|
||||
const name = fieldEl.getAttribute("name");
|
||||
const fieldType = this.getValueOrDefault(fieldEl.getAttribute("type"), "").toLowerCase();
|
||||
if (fieldType === "login") {
|
||||
cipher.login.username = text;
|
||||
} else if (fieldType === "password" || fieldType === "secret") {
|
||||
// safeInCloud allows for more than one password. we just insert them here and find the one used as password later
|
||||
this.processKvp(cipher, name, text, FieldType.Hidden);
|
||||
} else if (fieldType === "one_time_password") {
|
||||
cipher.login.totp = text;
|
||||
} else if (fieldType === "notes") {
|
||||
cipher.notes += text + "\n";
|
||||
} else if (fieldType === "weblogin" || fieldType === "website") {
|
||||
cipher.login.uris = this.makeUriArray(text);
|
||||
} else {
|
||||
this.processKvp(cipher, name, text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Array.from(this.querySelectorAllDirectChild(cardEl, "notes")).forEach((notesEl) => {
|
||||
cipher.notes += notesEl.textContent + "\n";
|
||||
});
|
||||
|
||||
this.setPassword(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
// Choose a password from all passwords. Take one that has password in its name, or the first one if there is no such entry
|
||||
// if its name is password, we can safely remove it form the fields. otherwise, it would maybe be best to keep it as a hidden field
|
||||
setPassword(cipher: CipherView) {
|
||||
const candidates = cipher.fields.filter((field) => field.type === FieldType.Hidden);
|
||||
if (!candidates.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let choice: FieldView;
|
||||
for (const field of candidates) {
|
||||
if (this.passwordFieldNames.includes(field.name.toLowerCase())) {
|
||||
choice = field;
|
||||
cipher.fields = cipher.fields.filter((f) => f !== choice);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!choice) {
|
||||
choice = candidates[0];
|
||||
}
|
||||
|
||||
cipher.login.password = choice.value;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class SaferPassCsvImporter 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((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(this.nameFromUrl(value.url), "--");
|
||||
cipher.notes = this.getValueOrDefault(value.notes);
|
||||
cipher.login.username = this.getValueOrDefault(value.username);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
cipher.login.uris = this.makeUriArray(value.url);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class SecureSafeCsvImporter 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((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value.Title);
|
||||
cipher.notes = this.getValueOrDefault(value.Comment);
|
||||
cipher.login.uris = this.makeUriArray(value.Url);
|
||||
cipher.login.password = this.getValueOrDefault(value.Password);
|
||||
cipher.login.username = this.getValueOrDefault(value.Username);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherView } from "../vault/models/view/cipher.view";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class SplashIdCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, false);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((value) => {
|
||||
if (value.length < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.processFolder(result, this.getValueOrDefault(value[value.length - 1]));
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.notes = this.getValueOrDefault(value[value.length - 2], "");
|
||||
cipher.name = this.getValueOrDefault(value[1], "--");
|
||||
|
||||
if (value[0] === "Web Logins" || value[0] === "Servers" || value[0] === "Email Accounts") {
|
||||
cipher.login.username = this.getValueOrDefault(value[2]);
|
||||
cipher.login.password = this.getValueOrDefault(value[3]);
|
||||
cipher.login.uris = this.makeUriArray(value[4]);
|
||||
this.parseFieldsToNotes(cipher, 5, value);
|
||||
} else {
|
||||
this.parseFieldsToNotes(cipher, 2, value);
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private parseFieldsToNotes(cipher: CipherView, startIndex: number, value: any) {
|
||||
// last 3 rows do not get parsed
|
||||
for (let i = startIndex; i < value.length - 3; i++) {
|
||||
if (this.isNullOrWhitespace(value[i])) {
|
||||
continue;
|
||||
}
|
||||
cipher.notes += value[i] + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class StickyPasswordXmlImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const doc = this.parseXml(data);
|
||||
if (doc == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
const loginNodes = doc.querySelectorAll("root > Database > Logins > Login");
|
||||
Array.from(loginNodes).forEach((loginNode) => {
|
||||
const accountId = loginNode.getAttribute("ID");
|
||||
if (this.isNullOrWhitespace(accountId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const usernameText = loginNode.getAttribute("Name");
|
||||
const passwordText = loginNode.getAttribute("Password");
|
||||
let titleText: string = null;
|
||||
let linkText: string = null;
|
||||
let notesText: string = null;
|
||||
let groupId: string = null;
|
||||
let groupText: string = null;
|
||||
|
||||
const accountLogin = doc.querySelector(
|
||||
"root > Database > Accounts > Account > " +
|
||||
'LoginLinks > Login[SourceLoginID="' +
|
||||
accountId +
|
||||
'"]'
|
||||
);
|
||||
if (accountLogin != null) {
|
||||
const account = accountLogin.parentElement.parentElement;
|
||||
if (account != null) {
|
||||
titleText = account.getAttribute("Name");
|
||||
linkText = account.getAttribute("Link");
|
||||
groupId = account.getAttribute("ParentID");
|
||||
notesText = account.getAttribute("Comments");
|
||||
if (!this.isNullOrWhitespace(notesText)) {
|
||||
notesText = notesText.split("/n").join("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.isNullOrWhitespace(groupId)) {
|
||||
groupText = this.buildGroupText(doc, groupId, "");
|
||||
this.processFolder(result, groupText);
|
||||
}
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(titleText, "--");
|
||||
cipher.notes = this.getValueOrDefault(notesText);
|
||||
cipher.login.username = this.getValueOrDefault(usernameText);
|
||||
cipher.login.password = this.getValueOrDefault(passwordText);
|
||||
cipher.login.uris = this.makeUriArray(linkText);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
buildGroupText(doc: Document, groupId: string, groupText: string): string {
|
||||
const group = doc.querySelector('root > Database > Groups > Group[ID="' + groupId + '"]');
|
||||
if (group == null) {
|
||||
return groupText;
|
||||
}
|
||||
if (!this.isNullOrWhitespace(groupText)) {
|
||||
groupText = "/" + groupText;
|
||||
}
|
||||
groupText = group.getAttribute("Name") + groupText;
|
||||
return this.buildGroupText(doc, group.getAttribute("ParentID"), groupText);
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherType } from "../vault/enums/cipher-type";
|
||||
import { CardView } from "../vault/models/view/card.view";
|
||||
import { SecureNoteView } from "../vault/models/view/secure-note.view";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
const PropertiesToIgnore = [
|
||||
"kind",
|
||||
"autologin",
|
||||
"favorite",
|
||||
"hexcolor",
|
||||
"protectedwithpassword",
|
||||
"subdomainonly",
|
||||
"type",
|
||||
"tk_export_version",
|
||||
"note",
|
||||
"title",
|
||||
"document_content",
|
||||
];
|
||||
|
||||
export class TrueKeyCsvImporter 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((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.favorite = this.getValueOrDefault(value.favorite, "").toLowerCase() === "true";
|
||||
cipher.name = this.getValueOrDefault(value.name, "--");
|
||||
cipher.notes = this.getValueOrDefault(value.memo, "");
|
||||
cipher.login.username = this.getValueOrDefault(value.login);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
cipher.login.uris = this.makeUriArray(value.url);
|
||||
|
||||
if (value.kind !== "login") {
|
||||
cipher.name = this.getValueOrDefault(value.title, "--");
|
||||
cipher.notes = this.getValueOrDefault(value.note, "");
|
||||
}
|
||||
|
||||
if (value.kind === "cc") {
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
cipher.card.cardholderName = this.getValueOrDefault(value.cardholder);
|
||||
cipher.card.number = this.getValueOrDefault(value.number);
|
||||
cipher.card.brand = this.getCardBrand(cipher.card.number);
|
||||
if (!this.isNullOrWhitespace(value.expiryDate)) {
|
||||
try {
|
||||
const expDate = new Date(value.expiryDate);
|
||||
cipher.card.expYear = expDate.getFullYear().toString();
|
||||
cipher.card.expMonth = (expDate.getMonth() + 1).toString();
|
||||
} catch {
|
||||
// Ignore error
|
||||
}
|
||||
}
|
||||
} else if (value.kind !== "login") {
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
if (!this.isNullOrWhitespace(cipher.notes)) {
|
||||
cipher.notes = this.getValueOrDefault(value.document_content, "");
|
||||
}
|
||||
for (const property in value) {
|
||||
if (
|
||||
value.hasOwnProperty(property) && // eslint-disable-line
|
||||
PropertiesToIgnore.indexOf(property.toLowerCase()) < 0 &&
|
||||
!this.isNullOrWhitespace(value[property])
|
||||
) {
|
||||
this.processKvp(cipher, property, value[property]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class UpmCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, false);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((value) => {
|
||||
if (value.length !== 5) {
|
||||
return;
|
||||
}
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value[0], "--");
|
||||
cipher.notes = this.getValueOrDefault(value[4]);
|
||||
cipher.login.username = this.getValueOrDefault(value[1]);
|
||||
cipher.login.password = this.getValueOrDefault(value[2]);
|
||||
cipher.login.uris = this.makeUriArray(value[3]);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class YotiCsvImporter 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((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value.Name, "--");
|
||||
cipher.login.username = this.getValueOrDefault(value["User name"]);
|
||||
cipher.login.password = this.getValueOrDefault(value.Password);
|
||||
cipher.login.uris = this.makeUriArray(value.URL);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import { ImportResult } from "../models/domain/import-result";
|
||||
import { CipherView } from "../vault/models/view/cipher.view";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class ZohoVaultCsvImporter 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((value) => {
|
||||
if (
|
||||
this.isNullOrWhitespace(value["Password Name"]) &&
|
||||
this.isNullOrWhitespace(value["Secret Name"])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.processFolder(result, this.getValueOrDefault(value.ChamberName));
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.favorite = this.getValueOrDefault(value.Favorite, "0") === "1";
|
||||
cipher.notes = this.getValueOrDefault(value.Notes);
|
||||
cipher.name = this.getValueOrDefault(
|
||||
value["Password Name"],
|
||||
this.getValueOrDefault(value["Secret Name"], "--")
|
||||
);
|
||||
cipher.login.uris = this.makeUriArray(
|
||||
this.getValueOrDefault(value["Password URL"], this.getValueOrDefault(value["Secret URL"]))
|
||||
);
|
||||
this.parseData(cipher, value.SecretData);
|
||||
this.parseData(cipher, value.CustomData);
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private parseData(cipher: CipherView, data: string) {
|
||||
if (this.isNullOrWhitespace(data)) {
|
||||
return;
|
||||
}
|
||||
const dataLines = this.splitNewLine(data);
|
||||
dataLines.forEach((line) => {
|
||||
const delimPosition = line.indexOf(":");
|
||||
if (delimPosition < 0) {
|
||||
return;
|
||||
}
|
||||
const field = line.substring(0, delimPosition);
|
||||
const value = line.length > delimPosition ? line.substring(delimPosition + 1) : null;
|
||||
if (
|
||||
this.isNullOrWhitespace(field) ||
|
||||
this.isNullOrWhitespace(value) ||
|
||||
field === "SecretType"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const fieldLower = field.toLowerCase();
|
||||
if (cipher.login.username == null && this.usernameFieldNames.indexOf(fieldLower) > -1) {
|
||||
cipher.login.username = value;
|
||||
} else if (
|
||||
cipher.login.password == null &&
|
||||
this.passwordFieldNames.indexOf(fieldLower) > -1
|
||||
) {
|
||||
cipher.login.password = value;
|
||||
} else {
|
||||
this.processKvp(cipher, field, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { CollectionView } from "../../admin-console/models/view/collection.view";
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
import { FolderView } from "../../vault/models/view/folder.view";
|
||||
|
||||
export class ImportResult {
|
||||
success = false;
|
||||
missingPassword = false;
|
||||
errorMessage: string;
|
||||
ciphers: CipherView[] = [];
|
||||
folders: FolderView[] = [];
|
||||
folderRelationships: [number, number][] = [];
|
||||
collections: CollectionView[] = [];
|
||||
collectionRelationships: [number, number][] = [];
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { ImportApiServiceAbstraction } from "../../abstractions/import/import-api.service.abstraction";
|
||||
import { ImportCiphersRequest } from "../../models/request/import-ciphers.request";
|
||||
import { ImportOrganizationCiphersRequest } from "../../models/request/import-organization-ciphers.request";
|
||||
|
||||
export class ImportApiService implements ImportApiServiceAbstraction {
|
||||
constructor(private apiService: ApiService) {}
|
||||
|
||||
async postImportCiphers(request: ImportCiphersRequest): Promise<any> {
|
||||
return await this.apiService.send("POST", "/ciphers/import", request, true, false);
|
||||
}
|
||||
|
||||
async postImportOrganizationCiphers(
|
||||
organizationId: string,
|
||||
request: ImportOrganizationCiphersRequest
|
||||
): Promise<any> {
|
||||
return await this.apiService.send(
|
||||
"POST",
|
||||
"/ciphers/import-organization?organizationId=" + organizationId,
|
||||
request,
|
||||
true,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { I18nService } from "../../abstractions/i18n.service";
|
||||
import { ImportApiServiceAbstraction } from "../../abstractions/import/import-api.service.abstraction";
|
||||
import { ImportService as ImportServiceAbstraction } from "../../abstractions/import/import.service.abstraction";
|
||||
import { CollectionService } from "../../admin-console/abstractions/collection.service";
|
||||
import { CollectionRequest } from "../../admin-console/models/request/collection.request";
|
||||
import {
|
||||
featuredImportOptions,
|
||||
ImportOption,
|
||||
ImportType,
|
||||
regularImportOptions,
|
||||
} from "../../enums/importOptions";
|
||||
import { AscendoCsvImporter } from "../../importers/ascendo-csv-importer";
|
||||
import { AvastCsvImporter } from "../../importers/avast-csv-importer";
|
||||
import { AvastJsonImporter } from "../../importers/avast-json-importer";
|
||||
import { AviraCsvImporter } from "../../importers/avira-csv-importer";
|
||||
import { BitwardenCsvImporter } from "../../importers/bitwarden-csv-importer";
|
||||
import { BitwardenJsonImporter } from "../../importers/bitwarden-json-importer";
|
||||
import { BitwardenPasswordProtectedImporter } from "../../importers/bitwarden-password-protected-importer";
|
||||
import { BlackBerryCsvImporter } from "../../importers/blackberry-csv-importer";
|
||||
import { BlurCsvImporter } from "../../importers/blur-csv-importer";
|
||||
import { ButtercupCsvImporter } from "../../importers/buttercup-csv-importer";
|
||||
import { ChromeCsvImporter } from "../../importers/chrome-csv-importer";
|
||||
import { ClipperzHtmlImporter } from "../../importers/clipperz-html-importer";
|
||||
import { CodebookCsvImporter } from "../../importers/codebook-csv-importer";
|
||||
import { DashlaneCsvImporter } from "../../importers/dashlane/dashlane-csv-importer";
|
||||
import { DashlaneJsonImporter } from "../../importers/dashlane/dashlane-json-importer";
|
||||
import { EncryptrCsvImporter } from "../../importers/encryptr-csv-importer";
|
||||
import { EnpassCsvImporter } from "../../importers/enpass/enpass-csv-importer";
|
||||
import { EnpassJsonImporter } from "../../importers/enpass/enpass-json-importer";
|
||||
import { FirefoxCsvImporter } from "../../importers/firefox-csv-importer";
|
||||
import { FSecureFskImporter } from "../../importers/fsecure/fsecure-fsk-importer";
|
||||
import { GnomeJsonImporter } from "../../importers/gnome-json-importer";
|
||||
import { ImportError } from "../../importers/import-error";
|
||||
import { Importer } from "../../importers/importer";
|
||||
import { KasperskyTxtImporter } from "../../importers/kaspersky-txt-importer";
|
||||
import { KeePass2XmlImporter } from "../../importers/keepass2-xml-importer";
|
||||
import { KeePassXCsvImporter } from "../../importers/keepassx-csv-importer";
|
||||
import { KeeperCsvImporter } from "../../importers/keeper/keeper-csv-importer";
|
||||
import { LastPassCsvImporter } from "../../importers/lastpass-csv-importer";
|
||||
import { LogMeOnceCsvImporter } from "../../importers/logmeonce-csv-importer";
|
||||
import { MeldiumCsvImporter } from "../../importers/meldium-csv-importer";
|
||||
import { MSecureCsvImporter } from "../../importers/msecure-csv-importer";
|
||||
import { MykiCsvImporter } from "../../importers/myki-csv-importer";
|
||||
import { NordPassCsvImporter } from "../../importers/nordpass-csv-importer";
|
||||
import { OnePassword1PifImporter } from "../../importers/onepassword/onepassword-1pif-importer";
|
||||
import { OnePassword1PuxImporter } from "../../importers/onepassword/onepassword-1pux-importer";
|
||||
import { OnePasswordMacCsvImporter } from "../../importers/onepassword/onepassword-mac-csv-importer";
|
||||
import { OnePasswordWinCsvImporter } from "../../importers/onepassword/onepassword-win-csv-importer";
|
||||
import { PadlockCsvImporter } from "../../importers/padlock-csv-importer";
|
||||
import { PassKeepCsvImporter } from "../../importers/passkeep-csv-importer";
|
||||
import { PasskyJsonImporter } from "../../importers/passky/passky-json-importer";
|
||||
import { PassmanJsonImporter } from "../../importers/passman-json-importer";
|
||||
import { PasspackCsvImporter } from "../../importers/passpack-csv-importer";
|
||||
import { PasswordAgentCsvImporter } from "../../importers/passwordagent-csv-importer";
|
||||
import { PasswordBossJsonImporter } from "../../importers/passwordboss-json-importer";
|
||||
import { PasswordDragonXmlImporter } from "../../importers/passworddragon-xml-importer";
|
||||
import { PasswordSafeXmlImporter } from "../../importers/passwordsafe-xml-importer";
|
||||
import { PasswordWalletTxtImporter } from "../../importers/passwordwallet-txt-importer";
|
||||
import { PsonoJsonImporter } from "../../importers/psono/psono-json-importer";
|
||||
import { RememBearCsvImporter } from "../../importers/remembear-csv-importer";
|
||||
import { RoboFormCsvImporter } from "../../importers/roboform-csv-importer";
|
||||
import { SafariCsvImporter } from "../../importers/safari-csv-importer";
|
||||
import { SafeInCloudXmlImporter } from "../../importers/safeincloud-xml-importer";
|
||||
import { SaferPassCsvImporter } from "../../importers/saferpass-csv-importer";
|
||||
import { SecureSafeCsvImporter } from "../../importers/securesafe-csv-importer";
|
||||
import { SplashIdCsvImporter } from "../../importers/splashid-csv-importer";
|
||||
import { StickyPasswordXmlImporter } from "../../importers/stickypassword-xml-importer";
|
||||
import { TrueKeyCsvImporter } from "../../importers/truekey-csv-importer";
|
||||
import { UpmCsvImporter } from "../../importers/upm-csv-importer";
|
||||
import { YotiCsvImporter } from "../../importers/yoti-csv-importer";
|
||||
import { ZohoVaultCsvImporter } from "../../importers/zohovault-csv-importer";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { ImportResult } from "../../models/domain/import-result";
|
||||
import { ImportCiphersRequest } from "../../models/request/import-ciphers.request";
|
||||
import { ImportOrganizationCiphersRequest } from "../../models/request/import-organization-ciphers.request";
|
||||
import { KvpRequest } from "../../models/request/kvp.request";
|
||||
import { ErrorResponse } from "../../models/response/error.response";
|
||||
import { CipherService } from "../../vault/abstractions/cipher.service";
|
||||
import { FolderService } from "../../vault/abstractions/folder/folder.service.abstraction";
|
||||
import { CipherType } from "../../vault/enums/cipher-type";
|
||||
import { CipherRequest } from "../../vault/models/request/cipher.request";
|
||||
import { FolderRequest } from "../../vault/models/request/folder.request";
|
||||
import { CipherView } from "../../vault/models/view/cipher.view";
|
||||
|
||||
export class ImportService implements ImportServiceAbstraction {
|
||||
featuredImportOptions = featuredImportOptions as readonly ImportOption[];
|
||||
|
||||
regularImportOptions = regularImportOptions as readonly ImportOption[];
|
||||
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private folderService: FolderService,
|
||||
private importApiService: ImportApiServiceAbstraction,
|
||||
private i18nService: I18nService,
|
||||
private collectionService: CollectionService,
|
||||
private cryptoService: CryptoService
|
||||
) {}
|
||||
|
||||
getImportOptions(): ImportOption[] {
|
||||
return this.featuredImportOptions.concat(this.regularImportOptions);
|
||||
}
|
||||
|
||||
async import(
|
||||
importer: Importer,
|
||||
fileContents: string,
|
||||
organizationId: string = null
|
||||
): Promise<ImportError> {
|
||||
const importResult = await importer.parse(fileContents);
|
||||
if (importResult.success) {
|
||||
if (importResult.folders.length === 0 && importResult.ciphers.length === 0) {
|
||||
return new ImportError(this.i18nService.t("importNothingError"));
|
||||
} else if (importResult.ciphers.length > 0) {
|
||||
const halfway = Math.floor(importResult.ciphers.length / 2);
|
||||
const last = importResult.ciphers.length - 1;
|
||||
|
||||
if (
|
||||
this.badData(importResult.ciphers[0]) &&
|
||||
this.badData(importResult.ciphers[halfway]) &&
|
||||
this.badData(importResult.ciphers[last])
|
||||
) {
|
||||
return new ImportError(this.i18nService.t("importFormatError"));
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.postImport(importResult, organizationId);
|
||||
} catch (error) {
|
||||
const errorResponse = new ErrorResponse(error, 400);
|
||||
return this.handleServerError(errorResponse, importResult);
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
if (!Utils.isNullOrWhitespace(importResult.errorMessage)) {
|
||||
return new ImportError(importResult.errorMessage, importResult.missingPassword);
|
||||
} else {
|
||||
return new ImportError(
|
||||
this.i18nService.t("importFormatError"),
|
||||
importResult.missingPassword
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getImporter(
|
||||
format: ImportType | "bitwardenpasswordprotected",
|
||||
organizationId: string = null,
|
||||
password: string = null
|
||||
): Importer {
|
||||
const importer = this.getImporterInstance(format, password);
|
||||
if (importer == null) {
|
||||
return null;
|
||||
}
|
||||
importer.organizationId = organizationId;
|
||||
return importer;
|
||||
}
|
||||
|
||||
private getImporterInstance(format: ImportType | "bitwardenpasswordprotected", password: string) {
|
||||
if (format == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case "bitwardencsv":
|
||||
return new BitwardenCsvImporter();
|
||||
case "bitwardenjson":
|
||||
return new BitwardenJsonImporter(this.cryptoService, this.i18nService);
|
||||
case "bitwardenpasswordprotected":
|
||||
return new BitwardenPasswordProtectedImporter(
|
||||
this.cryptoService,
|
||||
this.i18nService,
|
||||
password
|
||||
);
|
||||
case "lastpasscsv":
|
||||
case "passboltcsv":
|
||||
return new LastPassCsvImporter();
|
||||
case "keepassxcsv":
|
||||
return new KeePassXCsvImporter();
|
||||
case "aviracsv":
|
||||
return new AviraCsvImporter();
|
||||
case "blurcsv":
|
||||
return new BlurCsvImporter();
|
||||
case "safeincloudxml":
|
||||
return new SafeInCloudXmlImporter();
|
||||
case "padlockcsv":
|
||||
return new PadlockCsvImporter();
|
||||
case "keepass2xml":
|
||||
return new KeePass2XmlImporter();
|
||||
case "chromecsv":
|
||||
case "operacsv":
|
||||
case "vivaldicsv":
|
||||
return new ChromeCsvImporter();
|
||||
case "firefoxcsv":
|
||||
return new FirefoxCsvImporter();
|
||||
case "upmcsv":
|
||||
return new UpmCsvImporter();
|
||||
case "saferpasscsv":
|
||||
return new SaferPassCsvImporter();
|
||||
case "safaricsv":
|
||||
return new SafariCsvImporter();
|
||||
case "meldiumcsv":
|
||||
return new MeldiumCsvImporter();
|
||||
case "1password1pif":
|
||||
return new OnePassword1PifImporter();
|
||||
case "1password1pux":
|
||||
return new OnePassword1PuxImporter();
|
||||
case "1passwordwincsv":
|
||||
return new OnePasswordWinCsvImporter();
|
||||
case "1passwordmaccsv":
|
||||
return new OnePasswordMacCsvImporter();
|
||||
case "keepercsv":
|
||||
return new KeeperCsvImporter();
|
||||
// case "keeperjson":
|
||||
// return new KeeperJsonImporter();
|
||||
case "passworddragonxml":
|
||||
return new PasswordDragonXmlImporter();
|
||||
case "enpasscsv":
|
||||
return new EnpassCsvImporter();
|
||||
case "enpassjson":
|
||||
return new EnpassJsonImporter();
|
||||
case "pwsafexml":
|
||||
return new PasswordSafeXmlImporter();
|
||||
case "dashlanecsv":
|
||||
return new DashlaneCsvImporter();
|
||||
case "dashlanejson":
|
||||
return new DashlaneJsonImporter();
|
||||
case "msecurecsv":
|
||||
return new MSecureCsvImporter();
|
||||
case "stickypasswordxml":
|
||||
return new StickyPasswordXmlImporter();
|
||||
case "truekeycsv":
|
||||
return new TrueKeyCsvImporter();
|
||||
case "clipperzhtml":
|
||||
return new ClipperzHtmlImporter();
|
||||
case "roboformcsv":
|
||||
return new RoboFormCsvImporter();
|
||||
case "ascendocsv":
|
||||
return new AscendoCsvImporter();
|
||||
case "passwordbossjson":
|
||||
return new PasswordBossJsonImporter();
|
||||
case "zohovaultcsv":
|
||||
return new ZohoVaultCsvImporter();
|
||||
case "splashidcsv":
|
||||
return new SplashIdCsvImporter();
|
||||
case "passkeepcsv":
|
||||
return new PassKeepCsvImporter();
|
||||
case "gnomejson":
|
||||
return new GnomeJsonImporter();
|
||||
case "passwordagentcsv":
|
||||
return new PasswordAgentCsvImporter();
|
||||
case "passpackcsv":
|
||||
return new PasspackCsvImporter();
|
||||
case "passmanjson":
|
||||
return new PassmanJsonImporter();
|
||||
case "avastcsv":
|
||||
return new AvastCsvImporter();
|
||||
case "avastjson":
|
||||
return new AvastJsonImporter();
|
||||
case "fsecurefsk":
|
||||
return new FSecureFskImporter();
|
||||
case "kasperskytxt":
|
||||
return new KasperskyTxtImporter();
|
||||
case "remembearcsv":
|
||||
return new RememBearCsvImporter();
|
||||
case "passwordwallettxt":
|
||||
return new PasswordWalletTxtImporter();
|
||||
case "mykicsv":
|
||||
return new MykiCsvImporter();
|
||||
case "securesafecsv":
|
||||
return new SecureSafeCsvImporter();
|
||||
case "logmeoncecsv":
|
||||
return new LogMeOnceCsvImporter();
|
||||
case "blackberrycsv":
|
||||
return new BlackBerryCsvImporter();
|
||||
case "buttercupcsv":
|
||||
return new ButtercupCsvImporter();
|
||||
case "codebookcsv":
|
||||
return new CodebookCsvImporter();
|
||||
case "encryptrcsv":
|
||||
return new EncryptrCsvImporter();
|
||||
case "yoticsv":
|
||||
return new YotiCsvImporter();
|
||||
case "nordpasscsv":
|
||||
return new NordPassCsvImporter();
|
||||
case "psonojson":
|
||||
return new PsonoJsonImporter();
|
||||
case "passkyjson":
|
||||
return new PasskyJsonImporter();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async postImport(importResult: ImportResult, organizationId: string = null) {
|
||||
if (organizationId == null) {
|
||||
const request = new ImportCiphersRequest();
|
||||
for (let i = 0; i < importResult.ciphers.length; i++) {
|
||||
const c = await this.cipherService.encrypt(importResult.ciphers[i]);
|
||||
request.ciphers.push(new CipherRequest(c));
|
||||
}
|
||||
if (importResult.folders != null) {
|
||||
for (let i = 0; i < importResult.folders.length; i++) {
|
||||
const f = await this.folderService.encrypt(importResult.folders[i]);
|
||||
request.folders.push(new FolderRequest(f));
|
||||
}
|
||||
}
|
||||
if (importResult.folderRelationships != null) {
|
||||
importResult.folderRelationships.forEach((r) =>
|
||||
request.folderRelationships.push(new KvpRequest(r[0], r[1]))
|
||||
);
|
||||
}
|
||||
return await this.importApiService.postImportCiphers(request);
|
||||
} else {
|
||||
const request = new ImportOrganizationCiphersRequest();
|
||||
for (let i = 0; i < importResult.ciphers.length; i++) {
|
||||
importResult.ciphers[i].organizationId = organizationId;
|
||||
const c = await this.cipherService.encrypt(importResult.ciphers[i]);
|
||||
request.ciphers.push(new CipherRequest(c));
|
||||
}
|
||||
if (importResult.collections != null) {
|
||||
for (let i = 0; i < importResult.collections.length; i++) {
|
||||
importResult.collections[i].organizationId = organizationId;
|
||||
const c = await this.collectionService.encrypt(importResult.collections[i]);
|
||||
request.collections.push(new CollectionRequest(c));
|
||||
}
|
||||
}
|
||||
if (importResult.collectionRelationships != null) {
|
||||
importResult.collectionRelationships.forEach((r) =>
|
||||
request.collectionRelationships.push(new KvpRequest(r[0], r[1]))
|
||||
);
|
||||
}
|
||||
return await this.importApiService.postImportOrganizationCiphers(organizationId, request);
|
||||
}
|
||||
}
|
||||
|
||||
private badData(c: CipherView) {
|
||||
return (
|
||||
(c.name == null || c.name === "--") &&
|
||||
c.type === CipherType.Login &&
|
||||
c.login != null &&
|
||||
Utils.isNullOrWhitespace(c.login.password)
|
||||
);
|
||||
}
|
||||
|
||||
private handleServerError(errorResponse: ErrorResponse, importResult: ImportResult): ImportError {
|
||||
if (errorResponse.validationErrors == null) {
|
||||
return new ImportError(errorResponse.message);
|
||||
}
|
||||
|
||||
let errorMessage = "";
|
||||
|
||||
Object.entries(errorResponse.validationErrors).forEach(([key, value], index) => {
|
||||
let item;
|
||||
let itemType;
|
||||
const i = Number(key.match(/[0-9]+/)[0]);
|
||||
|
||||
switch (key.match(/^\w+/)[0]) {
|
||||
case "Ciphers":
|
||||
item = importResult.ciphers[i];
|
||||
itemType = CipherType[item.type];
|
||||
break;
|
||||
case "Folders":
|
||||
item = importResult.folders[i];
|
||||
itemType = "Folder";
|
||||
break;
|
||||
case "Collections":
|
||||
item = importResult.collections[i];
|
||||
itemType = "Collection";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
errorMessage += "\n\n";
|
||||
}
|
||||
|
||||
if (itemType !== "Folder" && itemType !== "Collection") {
|
||||
errorMessage += "[" + (i + 1) + "] ";
|
||||
}
|
||||
|
||||
errorMessage += "[" + itemType + '] "' + item.name + '": ' + value;
|
||||
});
|
||||
|
||||
return new ImportError(errorMessage);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user