2013-06-25 15:43:15 +04:00
|
|
|
// canThis(someUser).edit.posts([id]|[[ids]])
|
|
|
|
// canThis(someUser).edit.post(somePost|somePostId)
|
|
|
|
|
2017-09-25 12:17:06 +03:00
|
|
|
var _ = require('lodash'),
|
|
|
|
Promise = require('bluebird'),
|
|
|
|
models = require('../models'),
|
|
|
|
errors = require('../errors'),
|
|
|
|
i18n = require('../i18n'),
|
|
|
|
effectivePerms = require('./effective'),
|
|
|
|
parseContext = require('./parse-context'),
|
2013-06-25 15:43:15 +04:00
|
|
|
init,
|
|
|
|
canThis,
|
|
|
|
CanThisResult,
|
|
|
|
exported;
|
|
|
|
|
2013-08-16 03:22:08 +04:00
|
|
|
function hasActionsMap() {
|
|
|
|
// Just need to find one key in the actionsMap
|
|
|
|
|
2016-06-11 21:23:27 +03:00
|
|
|
return _.some(exported.actionsMap, function (val, key) {
|
2013-10-31 22:02:34 +04:00
|
|
|
/*jslint unparam:true*/
|
2014-05-08 06:58:01 +04:00
|
|
|
return Object.hasOwnProperty.call(exported.actionsMap, key);
|
2013-08-16 03:22:08 +04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2013-06-25 15:43:15 +04:00
|
|
|
// Base class for canThis call results
|
|
|
|
CanThisResult = function () {
|
2014-02-12 07:40:39 +04:00
|
|
|
return;
|
2013-06-25 15:43:15 +04:00
|
|
|
};
|
|
|
|
|
2014-09-10 08:06:24 +04:00
|
|
|
CanThisResult.prototype.buildObjectTypeHandlers = function (objTypes, actType, context, permissionLoad) {
|
2014-09-22 00:54:32 +04:00
|
|
|
var objectTypeModelMap = {
|
2017-09-25 12:17:06 +03:00
|
|
|
post: models.Post,
|
|
|
|
role: models.Role,
|
|
|
|
user: models.User,
|
|
|
|
permission: models.Permission,
|
|
|
|
setting: models.Settings,
|
|
|
|
subscriber: models.Subscriber,
|
|
|
|
invite: models.Invite
|
2014-09-22 00:54:32 +04:00
|
|
|
};
|
2016-04-29 13:58:40 +03:00
|
|
|
|
2013-06-25 15:43:15 +04:00
|
|
|
// Iterate through the object types, i.e. ['post', 'tag', 'user']
|
2014-09-10 08:06:24 +04:00
|
|
|
return _.reduce(objTypes, function (objTypeHandlers, objType) {
|
2014-02-12 07:40:39 +04:00
|
|
|
// Grab the TargetModel through the objectTypeModelMap
|
2014-09-10 08:06:24 +04:00
|
|
|
var TargetModel = objectTypeModelMap[objType];
|
2013-06-25 15:43:15 +04:00
|
|
|
|
|
|
|
// Create the 'handler' for the object type;
|
|
|
|
// the '.post()' in canThis(user).edit.post()
|
2014-09-10 08:06:24 +04:00
|
|
|
objTypeHandlers[objType] = function (modelOrId) {
|
2013-06-25 15:43:15 +04:00
|
|
|
var modelId;
|
|
|
|
|
2014-03-21 03:48:06 +04:00
|
|
|
// If it's an internal request, resolve immediately
|
|
|
|
if (context.internal) {
|
2014-08-17 10:17:23 +04:00
|
|
|
return Promise.resolve();
|
2014-03-21 03:48:06 +04:00
|
|
|
}
|
|
|
|
|
2013-06-25 15:43:15 +04:00
|
|
|
if (_.isNumber(modelOrId) || _.isString(modelOrId)) {
|
|
|
|
// It's an id already, do nothing
|
|
|
|
modelId = modelOrId;
|
|
|
|
} else if (modelOrId) {
|
|
|
|
// It's a model, get the id
|
|
|
|
modelId = modelOrId.id;
|
|
|
|
}
|
|
|
|
// Wait for the user loading to finish
|
2014-02-12 07:40:39 +04:00
|
|
|
return permissionLoad.then(function (loadedPermissions) {
|
2013-06-25 15:43:15 +04:00
|
|
|
// Iterate through the user permissions looking for an affirmation
|
2014-07-23 22:17:29 +04:00
|
|
|
var userPermissions = loadedPermissions.user ? loadedPermissions.user.permissions : null,
|
|
|
|
appPermissions = loadedPermissions.app ? loadedPermissions.app.permissions : null,
|
2014-02-12 07:40:39 +04:00
|
|
|
hasUserPermission,
|
|
|
|
hasAppPermission,
|
|
|
|
checkPermission = function (perm) {
|
|
|
|
var permObjId;
|
|
|
|
|
|
|
|
// Look for a matching action type and object type first
|
2014-09-10 08:06:24 +04:00
|
|
|
if (perm.get('action_type') !== actType || perm.get('object_type') !== objType) {
|
2014-02-12 07:40:39 +04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Grab the object id (if specified, could be null)
|
|
|
|
permObjId = perm.get('object_id');
|
|
|
|
|
|
|
|
// If we didn't specify a model (any thing)
|
|
|
|
// or the permission didn't have an id scope set
|
|
|
|
// then the "thing" has permission
|
|
|
|
if (!modelId || !permObjId) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, check if the id's match
|
|
|
|
// TODO: String vs Int comparison possibility here?
|
|
|
|
return modelId === permObjId;
|
|
|
|
};
|
2014-07-23 22:17:29 +04:00
|
|
|
|
2016-06-11 21:23:27 +03:00
|
|
|
if (loadedPermissions.user && _.some(loadedPermissions.user.roles, {name: 'Owner'})) {
|
2014-07-23 22:17:29 +04:00
|
|
|
hasUserPermission = true;
|
|
|
|
} else if (!_.isEmpty(userPermissions)) {
|
2016-06-11 21:23:27 +03:00
|
|
|
hasUserPermission = _.some(userPermissions, checkPermission);
|
2014-02-12 07:40:39 +04:00
|
|
|
}
|
2013-06-09 03:39:24 +04:00
|
|
|
|
2014-02-12 07:40:39 +04:00
|
|
|
// Check app permissions if they were passed
|
|
|
|
hasAppPermission = true;
|
|
|
|
if (!_.isNull(appPermissions)) {
|
2016-06-11 21:23:27 +03:00
|
|
|
hasAppPermission = _.some(appPermissions, checkPermission);
|
2014-02-12 07:40:39 +04:00
|
|
|
}
|
2013-06-05 07:47:11 +04:00
|
|
|
|
2014-05-14 05:49:07 +04:00
|
|
|
// Offer a chance for the TargetModel to override the results
|
2014-07-23 22:17:29 +04:00
|
|
|
if (TargetModel && _.isFunction(TargetModel.permissible)) {
|
|
|
|
return TargetModel.permissible(
|
2014-09-10 08:06:24 +04:00
|
|
|
modelId, actType, context, loadedPermissions, hasUserPermission, hasAppPermission
|
2014-07-23 22:17:29 +04:00
|
|
|
);
|
2013-06-05 07:47:11 +04:00
|
|
|
}
|
|
|
|
|
2014-05-14 05:49:07 +04:00
|
|
|
if (hasUserPermission && hasAppPermission) {
|
2014-08-17 10:17:23 +04:00
|
|
|
return;
|
2014-05-14 05:49:07 +04:00
|
|
|
}
|
2014-08-17 10:17:23 +04:00
|
|
|
|
2016-10-06 15:27:35 +03:00
|
|
|
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.permissions.noPermissionToAction')}));
|
2013-06-05 07:47:11 +04:00
|
|
|
});
|
2013-06-25 15:43:15 +04:00
|
|
|
};
|
|
|
|
|
2014-09-10 08:06:24 +04:00
|
|
|
return objTypeHandlers;
|
2014-02-12 07:40:39 +04:00
|
|
|
}, {});
|
2013-06-25 15:43:15 +04:00
|
|
|
};
|
|
|
|
|
2014-02-12 07:40:39 +04:00
|
|
|
CanThisResult.prototype.beginCheck = function (context) {
|
2013-06-25 15:43:15 +04:00
|
|
|
var self = this,
|
2014-02-12 07:40:39 +04:00
|
|
|
userPermissionLoad,
|
|
|
|
appPermissionLoad,
|
|
|
|
permissionsLoad;
|
|
|
|
|
|
|
|
// Get context.user and context.app
|
|
|
|
context = parseContext(context);
|
2013-06-25 15:43:15 +04:00
|
|
|
|
2013-08-16 03:22:08 +04:00
|
|
|
if (!hasActionsMap()) {
|
2015-11-12 15:29:45 +03:00
|
|
|
throw new Error(i18n.t('errors.permissions.noActionsMapFound.error'));
|
2013-08-16 03:22:08 +04:00
|
|
|
}
|
|
|
|
|
2014-03-21 03:48:06 +04:00
|
|
|
// Kick off loading of effective user permissions if necessary
|
|
|
|
if (context.user) {
|
|
|
|
userPermissionLoad = effectivePerms.user(context.user);
|
|
|
|
} else {
|
|
|
|
// Resolve null if no context.user to prevent db call
|
2014-08-17 10:17:23 +04:00
|
|
|
userPermissionLoad = Promise.resolve(null);
|
2014-03-21 03:48:06 +04:00
|
|
|
}
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
|
2014-02-12 07:40:39 +04:00
|
|
|
// Kick off loading of effective app permissions if necessary
|
|
|
|
if (context.app) {
|
|
|
|
appPermissionLoad = effectivePerms.app(context.app);
|
|
|
|
} else {
|
|
|
|
// Resolve null if no context.app
|
2014-08-17 10:17:23 +04:00
|
|
|
appPermissionLoad = Promise.resolve(null);
|
2014-02-12 07:40:39 +04:00
|
|
|
}
|
2013-06-25 15:43:15 +04:00
|
|
|
|
2014-03-21 03:48:06 +04:00
|
|
|
// Wait for both user and app permissions to load
|
2014-08-17 10:17:23 +04:00
|
|
|
permissionsLoad = Promise.all([userPermissionLoad, appPermissionLoad]).then(function (result) {
|
2014-02-12 07:40:39 +04:00
|
|
|
return {
|
|
|
|
user: result[0],
|
|
|
|
app: result[1]
|
|
|
|
};
|
|
|
|
});
|
2013-06-25 15:43:15 +04:00
|
|
|
|
|
|
|
// Iterate through the actions and their related object types
|
2014-09-10 08:06:24 +04:00
|
|
|
_.each(exported.actionsMap, function (objTypes, actType) {
|
2013-06-25 15:43:15 +04:00
|
|
|
// Build up the object type handlers;
|
|
|
|
// the '.post()' parts in canThis(user).edit.post()
|
2014-09-10 08:06:24 +04:00
|
|
|
var objTypeHandlers = self.buildObjectTypeHandlers(objTypes, actType, context, permissionsLoad);
|
2013-06-25 15:43:15 +04:00
|
|
|
|
|
|
|
// Define a property for the action on the result;
|
|
|
|
// the '.edit' in canThis(user).edit.post()
|
2014-09-10 08:06:24 +04:00
|
|
|
Object.defineProperty(self, actType, {
|
2013-06-25 15:43:15 +04:00
|
|
|
writable: false,
|
|
|
|
enumerable: false,
|
|
|
|
configurable: false,
|
2014-09-10 08:06:24 +04:00
|
|
|
value: objTypeHandlers
|
2013-06-25 15:43:15 +04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Return this for chaining
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
2014-02-12 07:40:39 +04:00
|
|
|
canThis = function (context) {
|
2013-06-25 15:43:15 +04:00
|
|
|
var result = new CanThisResult();
|
|
|
|
|
2014-02-12 07:40:39 +04:00
|
|
|
return result.beginCheck(context);
|
2013-06-25 15:43:15 +04:00
|
|
|
};
|
|
|
|
|
2017-09-25 12:17:06 +03:00
|
|
|
init = function (options) {
|
2016-08-23 15:07:25 +03:00
|
|
|
options = options || {};
|
|
|
|
|
2013-06-25 15:43:15 +04:00
|
|
|
// Load all the permissions
|
2017-09-25 12:17:06 +03:00
|
|
|
return models.Permission.findAll(options).then(function (perms) {
|
2013-06-25 15:43:15 +04:00
|
|
|
var seenActions = {};
|
|
|
|
|
|
|
|
exported.actionsMap = {};
|
|
|
|
|
|
|
|
// Build a hash map of the actions on objects, i.e
|
|
|
|
/*
|
|
|
|
{
|
|
|
|
'edit': ['post', 'tag', 'user', 'page'],
|
|
|
|
'delete': ['post', 'user'],
|
|
|
|
'create': ['post', 'user', 'page']
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
_.each(perms.models, function (perm) {
|
2014-09-10 08:06:24 +04:00
|
|
|
var actionType = perm.get('action_type'),
|
|
|
|
objectType = perm.get('object_type');
|
2013-06-25 15:43:15 +04:00
|
|
|
|
2014-09-10 08:06:24 +04:00
|
|
|
exported.actionsMap[actionType] = exported.actionsMap[actionType] || [];
|
|
|
|
seenActions[actionType] = seenActions[actionType] || {};
|
2013-06-25 15:43:15 +04:00
|
|
|
|
|
|
|
// Check if we've already seen this action -> object combo
|
2014-09-10 08:06:24 +04:00
|
|
|
if (seenActions[actionType][objectType]) {
|
2013-06-25 15:43:15 +04:00
|
|
|
return;
|
|
|
|
}
|
2013-06-05 07:47:11 +04:00
|
|
|
|
2014-09-10 08:06:24 +04:00
|
|
|
exported.actionsMap[actionType].push(objectType);
|
|
|
|
seenActions[actionType][objectType] = true;
|
2013-06-05 07:47:11 +04:00
|
|
|
});
|
|
|
|
|
2014-08-17 10:17:23 +04:00
|
|
|
return exported.actionsMap;
|
2013-06-25 15:43:15 +04:00
|
|
|
});
|
|
|
|
};
|
2013-06-05 07:47:11 +04:00
|
|
|
|
2013-06-25 15:43:15 +04:00
|
|
|
module.exports = exported = {
|
|
|
|
init: init,
|
|
|
|
canThis: canThis,
|
2017-09-25 12:17:06 +03:00
|
|
|
// @TODO: Make it so that we don't need to export these
|
2015-06-27 21:09:25 +03:00
|
|
|
parseContext: parseContext,
|
2017-09-25 12:17:06 +03:00
|
|
|
applyPublicRules: require('./public'),
|
2013-06-25 15:43:15 +04:00
|
|
|
actionsMap: {}
|
2013-10-31 22:02:34 +04:00
|
|
|
};
|