1
0
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:
Daniel James Smith
2023-03-23 11:43:27 +01:00
committed by GitHub
parent 7cfabf053c
commit a5a12a6723
202 changed files with 706 additions and 479 deletions

View File

@@ -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>;
}

View File

@@ -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;
}

View File

@@ -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"];

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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(" ");
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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
);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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]);
});
}
}

View File

@@ -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);
});
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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
);
}
}

View File

@@ -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);
}
}
});
}
}

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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);
}
}

View File

@@ -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");
});
});

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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",
},
},
};

View File

@@ -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);
}
}

View File

@@ -1,5 +0,0 @@
export class ImportError extends Error {
constructor(message?: string, public passwordRequired: boolean = false) {
super(message);
}
}

View File

@@ -1,6 +0,0 @@
import { ImportResult } from "../models/domain/import-result";
export interface Importer {
organizationId: string;
parse(data: string): Promise<ImportResult>;
}

View File

@@ -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;
}
}

View File

@@ -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);
});
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
});
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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]);
});
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
});
}
}

View File

@@ -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;
});
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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]));
}
}

View File

@@ -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);
}
}

View File

@@ -1,11 +0,0 @@
export interface PasskyJsonExport {
encrypted: boolean;
passwords: LoginEntry[];
}
export interface LoginEntry {
website: string;
username: string;
password: string;
message: string;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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());
});
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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";
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
});
}
}

View File

@@ -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][] = [];
}

View File

@@ -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
);
}
}

View File

@@ -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);
}
}