Enhance SafeInCloud import (#307)

* don't import deleted cards

* keep favourite status while importing from saveInCloud

* import all passwords from saveInCloud

* add test data
This commit is contained in:
Paul Sieben 2021-03-16 21:06:12 +01:00 committed by GitHub
parent 7cc23dab72
commit a36f1c25d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 100 additions and 4 deletions

View File

@ -0,0 +1,62 @@
export const data = `
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<database> <!-- LABELS -->
<label name="Business" id="1"></label>
<label name="Samples" id="3"></label>
<label name="Web Accounts" id="4" type="web_accounts"></label> <!-- TEMPLATES -->
<card title="Web Account" id="102" symbol="web_site" color="gray" template="true" autofill="on">
<field name="Login" type="login" autofill="username"></field>
<field name="Password" type="password" autofill="current-password"></field>
<field name="Website" type="website" autofill="url"></field>
<field name="One-time password" type="one_time_password" autofill="one-time-code"></field>
</card>
<card title="Note (Sample)" id="206" symbol="note" color="yellow" type="note" autofill="off">
<notes>This is a sample note.</notes>
<label_id>3</label_id>
</card>
<ghost id="2" time_stamp="1615910263225"></ghost>
<card title="Visa Card (Sample)" id="205" symbol="visa" color="0xff3f5ca8" autofill="on" time_stamp="1615910280931">
<field name="Number" type="number" autofill="cc-number">5555123456789000</field>
<field name="Owner" type="text" autofill="cc-name">John Smith</field>
<field name="Expires" type="expiry" autofill="cc-exp">01/23</field>
<field name="CVV" type="pin" autofill="cc-csc">555</field>
<field name="PIN" type="pin" autofill="off">1111</field>
<field name="Blocking" type="phone" autofill="off">555-0153</field>
<label_id>1</label_id>
</card>
<card title="Facebook (Sample)" id="301" symbol="f" color="0xff3f5ca8" autofill="on" deleted="true" time_stamp="1615910298700">
<field name="Login" type="login" autofill="username">john555@gmail.com</field>
<field name="Password" type="password" autofill="current-password">early91*Fail*</field>
<field name="Website" type="website" autofill="url">https://www.facebook.com</field>
<label_id>3</label_id>
</card>
<card title="Google (Sample)" id="300" symbol="g" color="blue" autofill="on" prev_stamp="1615910345167" time_stamp="1615910386722">
<field name="Email" type="login" autofill="username">john555@gmail.com</field>
<field name="Password" type="password" score="4" hash="5c76cb4d2fd87820be94530315581e14" autofill="current-password">plain79{Area{</field>
<field name="Website" type="website" autofill="url">https://www.google.com</field>
<label_id>3</label_id>
<field type="one_time_password" name="One-time password" history="{&quot;1615910350891&quot;:&quot;&quot;}" autofill="one-time-code">thisisanotp</field>
<field type="secret" name="2FA-Reset" history="{&quot;1615910350891&quot;:&quot;&quot;}" autofill="off">thisshouldbehidden</field>
</card>
<card title="Passport (Sample)" id="203" symbol="passport" color="purple" autofill="off" time_stamp="1615910424608">
<field name="Number" type="number" autofill="off">555111111</field>
<field name="Name" type="text" autofill="off">John Smith</field>
<field name="Birthday" type="date" autofill="off">05/05/1980</field>
<field name="Issued" type="date" autofill="off">01/01/2018</field>
<field name="Expires" type="expiry" autofill="off" score="1830380399000" hash="52f13d61109f06e642f86caf5e140474">01/01/2028</field>
<label_id>3</label_id>
<notes>This is a note attached to a card</notes>
</card>
<card title="Twitter (Sample)" id="302" symbol="t" color="blue" autofill="off" time_stamp="1615910462627">
<field name="Login" type="login" autofill="username">john555@gmail.com</field>
<field name="Website" type="website" autofill="url">https://twitter.com</field>
<label_id>3</label_id>
<field type="password" name="Secret login data" autofill="username" history="{&quot;1615910438286&quot;:&quot;&quot;}" score="0" hash="f9f910baf9c2cfb4f640e2d231f4a39b">shouldbepassword</field>
</card>
<card title="Laptop (Sample)" id="303" symbol="laptop" color="gray" autofill="off" star="false" prev_stamp="1615910472161" time_stamp="1615910473734">
<field name="Login" type="login" autofill="username">john555</field>
<field name="Password" type="password" autofill="current-password">Save63\apple\</field>
<label_id>3</label_id>
</card>
</database>
`

View File

@ -9,6 +9,9 @@ import { SecureNoteView } from '../models/view/secureNoteView';
import { CipherType } from '../enums/cipherType'; import { CipherType } from '../enums/cipherType';
import { SecureNoteType } from '../enums/secureNoteType'; import { SecureNoteType } from '../enums/secureNoteType';
import { FieldType } from '../enums';
import { CipherView, FieldView } from '../models/view';
export class SafeInCloudXmlImporter extends BaseImporter implements Importer { export class SafeInCloudXmlImporter extends BaseImporter implements Importer {
parse(data: string): Promise<ImportResult> { parse(data: string): Promise<ImportResult> {
const result = new ImportResult(); const result = new ImportResult();
@ -39,7 +42,7 @@ export class SafeInCloudXmlImporter extends BaseImporter implements Importer {
}); });
Array.from(doc.querySelectorAll('database > card')).forEach(cardEl => { Array.from(doc.querySelectorAll('database > card')).forEach(cardEl => {
if (cardEl.getAttribute('template') === 'true') { if (cardEl.getAttribute('template') === 'true' || cardEl.getAttribute('deleted') === 'true') {
return; return;
} }
@ -54,6 +57,10 @@ export class SafeInCloudXmlImporter extends BaseImporter implements Importer {
const cipher = this.initLoginCipher(); const cipher = this.initLoginCipher();
cipher.name = this.getValueOrDefault(cardEl.getAttribute('title'), '--'); cipher.name = this.getValueOrDefault(cardEl.getAttribute('title'), '--');
if (cardEl.getAttribute('star') === 'true') {
cipher.favorite = true;
}
const cardType = cardEl.getAttribute('type'); const cardType = cardEl.getAttribute('type');
if (cardType === 'note') { if (cardType === 'note') {
cipher.type = CipherType.SecureNote; cipher.type = CipherType.SecureNote;
@ -69,15 +76,17 @@ export class SafeInCloudXmlImporter extends BaseImporter implements Importer {
const fieldType = this.getValueOrDefault(fieldEl.getAttribute('type'), '').toLowerCase(); const fieldType = this.getValueOrDefault(fieldEl.getAttribute('type'), '').toLowerCase();
if (fieldType === 'login') { if (fieldType === 'login') {
cipher.login.username = text; cipher.login.username = text;
} else if (fieldType === 'password') { } else if (fieldType === 'password' || fieldType === 'secret') {
cipher.login.password = text; // 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') { } else if (fieldType === 'one_time_password') {
cipher.login.totp = text; cipher.login.totp = text;
} else if (fieldType === 'notes') { } else if (fieldType === 'notes') {
cipher.notes += (text + '\n'); cipher.notes += (text + '\n');
} else if (fieldType === 'weblogin' || fieldType === 'website') { } else if (fieldType === 'weblogin' || fieldType === 'website') {
cipher.login.uris = this.makeUriArray(text); cipher.login.uris = this.makeUriArray(text);
} else { }
else {
this.processKvp(cipher, name, text); this.processKvp(cipher, name, text);
} }
}); });
@ -87,6 +96,7 @@ export class SafeInCloudXmlImporter extends BaseImporter implements Importer {
cipher.notes += (notesEl.textContent + '\n'); cipher.notes += (notesEl.textContent + '\n');
}); });
this.setPassword(cipher);
this.cleanupCipher(cipher); this.cleanupCipher(cipher);
result.ciphers.push(cipher); result.ciphers.push(cipher);
}); });
@ -98,4 +108,28 @@ export class SafeInCloudXmlImporter extends BaseImporter implements Importer {
result.success = true; result.success = true;
return Promise.resolve(result); 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;
}
} }