From a36f1c25d831fc3e2f50e8478c3d571b3b3b466f Mon Sep 17 00:00:00 2001 From: Paul Sieben <67124476+PaulVII@users.noreply.github.com> Date: Tue, 16 Mar 2021 21:06:12 +0100 Subject: [PATCH] Enhance SafeInCloud import (#307) * don't import deleted cards * keep favourite status while importing from saveInCloud * import all passwords from saveInCloud * add test data --- .../testData/safeInCloud/testData.xml.ts | 62 +++++++++++++++++++ src/importers/safeInCloudXmlImporter.ts | 42 +++++++++++-- 2 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 spec/common/importers/testData/safeInCloud/testData.xml.ts diff --git a/spec/common/importers/testData/safeInCloud/testData.xml.ts b/spec/common/importers/testData/safeInCloud/testData.xml.ts new file mode 100644 index 0000000000..9848bb90b1 --- /dev/null +++ b/spec/common/importers/testData/safeInCloud/testData.xml.ts @@ -0,0 +1,62 @@ +export const data = ` + + + + + + + + + + + + + This is a sample note. + 3 + + + + 5555123456789000 + John Smith + 01/23 + 555 + 1111 + 555-0153 + 1 + + + john555@gmail.com + early91*Fail* + https://www.facebook.com + 3 + + + john555@gmail.com + plain79{Area{ + https://www.google.com + 3 + thisisanotp + thisshouldbehidden + + + 555111111 + John Smith + 05/05/1980 + 01/01/2018 + 01/01/2028 + 3 + This is a note attached to a card + + + john555@gmail.com + https://twitter.com + 3 + shouldbepassword + + + john555 + Save63\apple\ + 3 + + +` \ No newline at end of file diff --git a/src/importers/safeInCloudXmlImporter.ts b/src/importers/safeInCloudXmlImporter.ts index f1a9569250..6e766cc03d 100644 --- a/src/importers/safeInCloudXmlImporter.ts +++ b/src/importers/safeInCloudXmlImporter.ts @@ -9,6 +9,9 @@ import { SecureNoteView } from '../models/view/secureNoteView'; import { CipherType } from '../enums/cipherType'; import { SecureNoteType } from '../enums/secureNoteType'; +import { FieldType } from '../enums'; +import { CipherView, FieldView } from '../models/view'; + export class SafeInCloudXmlImporter extends BaseImporter implements Importer { parse(data: string): Promise { const result = new ImportResult(); @@ -39,7 +42,7 @@ export class SafeInCloudXmlImporter extends BaseImporter implements Importer { }); Array.from(doc.querySelectorAll('database > card')).forEach(cardEl => { - if (cardEl.getAttribute('template') === 'true') { + if (cardEl.getAttribute('template') === 'true' || cardEl.getAttribute('deleted') === 'true') { return; } @@ -54,6 +57,10 @@ export class SafeInCloudXmlImporter extends BaseImporter implements Importer { 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; @@ -69,15 +76,17 @@ export class SafeInCloudXmlImporter extends BaseImporter implements Importer { const fieldType = this.getValueOrDefault(fieldEl.getAttribute('type'), '').toLowerCase(); if (fieldType === 'login') { cipher.login.username = text; - } else if (fieldType === 'password') { - cipher.login.password = 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 { + } + else { this.processKvp(cipher, name, text); } }); @@ -87,6 +96,7 @@ export class SafeInCloudXmlImporter extends BaseImporter implements Importer { cipher.notes += (notesEl.textContent + '\n'); }); + this.setPassword(cipher); this.cleanupCipher(cipher); result.ciphers.push(cipher); }); @@ -98,4 +108,28 @@ export class SafeInCloudXmlImporter extends BaseImporter implements Importer { 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; + } }