2017-01-31 07:50:28 +01:00
|
|
|
const Backbone = require('backbone');
|
|
|
|
const GroupCollection = require('../collections/group-collection');
|
|
|
|
const GroupModel = require('./group-model');
|
|
|
|
const IconUrl = require('../util/icon-url');
|
|
|
|
const Logger = require('../util/logger');
|
2019-09-08 20:28:02 +02:00
|
|
|
const KdbxToHtml = require('../comp/kdbx-to-html');
|
2017-01-31 07:50:28 +01:00
|
|
|
const kdbxweb = require('kdbxweb');
|
2019-08-18 10:17:09 +02:00
|
|
|
const demoFileData = require('demo.kdbx');
|
2015-10-17 23:49:24 +02:00
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
const logger = new Logger('file');
|
2015-12-12 09:53:50 +01:00
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
const FileModel = Backbone.Model.extend({
|
2015-10-17 23:49:24 +02:00
|
|
|
defaults: {
|
2015-12-02 21:50:31 +01:00
|
|
|
id: '',
|
2016-06-05 12:25:50 +02:00
|
|
|
uuid: '',
|
2015-10-17 23:49:24 +02:00
|
|
|
name: '',
|
|
|
|
keyFileName: '',
|
2015-10-21 22:17:53 +02:00
|
|
|
passwordLength: 0,
|
2015-10-17 23:49:24 +02:00
|
|
|
path: '',
|
2016-03-12 21:08:49 +01:00
|
|
|
opts: null,
|
2015-10-25 17:27:34 +01:00
|
|
|
storage: null,
|
2015-10-17 23:49:24 +02:00
|
|
|
modified: false,
|
2015-12-11 18:50:44 +01:00
|
|
|
dirty: false,
|
2015-10-17 23:49:24 +02:00
|
|
|
open: false,
|
|
|
|
created: false,
|
|
|
|
demo: false,
|
2015-10-24 18:43:30 +02:00
|
|
|
groups: null,
|
|
|
|
oldPasswordLength: 0,
|
|
|
|
oldKeyFileName: '',
|
|
|
|
passwordChanged: false,
|
2015-10-25 17:27:34 +01:00
|
|
|
keyFileChanged: false,
|
2016-07-03 18:46:43 +02:00
|
|
|
keyChangeForce: -1,
|
2015-12-10 22:31:47 +01:00
|
|
|
syncing: false,
|
2015-12-11 21:51:16 +01:00
|
|
|
syncError: null,
|
2016-08-16 22:11:54 +02:00
|
|
|
syncDate: null,
|
|
|
|
backup: null
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
db: null,
|
2015-12-05 14:04:09 +01:00
|
|
|
entryMap: null,
|
|
|
|
groupMap: null,
|
2015-10-17 23:49:24 +02:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
initialize() {
|
2015-12-05 14:04:09 +01:00
|
|
|
this.entryMap = {};
|
|
|
|
this.groupMap = {};
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
open(password, fileData, keyFileData, callback) {
|
2015-10-17 23:49:24 +02:00
|
|
|
try {
|
2017-01-31 07:50:28 +01:00
|
|
|
const credentials = new kdbxweb.Credentials(password, keyFileData);
|
|
|
|
const ts = logger.ts();
|
2017-01-29 23:28:04 +01:00
|
|
|
|
|
|
|
kdbxweb.Kdbx.load(fileData, credentials)
|
|
|
|
.then(db => {
|
2015-11-03 22:59:57 +01:00
|
|
|
this.db = db;
|
2015-12-13 22:27:26 +01:00
|
|
|
this.readModel();
|
2016-02-26 21:45:54 +01:00
|
|
|
this.setOpenFile({ passwordLength: password ? password.textLength : 0 });
|
2015-11-06 21:30:36 +01:00
|
|
|
if (keyFileData) {
|
|
|
|
kdbxweb.ByteUtils.zeroBuffer(keyFileData);
|
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
logger.info(
|
|
|
|
'Opened file ' +
|
|
|
|
this.get('name') +
|
|
|
|
': ' +
|
|
|
|
logger.ts(ts) +
|
|
|
|
', ' +
|
|
|
|
this.kdfArgsToString(db.header) +
|
|
|
|
', ' +
|
|
|
|
Math.round(fileData.byteLength / 1024) +
|
|
|
|
' kB'
|
|
|
|
);
|
2015-12-06 21:32:41 +01:00
|
|
|
callback();
|
2017-01-29 23:28:04 +01:00
|
|
|
})
|
|
|
|
.catch(err => {
|
2019-08-18 08:05:38 +02:00
|
|
|
if (
|
|
|
|
err.code === kdbxweb.Consts.ErrorCodes.InvalidKey &&
|
|
|
|
password &&
|
|
|
|
!password.byteLength
|
|
|
|
) {
|
|
|
|
logger.info(
|
|
|
|
'Error opening file with empty password, try to open with null password'
|
|
|
|
);
|
2017-01-29 23:28:04 +01:00
|
|
|
return this.open(null, fileData, keyFileData, callback);
|
|
|
|
}
|
|
|
|
logger.error('Error opening file', err.code, err.message, err);
|
|
|
|
callback(err);
|
|
|
|
});
|
2015-10-17 23:49:24 +02:00
|
|
|
} catch (e) {
|
2015-12-12 09:53:50 +01:00
|
|
|
logger.error('Error opening file', e, e.code, e.message, e);
|
2015-12-06 21:32:41 +01:00
|
|
|
callback(e);
|
2015-10-17 23:49:24 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
kdfArgsToString(header) {
|
2017-01-31 23:18:58 +01:00
|
|
|
if (header.kdfParameters) {
|
2019-08-16 23:05:39 +02:00
|
|
|
return header.kdfParameters
|
|
|
|
.keys()
|
|
|
|
.map(key => {
|
|
|
|
const val = header.kdfParameters.get(key);
|
|
|
|
if (val instanceof ArrayBuffer) {
|
2019-08-18 10:17:09 +02:00
|
|
|
return undefined;
|
2019-08-16 23:05:39 +02:00
|
|
|
}
|
|
|
|
return key + '=' + val;
|
|
|
|
})
|
|
|
|
.filter(p => p)
|
|
|
|
.join('&');
|
2017-01-31 23:18:58 +01:00
|
|
|
} else if (header.keyEncryptionRounds) {
|
|
|
|
return header.keyEncryptionRounds + ' rounds';
|
|
|
|
} else {
|
|
|
|
return '?';
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
create(name) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const password = kdbxweb.ProtectedValue.fromString('');
|
|
|
|
const credentials = new kdbxweb.Credentials(password);
|
2015-10-17 23:49:24 +02:00
|
|
|
this.db = kdbxweb.Kdbx.create(credentials, name);
|
2015-12-13 22:27:26 +01:00
|
|
|
this.set('name', name);
|
2015-10-17 23:49:24 +02:00
|
|
|
this.readModel();
|
2019-08-18 10:17:09 +02:00
|
|
|
this.set({ open: true, created: true, name });
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
importWithXml(fileXml, callback) {
|
2016-03-01 04:29:20 +01:00
|
|
|
try {
|
2017-01-31 07:50:28 +01:00
|
|
|
const ts = logger.ts();
|
|
|
|
const password = kdbxweb.ProtectedValue.fromString('');
|
|
|
|
const credentials = new kdbxweb.Credentials(password);
|
2017-01-29 23:28:04 +01:00
|
|
|
kdbxweb.Kdbx.loadXml(fileXml, credentials)
|
|
|
|
.then(db => {
|
2016-03-01 04:29:20 +01:00
|
|
|
this.db = db;
|
|
|
|
this.readModel();
|
|
|
|
this.set({ open: true, created: true });
|
2016-03-01 22:17:19 +01:00
|
|
|
logger.info('Imported file ' + this.get('name') + ': ' + logger.ts(ts));
|
2016-03-01 04:29:20 +01:00
|
|
|
callback();
|
2017-01-29 23:28:04 +01:00
|
|
|
})
|
|
|
|
.catch(err => {
|
|
|
|
logger.error('Error importing file', err.code, err.message, err);
|
|
|
|
callback(err);
|
|
|
|
});
|
2016-03-01 04:29:20 +01:00
|
|
|
} catch (e) {
|
|
|
|
logger.error('Error importing file', e, e.code, e.message, e);
|
|
|
|
callback(e);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
openDemo(callback) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const password = kdbxweb.ProtectedValue.fromString('demo');
|
|
|
|
const credentials = new kdbxweb.Credentials(password);
|
2019-08-18 08:05:38 +02:00
|
|
|
const demoFile = kdbxweb.ByteUtils.arrayToBuffer(
|
|
|
|
kdbxweb.ByteUtils.base64ToBytes(demoFileData)
|
|
|
|
);
|
2019-08-16 23:05:39 +02:00
|
|
|
kdbxweb.Kdbx.load(demoFile, credentials).then(db => {
|
|
|
|
this.db = db;
|
|
|
|
this.set('name', 'Demo');
|
|
|
|
this.readModel();
|
|
|
|
this.setOpenFile({ passwordLength: 4, demo: true });
|
|
|
|
callback();
|
|
|
|
});
|
2015-10-24 18:43:30 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setOpenFile(props) {
|
2015-10-24 18:43:30 +02:00
|
|
|
_.extend(props, {
|
|
|
|
open: true,
|
|
|
|
oldKeyFileName: this.get('keyFileName'),
|
2015-10-24 21:06:44 +02:00
|
|
|
oldPasswordLength: props.passwordLength,
|
|
|
|
passwordChanged: false,
|
|
|
|
keyFileChanged: false
|
2015-10-24 18:43:30 +02:00
|
|
|
});
|
|
|
|
this.set(props);
|
2015-10-24 21:06:44 +02:00
|
|
|
this._oldPasswordHash = this.db.credentials.passwordHash;
|
|
|
|
this._oldKeyFileHash = this.db.credentials.keyFileHash;
|
|
|
|
this._oldKeyChangeDate = this.db.meta.keyChanged;
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
readModel() {
|
2017-01-31 07:50:28 +01:00
|
|
|
const groups = new GroupCollection();
|
2019-08-16 23:05:39 +02:00
|
|
|
this.set(
|
|
|
|
{
|
|
|
|
uuid: this.db.getDefaultGroup().uuid.toString(),
|
2019-08-18 10:17:09 +02:00
|
|
|
groups,
|
2019-09-14 09:34:23 +02:00
|
|
|
formatVersion: this.db.header.versionMajor,
|
2019-08-16 23:05:39 +02:00
|
|
|
defaultUser: this.db.meta.defaultUser,
|
|
|
|
recycleBinEnabled: this.db.meta.recycleBinEnabled,
|
|
|
|
historyMaxItems: this.db.meta.historyMaxItems,
|
|
|
|
historyMaxSize: this.db.meta.historyMaxSize,
|
|
|
|
keyEncryptionRounds: this.db.header.keyEncryptionRounds,
|
|
|
|
keyChangeForce: this.db.meta.keyChangeForce,
|
2019-09-14 09:34:23 +02:00
|
|
|
kdfName: this.readKdfName(),
|
2019-08-16 23:05:39 +02:00
|
|
|
kdfParameters: this.readKdfParams()
|
|
|
|
},
|
|
|
|
{ silent: true }
|
|
|
|
);
|
2015-12-05 14:04:09 +01:00
|
|
|
this.db.groups.forEach(function(group) {
|
2017-01-31 07:50:28 +01:00
|
|
|
let groupModel = this.getGroup(this.subId(group.uuid.id));
|
2015-12-05 14:04:09 +01:00
|
|
|
if (groupModel) {
|
|
|
|
groupModel.setGroup(group, this);
|
|
|
|
} else {
|
|
|
|
groupModel = GroupModel.fromGroup(group, this);
|
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
groups.add(groupModel);
|
|
|
|
}, this);
|
2015-12-05 14:04:09 +01:00
|
|
|
this.buildObjectMap();
|
2016-08-14 20:53:55 +02:00
|
|
|
this.resolveFieldReferences();
|
2015-12-05 14:04:09 +01:00
|
|
|
},
|
|
|
|
|
2019-09-14 09:34:23 +02:00
|
|
|
readKdfName() {
|
|
|
|
if (this.db.header.versionMajor === 4 && this.db.header.kdfParameters) {
|
|
|
|
const kdfParameters = this.db.header.kdfParameters;
|
|
|
|
let uuid = kdfParameters.get('$UUID');
|
|
|
|
if (uuid) {
|
|
|
|
uuid = kdbxweb.ByteUtils.bytesToBase64(uuid);
|
|
|
|
switch (uuid) {
|
|
|
|
case kdbxweb.Consts.KdfId.Argon2:
|
|
|
|
return 'Argon2';
|
|
|
|
case kdbxweb.Consts.KdfId.Aes:
|
|
|
|
return 'Aes';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 'Unknown';
|
|
|
|
} else {
|
|
|
|
return 'Aes';
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
readKdfParams() {
|
2017-01-31 23:18:58 +01:00
|
|
|
const kdfParameters = this.db.header.kdfParameters;
|
|
|
|
if (!kdfParameters) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
let uuid = kdfParameters.get('$UUID');
|
|
|
|
if (!uuid) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
uuid = kdbxweb.ByteUtils.bytesToBase64(uuid);
|
2019-09-14 09:34:23 +02:00
|
|
|
switch (uuid) {
|
|
|
|
case kdbxweb.Consts.KdfId.Argon2:
|
|
|
|
return {
|
|
|
|
parallelism: kdfParameters.get('P').valueOf(),
|
|
|
|
iterations: kdfParameters.get('I').valueOf(),
|
|
|
|
memory: kdfParameters.get('M').valueOf()
|
|
|
|
};
|
|
|
|
case kdbxweb.Consts.KdfId.Aes:
|
|
|
|
return {
|
|
|
|
rounds: kdfParameters.get('R').valueOf()
|
|
|
|
};
|
|
|
|
default:
|
|
|
|
return undefined;
|
2017-01-31 23:18:58 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
subId(id) {
|
2016-06-05 12:25:50 +02:00
|
|
|
return this.id + ':' + id;
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
buildObjectMap() {
|
2017-01-31 07:50:28 +01:00
|
|
|
const entryMap = {};
|
|
|
|
const groupMap = {};
|
2019-08-16 23:05:39 +02:00
|
|
|
this.forEachGroup(
|
|
|
|
group => {
|
|
|
|
groupMap[group.id] = group;
|
|
|
|
group.forEachOwnEntry(null, entry => {
|
|
|
|
entryMap[entry.id] = entry;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
{ includeDisabled: true }
|
|
|
|
);
|
2015-12-05 14:04:09 +01:00
|
|
|
this.entryMap = entryMap;
|
|
|
|
this.groupMap = groupMap;
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
resolveFieldReferences() {
|
2017-01-31 07:50:28 +01:00
|
|
|
const entryMap = this.entryMap;
|
2016-08-14 20:53:55 +02:00
|
|
|
Object.keys(entryMap).forEach(e => {
|
|
|
|
entryMap[e].resolveFieldReferences();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
reload() {
|
2015-12-05 14:04:09 +01:00
|
|
|
this.buildObjectMap();
|
2015-12-13 22:27:26 +01:00
|
|
|
this.readModel();
|
2015-12-05 14:04:09 +01:00
|
|
|
this.trigger('reload', this);
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
mergeOrUpdate(fileData, remoteKey, callback) {
|
2017-01-29 23:28:04 +01:00
|
|
|
let credentials;
|
|
|
|
let credentialsPromise = Promise.resolve();
|
2016-01-16 21:32:50 +01:00
|
|
|
if (remoteKey) {
|
|
|
|
credentials = new kdbxweb.Credentials(kdbxweb.ProtectedValue.fromString(''));
|
2017-01-29 23:28:04 +01:00
|
|
|
credentialsPromise = credentials.ready.then(() => {
|
2017-01-31 07:50:28 +01:00
|
|
|
const promises = [];
|
2017-01-29 23:28:04 +01:00
|
|
|
if (remoteKey.password) {
|
|
|
|
promises.push(credentials.setPassword(remoteKey.password));
|
2016-01-16 21:32:50 +01:00
|
|
|
} else {
|
2017-01-29 23:28:04 +01:00
|
|
|
credentials.passwordHash = this.db.credentials.passwordHash;
|
2016-01-16 21:32:50 +01:00
|
|
|
}
|
2017-01-29 23:28:04 +01:00
|
|
|
if (remoteKey.keyFileName) {
|
|
|
|
if (remoteKey.keyFileData) {
|
|
|
|
promises.push(credentials.setKeyFile(remoteKey.keyFileData));
|
|
|
|
} else {
|
|
|
|
credentials.keyFileHash = this.db.credentials.keyFileHash;
|
|
|
|
}
|
|
|
|
}
|
2017-05-17 20:32:46 +02:00
|
|
|
return Promise.all(promises);
|
2017-01-29 23:28:04 +01:00
|
|
|
});
|
2016-01-16 21:32:50 +01:00
|
|
|
} else {
|
|
|
|
credentials = this.db.credentials;
|
|
|
|
}
|
2017-01-29 23:28:04 +01:00
|
|
|
credentialsPromise.then(() => {
|
|
|
|
kdbxweb.Kdbx.load(fileData, credentials)
|
|
|
|
.then(remoteDb => {
|
|
|
|
if (this.get('modified')) {
|
|
|
|
try {
|
|
|
|
if (remoteKey && remoteDb.meta.keyChanged > this.db.meta.keyChanged) {
|
|
|
|
this.db.credentials = remoteDb.credentials;
|
|
|
|
this.set('keyFileName', remoteKey.keyFileName || '');
|
|
|
|
if (remoteKey.password) {
|
|
|
|
this.set('passwordLength', remoteKey.password.textLength);
|
|
|
|
}
|
2016-01-16 21:32:50 +01:00
|
|
|
}
|
2017-01-29 23:28:04 +01:00
|
|
|
this.db.merge(remoteDb);
|
|
|
|
} catch (e) {
|
|
|
|
logger.error('File merge error', e);
|
|
|
|
return callback(e);
|
2016-01-16 21:32:50 +01:00
|
|
|
}
|
2017-01-29 23:28:04 +01:00
|
|
|
} else {
|
|
|
|
this.db = remoteDb;
|
2015-12-10 22:31:47 +01:00
|
|
|
}
|
2017-01-29 23:28:04 +01:00
|
|
|
this.set('dirty', true);
|
|
|
|
this.reload();
|
|
|
|
callback();
|
|
|
|
})
|
|
|
|
.catch(err => {
|
|
|
|
logger.error('Error opening file to merge', err.code, err.message, err);
|
|
|
|
callback(err);
|
|
|
|
});
|
2016-07-17 13:30:38 +02:00
|
|
|
});
|
2015-12-10 20:44:02 +01:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
getLocalEditState() {
|
2015-12-10 22:31:47 +01:00
|
|
|
return this.db.getLocalEditState();
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setLocalEditState(editState) {
|
2015-12-10 22:31:47 +01:00
|
|
|
this.db.setLocalEditState(editState);
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
close() {
|
2015-11-17 21:57:32 +01:00
|
|
|
this.set({
|
|
|
|
keyFileName: '',
|
|
|
|
passwordLength: 0,
|
|
|
|
modified: false,
|
2015-12-11 18:50:44 +01:00
|
|
|
dirty: false,
|
2015-11-17 21:57:32 +01:00
|
|
|
open: false,
|
|
|
|
created: false,
|
|
|
|
groups: null,
|
|
|
|
passwordChanged: false,
|
|
|
|
keyFileChanged: false,
|
|
|
|
syncing: false
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
getEntry(id) {
|
2015-12-05 14:04:09 +01:00
|
|
|
return this.entryMap[id];
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
getGroup(id) {
|
2015-12-05 14:04:09 +01:00
|
|
|
return this.groupMap[id];
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
forEachEntry(filter, callback) {
|
2017-01-31 07:50:28 +01:00
|
|
|
let top = this;
|
2015-10-17 23:49:24 +02:00
|
|
|
if (filter.trash) {
|
2019-08-18 08:05:38 +02:00
|
|
|
top = this.getGroup(
|
|
|
|
this.db.meta.recycleBinUuid ? this.subId(this.db.meta.recycleBinUuid.id) : null
|
|
|
|
);
|
2015-10-17 23:49:24 +02:00
|
|
|
} else if (filter.group) {
|
|
|
|
top = this.getGroup(filter.group);
|
|
|
|
}
|
|
|
|
if (top) {
|
|
|
|
if (top.forEachOwnEntry) {
|
|
|
|
top.forEachOwnEntry(filter, callback);
|
|
|
|
}
|
2015-11-06 21:42:37 +01:00
|
|
|
if (!filter.group || filter.subGroups) {
|
2016-07-17 13:30:38 +02:00
|
|
|
top.forEachGroup(group => {
|
2015-11-06 21:42:37 +01:00
|
|
|
group.forEachOwnEntry(filter, callback);
|
2017-01-29 19:03:38 +01:00
|
|
|
}, filter);
|
2015-11-06 21:42:37 +01:00
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
forEachGroup(callback, filter) {
|
2016-07-17 13:30:38 +02:00
|
|
|
this.get('groups').forEach(group => {
|
2015-10-17 23:49:24 +02:00
|
|
|
if (callback(group) !== false) {
|
2017-01-29 19:03:38 +01:00
|
|
|
group.forEachGroup(callback, filter);
|
2015-10-17 23:49:24 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
getTrashGroup() {
|
2019-08-18 08:05:38 +02:00
|
|
|
return this.db.meta.recycleBinEnabled
|
|
|
|
? this.getGroup(this.subId(this.db.meta.recycleBinUuid.id))
|
|
|
|
: null;
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
getEntryTemplatesGroup() {
|
2019-08-18 08:05:38 +02:00
|
|
|
return this.db.meta.entryTemplatesGroup
|
|
|
|
? this.getGroup(this.subId(this.db.meta.entryTemplatesGroup.id))
|
|
|
|
: null;
|
2017-05-03 20:44:16 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
createEntryTemplatesGroup() {
|
2017-05-03 20:44:16 +02:00
|
|
|
const rootGroup = this.get('groups').first();
|
|
|
|
const templatesGroup = GroupModel.newGroup(rootGroup, this);
|
|
|
|
templatesGroup.setName('Templates');
|
|
|
|
this.db.meta.entryTemplatesGroup = templatesGroup.group.uuid;
|
|
|
|
this.reload();
|
|
|
|
return templatesGroup;
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setModified() {
|
2015-10-17 23:49:24 +02:00
|
|
|
if (!this.get('demo')) {
|
2015-12-11 18:50:44 +01:00
|
|
|
this.set({ modified: true, dirty: true });
|
2019-01-02 15:19:19 +01:00
|
|
|
Backbone.trigger('file-modified');
|
2015-10-17 23:49:24 +02:00
|
|
|
}
|
2015-10-18 16:02:00 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
getData(cb) {
|
2015-11-22 09:45:40 +01:00
|
|
|
this.db.cleanup({
|
|
|
|
historyRules: true,
|
2015-12-17 21:22:36 +01:00
|
|
|
customIcons: true,
|
|
|
|
binaries: true
|
2015-11-22 09:45:40 +01:00
|
|
|
});
|
2015-12-17 21:22:36 +01:00
|
|
|
this.db.cleanup({ binaries: true });
|
2019-08-16 23:05:39 +02:00
|
|
|
this.db
|
|
|
|
.save()
|
2017-01-29 23:28:04 +01:00
|
|
|
.then(data => {
|
|
|
|
cb(data);
|
|
|
|
})
|
|
|
|
.catch(err => {
|
2016-07-17 13:30:38 +02:00
|
|
|
logger.error('Error saving file', this.get('name'), err);
|
2017-01-29 23:28:04 +01:00
|
|
|
cb(undefined, err);
|
|
|
|
});
|
2015-10-18 16:02:00 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
getXml(cb) {
|
2019-09-08 18:09:13 +02:00
|
|
|
this.db.saveXml(true).then(xml => {
|
2019-08-16 23:05:39 +02:00
|
|
|
cb(xml);
|
|
|
|
});
|
2015-10-24 18:43:30 +02:00
|
|
|
},
|
|
|
|
|
2019-09-08 20:28:02 +02:00
|
|
|
getHtml(cb) {
|
|
|
|
cb(
|
|
|
|
KdbxToHtml.convert(this.db, {
|
|
|
|
name: this.get('name')
|
|
|
|
})
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
getKeyFileHash() {
|
2017-01-31 07:50:28 +01:00
|
|
|
const hash = this.db.credentials.keyFileHash;
|
2016-02-14 12:20:21 +01:00
|
|
|
return hash ? kdbxweb.ByteUtils.bytesToBase64(hash.getBinary()) : null;
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
forEachEntryTemplate(callback) {
|
2017-05-02 21:22:08 +02:00
|
|
|
if (!this.db.meta.entryTemplatesGroup) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const group = this.getGroup(this.subId(this.db.meta.entryTemplatesGroup.id));
|
|
|
|
if (!group) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
group.forEachOwnEntry({}, callback);
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setSyncProgress() {
|
2015-12-10 22:31:47 +01:00
|
|
|
this.set({ syncing: true });
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setSyncComplete(path, storage, error, savedToCache) {
|
2015-12-11 18:50:44 +01:00
|
|
|
if (!error) {
|
|
|
|
this.db.removeLocalEditState();
|
|
|
|
}
|
2017-01-31 07:50:28 +01:00
|
|
|
const modified = this.get('modified') && !!error;
|
|
|
|
const dirty = this.get('dirty') && !savedToCache;
|
2015-12-10 22:31:47 +01:00
|
|
|
this.set({
|
|
|
|
created: false,
|
|
|
|
path: path || this.get('path'),
|
|
|
|
storage: storage || this.get('storage'),
|
2019-08-18 10:17:09 +02:00
|
|
|
modified,
|
|
|
|
dirty,
|
2015-12-10 22:31:47 +01:00
|
|
|
syncing: false,
|
|
|
|
syncError: error
|
|
|
|
});
|
2019-01-09 23:25:45 +01:00
|
|
|
|
|
|
|
const shouldResetFingerprint = this.get('passwordChanged') && this.has('fingerprint');
|
|
|
|
if (shouldResetFingerprint && !error) {
|
|
|
|
this.set({ fingerprint: null });
|
|
|
|
}
|
|
|
|
|
2016-03-19 12:17:52 +01:00
|
|
|
if (!this.get('open')) {
|
|
|
|
return;
|
|
|
|
}
|
2015-10-24 21:06:44 +02:00
|
|
|
this.setOpenFile({ passwordLength: this.get('passwordLength') });
|
2019-03-31 14:51:35 +02:00
|
|
|
this.forEachEntry({ includeDisabled: true }, entry => entry.setSaved());
|
2015-10-24 21:06:44 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setPassword(password) {
|
2015-10-24 18:43:30 +02:00
|
|
|
this.db.credentials.setPassword(password);
|
|
|
|
this.db.meta.keyChanged = new Date();
|
2016-01-16 15:19:33 +01:00
|
|
|
this.set({ passwordLength: password.textLength, passwordChanged: true });
|
2015-10-24 18:43:30 +02:00
|
|
|
this.setModified();
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
resetPassword() {
|
2015-10-24 18:43:30 +02:00
|
|
|
this.db.credentials.passwordHash = this._oldPasswordHash;
|
|
|
|
if (this.db.credentials.keyFileHash === this._oldKeyFileHash) {
|
|
|
|
this.db.meta.keyChanged = this._oldKeyChangeDate;
|
|
|
|
}
|
|
|
|
this.set({ passwordLength: this.get('oldPasswordLength'), passwordChanged: false });
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setKeyFile(keyFile, keyFileName) {
|
2015-10-24 18:43:30 +02:00
|
|
|
this.db.credentials.setKeyFile(keyFile);
|
|
|
|
this.db.meta.keyChanged = new Date();
|
2019-08-18 10:17:09 +02:00
|
|
|
this.set({ keyFileName, keyFileChanged: true });
|
2015-10-24 18:43:30 +02:00
|
|
|
this.setModified();
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
generateAndSetKeyFile() {
|
2017-01-31 07:50:28 +01:00
|
|
|
const keyFile = kdbxweb.Credentials.createRandomKeyFile();
|
|
|
|
const keyFileName = 'Generated';
|
2015-10-24 18:43:30 +02:00
|
|
|
this.setKeyFile(keyFile, keyFileName);
|
|
|
|
return keyFile;
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
resetKeyFile() {
|
2015-10-24 18:43:30 +02:00
|
|
|
this.db.credentials.keyFileHash = this._oldKeyFileHash;
|
|
|
|
if (this.db.credentials.passwordHash === this._oldPasswordHash) {
|
|
|
|
this.db.meta.keyChanged = this._oldKeyChangeDate;
|
|
|
|
}
|
|
|
|
this.set({ keyFileName: this.get('oldKeyFileName'), keyFileChanged: false });
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
removeKeyFile() {
|
2015-10-24 18:43:30 +02:00
|
|
|
this.db.credentials.keyFileHash = null;
|
2017-01-31 07:50:28 +01:00
|
|
|
const changed = !!this._oldKeyFileHash;
|
2015-10-24 18:43:30 +02:00
|
|
|
if (!changed && this.db.credentials.passwordHash === this._oldPasswordHash) {
|
|
|
|
this.db.meta.keyChanged = this._oldKeyChangeDate;
|
|
|
|
}
|
2018-12-28 13:16:19 +01:00
|
|
|
this.set({ keyFileName: '', keyFilePath: '', keyFileChanged: changed });
|
2018-12-28 19:11:45 +01:00
|
|
|
Backbone.trigger('unset-keyfile', this.id);
|
2016-02-14 12:20:21 +01:00
|
|
|
this.setModified();
|
2015-10-24 18:43:30 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
isKeyChangePending(force) {
|
2016-07-03 18:46:43 +02:00
|
|
|
if (!this.db.meta.keyChanged) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-01-31 07:50:28 +01:00
|
|
|
const expiryDays = force ? this.db.meta.keyChangeForce : this.db.meta.keyChangeRec;
|
2016-07-03 18:46:43 +02:00
|
|
|
if (!expiryDays || expiryDays < 0 || isNaN(expiryDays)) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-01-31 07:50:28 +01:00
|
|
|
const daysDiff = (Date.now() - this.db.meta.keyChanged) / 1000 / 3600 / 24;
|
2016-07-03 18:46:43 +02:00
|
|
|
return daysDiff > expiryDays;
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setKeyChange(force, days) {
|
2016-07-03 18:46:43 +02:00
|
|
|
if (isNaN(days) || !days || days < 0) {
|
|
|
|
days = -1;
|
|
|
|
}
|
2017-01-31 07:50:28 +01:00
|
|
|
const prop = force ? 'keyChangeForce' : 'keyChangeRec';
|
2016-07-03 18:46:43 +02:00
|
|
|
this.db.meta[prop] = days;
|
|
|
|
this.set(prop, days);
|
|
|
|
this.setModified();
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setName(name) {
|
2015-10-24 18:43:30 +02:00
|
|
|
this.db.meta.name = name;
|
|
|
|
this.db.meta.nameChanged = new Date();
|
|
|
|
this.set('name', name);
|
2019-08-16 23:05:39 +02:00
|
|
|
this.get('groups')
|
|
|
|
.first()
|
|
|
|
.setName(name);
|
2015-10-24 18:43:30 +02:00
|
|
|
this.setModified();
|
2015-12-13 15:59:41 +01:00
|
|
|
this.reload();
|
2015-10-24 18:43:30 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setDefaultUser(defaultUser) {
|
2015-10-24 18:43:30 +02:00
|
|
|
this.db.meta.defaultUser = defaultUser;
|
|
|
|
this.db.meta.defaultUserChanged = new Date();
|
|
|
|
this.set('defaultUser', defaultUser);
|
|
|
|
this.setModified();
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setRecycleBinEnabled(enabled) {
|
2015-10-24 18:43:30 +02:00
|
|
|
enabled = !!enabled;
|
|
|
|
this.db.meta.recycleBinEnabled = enabled;
|
|
|
|
if (enabled) {
|
|
|
|
this.db.createRecycleBin();
|
|
|
|
}
|
|
|
|
this.set('setRecycleBinEnabled', enabled);
|
|
|
|
this.setModified();
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setHistoryMaxItems(count) {
|
2015-10-24 18:43:30 +02:00
|
|
|
this.db.meta.historyMaxItems = count;
|
|
|
|
this.set('historyMaxItems', count);
|
|
|
|
this.setModified();
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setHistoryMaxSize(size) {
|
2015-10-24 18:43:30 +02:00
|
|
|
this.db.meta.historyMaxSize = size;
|
|
|
|
this.set('historyMaxSize', size);
|
|
|
|
this.setModified();
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setKeyEncryptionRounds(rounds) {
|
2015-10-24 18:43:30 +02:00
|
|
|
this.db.header.keyEncryptionRounds = rounds;
|
|
|
|
this.set('keyEncryptionRounds', rounds);
|
|
|
|
this.setModified();
|
2015-11-08 22:25:00 +01:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setKdfParameter(field, value) {
|
2017-01-31 23:18:58 +01:00
|
|
|
const ValueType = kdbxweb.VarDictionary.ValueType;
|
|
|
|
switch (field) {
|
|
|
|
case 'memory':
|
|
|
|
this.db.header.kdfParameters.set('M', ValueType.UInt64, kdbxweb.Int64.from(value));
|
|
|
|
break;
|
|
|
|
case 'iterations':
|
|
|
|
this.db.header.kdfParameters.set('I', ValueType.UInt64, kdbxweb.Int64.from(value));
|
|
|
|
break;
|
|
|
|
case 'parallelism':
|
|
|
|
this.db.header.kdfParameters.set('P', ValueType.UInt32, value);
|
|
|
|
break;
|
2019-09-14 09:34:23 +02:00
|
|
|
case 'rounds':
|
|
|
|
this.db.header.kdfParameters.set('R', ValueType.UInt32, value);
|
|
|
|
break;
|
2017-01-31 23:18:58 +01:00
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.set('kdfParameters', this.readKdfParams());
|
|
|
|
this.setModified();
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
emptyTrash() {
|
2017-01-31 07:50:28 +01:00
|
|
|
const trashGroup = this.getTrashGroup();
|
2015-11-08 22:25:00 +01:00
|
|
|
if (trashGroup) {
|
2017-01-31 07:50:28 +01:00
|
|
|
let modified = false;
|
2019-08-16 23:05:39 +02:00
|
|
|
trashGroup
|
|
|
|
.getOwnSubGroups()
|
|
|
|
.slice()
|
|
|
|
.forEach(function(group) {
|
|
|
|
this.db.move(group, null);
|
|
|
|
modified = true;
|
|
|
|
}, this);
|
2019-03-30 13:22:39 +01:00
|
|
|
trashGroup.group.entries.slice().forEach(function(entry) {
|
2015-11-08 22:25:00 +01:00
|
|
|
this.db.move(entry, null);
|
2016-07-19 19:42:47 +02:00
|
|
|
modified = true;
|
2015-11-08 22:25:00 +01:00
|
|
|
}, this);
|
2019-03-30 13:22:39 +01:00
|
|
|
trashGroup.get('items').reset();
|
2015-11-08 22:25:00 +01:00
|
|
|
trashGroup.get('entries').reset();
|
2016-07-19 19:42:47 +02:00
|
|
|
if (modified) {
|
|
|
|
this.setModified();
|
|
|
|
}
|
2015-11-08 22:25:00 +01:00
|
|
|
}
|
2015-11-22 09:21:12 +01:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
getCustomIcons() {
|
2016-07-17 13:30:38 +02:00
|
|
|
return _.mapObject(this.db.meta.customIcons, customIcon => IconUrl.toDataUrl(customIcon));
|
2015-11-22 09:21:12 +01:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
addCustomIcon(iconData) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const uuid = kdbxweb.KdbxUuid.random();
|
2019-08-18 08:05:38 +02:00
|
|
|
this.db.meta.customIcons[uuid] = kdbxweb.ByteUtils.arrayToBuffer(
|
|
|
|
kdbxweb.ByteUtils.base64ToBytes(iconData)
|
|
|
|
);
|
2016-06-05 12:25:50 +02:00
|
|
|
return uuid.toString();
|
2016-04-17 22:02:39 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
renameTag(from, to) {
|
2016-07-17 13:30:38 +02:00
|
|
|
this.forEachEntry({}, entry => entry.renameTag(from, to));
|
2019-09-14 09:34:23 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
setFormatVersion(version) {
|
|
|
|
this.db.setVersion(version);
|
|
|
|
this.setModified();
|
|
|
|
this.readModel();
|
|
|
|
},
|
|
|
|
|
|
|
|
setKdf(kdfName) {
|
|
|
|
const kdfParameters = this.db.header.kdfParameters;
|
|
|
|
if (!kdfParameters) {
|
|
|
|
throw new Error('Cannot set KDF on this version');
|
|
|
|
}
|
|
|
|
switch (kdfName) {
|
|
|
|
case 'Aes':
|
|
|
|
this.db.setKdf(kdbxweb.Consts.KdfId.Aes);
|
|
|
|
break;
|
|
|
|
case 'Argon2':
|
|
|
|
this.db.setKdf(kdbxweb.Consts.KdfId.Argon2);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error('Bad KDF name');
|
|
|
|
}
|
|
|
|
this.setModified();
|
|
|
|
this.readModel();
|
2015-10-17 23:49:24 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-02-14 12:20:21 +01:00
|
|
|
FileModel.createKeyFileWithHash = function(hash) {
|
|
|
|
return kdbxweb.Credentials.createKeyFileWithHash(hash);
|
|
|
|
};
|
|
|
|
|
2015-10-17 23:49:24 +02:00
|
|
|
module.exports = FileModel;
|