keeweb/app/scripts/models/file-model.js

405 lines
13 KiB
JavaScript
Raw Normal View History

2015-10-17 23:49:24 +02:00
'use strict';
var Backbone = require('backbone'),
GroupCollection = require('../collections/group-collection'),
GroupModel = require('./group-model'),
2015-11-21 23:15:51 +01:00
IconUrl = require('../util/icon-url'),
2015-10-17 23:49:24 +02:00
kdbxweb = require('kdbxweb'),
demoFileData = require('base64!../../resources/Demo.kdbx');
var FileModel = Backbone.Model.extend({
defaults: {
2015-12-02 21:50:31 +01:00
id: '',
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: '',
2015-10-25 17:27:34 +01:00
storage: null,
2015-10-17 23:49:24 +02:00
modified: false,
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,
2015-12-10 22:31:47 +01:00
syncing: false,
syncError: 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
initialize: function() {
2015-12-05 14:04:09 +01:00
this.entryMap = {};
this.groupMap = {};
2015-10-17 23:49:24 +02:00
},
2015-12-06 21:32:41 +01:00
open: function(password, fileData, keyFileData, callback) {
2015-10-17 23:49:24 +02:00
var len = password.value.length,
byteLength = 0,
value = new Uint8Array(len * 4),
salt = kdbxweb.Random.getBytes(len * 4),
ch, bytes;
for (var i = 0; i < len; i++) {
ch = String.fromCharCode(password.value.charCodeAt(i) ^ password.salt[i]);
bytes = kdbxweb.ByteUtils.stringToBytes(ch);
for (var j = 0; j < bytes.length; j++) {
value[byteLength] = bytes[j] ^ salt[byteLength];
byteLength++;
}
}
password = new kdbxweb.ProtectedValue(value.buffer.slice(0, byteLength), salt.buffer.slice(0, byteLength));
try {
var credentials = new kdbxweb.Credentials(password, keyFileData);
2015-11-06 21:30:36 +01:00
var start = performance.now();
kdbxweb.Kdbx.load(fileData, credentials, (function(db, err) {
if (err) {
2015-11-04 21:23:55 +01:00
console.error('Error opening file', err.code, err.message, err);
2015-12-06 21:32:41 +01:00
callback(err);
} else {
this.db = db;
this.readModel(this.get('name'));
this.setOpenFile({ passwordLength: len });
2015-11-06 21:30:36 +01:00
if (keyFileData) {
kdbxweb.ByteUtils.zeroBuffer(keyFileData);
}
console.log('Opened file ' + this.get('name') + ': ' + Math.round(performance.now() - start) + 'ms, ' +
db.header.keyEncryptionRounds + ' rounds, ' + Math.round(fileData.byteLength / 1024) + ' kB');
2015-12-06 21:32:41 +01:00
callback();
}
}).bind(this));
2015-10-17 23:49:24 +02:00
} catch (e) {
2015-11-04 21:23:55 +01:00
console.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
}
},
create: function(name) {
var password = kdbxweb.ProtectedValue.fromString('');
var credentials = new kdbxweb.Credentials(password);
this.db = kdbxweb.Kdbx.create(credentials, name);
this.readModel();
2015-12-07 20:07:56 +01:00
this.set({ open: true, created: true, name: name });
2015-10-17 23:49:24 +02:00
},
2015-12-06 21:32:41 +01:00
openDemo: function(callback) {
2015-10-17 23:49:24 +02:00
var password = kdbxweb.ProtectedValue.fromString('demo');
var credentials = new kdbxweb.Credentials(password);
var demoFile = kdbxweb.ByteUtils.arrayToBuffer(kdbxweb.ByteUtils.base64ToBytes(demoFileData));
kdbxweb.Kdbx.load(demoFile, credentials, (function(db) {
this.db = db;
this.readModel();
2015-12-02 21:50:31 +01:00
this.setOpenFile({passwordLength: 4, demo: true, name: 'Demo' });
2015-12-06 21:32:41 +01:00
callback();
}).bind(this));
2015-10-24 18:43:30 +02:00
},
setOpenFile: function(props) {
_.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
},
readModel: function(topGroupTitle) {
var groups = new GroupCollection();
2015-10-22 22:39:30 +02:00
this.set({
2015-12-02 21:50:31 +01:00
id: this.db.getDefaultGroup().uuid.toString(),
2015-10-22 22:39:30 +02:00
groups: groups,
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
}, { silent: true });
2015-12-05 14:04:09 +01:00
this.db.groups.forEach(function(group) {
var groupModel = this.getGroup(group.uuid.id);
if (groupModel) {
groupModel.setGroup(group, this);
} else {
groupModel = GroupModel.fromGroup(group, this);
}
if (topGroupTitle) {
2015-10-17 23:49:24 +02:00
groupModel.set({title: topGroupTitle});
}
groups.add(groupModel);
}, this);
2015-12-05 14:04:09 +01:00
this.buildObjectMap();
},
buildObjectMap: function() {
var entryMap = {};
var groupMap = {};
this.forEachGroup(function(group) {
groupMap[group.id] = group;
group.forEachOwnEntry(null, function(entry) {
entryMap[entry.id] = entry;
});
}, true);
this.entryMap = entryMap;
this.groupMap = groupMap;
},
reload: function() {
this.buildObjectMap();
this.readModel(this.get('name'));
this.trigger('reload', this);
2015-10-17 23:49:24 +02:00
},
2015-12-10 22:31:47 +01:00
mergeOrUpdate: function(fileData, callback) {
2015-12-10 20:44:02 +01:00
kdbxweb.Kdbx.load(fileData, this.db.credentials, (function(remoteDb, err) {
if (err) {
console.error('Error opening file to merge', err.code, err.message, err);
} else {
2015-12-10 22:31:47 +01:00
if (this.get('modified')) {
try {
this.db.merge(remoteDb);
} catch (e) {
console.error('File merge error', e);
return callback(e);
}
} else {
this.db = remoteDb;
this.reload();
}
2015-12-10 20:44:02 +01:00
}
callback(err);
}).bind(this));
},
2015-12-10 22:31:47 +01:00
getLocalEditState: function() {
return this.db.getLocalEditState();
},
setLocalEditState: function(editState) {
this.db.setLocalEditState(editState);
},
removeLocalEditState: function() {
this.db.removeLocalEditState();
},
2015-11-17 21:57:32 +01:00
close: function() {
this.set({
keyFileName: '',
passwordLength: 0,
modified: false,
open: false,
created: false,
groups: null,
passwordChanged: false,
keyFileChanged: false,
syncing: false
});
},
2015-12-05 14:04:09 +01:00
getEntry: function(id) {
return this.entryMap[id];
},
2015-10-17 23:49:24 +02:00
getGroup: function(id) {
2015-12-05 14:04:09 +01:00
return this.groupMap[id];
2015-10-17 23:49:24 +02:00
},
forEachEntry: function(filter, callback) {
var top = this;
if (filter.trash) {
top = this.getGroup(this.db.meta.recycleBinUuid ? this.db.meta.recycleBinUuid.id : null);
} else if (filter.group) {
top = this.getGroup(filter.group);
}
if (top) {
if (top.forEachOwnEntry) {
top.forEachOwnEntry(filter, callback);
}
if (!filter.group || filter.subGroups) {
top.forEachGroup(function (group) {
group.forEachOwnEntry(filter, callback);
});
}
2015-10-17 23:49:24 +02:00
}
},
forEachGroup: function(callback, includeDisabled) {
this.get('groups').forEach(function(group) {
if (callback(group) !== false) {
group.forEachGroup(callback, includeDisabled);
}
});
},
getTrashGroup: function() {
return this.db.meta.recycleBinEnabled ? this.getGroup(this.db.meta.recycleBinUuid.id) : null;
},
setModified: function() {
if (!this.get('demo')) {
this.set('modified', true);
}
2015-10-18 16:02:00 +02:00
},
getData: function(cb) {
2015-11-22 09:45:40 +01:00
this.db.cleanup({
historyRules: true,
customIcons: true
});
2015-12-10 20:44:02 +01:00
var that = this;
this.db.save(function(data, err) {
if (err) {
console.error('Error saving file', that.get('name'), err);
}
cb(data, err);
});
2015-10-18 16:02:00 +02:00
},
getXml: function(cb) {
this.db.saveXml(cb);
2015-10-24 18:43:30 +02:00
},
2015-12-10 22:31:47 +01:00
setSyncProgress: function() {
this.set({ syncing: true });
},
setSyncComplete: function(path, storage, error) {
var modified = this.get('modified') && !!error;
this.set({
created: false,
path: path || this.get('path'),
storage: storage || this.get('storage'),
modified: modified,
syncing: false,
syncError: error
});
2015-10-24 21:06:44 +02:00
this.setOpenFile({ passwordLength: this.get('passwordLength') });
this.forEachEntry({}, function(entry) {
entry.unsaved = false;
});
},
2015-10-24 18:43:30 +02:00
setPassword: function(password) {
this.db.credentials.setPassword(password);
this.db.meta.keyChanged = new Date();
this.set({ passwordLength: password.byteLength, passwordChanged: true });
this.setModified();
},
resetPassword: function() {
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 });
},
setKeyFile: function(keyFile, keyFileName) {
this.db.credentials.setKeyFile(keyFile);
this.db.meta.keyChanged = new Date();
this.set({ keyFileName: keyFileName, keyFileChanged: true });
this.setModified();
},
generateAndSetKeyFile: function() {
var keyFile = kdbxweb.Credentials.createRandomKeyFile();
var keyFileName = 'Generated';
this.setKeyFile(keyFile, keyFileName);
return keyFile;
},
resetKeyFile: function() {
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 });
},
removeKeyFile: function() {
this.db.credentials.keyFileHash = null;
var changed = !!this._oldKeyFileHash;
if (!changed && this.db.credentials.passwordHash === this._oldPasswordHash) {
this.db.meta.keyChanged = this._oldKeyChangeDate;
}
this.set({ keyFileName: '', keyFileChanged: changed });
},
setName: function(name) {
this.db.meta.name = name;
this.db.meta.nameChanged = new Date();
this.set('name', name);
2015-10-31 20:09:32 +01:00
this.get('groups').first().setName(name);
2015-10-24 18:43:30 +02:00
this.setModified();
},
setDefaultUser: function(defaultUser) {
this.db.meta.defaultUser = defaultUser;
this.db.meta.defaultUserChanged = new Date();
this.set('defaultUser', defaultUser);
this.setModified();
},
setRecycleBinEnabled: function(enabled) {
enabled = !!enabled;
this.db.meta.recycleBinEnabled = enabled;
if (enabled) {
this.db.createRecycleBin();
}
this.set('setRecycleBinEnabled', enabled);
this.setModified();
},
setHistoryMaxItems: function(count) {
this.db.meta.historyMaxItems = count;
this.set('historyMaxItems', count);
this.setModified();
},
setHistoryMaxSize: function(size) {
this.db.meta.historyMaxSize = size;
this.set('historyMaxSize', size);
this.setModified();
},
setKeyEncryptionRounds: function(rounds) {
this.db.header.keyEncryptionRounds = rounds;
this.set('keyEncryptionRounds', rounds);
this.setModified();
2015-11-08 22:25:00 +01:00
},
emptyTrash: function() {
var trashGroup = this.getTrashGroup();
if (trashGroup) {
trashGroup.getOwnSubGroups().slice().forEach(function(group) {
this.db.move(group, null);
}, this);
trashGroup.group.entries.forEach(function(entry) {
this.db.move(entry, null);
}, this);
trashGroup.get('entries').reset();
}
2015-11-22 09:21:12 +01:00
},
getCustomIcons: function() {
return _.mapObject(this.db.meta.customIcons, function(customIcon) {
return IconUrl.toDataUrl(customIcon);
});
},
addCustomIcon: function(iconData) {
2015-11-22 11:59:13 +01:00
var id = kdbxweb.KdbxUuid.random();
2015-11-22 09:21:12 +01:00
this.db.meta.customIcons[id] = kdbxweb.ByteUtils.arrayToBuffer(kdbxweb.ByteUtils.base64ToBytes(iconData));
return id.toString();
2015-10-17 23:49:24 +02:00
}
});
module.exports = FileModel;