UNPKG

yunkong2.js-controller

Version:

Updated by reinstall.js on 2018-06-11T15:19:56.688Z

1,237 lines (1,110 loc) 76.2 kB
/** * Object DB in REDIS - Client * * Copyright 2014-2018 bluefox <dogafox@gmail.com> * * MIT License * */ /* jshint -W097 */ /* jshint strict:false */ /* jslint node: true */ /* jshint -W061 */ 'use strict'; const extend = require('node.extend'); const tools = require(__dirname + '/../tools'); const redis = require('redis'); const stream = require('stream'); const util = require('util'); const Writable = stream.Writable; let memStore = {}; /* Writable memory stream */ function WMStrm(key, options) { // allow use without new operator if (!(this instanceof WMStrm)) return new WMStrm(key, options); Writable.call(this, options); // init super this.key = key; // save key memStore[key] = new Buffer(''); // empty } util.inherits(WMStrm, Writable); WMStrm.prototype._write = function (chunk, enc, cb) { if (chunk) { // our memory store stores things in buffers let buffer = (Buffer.isBuffer(chunk)) ? chunk : // already is Buffer use it new Buffer(chunk, enc); // string, convert // concatenate to the buffer already there if (!memStore[this.key]) { memStore[this.key] = new Buffer(''); console.log('memstore for ' + this.key + ' is null'); } memStore[this.key] = Buffer.concat([memStore[this.key], buffer]); } if (!cb) throw 'Callback is empty'; cb(); }; function ObjectsInRedis(settings) { settings = settings || {}; const redisNamespace = (settings.redisNamespace || 'config') + '.'; const ioRegExp = new RegExp('^' + redisNamespace); const onChange = settings.change; // on change handler const redisNamespaceL = redisNamespace.length; let client; let sub; let that = this; let users = {}; let groups = {}; let fileOptions = {}; let preserveSettings = []; let regUser = /^system\.user/; let regGroup = /^system\.group/; let defaultAcl = { groups: [], acl: { file: { list: false, read: false, write: false, create: false, 'delete': false }, object: { list: false, read: false, write: false, 'delete': false } } }; let defaultNewAcl = settings.defaultNewAcl || null; let log = settings.logger; if (!log) { log = { silly: function (msg) {/*console.log(msg);*/}, debug: function (msg) {/*console.log(msg);*/}, info: function (msg) {/*console.log(msg);*/}, warn: function (msg) { console.log(msg); }, error: function (msg) { console.log(msg); } }; } else if (!log.silly) { log.silly = log.debug; } // -------------- FILE FUNCTIONS ------------------------------------------- function checkFile(id, name, options, flag) { // read file settings from redis if (typeof fileOptions[id][name].acl !== 'object') { fileOptions[id][name] = { mimeType: fileOptions[id][name], acl: { owner: (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin', ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator', permissions: (defaultNewAcl && defaultNewAcl.file) || 0x644 // '0644' } }; } // Set default owner group fileOptions[id][name].acl.ownerGroup = fileOptions[id][name].acl.ownerGroup || (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator'; fileOptions[id][name].acl.owner = fileOptions[id][name].acl.owner || (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin'; fileOptions[id][name].acl.permissions = fileOptions[id][name].acl.permissions || (defaultNewAcl && defaultNewAcl.file) || 0x644; // '0644' if (options.user !== 'system.user.admin' && options.groups.indexOf('system.group.administrator') === -1 && fileOptions[id][name].acl) { if (fileOptions[id][name].acl.owner !== options.user) { // Check if the user is in the group if (options.groups.indexOf(fileOptions[id][name].acl.ownerGroup) !== -1) { // Check group rights if (!(fileOptions[id][name].acl.permissions & (flag << 4))) { return false; } } else { // everybody if (!(fileOptions[id][name].acl.permissions & flag)) { return false; } } } else { // Check user rights if (!(fileOptions[id][name].acl.permissions & (flag << 8))) { return false; } } } return true; } function checkFileRights(id, name, options, flag, callback) { options = options || {}; if (!options.user) { // Before files converted, lets think: if no options it is admin options = { user: 'system.user.admin', params: options, group: 'system.group.administrator' }; } if (options.checked) { return callback(null, options); } if (!options.acl) { that.getUserGroup(options.user, function (user, groups, acl) { options.acl = acl || {}; options.groups = groups; options.group = groups ? groups[0] : null; checkFileRights(id, name, options, flag, callback); }); return; } // If user may write if (flag === 2 && !options.acl.file.write) {// write return callback('permissionError', options); } // If user may read if (flag === 4 && !options.acl.file.read) {// read return callback('permissionError', options); } // read rights of file if (!fileOptions[id]) { // Redis actually does not support the file rights for users /*if (fs.existsSync(objectsDir + id + '/_data.json')) { try { fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'utf8')); } catch (e) { log.error('Cannot parse ' + objectsDir + id + '/_data.json: ' + e); } } else*/ { fileOptions[id] = {}; } } options.checked = true; if (!name || !fileOptions[id] || !fileOptions[id][name]) { return callback(null, options); } if (checkFile(id, name, options,flag)) { return callback(null, options); } else { return callback('permissionError', options); } /*if (typeof fileOptions[id][name].acl != 'object') { fileOptions[id][name] = { mimeType: fileOptions[id][name], acl: { owner: 'system.user.admin', permissions: 0x644, ownerGroup: 'system.group.administrator' } }; } // Set default onwer group fileOptions[id][name].acl.ownerGroup = fileOptions[id][name].acl.ownerGroup || 'system.group.administrator'; if (options.user != 'system.user.admin' && options.groups.indexOf('system.group.administrator') == -1 && fileOptions[id][name].acl) { if (fileOptions[id][name].acl.owner != options.user) { // Check if the user is in the group if (options.groups.indexOf(fileOptions[id][name].acl.ownerGroup) != -1) { // Check group rights if (!(fileOptions[id][name].acl.permissions & (flag << 4))) { return callback('permissionError', options); } } else { // everybody if (!(fileOptions[id][name].acl.permissions & flag)) { return callback('permissionError', options); } } } else { // Check user rights if (!(fileOptions[id][name].acl.permissions & (flag << 8))) { return callback('permissionError', options); } } } return callback(null, options);*/ } function setDefaultAcl(callback) { try { defaultNewAcl = Object.assign({}, objects['system.config'].common.defaultNewAcl); } catch (e) { defaultNewAcl = { owner: 'system.user.admin', ownerGroup: 'system.group.administrator', object: 0x664, state: 0x664, file: 0x664 }; objects['system.config'].common.defaultNewAcl = Object.assign({}, defaultNewAcl); } let count = 0; // Set all objects without ACL to this one // todo /* for (let id in objects) { if (objects.hasOwnProperty(id) && objects[id] && !objects[id].acl) { objects[id].acl = Object.assign({}, defaultNewAcl); delete objects[id].acl.file; if (objects[id].type !== 'state') { delete objects[id].acl.state; } count++; } } */ if (typeof callback === 'function') callback(null, count); } this.getUserGroup = function (user, callback) { if (!user || typeof user !== 'string' || !user.match(/^system\.user\./)) { console.log('invalid user name: ' + user); user = JSON.stringify(user); return callback.call(that, user, [], Object.assign({}, defaultAcl.acl)); } if (users[user]) { return callback.call(that, user, users[user].groups, users[user].acl); } // Read all groups this.getObjectList({startkey: 'system.group.', endkey: 'system.group.\u9999'}, {checked: true}, function (err, arr) { if (err) log.error(err); groups = []; if (arr) { // Read all groups for (let g = 0; g < arr.rows.length; g++) { groups[g] = arr.rows[g].value; if (groups[g]._id === 'system.group.administrator') { groups[g].common.acl = { file: { list: true, read: true, write: true, create: true, 'delete': true }, object: { list: true, read: true, write: true, create: true, 'delete': true }, users: { list: true, read: true, write: true, create: true, 'delete': true } }; } } } that.getObjectList({startkey: 'system.user.', endkey: 'system.user.\u9999'}, {checked: true}, function (err, arr) { if (err) log.error(err); users = {}; if (arr) { for (let i = 0; i < arr.rows.length; i++) { users[arr.rows[i].value._id] = Object.assign({}, defaultAcl); if (arr.rows[i].value._id === 'system.user.admin') { users['system.user.admin'].acl.file = { list: true, read: true, write: true, create: true, 'delete': true }; users['system.user.admin'].acl.object = { create: true, list: true, read: true, write: true, 'delete': true }; users['system.user.admin'].acl.users = { create: true, list: true, read: true, write: true, 'delete': true }; } } } for (let g = 0; g < groups.length; g++) { if (!groups[g].common.members) continue; for (let m = 0; m < groups[g].common.members.length; m++) { let u = groups[g].common.members[m]; users[u].groups.push(groups[g]._id); if (groups[g].common.acl && groups[g].common.acl.file) { if (!users[u].acl || !users[u].acl.file) { users[u].acl = users[u].acl || {}; users[u].acl.file = users[u].acl.file || {}; users[u].acl.file.create = groups[g].common.acl.file.create; users[u].acl.file.read = groups[g].common.acl.file.read; users[u].acl.file.write = groups[g].common.acl.file.write; users[u].acl.file['delete'] = groups[g].common.acl.file['delete']; users[u].acl.file.list = groups[g].common.acl.file.list; } else { users[u].acl.file.create = users[u].acl.file.create || groups[g].common.acl.file.create; users[u].acl.file.read = users[u].acl.file.read || groups[g].common.acl.file.read; users[u].acl.file.write = users[u].acl.file.write || groups[g].common.acl.file.write; users[u].acl.file['delete'] = users[u].acl.file['delete'] || groups[g].common.acl.file['delete']; users[u].acl.file.list = users[u].acl.file.list || groups[g].common.acl.file.list; } } if (groups[g].common.acl && groups[g].common.acl.object) { if (!users[u].acl || !users[u].acl.object) { users[u].acl = users[u].acl || {}; users[u].acl.object = users[u].acl.object || {}; users[u].acl.object.create = groups[g].common.acl.object.create; users[u].acl.object.read = groups[g].common.acl.object.read; users[u].acl.object.write = groups[g].common.acl.object.write; users[u].acl.object['delete'] = groups[g].common.acl.object['delete']; users[u].acl.object.list = groups[g].common.acl.object.list; } else { users[u].acl.object.create = users[u].acl.object.create || groups[g].common.acl.object.create; users[u].acl.object.read = users[u].acl.object.read || groups[g].common.acl.object.read; users[u].acl.object.write = users[u].acl.object.write || groups[g].common.acl.object.write; users[u].acl.object['delete'] = users[u].acl.object['delete'] || groups[g].common.acl.object['delete']; users[u].acl.object.list = users[u].acl.object.list || groups[g].common.acl.object.list; } } if (groups[g].common.acl && groups[g].common.acl.users) { if (!users[u].acl || !users[u].acl.users) { users[u].acl = users[u].acl || {}; users[u].acl.users = users[u].acl.users || {}; users[u].acl.users.create = groups[g].common.acl.users.create; users[u].acl.users.read = groups[g].common.acl.users.read; users[u].acl.users.write = groups[g].common.acl.users.write; users[u].acl.users['delete'] = groups[g].common.acl.users['delete']; users[u].acl.users.list = groups[g].common.acl.users.list; } else { users[u].acl.users.create = users[u].acl.users.create || groups[g].common.acl.users.create; users[u].acl.users.read = users[u].acl.users.read || groups[g].common.acl.users.read; users[u].acl.users.write = users[u].acl.users.write || groups[g].common.acl.users.write; users[u].acl.users['delete'] = users[u].acl.users['delete'] || groups[g].common.acl.users['delete']; users[u].acl.users.list = users[u].acl.users.list || groups[g].common.acl.users.list; } } } } callback.call(that, user, users[user] ? users[user].groups : [], users[user] ? users[user].acl : Object.assign({}, defaultAcl.acl)); }); }); }; const mimeTypes = { '.css': {type: 'text/css', binary: false}, '.bmp': {type: 'image/bmp', binary: true}, '.png': {type: 'image/png', binary: true}, '.jpg': {type: 'image/jpeg', binary: true}, '.jpeg': {type: 'image/jpeg', binary: true}, '.gif': {type: 'image/gif', binary: true}, '.ico': {type: 'image/x-icon', binary: true}, '.webp': {type: 'image/webp', binary: true}, '.wbmp': {type: 'image/vnd.wap.wbmp', binary: true}, '.tif': {type: 'image/tiff', binary: true}, '.js': {type: 'application/javascript', binary: false}, '.html': {type: 'text/html', binary: false}, '.htm': {type: 'text/html', binary: false}, '.json': {type: 'application/json', binary: false}, '.md': {type: 'text/markdown', binary: false}, '.xml': {type: 'text/xml', binary: false}, '.svg': {type: 'image/svg+xml', binary: false}, '.eot': {type: 'application/vnd.ms-fontobject', binary: true}, '.ttf': {type: 'application/font-sfnt', binary: true}, '.cur': {type: 'application/x-win-bitmap', binary: true}, '.woff': {type: 'application/font-woff', binary: true}, '.wav': {type: 'audio/wav', binary: true}, '.mp3': {type: 'audio/mpeg3', binary: true}, '.avi': {type: 'video/avi', binary: true}, '.qt': {type: 'video/quicktime', binary: true}, '.ppt': {type: 'application/vnd.ms-powerpoint', binary: true}, '.pptx': {type: 'application/vnd.ms-powerpoint', binary: true}, '.doc': {type: 'application/msword', binary: true}, '.docx': {type: 'application/msword', binary: true}, '.xls': {type: 'application/vnd.ms-excel', binary: true}, '.xlsx': {type: 'application/vnd.ms-excel', binary: true}, '.mp4': {type: 'video/mp4', binary: true}, '.mkv': {type: 'video/mkv', binary: true}, '.zip': {type: 'application/zip', binary: true}, '.ogg': {type: 'audio/ogg', binary: true}, '.manifest':{type: 'text/cache-manifest', binary: false}, '.pdf': {type: 'application/pdf', binary: true}, '.gz': {type: 'application/gzip', binary: true}, '.gzip': {type: 'application/gzip', binary: true}, }; this.getMimeType = function (ext) { if (ext instanceof Array) ext = ext[0]; ext = ext.toLowerCase(); let _mimeType = 'text/javascript'; let isBinary = false; if (mimeTypes[ext]) { _mimeType = mimeTypes[ext].type; isBinary = mimeTypes[ext].binary; } else { _mimeType = 'text/javascript'; } return {mimeType: _mimeType, isBinary: isBinary}; }; this.insert = function (id, attName, ignore, options, obj, callback) { if (typeof options === 'string') { options = {mimeType: options}; } //return pipe for write into redis let strm = new WMStrm(id + '/' + attName); strm.on('finish', function () { if (!memStore[id + '/' + attName]) log.error('File ' + id + ' / ' + attName + ' is empty'); that.writeFile(id, attName, memStore[id + '/' + attName] || '', options, function () { if (memStore[id + '/' + attName] !== undefined) delete memStore[id + '/' + attName]; if (callback) setImmediate(callback, null, null); }); }); return strm; }; this.writeFile = function (id, name, data, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (typeof options === 'string') { options = {mimeType: options}; } if (name[0] === '/') name = name.substring(1); // If file yet exists => check the permissions if (!options || !options.checked) { return checkFileRights(id, name, options, 0x2/*write*/, function (err, options) { if (err) { if (typeof callback === 'function') callback(err); } else { return that.writeFile(id, name, data, options, callback); } }); } let isBinary; let ext = name.match(/\.[^.]+$/); let mime = that.getMimeType(ext); let _mimeType = mime.mimeType; isBinary = mime.isBinary; if (!fileOptions[id][name]) { fileOptions[id][name] = {createdAt: (new Date()).getTime()}; } if (!fileOptions[id][name].acl) { fileOptions[id][name].acl = { owner: options.user || (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin', ownerGroup: options.group || (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator', permissions: options.mode || (defaultNewAcl && defaultNewAcl.file) || 0x644 }; } fileOptions[id][name].mimeType = options.mimeType || _mimeType; fileOptions[id][name].binary = isBinary; fileOptions[id][name].acl.ownerGroup = fileOptions[id][name].acl.ownerGroup || (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator'; fileOptions[id][name].modifiedAt = (new Date()).getTime(); states.getConfig(id, function (err, _obj) { if (_obj) { _obj._attachments = _obj._attachments || {}; if (!_obj._attachments[name]) { _obj._attachments[name] = fileOptions[id][name]; } states.setConfig(id, _obj, function () { states.setBinaryState('_files/' + id + '/' + name, data, callback); }); } else { states.setBinaryState('_files/' + id + '/' + name, data, callback); } }); }; this.readFile = function (id, name, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (name[0] === '/') name = name.substring(1); if (!options || !options.checked) { checkFileRights(id, name, options, 0x4/*read*/, function (err, options) { if (err) { if (typeof callback === 'function') callback(err); } else { return that.readFile(id, name, options, callback); } }); return; } states.getConfig(id, function (err, _obj) { states.getBinaryState('_files/' + id + '/' + name, function (err, buffer) { if (typeof callback === 'function') { let mimeType; if (_obj && _obj._attachments && _obj._attachments.mimeType) { mimeType = _obj._attachments.mimeType; if (!_obj._attachments.binary) { buffer = buffer.toString(); } } else { let ext = name.match(/\.[^.]+$/); let mime = that.getMimeType(ext); mimeType = mime.mimeType; } callback(err, buffer, mimeType); } }); }); }; this.unlink = function (id, name, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (name[0] === '/') name = name.substring(1); if (!options || !options.checked) { checkFileRights(id, name, options, 0x2/*write*/, function (err, options) { if (err) { if (typeof callback === 'function') callback(err); } else { if (!options.acl.file['delete']) { if (typeof callback === 'function') callback('permissionError'); } else { return that.unlink(id, name, options, callback); } } }); return; } states.getConfig(id, function (err, _obj) { if (_obj && _obj._attachments) { if (_obj._attachments[name]) { delete _obj._attachments[name]; } states.setConfig(id, _obj); } states.delBinaryState('_files/' + id + '/' + name, callback); }); }; this.readDir = function (id, name, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (!options || !options.checked) { checkFileRights(id, name, options, 0x4/*read*/, function (err, options) { if (err) { if (typeof callback === 'function') callback(err); } else { if (!options.acl.file.list) { if (typeof callback === 'function') callback('permissionError'); } else { return that.readDir(id, name, options, callback); } } }); return; } // TODO if (typeof callback === 'function') { setImmediate(function () { callback('not implemented'); }); } }; this.rename = function (id, oldName, newName, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (oldName[0] === '/') oldName = oldName.substring(1); if (newName[0] === '/') newName = newName.substring(1); if (!options || !options.checked) { checkFileRights(id, oldName, options, 0x2/*write*/, function (err, options) { if (err) { if (typeof callback === 'function') callback(err); } else { if (!options.acl.file.write) { if (typeof callback === 'function') callback('permissionError'); } else { return that.rename(id, oldName, newName, options, callback); } } }); return; } // TODO if (typeof callback === 'function') callback('not implemented'); }; this.touch = function (id, name, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (!options || !options.checked) { checkFileRights(id, null, options, 0x2/*write*/, function (err, options) { if (err) { if (typeof callback === 'function') callback(err); } else { return that.touch(id, name, options, callback); } }); return; } // TODO if (typeof callback === 'function') callback('not implemented'); }; this.rm = function (id, name, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (!options || !options.checked) { checkFileRights(id, null, options, 0x2/*write*/, function (err, options) { if (err) { if (typeof callback === 'function') callback(err); } else { if (!options.acl.file['delete']) { if (typeof callback === 'function') callback('permissionError'); } else { return that.rm(id, name, options, callback); } } }); return; } // TODO if (typeof callback === 'function') callback('not implemented'); }; this.mkdir = function (id, dirname, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (dirname[0] === '/') dirname = dirname.substring(1); if (!options || !options.checked) { checkFileRights(id, dirname, options, 0x2/*write*/, function (err, options) { if (err) { if (typeof callback === 'function') callback(err); } else { if (!options.acl.file.write) { if (typeof callback === 'function') callback('permissionError'); } else { return that.mkdir(id, dirname, options, callback); } } }); return; } // TODO if (typeof callback === 'function') callback('not implemented'); }; this.chownFile = function (id, name, options, callback) { if (typeof options === 'function') { callback = options; options = null; } options = options || {}; if (typeof options !== 'object') { options = {owner: options}; } if (name[0] === '/') name = name.substring(1); if (!options.ownerGroup && options.group) options.ownerGroup = options.group; if (!options.owner && options.user) options.owner = options.user; if (!options.owner) { log.error('user is not defined'); if (typeof callback === 'function') callback('invalid parameter'); return; } if (!options.ownerGroup) { // get user group this.getUserGroup(options.owner, function (user, groups /* , permissions */) { if (!groups || !groups[0]) { if (typeof callback === 'function') callback('user "' + options.owner + '" belongs to no group'); return; } else { options.ownerGroup = groups[0]; } that.chownFile(id, name, options, callback); }); return; } if (!options.checked) { checkFileRights(id, null, options, 0x2/*write*/, function (err, options) { if (err) { if (typeof callback === 'function') callback(err); } else { if (!options.acl.file.write) { if (typeof callback === 'function') callback('permissionError'); } else { return that.chownFile(id, name, options, callback); } } }); return; } // TODO if (typeof callback === 'function') callback('not implemented'); }; this.chmodFile = function (id, name, options, callback) { if (typeof options === 'function') { callback = options; options = null; } options = options || {}; if (name[0] === '/') name = name.substring(1); if (typeof options !== 'object') { options = {mode: options}; } if (options.mode === undefined) { log.error('mode is not defined'); if (typeof callback === 'function') callback('invalid parameter'); return; } else if (typeof options.mode === 'string') { options.mode = parseInt(options.mode, 16); } if (!options.checked) { checkFileRights(id, null, options, 0x2/*write*/, function (err, options) { if (err) { if (typeof callback === 'function') callback(err); } else { if (!options.acl.file.write) { if (typeof callback === 'function') callback('permissionError'); } else { return that.chmodFile(id, name, options, callback); } } }); return; } // TODO if (typeof callback === 'function') callback('not implemented'); }; this.enableFileCache = function (enabled, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (!options || !options.checked) { checkObjectRights(options, 0x2/*write*/, function (err, options) { if (err) { if (typeof callback === 'function') callback(err); } else { return this.enableFileCache(enabled, options, callback); } }.bind(this)); return; } if (typeof callback === 'function') { // cache cannot be enabled setImmediate(function () { callback(null, false); }); } }; // -------------- OBJECT FUNCTIONS ------------------------------------------- function checkObject(obj, options, flag) { // read rights of object if (!obj || !obj.common || !obj.acl || flag === 'list') { return true; } if (options.user !== 'system.user.admin' && options.groups && options.groups.indexOf('system.group.administrator') === -1) { if (obj.acl.owner !== options.user) { // Check if the user is in the group if (options.groups.indexOf(obj.acl.ownerGroup) !== -1) { // Check group rights if (!(obj.acl.object & (flag << 4))) { return false; } } else { // everybody if (!(obj.acl.object & flag)) { return false; } } } else { // Check group rights if (!(obj.acl.object & (flag << 8))) { return false; } } } return true; } function checkObjectRights(options, flag, callback) { options = options || {}; if (!options.user) { // Before files converted, lets think: if no options it is admin options = { user: 'system.user.admin', params: options, group: 'system.group.administrator', acl: { object: { read: true, write: true, 'delete': true, create: true, list: true }, file: { read: true, write: true, 'delete': true, create: true, list: true }, /* state: { read: true, write: true, 'delete': true, create: true, list: true },*/ users: { read: true, write: true, create: true, 'delete': true, list: true } } }; } if (options.checked) { return callback(null, options); } if (!options.acl) { that.getUserGroup(options.user, function (user, groups, acl) { options.acl = acl || {}; options.groups = groups; options.group = groups ? groups[0] : null; checkObjectRights(options, flag, callback); }); return; } // if user or group objects if (regUser.test(id) || regGroup.test(id)) { // If user may write if (flag === 2 && !options.acl.users.write) {// write return callback('permissionError', options); } // If user may read if (flag === 4 && !options.acl.users.read) {// read return callback('permissionError', options); } // If user may delete if (flag === 'delete' && !options.acl.users.delete) {// delete return callback('permissionError', options); } // If user may list if (flag === 'list' && !options.acl.users.list) {// list return callback('permissionError', options); } // If user may create if (flag === 'create' && !options.acl.users.create) {// create return callback('permissionError', options); } if (flag === 'delete') flag = 2; // write } // If user may write if (flag === 2 && !options.acl.object.write) {// write return callback('permissionError', options); } // If user may read if (flag === 4 && !options.acl.object.read) {// read return callback('permissionError', options); } // If user may delete if (flag === 'delete' && !options.acl.object.delete) {// delete return callback('permissionError', options); } // If user may list if (flag === 'list' && !options.acl.object.list) {// list return callback('permissionError', options); } if (flag === 'delete') flag = 2; // write options.checked = true; return callback(null, options); } function clone(obj) { if (obj === null || obj === undefined || typeof obj !== 'object') return obj; let temp = obj.constructor(); // changed for (let key in obj) { if (obj.hasOwnProperty(key)) { temp[key] = clone(obj[key]); } } return temp; } this.subscribe = (pattern, options, callback) => { if (!options || !options.checked) { checkObjectRights(options, 'list', (err, options) => { if (err) { if (typeof callback === 'function') callback(err); } else { return this.subscribe(pattern, options, callback); } }); return; } log.silly(settings.namespace + ' redis psubscribe ' + redisNamespace + pattern); sub.psubscribe(redisNamespace + pattern, function (err) { if (typeof callback === 'function') callback(err); }); }; this.unsubscribe = (pattern, options, callback) => { if (!options || !options.checked) { checkObjectRights(options, 'list', (err, options) => { if (err) { if (typeof callback === 'function') callback(err); } else { return this.unsubscribe(pattern, options, callback); } }); return; } log.silly(settings.namespace + ' redis punsubscribe ' + redisNamespace + pattern); sub.punsubscribe(redisNamespace + pattern, function (err) { if (typeof callback === 'function') callback(err); }); }; this.chownObject = function (pattern, options, callback) { options = options || {}; if (typeof options !== 'object') { options = {owner: options}; } // renaming if (!options.ownerGroup && options.group) options.ownerGroup = options.group; if (!options.owner && options.user) options.owner = options.user; if (!options.owner) { log.error('user is not defined'); if (typeof callback === 'function') callback('invalid parameter'); return; } if (!options.ownerGroup) { // get user group this.getUserGroup(options.owner, (user, groups /* , permissions */) => { if (!groups || !groups[0]) { if (typeof callback === 'function') callback('user "' + options.owner + '" belongs to no group'); return; } else { options.ownerGroup = groups[0]; } this.chownObject(pattern, options, callback); }); return; } if (!options.checked) { checkObjectRights(options, 0x2/*write*/, (err, options) => { if (err) { if (typeof callback === 'function') callback(err); } else { if (!options.acl.object || !options.acl.object.write) { if (typeof callback === 'function') callback('permissionError'); } else { return this.chownObject(pattern, options, callback); } } }); } // TODO }; this.chmodObject = function (pattern, options, callback) { options = options || {}; if (typeof options !== 'object') { options = {object: options}; } if (options.mode && !options.object) options.object = options.mode; if (options.object === undefined) { log.error('mode is not defined'); if (typeof callback === 'function') callback('invalid parameter'); return; } else if (typeof options.mode === 'string') { options.mode = parseInt(options.mode, 16); } if (!options.checked) { checkObjectRights(options, 0x2/*write*/, (err, options) => { if (err) { if (typeof callback === 'function') callback(err); } else { if (!options.acl.file.write) { if (typeof callback === 'function') callback('permissionError'); } else { return that.chmodObject(pattern, options, callback); } } }); } // TODO }; this.getObject = function (id, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (!options || !options.checked) { checkObjectRights(options, 0x4/*read*/, (err, options) => { if (err) { if (typeof callback === 'function') callback(err); } else { return this.getObject(id, options, callback); } }); return; } if (typeof callback === 'function') { client.get(redisNamespace + id, function (err, obj) { if (err) { log.warn(settings.namespace + ' redis get ' + id + ', error - ' + err); } else { log.silly(settings.namespace + ' redis get ' + id + ' ok: ' + obj); } try { obj = obj ? JSON.parse(obj) : null; } catch (e) { log.error(`Cannot parse ${id} - ${obj}: ${JSON.stringify(e)}`); } callback(err, obj ? JSON.parse(obj) : null); }); } }; this.getKeys = function (pattern, options, callback, dontModify) { if (typeof options === 'function') { callback = options; options = null; } if (!options || !options.checked) { checkObjectRights(options, 'list', (err, options) => { if (err) { if (typeof callback === 'function') callback(err); } else { return this.getConfigKeys(pattern, options, callback, dontModify); } }); return; } let r = new RegExp(tools.pattern2RegEx(pattern)); client.keys(redisNamespace + pattern, (err, keys) => { log.silly(settings.namespace + ' redis keys ' + obj.length + ' ' + pattern); if (typeof callback === 'function') { let result = []; if (keys) { keys.sort(); if (options.user === 'system.user.admin' || options.group === 'system.group.administrator') { setImmediate(function () { callback(err, keys); }); } else { let result = []; // Check permissions client.mget(keys, function (err, objs) { for (let i = 0; i < keys.length; i++) { if (r.test(keys[i]) && checkObject(objs[i], options, 0x4/*read*/)) { if (!dontModify) { result.push(keys[i].substring(redisNamespaceL)); } else { result.push(keys[i]); } } } setImmediate(function () { callback(err, result); }); }) } } else { setImmediate(function () {