mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 03:44:29 +03:00
Implement a permissable interface on models
Added checks to the canThis process for a `permissable()` function that would allow Models to override the permissions process.
This commit is contained in:
parent
aa659d29b6
commit
1effc4e772
@ -5,6 +5,7 @@
|
|||||||
var Post,
|
var Post,
|
||||||
Posts,
|
Posts,
|
||||||
_ = require('underscore'),
|
_ = require('underscore'),
|
||||||
|
when = require('when'),
|
||||||
Showdown = require('showdown'),
|
Showdown = require('showdown'),
|
||||||
converter = new Showdown.converter(),
|
converter = new Showdown.converter(),
|
||||||
User = require('./user').User,
|
User = require('./user').User,
|
||||||
@ -134,6 +135,41 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
permissable: function (postModelOrId, userId, action_type, userPermissions) {
|
||||||
|
var self = this,
|
||||||
|
hasPermission,
|
||||||
|
postModel = postModelOrId;
|
||||||
|
|
||||||
|
// If we passed in an id instead of a model, get the model
|
||||||
|
// then check the permissions
|
||||||
|
if (_.isNumber(postModelOrId) || _.isString(postModelOrId)) {
|
||||||
|
return this.read({id: postModelOrId}).then(function (foundPostModel) {
|
||||||
|
return self.permissable(foundPostModel, userId, action_type, userPermissions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This logic is temporary, will probably need to be updated
|
||||||
|
|
||||||
|
hasPermission = _.any(userPermissions, function (perm) {
|
||||||
|
if (perm.get('object_type') !== 'post') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// True, if no object_id specified, or it matches
|
||||||
|
return !perm.get('object_id') || perm.get('object_id') === postModel.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If this is the author of the post, allow it.
|
||||||
|
hasPermission = hasPermission || userId === postModel.get('author_id');
|
||||||
|
|
||||||
|
if (hasPermission) {
|
||||||
|
return when.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, you shall not pass.
|
||||||
|
return when.reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
var _ = require('underscore'),
|
var _ = require('underscore'),
|
||||||
when = require('when'),
|
when = require('when'),
|
||||||
Models = require('../models'),
|
Models = require('../models'),
|
||||||
|
objectTypeModelMap = require('./objectTypeModelMap'),
|
||||||
UserProvider = Models.User,
|
UserProvider = Models.User,
|
||||||
PermissionsProvider = Models.Permission,
|
PermissionsProvider = Models.Permission,
|
||||||
init,
|
init,
|
||||||
@ -20,12 +21,13 @@
|
|||||||
this.userPermissionLoad = false;
|
this.userPermissionLoad = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
CanThisResult.prototype.buildObjectTypeHandlers = function (obj_types, act_type) {
|
CanThisResult.prototype.buildObjectTypeHandlers = function (obj_types, act_type, userId) {
|
||||||
var self = this,
|
var self = this,
|
||||||
obj_type_handlers = {};
|
obj_type_handlers = {};
|
||||||
|
|
||||||
// Iterate through the object types, i.e. ['post', 'tag', 'user']
|
// Iterate through the object types, i.e. ['post', 'tag', 'user']
|
||||||
_.each(obj_types, function (obj_type) {
|
_.each(obj_types, function (obj_type) {
|
||||||
|
var TargetModel = objectTypeModelMap[obj_type];
|
||||||
|
|
||||||
// Create the 'handler' for the object type;
|
// Create the 'handler' for the object type;
|
||||||
// the '.post()' in canThis(user).edit.post()
|
// the '.post()' in canThis(user).edit.post()
|
||||||
@ -42,35 +44,51 @@
|
|||||||
|
|
||||||
// Wait for the user loading to finish
|
// Wait for the user loading to finish
|
||||||
return self.userPermissionLoad.then(function (userPermissions) {
|
return self.userPermissionLoad.then(function (userPermissions) {
|
||||||
|
|
||||||
// Iterate through the user permissions looking for an affirmation
|
// Iterate through the user permissions looking for an affirmation
|
||||||
var hasPermission = _.any(userPermissions, function (userPermission) {
|
var hasPermission;
|
||||||
var permObjId;
|
|
||||||
|
|
||||||
// Look for a matching action type and object type first
|
// Allow for a target model to implement a "Permissable" interface
|
||||||
if (userPermission.get('action_type') !== act_type || userPermission.get('object_type') !== obj_type) {
|
if (TargetModel && _.isFunction(TargetModel.permissable)) {
|
||||||
return false;
|
return TargetModel.permissable(modelId, userId, act_type, userPermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grab the object id (if specified, could be null)
|
// Otherwise, check all the permissions for matching object id
|
||||||
permObjId = userPermission.get('object_id');
|
hasPermission = _.any(userPermissions, function (userPermission) {
|
||||||
|
var permObjId;
|
||||||
|
|
||||||
// If we didn't specify a model (any thing)
|
// Look for a matching action type and object type first
|
||||||
// or the permission didn't have an id scope set
|
if (userPermission.get('action_type') !== act_type || userPermission.get('object_type') !== obj_type) {
|
||||||
// then the user has permission
|
return false;
|
||||||
if (!modelId || !permObjId) {
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, check if the id's match
|
// Grab the object id (if specified, could be null)
|
||||||
// TODO: String vs Int comparison possibility here?
|
permObjId = userPermission.get('object_id');
|
||||||
return modelId === permObjId;
|
|
||||||
});
|
// If we didn't specify a model (any thing)
|
||||||
|
// or the permission didn't have an id scope set
|
||||||
|
// then the user has permission
|
||||||
|
if (!modelId || !permObjId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, check if the id's match
|
||||||
|
// TODO: String vs Int comparison possibility here?
|
||||||
|
return modelId === permObjId;
|
||||||
|
});
|
||||||
|
|
||||||
if (hasPermission) {
|
if (hasPermission) {
|
||||||
return when.resolve();
|
return when.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return when.reject();
|
||||||
|
}).otherwise(function() {
|
||||||
|
// No permissions loaded, or error loading permissions
|
||||||
|
|
||||||
|
// Still check for permissable without permissions
|
||||||
|
if (TargetModel && _.isFunction(TargetModel.permissable)) {
|
||||||
|
return TargetModel.permissable(modelId, userId, act_type, []);
|
||||||
|
}
|
||||||
|
|
||||||
return when.reject();
|
return when.reject();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -80,12 +98,13 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
CanThisResult.prototype.beginCheck = function (user) {
|
CanThisResult.prototype.beginCheck = function (user) {
|
||||||
var self = this;
|
var self = this,
|
||||||
|
userId = user.id || user;
|
||||||
|
|
||||||
// TODO: Switch logic based on object type; user, role, post.
|
// TODO: Switch logic based on object type; user, role, post.
|
||||||
|
|
||||||
// Kick off the fetching of the user data
|
// Kick off the fetching of the user data
|
||||||
this.userPermissionLoad = UserProvider.effectivePermissions(user.id || user);
|
this.userPermissionLoad = UserProvider.effectivePermissions(userId);
|
||||||
|
|
||||||
// Iterate through the actions and their related object types
|
// Iterate through the actions and their related object types
|
||||||
// We should have loaded these through a permissions.init() call previously
|
// We should have loaded these through a permissions.init() call previously
|
||||||
@ -93,7 +112,7 @@
|
|||||||
_.each(exported.actionsMap, function (obj_types, act_type) {
|
_.each(exported.actionsMap, function (obj_types, act_type) {
|
||||||
// Build up the object type handlers;
|
// Build up the object type handlers;
|
||||||
// the '.post()' parts in canThis(user).edit.post()
|
// the '.post()' parts in canThis(user).edit.post()
|
||||||
var obj_type_handlers = self.buildObjectTypeHandlers(obj_types, act_type);
|
var obj_type_handlers = self.buildObjectTypeHandlers(obj_types, act_type, userId);
|
||||||
|
|
||||||
// Define a property for the action on the result;
|
// Define a property for the action on the result;
|
||||||
// the '.edit' in canThis(user).edit.post()
|
// the '.edit' in canThis(user).edit.post()
|
||||||
|
11
core/shared/permissions/objectTypeModelMap.js
Normal file
11
core/shared/permissions/objectTypeModelMap.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
'post': require('../models/post').Post,
|
||||||
|
'role': require('../models/role').Role,
|
||||||
|
'user': require('../models/user').User,
|
||||||
|
'permission': require('../models/permission').Permission,
|
||||||
|
'setting': require('../models/setting').Setting
|
||||||
|
};
|
||||||
|
}());
|
@ -6,12 +6,14 @@
|
|||||||
var _ = require("underscore"),
|
var _ = require("underscore"),
|
||||||
when = require('when'),
|
when = require('when'),
|
||||||
should = require('should'),
|
should = require('should'),
|
||||||
|
sinon = require('sinon'),
|
||||||
errors = require('../../shared/errorHandling'),
|
errors = require('../../shared/errorHandling'),
|
||||||
helpers = require('./helpers'),
|
helpers = require('./helpers'),
|
||||||
permissions = require('../../shared/permissions'),
|
permissions = require('../../shared/permissions'),
|
||||||
Models = require('../../shared/models'),
|
Models = require('../../shared/models'),
|
||||||
UserProvider = Models.User,
|
UserProvider = Models.User,
|
||||||
PermissionsProvider = Models.Permission;
|
PermissionsProvider = Models.Permission,
|
||||||
|
PostProvider = Models.Post;
|
||||||
|
|
||||||
describe('permissions', function () {
|
describe('permissions', function () {
|
||||||
|
|
||||||
@ -33,6 +35,21 @@
|
|||||||
{ act: "remove", obj: "user" }
|
{ act: "remove", obj: "user" }
|
||||||
],
|
],
|
||||||
currTestPermId = 1,
|
currTestPermId = 1,
|
||||||
|
currTestUserId = 1,
|
||||||
|
createTestUser = function (email_address) {
|
||||||
|
if (!email_address) {
|
||||||
|
currTestUserId += 1;
|
||||||
|
email_address = "test" + currTestPermId + "@test.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
var newUser = {
|
||||||
|
id: currTestUserId,
|
||||||
|
email_address: email_address,
|
||||||
|
password: "testing123"
|
||||||
|
};
|
||||||
|
|
||||||
|
return UserProvider.add(newUser);
|
||||||
|
},
|
||||||
createPermission = function (name, act, obj) {
|
createPermission = function (name, act, obj) {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
currTestPermId += 1;
|
currTestPermId += 1;
|
||||||
@ -129,29 +146,33 @@
|
|||||||
description: "test2 description"
|
description: "test2 description"
|
||||||
});
|
});
|
||||||
|
|
||||||
testRole.save().then(function () {
|
testRole.save()
|
||||||
return testRole.load('permissions');
|
.then(function () {
|
||||||
}).then(function () {
|
return testRole.load('permissions');
|
||||||
var rolePermission = new Models.Permission({
|
})
|
||||||
name: "test edit posts",
|
.then(function () {
|
||||||
action_type: 'edit',
|
var rolePermission = new Models.Permission({
|
||||||
object_type: 'post'
|
name: "test edit posts",
|
||||||
|
action_type: 'edit',
|
||||||
|
object_type: 'post'
|
||||||
|
});
|
||||||
|
|
||||||
|
testRole.related('permissions').length.should.equal(0);
|
||||||
|
|
||||||
|
return rolePermission.save().then(function () {
|
||||||
|
return testRole.permissions().attach(rolePermission);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return Models.Role.read({id: testRole.id}, { withRelated: ['permissions']});
|
||||||
|
})
|
||||||
|
.then(function (updatedRole) {
|
||||||
|
should.exist(updatedRole);
|
||||||
|
|
||||||
|
updatedRole.related('permissions').length.should.equal(1);
|
||||||
|
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
testRole.related('permissions').length.should.equal(0);
|
|
||||||
|
|
||||||
return rolePermission.save().then(function () {
|
|
||||||
return testRole.permissions().attach(rolePermission);
|
|
||||||
});
|
|
||||||
}).then(function () {
|
|
||||||
return Models.Role.read({id: testRole.id}, { withRelated: ['permissions']});
|
|
||||||
}).then(function (updatedRole) {
|
|
||||||
should.exist(updatedRole);
|
|
||||||
|
|
||||||
updatedRole.related('permissions').length.should.equal(1);
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not allow edit post without permission', function (done) {
|
it('does not allow edit post without permission', function (done) {
|
||||||
@ -221,6 +242,56 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can use permissable function on Model to allow something', function (done) {
|
||||||
|
var testUser,
|
||||||
|
permissableStub = sinon.stub(PostProvider, 'permissable', function () {
|
||||||
|
return when.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
createTestUser()
|
||||||
|
.then(function (createdTestUser) {
|
||||||
|
testUser = createdTestUser;
|
||||||
|
|
||||||
|
return permissions.canThis(testUser).edit.post(123);
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
permissableStub.restore();
|
||||||
|
|
||||||
|
permissableStub.calledWith(123, testUser.id, 'edit').should.equal(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.otherwise(function () {
|
||||||
|
permissableStub.restore();
|
||||||
|
errors.logError(new Error("Did not allow testUser"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can use permissable function on Model to forbid something', function (done) {
|
||||||
|
var testUser,
|
||||||
|
permissableStub = sinon.stub(PostProvider, 'permissable', function () {
|
||||||
|
return when.reject();
|
||||||
|
});
|
||||||
|
|
||||||
|
createTestUser()
|
||||||
|
.then(function (createdTestUser) {
|
||||||
|
testUser = createdTestUser;
|
||||||
|
|
||||||
|
return permissions.canThis(testUser).edit.post(123);
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
permissableStub.restore();
|
||||||
|
|
||||||
|
errors.logError(new Error("Allowed testUser to edit post"));
|
||||||
|
})
|
||||||
|
.otherwise(function () {
|
||||||
|
permissableStub.restore();
|
||||||
|
permissableStub.calledWith(123, testUser.id, 'edit').should.equal(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}());
|
}());
|
Loading…
Reference in New Issue
Block a user