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:
parent
7cc23dab72
commit
a36f1c25d8
|
@ -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="{"1615910350891":""}" autofill="one-time-code">thisisanotp</field>
|
||||||
|
<field type="secret" name="2FA-Reset" history="{"1615910350891":""}" 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="{"1615910438286":""}" 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>
|
||||||
|
`
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue