1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 07:13:32 +00:00

[PS-2108] Enpass importer add support for androidurl's (#4314)

* Move and rename importers ater new naming convention

Create a subfolder to hold all enpass-importers
Change names to new naming convention
Fix imports
Remove entries from whitelist

* Added types for exported enpass json file

* Add unit tests to verify for current behaviour

* Prefer types over enums

* Replace `any` types with defined Enpass types

* Add support for parsing Android urls

Fixes #2831
Added test-file with several combinations
Wrote test cases to verify
This commit is contained in:
Daniel James Smith
2023-01-24 21:03:11 +01:00
committed by GitHub
parent 3976271d61
commit ae3edcc34d
11 changed files with 902 additions and 34 deletions

View File

@@ -1,11 +1,10 @@
import { CipherType } from "../enums/cipherType";
import { SecureNoteType } from "../enums/secureNoteType";
import { ImportResult } from "../models/domain/import-result";
import { CardView } from "../models/view/card.view";
import { SecureNoteView } from "../models/view/secure-note.view";
import { BaseImporter } from "./base-importer";
import { Importer } from "./importer";
import { CipherType } from "../../enums/cipherType";
import { SecureNoteType } from "../../enums/secureNoteType";
import { ImportResult } from "../../models/domain/import-result";
import { CardView } from "../../models/view/card.view";
import { SecureNoteView } from "../../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> {

View File

@@ -1,17 +1,21 @@
import { CipherType } from "../enums/cipherType";
import { FieldType } from "../enums/fieldType";
import { ImportResult } from "../models/domain/import-result";
import { CardView } from "../models/view/card.view";
import { CipherView } from "../models/view/cipher.view";
import { FolderView } from "../models/view/folder.view";
import { CipherType } from "../../enums/cipherType";
import { FieldType } from "../../enums/fieldType";
import { ImportResult } from "../../models/domain/import-result";
import { CardView } from "../../models/view/card.view";
import { CipherView } from "../../models/view/cipher.view";
import { FolderView } from "../../models/view/folder.view";
import { BaseImporter } from "../base-importer";
import { Importer } from "../importer";
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 = JSON.parse(data);
const results: EnpassJsonFile = JSON.parse(data);
if (results == null || results.items == null || results.items.length === 0) {
result.success = false;
return Promise.resolve(result);
@@ -28,7 +32,7 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
result.folders.push(f);
});
results.items.forEach((item: any) => {
results.items.forEach((item) => {
if (item.folders != null && item.folders.length > 0 && foldersIndexMap.has(item.folders[0])) {
result.folderRelationships.push([
result.ciphers.length,
@@ -50,7 +54,7 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
this.processCard(cipher, item.fields);
} else if (
item.template_type.indexOf("identity.") < 0 &&
item.fields.some((f: any) => f.type === "password" && !this.isNullOrWhitespace(f.value))
item.fields.some((f) => f.type === "password" && !this.isNullOrWhitespace(f.value))
) {
this.processLogin(cipher, item.fields);
} else {
@@ -68,9 +72,9 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
return Promise.resolve(result);
}
private processLogin(cipher: CipherView, fields: any[]) {
private processLogin(cipher: CipherView, fields: EnpassField[]) {
const urls: string[] = [];
fields.forEach((field: any) => {
fields.forEach((field) => {
if (this.isNullOrWhitespace(field.value) || field.type === "section") {
return;
}
@@ -86,6 +90,13 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
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,
@@ -98,10 +109,10 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
cipher.login.uris = this.makeUriArray(urls);
}
private processCard(cipher: CipherView, fields: any[]) {
private processCard(cipher: CipherView, fields: EnpassField[]) {
cipher.card = new CardView();
cipher.type = CipherType.Card;
fields.forEach((field: any) => {
fields.forEach((field) => {
if (
this.isNullOrWhitespace(field.value) ||
field.type === "section" ||
@@ -137,8 +148,8 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
});
}
private processNote(cipher: CipherView, fields: any[]) {
fields.forEach((field: any) => {
private processNote(cipher: CipherView, fields: EnpassField[]) {
fields.forEach((field) => {
if (this.isNullOrWhitespace(field.value) || field.type === "section") {
return;
}
@@ -151,17 +162,17 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
});
}
private buildFolderTree(folders: any[]): any[] {
private buildFolderTree(folders: EnpassFolder[]): EnpassFolderTreeItem[] {
if (folders == null) {
return [];
}
const folderTree: any[] = [];
const map = new Map<string, any>([]);
folders.forEach((obj: any) => {
const folderTree: EnpassFolderTreeItem[] = [];
const map = new Map<string, EnpassFolderTreeItem>([]);
folders.forEach((obj: EnpassFolderTreeItem) => {
map.set(obj.uuid, obj);
obj.children = [];
});
folders.forEach((obj: any) => {
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 {
@@ -171,11 +182,15 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
return folderTree;
}
private flattenFolderTree(titlePrefix: string, tree: any[], map: Map<string, string>) {
private flattenFolderTree(
titlePrefix: string,
tree: EnpassFolderTreeItem[],
map: Map<string, string>
) {
if (tree == null) {
return;
}
tree.forEach((f: any) => {
tree.forEach((f) => {
if (f.title != null && f.title.trim() !== "") {
let title = f.title.trim();
if (titlePrefix != null && titlePrefix.trim() !== "") {

View File

@@ -0,0 +1,79 @@
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

@@ -0,0 +1,85 @@
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

@@ -28,8 +28,8 @@ 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-csv-importer";
import { EnpassJsonImporter } from "../importers/enpass-json-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";