386 lines
12 KiB
TypeScript
386 lines
12 KiB
TypeScript
import { FieldType } from "@bitwarden/common/enums";
|
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
|
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
|
|
|
import { ImportResult } from "../../models/import-result";
|
|
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 = CardView.getCardBrandByPatterns(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;
|
|
}
|
|
}
|