This repository has been archived by the owner on Jun 17, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 138
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
importers for lastpass, bitwarden, and keepassx
- Loading branch information
Showing
6 changed files
with
613 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
import * as papa from 'papaparse'; | ||
|
||
import { LoginUriView } from '../models/view/loginUriView'; | ||
|
||
export abstract class BaseImporter { | ||
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 parseCsv(data: string, header: boolean): any[] { | ||
const result = papa.parse(data, { | ||
header: header, | ||
encoding: 'UTF-8', | ||
}); | ||
if (result.errors != null && result.errors.length > 0) { | ||
result.errors.forEach((e) => { | ||
// tslint:disable-next-line | ||
console.warn('Error parsing row ' + e.row + ': ' + e.message); | ||
}); | ||
return null; | ||
} | ||
return result.data; | ||
} | ||
|
||
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); | ||
loginUri.match = null; | ||
return [loginUri]; | ||
} | ||
|
||
if (uri.length > 0) { | ||
const returnArr: LoginUriView[] = []; | ||
uri.forEach((u) => { | ||
const loginUri = new LoginUriView(); | ||
loginUri.uri = this.fixUri(u); | ||
loginUri.match = null; | ||
returnArr.push(loginUri); | ||
}); | ||
return returnArr; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
protected fixUri(uri: string) { | ||
if (uri == null) { | ||
return null; | ||
} | ||
uri = uri.toLowerCase().trim(); | ||
if (uri.indexOf('://') === -1 && uri.indexOf('.') >= 0) { | ||
uri = 'http://' + uri; | ||
} | ||
if (uri.length > 1000) { | ||
return uri.substring(0, 1000); | ||
} | ||
return uri; | ||
} | ||
|
||
protected isNullOrWhitespace(str: string): boolean { | ||
return str == null || str.trim() === ''; | ||
} | ||
|
||
protected getValueOrDefault(str: string, defaultValue: string = null): string { | ||
if (this.isNullOrWhitespace(str)) { | ||
return defaultValue; | ||
} | ||
return str; | ||
} | ||
|
||
protected splitNewLine(str: string): string[] { | ||
return str.split(/(?:\r\n|\r|\n)/); | ||
} | ||
|
||
// 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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import { BaseImporter } from './baseImporter'; | ||
import { Importer } from './importer'; | ||
|
||
import { ImportResult } from '../models/domain/importResult'; | ||
|
||
import { CipherView } from '../models/view/cipherView'; | ||
import { FieldView } from '../models/view/fieldView'; | ||
import { FolderView } from '../models/view/folderView'; | ||
import { LoginView } from '../models/view/loginView'; | ||
import { SecureNoteView } from '../models/view/secureNoteView'; | ||
|
||
import { CipherType } from '../enums/cipherType'; | ||
import { FieldType } from '../enums/fieldType'; | ||
import { SecureNoteType } from '../enums/secureNoteType'; | ||
|
||
export class BitwardenCsvImporter extends BaseImporter implements Importer { | ||
import(data: string): ImportResult { | ||
const result = new ImportResult(); | ||
const results = this.parseCsv(data, true); | ||
if (results == null) { | ||
result.success = false; | ||
return result; | ||
} | ||
|
||
results.forEach((value) => { | ||
let folderIndex = result.folders.length; | ||
const cipherIndex = result.ciphers.length; | ||
const hasFolder = !this.isNullOrWhitespace(value.folder); | ||
let addFolder = hasFolder; | ||
|
||
if (hasFolder) { | ||
for (let i = 0; i < result.folders.length; i++) { | ||
if (result.folders[i].name === value.folder) { | ||
addFolder = false; | ||
folderIndex = i; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
const cipher = new CipherView(); | ||
cipher.type = CipherType.Login; | ||
cipher.favorite = this.getValueOrDefault(value.favorite, '0') !== '0' ? true : false; | ||
cipher.notes = this.getValueOrDefault(value.notes); | ||
cipher.name = this.getValueOrDefault(value.name, '--'); | ||
|
||
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 'login': | ||
case null: | ||
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; | ||
case 'note': | ||
cipher.type = CipherType.SecureNote; | ||
cipher.secureNote = new SecureNoteView(); | ||
cipher.secureNote.type = SecureNoteType.Generic; | ||
break; | ||
default: | ||
break; | ||
} | ||
|
||
result.ciphers.push(cipher); | ||
|
||
if (addFolder) { | ||
const f = new FolderView(); | ||
f.name = value.folder; | ||
result.folders.push(f); | ||
} | ||
if (hasFolder) { | ||
result.folderRelationships.set(cipherIndex, folderIndex); | ||
} | ||
}); | ||
|
||
return result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { ImportResult } from '../models/domain/importResult'; | ||
|
||
export interface Importer { | ||
import(data: string): ImportResult; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { BaseImporter } from './baseImporter'; | ||
import { Importer } from './importer'; | ||
|
||
import { ImportResult } from '../models/domain/importResult'; | ||
|
||
import { CipherView } from '../models/view/cipherView'; | ||
import { FolderView } from '../models/view/folderView'; | ||
import { LoginView } from '../models/view/loginView'; | ||
|
||
import { CipherType } from '../enums/cipherType'; | ||
|
||
export class KeePassXCsvImporter extends BaseImporter implements Importer { | ||
import(data: string): ImportResult { | ||
const result = new ImportResult(); | ||
const results = this.parseCsv(data, true); | ||
if (results == null) { | ||
result.success = false; | ||
return result; | ||
} | ||
|
||
results.forEach((value) => { | ||
value.Group = !this.isNullOrWhitespace(value.Group) && value.Group.startsWith('Root/') ? | ||
value.Group.replace('Root/', '') : value.Group; | ||
const groupName = !this.isNullOrWhitespace(value.Group) ? value.Group.split('/').join(' > ') : null; | ||
|
||
let folderIndex = result.folders.length; | ||
const cipherIndex = result.ciphers.length; | ||
const hasFolder = groupName != null; | ||
let addFolder = hasFolder; | ||
|
||
if (hasFolder) { | ||
for (let i = 0; i < result.folders.length; i++) { | ||
if (result.folders[i].name === groupName) { | ||
addFolder = false; | ||
folderIndex = i; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
const cipher = new CipherView(); | ||
cipher.type = CipherType.Login; | ||
cipher.favorite = false; | ||
cipher.notes = this.getValueOrDefault(value.Notes); | ||
cipher.name = this.getValueOrDefault(value.Title, '--'); | ||
cipher.login = new LoginView(); | ||
cipher.login.username = this.getValueOrDefault(value.Username); | ||
cipher.login.password = this.getValueOrDefault(value.Password); | ||
cipher.login.uris = this.makeUriArray(value.URL); | ||
|
||
if (!this.isNullOrWhitespace(value.Title)) { | ||
result.ciphers.push(cipher); | ||
} | ||
|
||
if (addFolder) { | ||
const f = new FolderView(); | ||
f.name = groupName; | ||
result.folders.push(f); | ||
} | ||
if (hasFolder) { | ||
result.folderRelationships.set(cipherIndex, folderIndex); | ||
} | ||
}); | ||
|
||
return result; | ||
} | ||
} |
Oops, something went wrong.