From 5a9ad98ff6373ae0e1c73ae191b32582d303666e Mon Sep 17 00:00:00 2001 From: Duc-Bao Trinh Date: Wed, 18 Sep 2024 11:32:11 +0200 Subject: [PATCH] Avoid Pass importer failing during line parsing [IDTEAM-3906] --- .../lib/import/providers/1password/1pif.reader.ts | 38 +++++++---- .../lib/import/providers/1password/1pux.reader.ts | 45 +++++++------ .../import/providers/bitwarden/bitwarden.reader.ts | 77 ++++++++++++---------- .../lib/import/providers/enpass/enpass.reader.ts | 34 ++++++---- .../import/providers/keeper/keeper.reader.spec.ts | 8 +-- .../lib/import/providers/keeper/keeper.reader.ts | 46 +++++++------ .../import/providers/lastpass/lastpass.reader.ts | 22 ++++--- .../import/providers/nordpass/nordpass.reader.ts | 30 +++++---- .../import/providers/roboform/roboform.reader.ts | 63 ++++++++++-------- 9 files changed, 212 insertions(+), 151 deletions(-) diff --git a/packages/pass/lib/import/providers/1password/1pif.reader.ts b/packages/pass/lib/import/providers/1password/1pif.reader.ts index 9168a83806..6e14a81feb 100644 --- a/packages/pass/lib/import/providers/1password/1pif.reader.ts +++ b/packages/pass/lib/import/providers/1password/1pif.reader.ts @@ -1,3 +1,5 @@ +import { c } from 'ttag'; + import { ImportProviderError } from '@proton/pass/lib/import/helpers/error'; import { getEmailOrUsername, @@ -97,19 +99,27 @@ export const read1Password1PifData = async ({ data }: { data: string }): Promise const ignored: string[] = []; const items: ItemImportIntent[] = parse1PifData(data) .map((item) => { - switch (item.typeName) { - case OnePassLegacyItemType.LOGIN: - return processLoginItem(item); - case OnePassLegacyItemType.NOTE: - return processNoteItem(item); - case OnePassLegacyItemType.PASSWORD: - return processPasswordItem(item); - case OnePassLegacyItemType.CREDIT_CARD: - return processCreditCardItem(item); - case OnePassLegacyItemType.IDENTITY: - return processIdentityItem(item); - default: - ignored.push(`[${item.typeName}] ${item.title ?? ''}`); + const type = item?.typeName ?? c('Label').t`Unknown`; + const title = item?.title ?? ''; + + try { + switch (item.typeName) { + case OnePassLegacyItemType.LOGIN: + return processLoginItem(item); + case OnePassLegacyItemType.NOTE: + return processNoteItem(item); + case OnePassLegacyItemType.PASSWORD: + return processPasswordItem(item); + case OnePassLegacyItemType.CREDIT_CARD: + return processCreditCardItem(item); + case OnePassLegacyItemType.IDENTITY: + return processIdentityItem(item); + default: + ignored.push(`[${type}] ${title}`); + } + } catch (err) { + ignored.push(`[${type}] ${title}`); + logger.warn('[Importer::1Password::1pif]', err); } }) .filter(truthy); @@ -124,7 +134,7 @@ export const read1Password1PifData = async ({ data }: { data: string }): Promise return { vaults, ignored, warnings: [] }; } catch (e) { - logger.warn('[Importer::1Password]', e); + logger.warn('[Importer::1Password::1pif]', e); throw new ImportProviderError('1Password', e); } }; diff --git a/packages/pass/lib/import/providers/1password/1pux.reader.ts b/packages/pass/lib/import/providers/1password/1pux.reader.ts index f6e3418f68..c73cf38ece 100644 --- a/packages/pass/lib/import/providers/1password/1pux.reader.ts +++ b/packages/pass/lib/import/providers/1password/1pux.reader.ts @@ -1,4 +1,5 @@ import jszip from 'jszip'; +import { c } from 'ttag'; import { ImportProviderError } from '@proton/pass/lib/import/helpers/error'; import { @@ -24,7 +25,7 @@ import { format1PasswordMonthYear, is1PasswordCCField, } from './1p.utils'; -import type { OnePass1PuxData, OnePassBaseItem, OnePassCreditCardFields, OnePassItem } from './1pux.types'; +import type { OnePass1PuxData, OnePassCreditCardFields, OnePassItem } from './1pux.types'; import { OnePassCategory, OnePassLoginDesignation, OnePassState, OnePasswordTypeMap } from './1pux.types'; const processNoteItem = ( @@ -133,24 +134,28 @@ export const read1Password1PuxData = async ({ data }: { data: ArrayBuffer }): Pr items: vault.items .filter((item) => item.state !== OnePassState.TRASHED) .map((item): Maybe => { - switch (item.categoryUuid) { - case OnePassCategory.LOGIN: - return processLoginItem(item); - case OnePassCategory.NOTE: - return processNoteItem(item); - case OnePassCategory.CREDIT_CARD: - return processCreditCardItem(item); - case OnePassCategory.PASSWORD: - return processPasswordItem(item); - case OnePassCategory.IDENTITY: - return processIdentityItem(item); - default: - const { categoryUuid, overview } = item as OnePassBaseItem; - ignored.push( - `[${OnePasswordTypeMap[categoryUuid] ?? 'Other'}] ${ - overview.title ?? overview.subtitle - }` - ); + const category = item?.categoryUuid; + const type = (category ? OnePasswordTypeMap[category] : null) ?? c('Label').t`Unknown`; + const title = item?.overview?.title ?? item?.overview?.subtitle ?? ''; + + try { + switch (item.categoryUuid) { + case OnePassCategory.LOGIN: + return processLoginItem(item); + case OnePassCategory.NOTE: + return processNoteItem(item); + case OnePassCategory.CREDIT_CARD: + return processCreditCardItem(item); + case OnePassCategory.PASSWORD: + return processPasswordItem(item); + case OnePassCategory.IDENTITY: + return processIdentityItem(item); + default: + ignored.push(`[${type}] ${title}`); + } + } catch (err) { + ignored.push(`[${type}] ${title}`); + logger.warn('[Importer::1Password::1pux]', err); } }) .filter(truthy), @@ -160,7 +165,7 @@ export const read1Password1PuxData = async ({ data }: { data: ArrayBuffer }): Pr return { vaults, ignored, warnings: [] }; } catch (e) { - logger.warn('[Importer::1Password]', e); + logger.warn('[Importer::1Password::1pux]', e); throw new ImportProviderError('1Password', e); } }; diff --git a/packages/pass/lib/import/providers/bitwarden/bitwarden.reader.ts b/packages/pass/lib/import/providers/bitwarden/bitwarden.reader.ts index b3406d1570..951f0b905b 100644 --- a/packages/pass/lib/import/providers/bitwarden/bitwarden.reader.ts +++ b/packages/pass/lib/import/providers/bitwarden/bitwarden.reader.ts @@ -63,42 +63,47 @@ export const readBitwardenData = ({ data }: { data: string }): ImportPayload => shareId: null, items: items .map((item): Maybe => { - switch (item.type) { - case BitwardenType.LOGIN: - const urls = extractBitwardenUrls(item); - return importLoginItem({ - name: item.name, - note: item.notes, - ...getEmailOrUsername(item.login.username), - password: item.login.password, - urls: urls.web, - totp: item.login.totp, - appIds: urls.android, - extraFields: extractBitwardenExtraFields(item.fields), - }); - case BitwardenType.NOTE: - addCustomFieldsWarning(ignored, item); - return importNoteItem({ - name: item.name, - note: item.notes, - }); - case BitwardenType.CREDIT_CARD: - addCustomFieldsWarning(ignored, item); - return importCreditCardItem({ - name: item.name, - note: item.notes, - cardholderName: item.card.cardholderName, - number: item.card.number, - verificationNumber: item.card.code, - expirationDate: formatBitwardenCCExpirationDate(item), - }); - case BitwardenType.IDENTITY: - addCustomFieldsWarning(ignored, item); - return importIdentityItem({ - name: item.name, - note: item.notes, - ...extractBitwardenIdentity(item), - }); + try { + switch (item.type) { + case BitwardenType.LOGIN: + const urls = extractBitwardenUrls(item); + return importLoginItem({ + name: item.name, + note: item.notes, + ...getEmailOrUsername(item.login.username), + password: item.login.password, + urls: urls.web, + totp: item.login.totp, + appIds: urls.android, + extraFields: extractBitwardenExtraFields(item.fields), + }); + case BitwardenType.NOTE: + addCustomFieldsWarning(ignored, item); + return importNoteItem({ + name: item.name, + note: item.notes, + }); + case BitwardenType.CREDIT_CARD: + addCustomFieldsWarning(ignored, item); + return importCreditCardItem({ + name: item.name, + note: item.notes, + cardholderName: item.card.cardholderName, + number: item.card.number, + verificationNumber: item.card.code, + expirationDate: formatBitwardenCCExpirationDate(item), + }); + case BitwardenType.IDENTITY: + addCustomFieldsWarning(ignored, item); + return importIdentityItem({ + name: item.name, + note: item.notes, + ...extractBitwardenIdentity(item), + }); + } + } catch (err) { + ignored.push(c('Error').t`[Error] an item could not be parsed`); + logger.warn('[Importer::Bitwarden]', err); } }) .filter(truthy), diff --git a/packages/pass/lib/import/providers/enpass/enpass.reader.ts b/packages/pass/lib/import/providers/enpass/enpass.reader.ts index eb442d26a4..64a0919f0a 100644 --- a/packages/pass/lib/import/providers/enpass/enpass.reader.ts +++ b/packages/pass/lib/import/providers/enpass/enpass.reader.ts @@ -117,19 +117,27 @@ export const readEnpassData = ({ data }: { data: string }): ImportPayload => { shareId: null, items: items .flatMap((item): Maybe => { - switch (item.category) { - case EnpassCategory.LOGIN: - case EnpassCategory.PASSWORD: - return processLoginItem(item); - case EnpassCategory.NOTE: - return processNoteItem(item); - case EnpassCategory.CREDIT_CARD: - return processCreditCardItem(item); - case EnpassCategory.IDENTITY: - return processIdentityItem(item); - default: - ignored.push(`[${capitalize(item.category) ?? 'Other'}] ${item.title}`); - return; + const type = capitalize(item?.category ?? c('Label').t`Unknown`); + const title = item?.title ?? ''; + + try { + switch (item.category) { + case EnpassCategory.LOGIN: + case EnpassCategory.PASSWORD: + return processLoginItem(item); + case EnpassCategory.NOTE: + return processNoteItem(item); + case EnpassCategory.CREDIT_CARD: + return processCreditCardItem(item); + case EnpassCategory.IDENTITY: + return processIdentityItem(item); + default: + ignored.push(`[${type}] ${title}`); + return; + } + } catch (err) { + ignored.push(`[${type}] ${title}`); + logger.warn('[Importer::Enpass]', err); } }) .filter(truthy), diff --git a/packages/pass/lib/import/providers/keeper/keeper.reader.spec.ts b/packages/pass/lib/import/providers/keeper/keeper.reader.spec.ts index a289ea573a..37b3d8ad57 100644 --- a/packages/pass/lib/import/providers/keeper/keeper.reader.spec.ts +++ b/packages/pass/lib/import/providers/keeper/keeper.reader.spec.ts @@ -325,9 +325,9 @@ describe('Import Keeper CSV', () => { test('should correctly hydrate ignored arrays', () => { expect(payload.ignored.length).toEqual(4); - expect(payload.ignored[0]).toEqual('[Other] address item'); - expect(payload.ignored[1]).toEqual('[Other] contact item'); - expect(payload.ignored[2]).toEqual('[Other] file attachment item'); - expect(payload.ignored[3]).toEqual('[Other] general item'); + expect(payload.ignored[0]).toEqual('[Unknown] address item'); + expect(payload.ignored[1]).toEqual('[Unknown] contact item'); + expect(payload.ignored[2]).toEqual('[Unknown] file attachment item'); + expect(payload.ignored[3]).toEqual('[Unknown] general item'); }); }); diff --git a/packages/pass/lib/import/providers/keeper/keeper.reader.ts b/packages/pass/lib/import/providers/keeper/keeper.reader.ts index 9677f3d489..86b937857f 100644 --- a/packages/pass/lib/import/providers/keeper/keeper.reader.ts +++ b/packages/pass/lib/import/providers/keeper/keeper.reader.ts @@ -85,27 +85,35 @@ export const readKeeperData = async ({ data }: { data: string }): Promise => { - if (isNoteItem(item)) { - return importNoteItem({ - name: item[1], - note: item[5], - }); - } + const type = c('Label').t`Unknown`; + const title = item?.[1] ?? ''; - if (isLoginItem(item)) { - return importLoginItem({ - name: item[1], - note: item[5], - ...getEmailOrUsername(item[2]), - password: item[3], - urls: [item[4]], - totp: extractTOTP(item), - extraFields: extractExtraFields(item), - }); - } + try { + if (isNoteItem(item)) { + return importNoteItem({ + name: item[1], + note: item[5], + }); + } - ignored.push(`[${c('Placeholder').t`Other`}] ${item[1]}`); - return; + if (isLoginItem(item)) { + return importLoginItem({ + name: item[1], + note: item[5], + ...getEmailOrUsername(item[2]), + password: item[3], + urls: [item[4]], + totp: extractTOTP(item), + extraFields: extractExtraFields(item), + }); + } + + ignored.push(`[${type}] ${title}`); + return; + } catch (err) { + ignored.push(`[${type}] ${title}`); + logger.warn('[Importer::Keeper]', err); + } }) .filter(truthy), }; diff --git a/packages/pass/lib/import/providers/lastpass/lastpass.reader.ts b/packages/pass/lib/import/providers/lastpass/lastpass.reader.ts index df640a388b..638c8b609d 100644 --- a/packages/pass/lib/import/providers/lastpass/lastpass.reader.ts +++ b/packages/pass/lib/import/providers/lastpass/lastpass.reader.ts @@ -92,16 +92,22 @@ export const readLastPassData = async ({ data }: { data: string }): Promise { - const isNote = item.url === 'http://sn'; - if (!isNote) return processLoginItem(item); + const noteType = extractLastPassFieldValue(item?.extra, 'NoteType'); + const type = capitalize(noteType ?? c('Label').t`Unknown`); + const title = item?.name ?? ''; - const noteType = extractLastPassFieldValue(item.extra, 'NoteType'); + try { + const isNote = item.url === 'http://sn'; + if (!isNote) return processLoginItem(item); + if (!noteType) return processNoteItem(item); + if (type === LastPassNoteType.CREDIT_CARD) return processCreditCardItem(item); + if (type === LastPassNoteType.ADDRESS) return processIdentityItem(item); - if (!noteType) return processNoteItem(item); - if (noteType === LastPassNoteType.CREDIT_CARD) return processCreditCardItem(item); - if (noteType === LastPassNoteType.ADDRESS) return processIdentityItem(item); - - ignored.push(`[${capitalize(noteType)}] ${item.name}`); + ignored.push(`[${type}] ${title}`); + } catch (err) { + ignored.push(`[${type}] ${title}`); + logger.warn('[Importer::LastPass]', err); + } }) .filter(truthy), }; diff --git a/packages/pass/lib/import/providers/nordpass/nordpass.reader.ts b/packages/pass/lib/import/providers/nordpass/nordpass.reader.ts index a3e2c27688..71a096d91d 100644 --- a/packages/pass/lib/import/providers/nordpass/nordpass.reader.ts +++ b/packages/pass/lib/import/providers/nordpass/nordpass.reader.ts @@ -79,17 +79,25 @@ export const readNordPassData = async ({ data }: { data: string }): Promise { - switch (item.type) { - case NordPassType.CREDIT_CARD: - return processCreditCardItem(item); - case NordPassType.IDENTITY: - return processIdentityItem(item); - case NordPassType.LOGIN: - return processLoginItem(item); - case NordPassType.NOTE: - return processNoteItem(item); - default: - ignored.push(`[${item.type ?? c('Placeholder').t`Other`}] ${item.name ?? ''}`); + const type = item?.type ?? c('Label').t`Unknown`; + const title = item?.name ?? ''; + + try { + switch (item.type) { + case NordPassType.CREDIT_CARD: + return processCreditCardItem(item); + case NordPassType.IDENTITY: + return processIdentityItem(item); + case NordPassType.LOGIN: + return processLoginItem(item); + case NordPassType.NOTE: + return processNoteItem(item); + default: + ignored.push(`[${type}] ${title}`); + } + } catch (err) { + ignored.push(`[${type}] ${title}`); + logger.warn('[Importer::NordPass]', err); } }) .filter(truthy), diff --git a/packages/pass/lib/import/providers/roboform/roboform.reader.ts b/packages/pass/lib/import/providers/roboform/roboform.reader.ts index 28c9156191..ca5066a087 100644 --- a/packages/pass/lib/import/providers/roboform/roboform.reader.ts +++ b/packages/pass/lib/import/providers/roboform/roboform.reader.ts @@ -1,3 +1,5 @@ +import { c } from 'ttag'; + import { readCSV } from '@proton/pass/lib/import/helpers/csv.reader'; import { ImportProviderError } from '@proton/pass/lib/import/helpers/error'; import { @@ -9,6 +11,7 @@ import { import type { ImportPayload, ImportVault } from '@proton/pass/lib/import/types'; import type { Maybe } from '@proton/pass/types'; import { groupByKey } from '@proton/pass/utils/array/group-by-key'; +import { truthy } from '@proton/pass/utils/fp/predicates'; import { logger } from '@proton/pass/utils/logger'; import lastItem from '@proton/utils/lastItem'; @@ -57,34 +60,42 @@ export const readRoboformData = async ({ data }: { data: string }): Promise { const vaultName = roboformItems[0].Folder; - const items = roboformItems.map((item) => { - /* Bookmark */ - if (item.Url && !item.MatchUrl && !item.Login && !item.Pwd) { - return importLoginItem({ - name: item.Name, - urls: [item.Url], - note: item.Note, - }); - } + const items = roboformItems + .map((item) => { + try { + /* Bookmark */ + if (item.Url && !item.MatchUrl && !item.Login && !item.Pwd) { + return importLoginItem({ + name: item.Name, + urls: [item.Url], + note: item.Note, + }); + } - /* Note */ - if (!item.Url && !item.MatchUrl && !item.Login && !item.Pwd) { - return importNoteItem({ - name: item.Name, - note: item.Note, - }); - } + /* Note */ + if (!item.Url && !item.MatchUrl && !item.Login && !item.Pwd) { + return importNoteItem({ + name: item.Name, + note: item.Note, + }); + } - /* Login */ - return importLoginItem({ - name: item.Name, - urls: [item.MatchUrl], - note: item.Note, - ...getEmailOrUsername(item.Login), - password: item.Pwd, - totp: formatOtpAuthUri(item), - }); - }); + /* Login */ + return importLoginItem({ + name: item.Name, + urls: [item.MatchUrl], + note: item.Note, + ...getEmailOrUsername(item.Login), + password: item.Pwd, + totp: formatOtpAuthUri(item), + }); + } catch (err) { + const title = item?.Name ?? ''; + ignored.push(`[${c('Label').t`Unknown`}] ${title}`); + logger.warn('[Importer::Roboform]', err); + } + }) + .filter(truthy); return { shareId: null, -- 2.11.4.GIT