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

521 lines
16 KiB
JavaScript
Raw Normal View History

2015-10-17 23:49:24 +02:00
'use strict';
var Backbone = require('backbone'),
AttachmentModel = require('./attachment-model'),
IconMap = require('../const/icon-map'),
Color = require('../util/color'),
2015-11-21 21:14:21 +01:00
IconUrl = require('../util/icon-url'),
2016-04-01 23:09:16 +02:00
Otp = require('../util/otp'),
2015-10-17 23:49:24 +02:00
kdbxweb = require('kdbxweb');
var EntryModel = Backbone.Model.extend({
defaults: {},
2016-01-13 19:00:22 +01:00
urlRegex: /^https?:\/\//i,
2015-10-17 23:49:24 +02:00
2016-04-03 12:44:43 +02:00
builtInFields: ['Title', 'Password', 'Notes', 'URL', 'UserName', 'TOTP Seed', 'TOTP Settings'],
2015-10-17 23:49:24 +02:00
initialize: function() {
},
setEntry: function(entry, group, file) {
this.entry = entry;
this.group = group;
this.file = file;
2015-12-05 14:04:09 +01:00
if (this.id === entry.uuid.id) {
this._checkUpdatedEntry();
}
2015-10-17 23:49:24 +02:00
this._fillByEntry();
},
_fillByEntry: function() {
var entry = this.entry;
2015-12-05 14:04:09 +01:00
this.set({id: entry.uuid.id}, {silent: true});
2015-12-13 15:59:41 +01:00
this.fileName = this.file.get('name');
2015-10-17 23:49:24 +02:00
this.title = entry.fields.Title || '';
2015-10-31 20:27:31 +01:00
this.password = entry.fields.Password || kdbxweb.ProtectedValue.fromString('');
2015-10-17 23:49:24 +02:00
this.notes = entry.fields.Notes || '';
this.url = entry.fields.URL || '';
2016-01-13 19:00:22 +01:00
this.displayUrl = this._getDisplayUrl(entry.fields.URL);
2015-10-17 23:49:24 +02:00
this.user = entry.fields.UserName || '';
this.iconId = entry.icon;
this.icon = this._iconFromId(entry.icon);
this.tags = entry.tags;
this.color = this._colorToModel(entry.bgColor) || this._colorToModel(entry.fgColor);
this.fields = this._fieldsToModel(entry.fields);
this.attachments = this._attachmentsToModel(entry.binaries);
this.created = entry.times.creationTime;
this.updated = entry.times.lastModTime;
this.expires = entry.times.expires ? entry.times.expiryTime : undefined;
this.expired = entry.times.expires && entry.times.expiryTime <= new Date();
this.historyLength = entry.history.length;
2015-11-12 22:52:28 +01:00
this._buildCustomIcon();
2015-10-17 23:49:24 +02:00
this._buildSearchText();
this._buildSearchTags();
this._buildSearchColor();
2016-04-23 16:50:40 +02:00
this._buildAutoType();
2015-10-17 23:49:24 +02:00
},
2015-12-05 14:04:09 +01:00
_checkUpdatedEntry: function() {
if (this.isJustCreated) {
this.isJustCreated = false;
}
if (this.unsaved && +this.updated !== +this.entry.times.lastModTime) {
this.unsaved = false;
}
},
2015-10-17 23:49:24 +02:00
_buildSearchText: function() {
var text = '';
_.forEach(this.entry.fields, function(value) {
if (typeof value === 'string') {
text += value.toLowerCase() + '\n';
}
});
this.entry.tags.forEach(function(tag) {
text += tag.toLowerCase() + '\n';
});
this.attachments.forEach(function(att) {
text += att.title.toLowerCase() + '\n';
});
this.searchText = text;
},
2015-11-12 22:52:28 +01:00
_buildCustomIcon: function() {
this.customIcon = null;
2015-11-21 23:15:51 +01:00
this.customIconId = null;
2015-11-12 22:52:28 +01:00
if (this.entry.customIcon) {
2015-11-21 21:14:21 +01:00
this.customIcon = IconUrl.toDataUrl(this.file.db.meta.customIcons[this.entry.customIcon]);
2015-11-21 23:15:51 +01:00
this.customIconId = this.entry.customIcon.toString();
2015-11-12 22:52:28 +01:00
}
},
2015-10-17 23:49:24 +02:00
_buildSearchTags: function() {
this.searchTags = this.entry.tags.map(function(tag) { return tag.toLowerCase(); });
},
_buildSearchColor: function() {
this.searchColor = this.color;
},
2016-04-23 16:50:40 +02:00
_buildAutoType: function() {
this.autoTypeEnabled = this.entry.autoType.enabled;
this.autoTypeObfuscation = this.entry.autoType.obfuscation === kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard;
this.autoTypeSequence = this.entry.autoType.defaultSequence;
2016-04-26 21:40:18 +02:00
this.autoTypeWindows = this.entry.autoType.items.map(this._convertAutoTypeItem);
},
_convertAutoTypeItem: function(item) {
return { window: item.window, sequence: item.keystrokeSequence };
2016-04-23 16:50:40 +02:00
},
2015-10-17 23:49:24 +02:00
_iconFromId: function(id) {
return IconMap[id];
},
2016-01-13 19:00:22 +01:00
_getDisplayUrl: function(url) {
if (!url) {
return '';
}
return url.replace(this.urlRegex, '');
},
2015-10-17 23:49:24 +02:00
_colorToModel: function(color) {
return color ? Color.getNearest(color) : null;
},
_fieldsToModel: function(fields) {
2016-01-13 21:33:14 +01:00
return _.omit(fields, this.builtInFields);
2015-10-17 23:49:24 +02:00
},
_attachmentsToModel: function(binaries) {
var att = [];
_.forEach(binaries, function(data, title) {
2015-12-17 21:22:36 +01:00
if (data && data.ref) {
data = this.file.db.meta.binaries[data.ref];
}
if (data) {
att.push(AttachmentModel.fromAttachment({data: data, title: title}));
}
2015-10-17 23:49:24 +02:00
}, this);
return att;
},
_entryModified: function() {
if (!this.unsaved) {
this.unsaved = true;
this.entry.pushHistory();
this.file.setModified();
}
2015-10-27 22:07:48 +01:00
if (this.isJustCreated) {
this.isJustCreated = false;
2015-10-27 20:40:34 +01:00
}
2015-10-17 23:49:24 +02:00
this.entry.times.update();
},
matches: function(filter) {
2015-12-05 14:04:09 +01:00
return !filter ||
(!filter.tagLower || this.searchTags.indexOf(filter.tagLower) >= 0) &&
2016-01-13 21:33:14 +01:00
(!filter.textLower || (filter.advanced ? this.matchesAdv(filter) : this.searchText.indexOf(filter.textLower) >= 0)) &&
2015-10-17 23:49:24 +02:00
(!filter.color || filter.color === true && this.searchColor || this.searchColor === filter.color);
},
2016-01-13 21:33:14 +01:00
matchesAdv: function(filter) {
var adv = filter.advanced;
var search, match;
if (adv.regex) {
try { search = new RegExp(filter.text, adv.cs ? '' : 'i'); }
catch (e) { return false; }
match = this.matchRegex;
} else if (adv.cs) {
search = filter.text;
match = this.matchString;
} else {
search = filter.textLower;
match = this.matchStringLower;
}
if (this.matchEntry(this.entry, adv, match, search)) {
return true;
}
if (adv.history) {
for (var i = 0, len = this.entry.history.length; i < len; i++) {
if (this.matchEntry(this.entry.history[0], adv, match, search)) {
return true;
}
}
}
return false;
},
matchString: function(str, find) {
2016-01-17 12:36:52 +01:00
if (str.isProtected) {
return str.includes(find);
}
2016-01-13 21:33:14 +01:00
return str.indexOf(find) >= 0;
},
matchStringLower: function(str, findLower) {
2016-01-17 12:36:52 +01:00
if (str.isProtected) {
return str.includesLower(findLower);
}
2016-01-13 21:33:14 +01:00
return str.toLowerCase().indexOf(findLower) >= 0;
},
matchRegex: function(str, regex) {
2016-01-17 12:36:52 +01:00
if (str.isProtected) {
str = str.getText();
}
2016-01-13 21:33:14 +01:00
return regex.test(str);
},
matchEntry: function(entry, adv, compare, search) {
var matchField = this.matchField;
if (adv.user && matchField(entry, 'UserName', compare, search)) {
return true;
}
if (adv.url && matchField(entry, 'URL', compare, search)) {
return true;
}
if (adv.notes && matchField(entry, 'Notes', compare, search)) {
return true;
}
if (adv.pass && matchField(entry, 'Password', compare, search)) {
return true;
}
2016-02-25 20:50:31 +01:00
if (adv.title && matchField(entry, 'Title', compare, search)) {
2016-01-13 21:33:14 +01:00
return true;
}
var matches = false;
if (adv.other || adv.protect) {
var builtInFields = this.builtInFields;
var fieldNames = Object.keys(entry.fields);
matches = fieldNames.some(function (field) {
if (builtInFields.indexOf(field) >= 0) {
return false;
}
if (typeof entry.fields[field] === 'string') {
return adv.other && matchField(entry, field, compare, search);
} else {
return adv.protect && matchField(entry, field, compare, search);
}
});
}
return matches;
},
matchField: function(entry, field, compare, search) {
var val = entry.fields[field];
2016-01-17 12:36:52 +01:00
return val ? compare(val, search) : false;
2016-01-13 21:33:14 +01:00
},
2015-10-17 23:49:24 +02:00
setColor: function(color) {
this._entryModified();
this.entry.bgColor = Color.getKnownBgColor(color);
this._fillByEntry();
},
setIcon: function(iconId) {
this._entryModified();
this.entry.icon = iconId;
2015-11-21 23:15:51 +01:00
this.entry.customIcon = undefined;
this._fillByEntry();
},
setCustomIcon: function(customIconId) {
this._entryModified();
this.entry.customIcon = new kdbxweb.KdbxUuid(customIconId);
2015-10-17 23:49:24 +02:00
this._fillByEntry();
},
setExpires: function(dt) {
this._entryModified();
this.entry.times.expiryTime = dt instanceof Date ? dt : undefined;
this.entry.times.expires = !!dt;
this._fillByEntry();
},
setTags: function(tags) {
this._entryModified();
this.entry.tags = tags;
this._fillByEntry();
},
2016-04-17 22:02:39 +02:00
renameTag: function(from, to) {
var ix = _.findIndex(this.entry.tags, function(tag) { return tag.toLowerCase() === from.toLowerCase(); });
if (ix < 0) {
return;
}
this._entryModified();
this.entry.tags.splice(ix, 1);
if (to) {
this.entry.tags.push(to);
}
this._fillByEntry();
},
2015-10-17 23:49:24 +02:00
setField: function(field, val) {
2016-01-16 15:19:33 +01:00
var hasValue = val && (typeof val === 'string' || val.isProtected && val.byteLength);
2016-01-13 21:33:14 +01:00
if (hasValue || this.builtInFields.indexOf(field) >= 0) {
2016-03-05 14:59:36 +01:00
this._entryModified();
2015-10-17 23:49:24 +02:00
this.entry.fields[field] = val;
2016-03-05 14:59:36 +01:00
} else if (this.entry.fields.hasOwnProperty(field)) {
this._entryModified();
2015-10-17 23:49:24 +02:00
delete this.entry.fields[field];
}
this._fillByEntry();
},
hasField: function(field) {
return this.entry.fields.hasOwnProperty(field);
},
addAttachment: function(name, data) {
this._entryModified();
2015-12-17 21:22:36 +01:00
var binaryId;
for (var i = 0; ; i++) {
if (!this.file.db.meta.binaries[i]) {
binaryId = i.toString();
break;
}
}
this.file.db.meta.binaries[binaryId] = data;
this.entry.binaries[name] = { ref: binaryId };
2015-10-17 23:49:24 +02:00
this._fillByEntry();
},
removeAttachment: function(name) {
this._entryModified();
delete this.entry.binaries[name];
this._fillByEntry();
},
getHistory: function() {
var history = this.entry.history.map(function(rec) {
return EntryModel.fromEntry(rec, this.group, this.file);
}, this);
history.push(this);
history.sort(function(x, y) { return x.updated - y.updated; });
return history;
},
deleteHistory: function(historyEntry) {
var ix = this.entry.history.indexOf(historyEntry);
if (ix >= 0) {
2015-12-05 14:04:09 +01:00
this.entry.removeHistory(ix);
2015-10-17 23:49:24 +02:00
}
this._fillByEntry();
},
revertToHistoryState: function(historyEntry) {
var ix = this.entry.history.indexOf(historyEntry);
if (ix < 0) {
return;
}
this.entry.pushHistory();
this.unsaved = true;
this.file.setModified();
this.entry.fields = {};
this.entry.binaries = {};
this.entry.copyFrom(historyEntry);
this._entryModified();
this._fillByEntry();
},
discardUnsaved: function() {
2015-12-05 14:04:09 +01:00
if (this.unsaved && this.entry.history.length) {
2015-10-17 23:49:24 +02:00
this.unsaved = false;
2015-12-05 14:04:09 +01:00
var historyEntry = this.entry.history[this.entry.history.length - 1];
this.entry.removeHistory(this.entry.history.length - 1);
2015-10-17 23:49:24 +02:00
this.entry.fields = {};
this.entry.binaries = {};
this.entry.copyFrom(historyEntry);
this._fillByEntry();
}
},
moveToTrash: function() {
2015-11-19 06:55:45 +01:00
this.file.setModified();
if (this.isJustCreated) {
this.isJustCreated = false;
}
2015-11-04 09:06:49 +01:00
this.file.db.remove(this.entry);
2015-12-05 14:04:09 +01:00
this.file.reload();
2015-10-27 20:40:34 +01:00
},
2015-11-09 19:15:39 +01:00
deleteFromTrash: function() {
2015-11-19 06:55:45 +01:00
this.file.setModified();
2015-11-09 19:15:39 +01:00
this.file.db.move(this.entry, null);
2015-12-05 14:04:09 +01:00
this.file.reload();
2015-11-09 19:15:39 +01:00
},
2015-10-27 20:40:34 +01:00
removeWithoutHistory: function() {
var ix = this.group.group.entries.indexOf(this.entry);
if (ix >= 0) {
this.group.group.entries.splice(ix, 1);
}
2015-12-05 14:04:09 +01:00
this.file.reload();
2016-04-01 23:09:16 +02:00
},
initOtpGenerator: function() {
var otpUrl;
if (this.fields.otp) {
otpUrl = this.fields.otp;
if (otpUrl.isProtected) {
otpUrl = otpUrl.getText();
}
2016-04-04 20:45:17 +02:00
if (Otp.isSecret(otpUrl)) {
otpUrl = Otp.makeUrl(otpUrl);
} else if (otpUrl.toLowerCase().lastIndexOf('otpauth:', 0) !== 0) {
2016-04-01 23:09:16 +02:00
// KeeOTP plugin format
var args = {};
otpUrl.split('&').forEach(function(part) {
var parts = part.split('=', 2);
args[parts[0]] = decodeURIComponent(parts[1]).replace(/=/g, '');
});
if (args.key) {
2016-04-04 20:45:17 +02:00
otpUrl = Otp.makeUrl(args.key, args.step, args.size);
2016-04-01 23:09:16 +02:00
}
}
2016-04-03 12:44:43 +02:00
} else if (this.entry.fields['TOTP Seed']) {
2016-04-01 23:09:16 +02:00
// TrayTOTP plugin format
2016-04-04 20:45:17 +02:00
var secret = this.entry.fields['TOTP Seed'];
if (secret.isProtected) {
secret = secret.getText();
2016-04-01 23:09:16 +02:00
}
2016-04-04 20:45:17 +02:00
if (secret) {
var settings = this.entry.fields['TOTP Settings'];
if (settings && settings.isProtected) {
settings = settings.getText();
2016-04-01 23:09:16 +02:00
}
2016-04-04 20:45:17 +02:00
var period, digits;
if (settings) {
settings = settings.split(';');
if (settings.length > 0 && settings[0] > 0) {
period = settings[0];
}
if (settings.length > 1 && settings[1] > 0) {
digits = settings[1];
}
2016-04-01 23:09:16 +02:00
}
2016-04-04 20:45:17 +02:00
otpUrl = Otp.makeUrl(secret, period, digits);
this.fields.otp = kdbxweb.ProtectedValue.fromString(otpUrl);
2016-04-01 23:09:16 +02:00
}
}
if (otpUrl) {
2016-04-02 15:20:48 +02:00
if (this.otpGenerator && this.otpGenerator.url === otpUrl) {
return;
}
2016-04-01 23:09:16 +02:00
try {
this.otpGenerator = Otp.parseUrl(otpUrl);
} catch (e) {
this.otpGenerator = null;
}
2016-04-02 15:20:48 +02:00
} else {
this.otpGenerator = null;
2016-04-01 23:09:16 +02:00
}
},
setOtp: function(otp) {
this.otpGenerator = otp;
2016-04-02 15:20:48 +02:00
this.setOtpUrl(otp.url);
},
setOtpUrl: function(url) {
2016-04-04 20:45:17 +02:00
this.setField('otp', url ? kdbxweb.ProtectedValue.fromString(url) : undefined);
2016-04-03 12:54:51 +02:00
delete this.entry.fields['TOTP Seed'];
delete this.entry.fields['TOTP Settings'];
2016-04-23 16:50:40 +02:00
},
getEffectiveEnableAutoType: function() {
if (typeof this.entry.autoType.enabled === 'boolean') {
return this.entry.autoType.enabled;
}
return this.group.getEffectiveEnableAutoType();
},
2016-04-23 17:05:33 +02:00
getEffectiveAutoTypeSeq: function() {
return this.entry.autoType.defaultSequence || this.group.getEffectiveAutoTypeSeq();
},
2016-04-23 16:50:40 +02:00
setEnableAutoType: function(enabled) {
this._entryModified();
if (enabled === this.group.getEffectiveEnableAutoType()) {
enabled = null;
}
this.entry.autoType.enabled = enabled;
this._buildAutoType();
},
setAutoTypeObfuscation: function(enabled) {
this._entryModified();
this.entry.autoType.obfuscation =
enabled ? kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard : kdbxweb.Consts.AutoTypeObfuscationOptions.None;
this._buildAutoType();
},
setAutoTypeSeq: function(seq) {
this._entryModified();
this.entry.autoType.defaultSequence = seq || undefined;
this._buildAutoType();
2015-10-17 23:49:24 +02:00
}
});
EntryModel.fromEntry = function(entry, group, file) {
var model = new EntryModel();
model.setEntry(entry, group, file);
return model;
};
EntryModel.newEntry = function(group, file) {
var model = new EntryModel();
var entry = file.db.createEntry(group.group);
model.setEntry(entry, group, file);
model.entry.times.update();
model.unsaved = true;
2015-10-27 22:07:48 +01:00
model.isJustCreated = true;
2015-10-27 20:40:34 +01:00
group.addEntry(model);
2015-10-17 23:49:24 +02:00
file.setModified();
return model;
};
module.exports = EntryModel;