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;
+ }
}