src/support/acl.js
"use strict";
const co = require('co');
const waigo = global.waigo,
_ = waigo._,
logger = waigo.load('support/logger'),
errors = waigo.load('support/errors');
const AclError = exports.AclError = errors.define('AclError');
class ACL {
constructor (App) {
this.App = App;
this.logger = logger.create('ACL');
}
/**
* Initialise ACL
*/
* startup () {
this.logger.info('Initialising');
yield this.reload();
// access to admin content must be protected
if (!this.res.admin) {
this.logger.info('Admin resources rules not found, so creating them now');
yield this.App.models.Acl.insert({
resource: 'admin',
entityType: 'role',
entity: 'admin'
});
yield this.reload();
}
// get notified of ACL updates
this._changeFeedCursor = yield this.App.models.Acl.onChange();
if (this._changeFeedCursor) {
this._changeFeedCursor.each(_.bind(this._onAclUpdated, this));
}
}
* shutdown () {
if (this._changeFeedCursor) {
yield this._changeFeedCursor.close();
this._changeFeedCursor = null;
}
}
/**
* Reload ACL rules from DB.
*/
* reload () {
this.logger.debug('Reloading rules from db');
let data = yield this.App.models.Acl.getAll();
let res = this.res = {},
users = this.users = {},
roles = this.roles = {};
data.forEach(function(doc){
// resource perspective
res[doc.resource] =
res[doc.resource] || {};
res[doc.resource][doc.entityType] =
res[doc.resource][doc.entityType] || {};
res[doc.resource][doc.entityType][doc.entity] = true;
// entity perspsective
let entity = ('user' === doc.entityType ? users : roles);
entity[doc.entity] = entity[doc.entity] || {};
entity[doc.entity][doc.resource] = true;
});
}
/**
* Callback for collection watcher.
*/
_onAclUpdated () {
this.logger.info('Detected ACL rules change...reloading');
co(this.reload())
.catch((err) => {
this.logger.error('Error reloading ACL', err.stack);
});
}
/**
* Get whether given user can access given resource.
* @param {String} resource Resource name.
* @param {Object} user User object.
* @return {Boolean} true if allowed; false otherwise.
*/
can (resource, user) {
this.logger.debug('can', resource, user.id);
// if resource name is "public" then everyone has access
if ('public' === resource) {
return true;
}
// if user is admin it's ok
if (user.isOneOf('admin')) {
return true;
}
// if no entry for resource then everyone has access
if (!_.get(this.res, resource)) {
return false;
}
// if user has access it's ok
if (_.get(this.users, user.id + '.' + resource)) {
return true;
}
// if one of user's roles has access it's ok
let roles = user.roles || [];
for (let role of roles) {
if (_.get(this.roles, role + '.' + resource)) {
return true;
}
}
return false;
}
/**
* Assert that given user can access given resource.
* @param {String} resource Resource name.
* @param {Object} user User object.
* @throws AclError if access disallowed.
*/
assert (resource, user) {
this.logger.debug('assert', resource, user.id);
if (!this.can(resource, user)) {
throw new AclError(`User ${user.id} does not have permission to access: ${resource}`, 403);
}
}
}
exports.ACL = ACL;
/**
* Initialise ACL
*
* @param {App} App The App instance.
*/
exports.init = function*(App) {
var a = new ACL(App);
yield a.startup();
return a;
};