mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 03:44:29 +03:00
Add app permission checking to canThis
- Pass permissions loading to buildObjectTypeHandlers to eliminate shared state - Load both app and user permissions to check - Check app permissions if present - Create apps table and App model - Move effectiveUserPermissions to permissions/effective - Change permissable interface to take context; user and app. - Add unit tests for app canThis checks and effective permissions
This commit is contained in:
parent
9447b4ec2a
commit
9369dd3bf7
@ -1,7 +1,7 @@
|
||||
{
|
||||
"core": {
|
||||
"databaseVersion": {
|
||||
"defaultValue": "002"
|
||||
"defaultValue": "003"
|
||||
},
|
||||
"dbHash": {
|
||||
"defaultValue": null
|
||||
|
@ -81,6 +81,11 @@ var db = {
|
||||
role_id: {type: 'integer', nullable: false},
|
||||
permission_id: {type: 'integer', nullable: false}
|
||||
},
|
||||
permissions_apps: {
|
||||
id: {type: 'increments', nullable: false, primary: true},
|
||||
app_id: {type: 'integer', nullable: false},
|
||||
permission_id: {type: 'integer', nullable: false}
|
||||
},
|
||||
sessions: {
|
||||
id: {type: 'string', nullable: false, primary: true},
|
||||
expires: {type: 'bigInteger', nullable: false},
|
||||
@ -115,12 +120,21 @@ var db = {
|
||||
id: {type: 'increments', nullable: false, primary: true},
|
||||
post_id: {type: 'integer', nullable: false, unsigned: true, references: 'id', inTable: 'posts'},
|
||||
tag_id: {type: 'integer', nullable: false, unsigned: true, references: 'id', inTable: 'tags'}
|
||||
},
|
||||
apps: {
|
||||
id: {type: 'increments', nullable: false, primary: true},
|
||||
uuid: {type: 'string', maxlength: 36, nullable: false},
|
||||
name: {type: 'string', maxlength: 150, nullable: false, unique: true},
|
||||
created_at: {type: 'dateTime', nullable: false},
|
||||
created_by: {type: 'integer', nullable: false},
|
||||
updated_at: {type: 'dateTime', nullable: true},
|
||||
updated_by: {type: 'integer', nullable: true}
|
||||
}
|
||||
};
|
||||
|
||||
function isPost(jsonData) {
|
||||
return jsonData.hasOwnProperty('html') && jsonData.hasOwnProperty('markdown')
|
||||
&& jsonData.hasOwnProperty('title') && jsonData.hasOwnProperty('slug');
|
||||
return jsonData.hasOwnProperty('html') && jsonData.hasOwnProperty('markdown') &&
|
||||
jsonData.hasOwnProperty('title') && jsonData.hasOwnProperty('slug');
|
||||
}
|
||||
|
||||
function isTag(jsonData) {
|
||||
|
27
core/server/models/app.js
Normal file
27
core/server/models/app.js
Normal file
@ -0,0 +1,27 @@
|
||||
var ghostBookshelf = require('./base'),
|
||||
App,
|
||||
Apps;
|
||||
|
||||
App = ghostBookshelf.Model.extend({
|
||||
tableName: 'apps',
|
||||
|
||||
permittedAttributes: ['id', 'uuid', 'name', 'created_at', 'created_by', 'updated_at', 'updated_by'],
|
||||
|
||||
validate: function () {
|
||||
ghostBookshelf.validator.check(this.get('name'), "App name cannot be blank").notEmpty();
|
||||
},
|
||||
|
||||
permissions: function () {
|
||||
// Have to use the require here because of circular dependencies
|
||||
return this.belongsToMany(require('./permission').Permission, 'permissions_apps');
|
||||
}
|
||||
});
|
||||
|
||||
Apps = ghostBookshelf.Collection.extend({
|
||||
model: App
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
App: App,
|
||||
Apps: Apps
|
||||
};
|
@ -11,6 +11,7 @@ module.exports = {
|
||||
Tag: require('./tag').Tag,
|
||||
Base: require('./base'),
|
||||
Session: require('./session').Session,
|
||||
App: require('./app').App,
|
||||
|
||||
init: function () {
|
||||
return migrations.init();
|
||||
|
@ -1,7 +1,9 @@
|
||||
var ghostBookshelf = require('./base'),
|
||||
_ = require('lodash'),
|
||||
when = require('when'),
|
||||
User = require('./user').User,
|
||||
Role = require('./role').Role,
|
||||
|
||||
App = require('./app').App,
|
||||
Permission,
|
||||
Permissions;
|
||||
|
||||
@ -15,6 +17,10 @@ Permission = ghostBookshelf.Model.extend({
|
||||
|
||||
users: function () {
|
||||
return this.belongsToMany(User);
|
||||
},
|
||||
|
||||
apps: function () {
|
||||
return this.belongsToMany(App);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -380,41 +380,52 @@ Post = ghostBookshelf.Model.extend({
|
||||
.catch(errors.logAndThrowError);
|
||||
},
|
||||
|
||||
permissable: function (postModelOrId, userId, action_type, userPermissions) {
|
||||
permissable: function (postModelOrId, context, action_type, loadedPermissions) {
|
||||
var self = this,
|
||||
userId = context.user,
|
||||
isAuthor,
|
||||
hasPermission,
|
||||
postModel = postModelOrId;
|
||||
userPermissions = loadedPermissions.user,
|
||||
appPermissions = loadedPermissions.app,
|
||||
postModel = postModelOrId,
|
||||
checkPermission = function (perm) {
|
||||
// Check for matching action type and object type
|
||||
if (perm.get('action_type') !== action_type ||
|
||||
perm.get('object_type') !== 'post') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If asking whether we can create posts,
|
||||
// and we have a create posts permission then go ahead and say yes
|
||||
if (action_type === 'create' && perm.get('action_type') === action_type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for either no object id or a matching one
|
||||
return !perm.get('object_id') || perm.get('object_id') === postModel.id;
|
||||
};
|
||||
|
||||
// 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);
|
||||
return self.permissable(foundPostModel, context, action_type, loadedPermissions);
|
||||
}, errors.logAndThrowError);
|
||||
}
|
||||
|
||||
// Check if any permissions apply for this user and post.
|
||||
hasPermission = _.any(userPermissions, function (perm) {
|
||||
// Check for matching action type and object type
|
||||
if (perm.get('action_type') !== action_type ||
|
||||
perm.get('object_type') !== 'post') {
|
||||
return false;
|
||||
}
|
||||
hasPermission = _.any(userPermissions, checkPermission);
|
||||
|
||||
// If asking whether we can create posts,
|
||||
// and we have a create posts permission then go ahead and say yes
|
||||
if (action_type === 'create' && perm.get('action_type') === action_type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for either no object id or a matching one
|
||||
return !perm.get('object_id') || perm.get('object_id') === postModel.id;
|
||||
});
|
||||
// If we have already have user permission and we passed in appPermissions check them
|
||||
if (hasPermission && !_.isNull(appPermissions)) {
|
||||
hasPermission = _.any(appPermissions, checkPermission);
|
||||
}
|
||||
|
||||
// If this is the author of the post, allow it.
|
||||
// Moved below the permissions checks because there may not be a postModel
|
||||
// in the case like canThis(user).create.post()
|
||||
hasPermission = hasPermission || (postModel && userId === postModel.get('author_id'));
|
||||
isAuthor = (postModel && userId === postModel.get('author_id'));
|
||||
hasPermission = hasPermission || isAuthor;
|
||||
|
||||
// Resolve if we have appropriate permissions
|
||||
if (hasPermission) {
|
||||
|
@ -334,35 +334,6 @@ User = ghostBookshelf.Model.extend({
|
||||
});
|
||||
},
|
||||
|
||||
effectivePermissions: function (id) {
|
||||
return this.read({id: id}, { withRelated: ['permissions', 'roles.permissions'] })
|
||||
.then(function (foundUser) {
|
||||
var seenPerms = {},
|
||||
rolePerms = _.map(foundUser.related('roles').models, function (role) {
|
||||
return role.related('permissions').models;
|
||||
}),
|
||||
allPerms = [];
|
||||
|
||||
rolePerms.push(foundUser.related('permissions').models);
|
||||
|
||||
_.each(rolePerms, function (rolePermGroup) {
|
||||
_.each(rolePermGroup, function (perm) {
|
||||
var key = perm.get('action_type') + '-' + perm.get('object_type') + '-' + perm.get('object_id');
|
||||
|
||||
// Only add perms once
|
||||
if (seenPerms[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
allPerms.push(perm);
|
||||
seenPerms[key] = true;
|
||||
});
|
||||
});
|
||||
|
||||
return when.resolve(allPerms);
|
||||
}, errors.logAndThrowError);
|
||||
},
|
||||
|
||||
gravatarLookup: function (userData) {
|
||||
var gravatarUrl = '//www.gravatar.com/avatar/' +
|
||||
crypto.createHash('md5').update(userData.email.toLowerCase().trim()).digest('hex') +
|
||||
|
50
core/server/permissions/effective.js
Normal file
50
core/server/permissions/effective.js
Normal file
@ -0,0 +1,50 @@
|
||||
var _ = require('lodash'),
|
||||
when = require('when'),
|
||||
Models = require('../models'),
|
||||
errors = require('../errorHandling'),
|
||||
User = Models.User,
|
||||
App = Models.App;
|
||||
|
||||
var effective = {
|
||||
user: function (id) {
|
||||
return User.read({id: id}, { withRelated: ['permissions', 'roles.permissions'] })
|
||||
.then(function (foundUser) {
|
||||
var seenPerms = {},
|
||||
rolePerms = _.map(foundUser.related('roles').models, function (role) {
|
||||
return role.related('permissions').models;
|
||||
}),
|
||||
allPerms = [];
|
||||
|
||||
rolePerms.push(foundUser.related('permissions').models);
|
||||
|
||||
_.each(rolePerms, function (rolePermGroup) {
|
||||
_.each(rolePermGroup, function (perm) {
|
||||
var key = perm.get('action_type') + '-' + perm.get('object_type') + '-' + perm.get('object_id');
|
||||
|
||||
// Only add perms once
|
||||
if (seenPerms[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
allPerms.push(perm);
|
||||
seenPerms[key] = true;
|
||||
});
|
||||
});
|
||||
|
||||
return allPerms;
|
||||
}, errors.logAndThrowError);
|
||||
},
|
||||
|
||||
app: function (appName) {
|
||||
return App.read({name: appName}, { withRelated: ['permissions'] })
|
||||
.then(function (foundApp) {
|
||||
if (!foundApp) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return foundApp.related('permissions').models;
|
||||
}, errors.logAndThrowError);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = effective;
|
@ -5,7 +5,7 @@ var _ = require('lodash'),
|
||||
when = require('when'),
|
||||
Models = require('../models'),
|
||||
objectTypeModelMap = require('./objectTypeModelMap'),
|
||||
UserProvider = Models.User,
|
||||
effectivePerms = require('./effective'),
|
||||
PermissionsProvider = Models.Permission,
|
||||
init,
|
||||
refresh,
|
||||
@ -22,17 +22,37 @@ function hasActionsMap() {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Move this to its own file so others can use it?
|
||||
function parseContext(context) {
|
||||
// Parse what's passed to canThis.beginCheck for standard user and app scopes
|
||||
var parsed = {
|
||||
user: null,
|
||||
app: null
|
||||
};
|
||||
|
||||
// Handle legacy passing of just userId or user model first
|
||||
if (context.id) {
|
||||
parsed.user = context.id;
|
||||
} else if (_.isNumber(context)) {
|
||||
parsed.user = context;
|
||||
} else if (_.isObject(context)) {
|
||||
// Otherwise, use the new hotness { user: id, app: id } format
|
||||
parsed.user = context.user;
|
||||
parsed.app = context.app;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// Base class for canThis call results
|
||||
CanThisResult = function () {
|
||||
this.userPermissionLoad = false;
|
||||
return;
|
||||
};
|
||||
|
||||
CanThisResult.prototype.buildObjectTypeHandlers = function (obj_types, act_type, userId) {
|
||||
var self = this,
|
||||
obj_type_handlers = {};
|
||||
|
||||
CanThisResult.prototype.buildObjectTypeHandlers = function (obj_types, act_type, context, permissionLoad) {
|
||||
// Iterate through the object types, i.e. ['post', 'tag', 'user']
|
||||
_.each(obj_types, function (obj_type) {
|
||||
return _.reduce(obj_types, function (obj_type_handlers, obj_type) {
|
||||
// Grab the TargetModel through the objectTypeModelMap
|
||||
var TargetModel = objectTypeModelMap[obj_type];
|
||||
|
||||
// Create the 'handler' for the object type;
|
||||
@ -49,78 +69,113 @@ CanThisResult.prototype.buildObjectTypeHandlers = function (obj_types, act_type,
|
||||
}
|
||||
|
||||
// Wait for the user loading to finish
|
||||
return self.userPermissionLoad.then(function (userPermissions) {
|
||||
return permissionLoad.then(function (loadedPermissions) {
|
||||
|
||||
// Iterate through the user permissions looking for an affirmation
|
||||
var hasPermission;
|
||||
var userPermissions = loadedPermissions.user,
|
||||
appPermissions = loadedPermissions.app,
|
||||
hasUserPermission,
|
||||
hasAppPermission,
|
||||
checkPermission = function (perm) {
|
||||
var permObjId;
|
||||
|
||||
// Look for a matching action type and object type first
|
||||
if (perm.get('action_type') !== act_type || perm.get('object_type') !== obj_type) {
|
||||
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;
|
||||
};
|
||||
|
||||
// Allow for a target model to implement a "Permissable" interface
|
||||
if (TargetModel && _.isFunction(TargetModel.permissable)) {
|
||||
return TargetModel.permissable(modelId, userId, act_type, userPermissions);
|
||||
return TargetModel.permissable(modelId, context, act_type, loadedPermissions);
|
||||
}
|
||||
|
||||
// Otherwise, check all the permissions for matching object id
|
||||
hasPermission = _.any(userPermissions, function (userPermission) {
|
||||
var permObjId;
|
||||
// Check user permissions for matching action, object and id.
|
||||
if (!_.isEmpty(userPermissions)) {
|
||||
hasUserPermission = _.any(userPermissions, checkPermission);
|
||||
}
|
||||
|
||||
// Look for a matching action type and object type first
|
||||
if (userPermission.get('action_type') !== act_type || userPermission.get('object_type') !== obj_type) {
|
||||
return false;
|
||||
}
|
||||
// If we already checked user permissions and they failed,
|
||||
// no need to check app permissions
|
||||
if (hasUserPermission === false) {
|
||||
return when.reject();
|
||||
}
|
||||
|
||||
// Grab the object id (if specified, could be null)
|
||||
permObjId = userPermission.get('object_id');
|
||||
// Check app permissions if they were passed
|
||||
hasAppPermission = true;
|
||||
if (!_.isNull(appPermissions)) {
|
||||
hasAppPermission = _.any(appPermissions, checkPermission);
|
||||
}
|
||||
|
||||
// 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 (hasUserPermission && hasAppPermission) {
|
||||
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 TargetModel.permissable(modelId, context, act_type, []);
|
||||
}
|
||||
|
||||
return when.reject();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
return obj_type_handlers;
|
||||
return obj_type_handlers;
|
||||
}, {});
|
||||
};
|
||||
|
||||
CanThisResult.prototype.beginCheck = function (user) {
|
||||
CanThisResult.prototype.beginCheck = function (context) {
|
||||
var self = this,
|
||||
userId = user.id || user;
|
||||
userPermissionLoad,
|
||||
appPermissionLoad,
|
||||
permissionsLoad;
|
||||
|
||||
// Get context.user and context.app
|
||||
context = parseContext(context);
|
||||
|
||||
if (!hasActionsMap()) {
|
||||
throw new Error("No actions map found, please call permissions.init() before use.");
|
||||
}
|
||||
|
||||
// TODO: Switch logic based on object type; user, role, post.
|
||||
// Kick off loading of effective user permissions
|
||||
userPermissionLoad = effectivePerms.user(context.user);
|
||||
|
||||
// Kick off the fetching of the user data
|
||||
this.userPermissionLoad = UserProvider.effectivePermissions(userId);
|
||||
// Kick off loading of effective app permissions if necessary
|
||||
if (context.app) {
|
||||
appPermissionLoad = effectivePerms.app(context.app);
|
||||
} else {
|
||||
// Resolve null if no context.app
|
||||
appPermissionLoad = when.resolve(null);
|
||||
}
|
||||
|
||||
permissionsLoad = when.all([userPermissionLoad, appPermissionLoad]).then(function (result) {
|
||||
return {
|
||||
user: result[0],
|
||||
app: result[1]
|
||||
};
|
||||
});
|
||||
|
||||
// Iterate through the actions and their related object types
|
||||
_.each(exported.actionsMap, function (obj_types, act_type) {
|
||||
// Build up the object type handlers;
|
||||
// the '.post()' parts in canThis(user).edit.post()
|
||||
var obj_type_handlers = self.buildObjectTypeHandlers(obj_types, act_type, userId);
|
||||
var obj_type_handlers = self.buildObjectTypeHandlers(obj_types, act_type, context, permissionsLoad);
|
||||
|
||||
// Define a property for the action on the result;
|
||||
// the '.edit' in canThis(user).edit.post()
|
||||
@ -136,10 +191,10 @@ CanThisResult.prototype.beginCheck = function (user) {
|
||||
return this;
|
||||
};
|
||||
|
||||
canThis = function (user) {
|
||||
canThis = function (context) {
|
||||
var result = new CanThisResult();
|
||||
|
||||
return result.beginCheck(user);
|
||||
return result.beginCheck(context);
|
||||
};
|
||||
|
||||
init = refresh = function () {
|
||||
|
@ -67,7 +67,7 @@ describe('User Model', function run() {
|
||||
it('can find gravatar', function (done) {
|
||||
var userData = testUtils.DataGenerator.forModel.users[4],
|
||||
gravatarStub = sinon.stub(UserModel, 'gravatarLookup', function (userData) {
|
||||
userData.image = 'http://www.gravatar.com/avatar/2fab21a4c4ed88e76add10650c73bae1?d=404'
|
||||
userData.image = 'http://www.gravatar.com/avatar/2fab21a4c4ed88e76add10650c73bae1?d=404';
|
||||
return when.resolve(userData);
|
||||
});
|
||||
|
||||
@ -220,16 +220,6 @@ describe('User Model', function run() {
|
||||
}).then(null, done);
|
||||
});
|
||||
|
||||
it("can get effective permissions", function (done) {
|
||||
UserModel.effectivePermissions(1).then(function (effectivePermissions) {
|
||||
should.exist(effectivePermissions);
|
||||
|
||||
effectivePermissions.length.should.be.above(0);
|
||||
|
||||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('can delete', function (done) {
|
||||
var firstUserId;
|
||||
|
||||
|
@ -15,6 +15,8 @@ describe("Exporter", function () {
|
||||
|
||||
should.exist(exporter);
|
||||
|
||||
var sandbox;
|
||||
|
||||
before(function (done) {
|
||||
testUtils.clearData().then(function () {
|
||||
done();
|
||||
@ -22,12 +24,14 @@ describe("Exporter", function () {
|
||||
});
|
||||
|
||||
beforeEach(function (done) {
|
||||
sandbox = sinon.sandbox.create();
|
||||
testUtils.initData().then(function () {
|
||||
done();
|
||||
}, done);
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
sandbox.restore();
|
||||
testUtils.clearData().then(function () {
|
||||
done();
|
||||
}, done);
|
||||
@ -35,8 +39,8 @@ describe("Exporter", function () {
|
||||
|
||||
it("exports data", function (done) {
|
||||
// Stub migrations to return 000 as the current database version
|
||||
var migrationStub = sinon.stub(migration, "getDatabaseVersion", function () {
|
||||
return when.resolve("002");
|
||||
var migrationStub = sandbox.stub(migration, "getDatabaseVersion", function () {
|
||||
return when.resolve("003");
|
||||
});
|
||||
|
||||
exporter().then(function (exportData) {
|
||||
@ -48,8 +52,8 @@ describe("Exporter", function () {
|
||||
should.exist(exportData.meta);
|
||||
should.exist(exportData.data);
|
||||
|
||||
exportData.meta.version.should.equal("002");
|
||||
_.findWhere(exportData.data.settings, {key: "databaseVersion"}).value.should.equal("002");
|
||||
exportData.meta.version.should.equal("003");
|
||||
_.findWhere(exportData.data.settings, {key: "databaseVersion"}).value.should.equal("003");
|
||||
|
||||
_.each(tables, function (name) {
|
||||
should.exist(exportData.data[name]);
|
||||
|
@ -23,15 +23,22 @@ describe("Import", function () {
|
||||
should.exist(exporter);
|
||||
should.exist(importer);
|
||||
|
||||
var sandbox;
|
||||
|
||||
beforeEach(function (done) {
|
||||
sandbox = sinon.sandbox.create();
|
||||
// clear database... we need to initialise it manually for each test
|
||||
testUtils.clearData().then(function () {
|
||||
done();
|
||||
}, done);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it("resolves 000", function (done) {
|
||||
var importStub = sinon.stub(Importer000, "importData", function () {
|
||||
var importStub = sandbox.stub(Importer000, "importData", function () {
|
||||
return when.resolve();
|
||||
}),
|
||||
fakeData = { test: true };
|
||||
@ -46,7 +53,7 @@ describe("Import", function () {
|
||||
});
|
||||
|
||||
it("resolves 001", function (done) {
|
||||
var importStub = sinon.stub(Importer001, "importData", function () {
|
||||
var importStub = sandbox.stub(Importer001, "importData", function () {
|
||||
return when.resolve();
|
||||
}),
|
||||
fakeData = { test: true };
|
||||
@ -61,7 +68,7 @@ describe("Import", function () {
|
||||
});
|
||||
|
||||
it("resolves 002", function (done) {
|
||||
var importStub = sinon.stub(Importer002, "importData", function () {
|
||||
var importStub = sandbox.stub(Importer002, "importData", function () {
|
||||
return when.resolve();
|
||||
}),
|
||||
fakeData = { test: true };
|
||||
@ -96,7 +103,7 @@ describe("Import", function () {
|
||||
|
||||
it("imports data from 000", function (done) {
|
||||
var exportData,
|
||||
migrationStub = sinon.stub(migration, "getDatabaseVersion", function () {
|
||||
migrationStub = sandbox.stub(migration, "getDatabaseVersion", function () {
|
||||
return when.resolve("000");
|
||||
});
|
||||
|
||||
@ -129,7 +136,7 @@ describe("Import", function () {
|
||||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("003", 'Wrong database version');
|
||||
|
||||
// test tags
|
||||
tags.length.should.equal(exportData.data.tags.length, 'no new tags');
|
||||
@ -207,7 +214,7 @@ describe("Import", function () {
|
||||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("003", 'Wrong database version');
|
||||
|
||||
// activeTheme should NOT have been overridden
|
||||
_.findWhere(settings, {key: "activeTheme"}).value.should.equal("casper", 'Wrong theme');
|
||||
@ -270,7 +277,7 @@ describe("Import", function () {
|
||||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("003", 'Wrong database version');
|
||||
|
||||
// test tags
|
||||
tags.length.should.equal(exportData.data.tags.length, 'no new tags');
|
||||
@ -316,7 +323,7 @@ describe("Import", function () {
|
||||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("003", 'Wrong database version');
|
||||
|
||||
// test tags
|
||||
tags.length.should.equal(exportData.data.tags.length, 'no new tags');
|
||||
@ -394,7 +401,7 @@ describe("Import", function () {
|
||||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("003", 'Wrong database version');
|
||||
|
||||
// activeTheme should NOT have been overridden
|
||||
_.findWhere(settings, {key: "activeTheme"}).value.should.equal("casper", 'Wrong theme');
|
||||
@ -457,7 +464,7 @@ describe("Import", function () {
|
||||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("003", 'Wrong database version');
|
||||
|
||||
// test tags
|
||||
tags.length.should.equal(exportData.data.tags.length, 'no new tags');
|
||||
@ -503,7 +510,7 @@ describe("Import", function () {
|
||||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("003", 'Wrong database version');
|
||||
|
||||
// test tags
|
||||
tags.length.should.equal(exportData.data.tags.length, 'no new tags');
|
||||
|
@ -8,6 +8,7 @@ var testUtils = require('../utils'),
|
||||
|
||||
// Stuff we are testing
|
||||
permissions = require('../../server/permissions'),
|
||||
effectivePerms = require('../../server/permissions/effective'),
|
||||
Models = require('../../server/models'),
|
||||
UserProvider = Models.User,
|
||||
PermissionsProvider = Models.Permission,
|
||||
@ -15,6 +16,8 @@ var testUtils = require('../utils'),
|
||||
|
||||
describe('Permissions', function () {
|
||||
|
||||
var sandbox;
|
||||
|
||||
before(function (done) {
|
||||
testUtils.clearData().then(function () {
|
||||
done();
|
||||
@ -22,13 +25,17 @@ describe('Permissions', function () {
|
||||
});
|
||||
|
||||
beforeEach(function (done) {
|
||||
sandbox = sinon.sandbox.create();
|
||||
testUtils.initData()
|
||||
.then(testUtils.insertDefaultUser).then(function () {
|
||||
.then(testUtils.insertDefaultUser)
|
||||
.then(testUtils.insertDefaultApp)
|
||||
.then(function () {
|
||||
done();
|
||||
}, done);
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
sandbox.restore();
|
||||
testUtils.clearData()
|
||||
.then(function () {
|
||||
done();
|
||||
@ -243,7 +250,6 @@ describe('Permissions', function () {
|
||||
.then(function (updatedUser) {
|
||||
|
||||
// TODO: Verify updatedUser.related('permissions') has the permission?
|
||||
|
||||
var canThisResult = permissions.canThis(updatedUser.id);
|
||||
|
||||
should.exist(canThisResult.edit);
|
||||
@ -258,7 +264,7 @@ describe('Permissions', function () {
|
||||
|
||||
it('can use permissable function on Model to allow something', function (done) {
|
||||
var testUser,
|
||||
permissableStub = sinon.stub(PostProvider, 'permissable', function () {
|
||||
permissableStub = sandbox.stub(PostProvider, 'permissable', function () {
|
||||
return when.resolve();
|
||||
});
|
||||
|
||||
@ -286,29 +292,109 @@ describe('Permissions', function () {
|
||||
|
||||
it('can use permissable function on Model to forbid something', function (done) {
|
||||
var testUser,
|
||||
permissableStub = sinon.stub(PostProvider, 'permissable', function () {
|
||||
permissableStub = sandbox.stub(PostProvider, 'permissable', function () {
|
||||
return when.reject();
|
||||
});
|
||||
|
||||
|
||||
// createTestUser()
|
||||
UserProvider.browse()
|
||||
.then(function (foundUser) {
|
||||
testUser = foundUser.models[0];
|
||||
|
||||
|
||||
return permissions.canThis(testUser).edit.post(123);
|
||||
})
|
||||
.then(function () {
|
||||
permissableStub.restore();
|
||||
|
||||
errors.logError(new Error("Allowed testUser to edit post"));
|
||||
done(new Error("Allowed testUser to edit post"));
|
||||
})
|
||||
.otherwise(function () {
|
||||
permissableStub.restore();
|
||||
permissableStub.calledWith(123, testUser.id, 'edit').should.equal(true);
|
||||
|
||||
permissableStub.calledWith(123, { user: testUser.id, app: null }, 'edit').should.equal(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("can get effective user permissions", function (done) {
|
||||
effectivePerms.user(1).then(function (effectivePermissions) {
|
||||
should.exist(effectivePermissions);
|
||||
|
||||
effectivePermissions.length.should.be.above(0);
|
||||
|
||||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('can check an apps effective permissions', function (done) {
|
||||
effectivePerms.app('Kudos')
|
||||
.then(function (effectivePermissions) {
|
||||
should.exist(effectivePermissions);
|
||||
|
||||
effectivePermissions.length.should.be.above(0);
|
||||
|
||||
done();
|
||||
})
|
||||
.otherwise(done);
|
||||
});
|
||||
it('does not allow an app to edit a post without permission', function (done) {
|
||||
// Change the author of the post so the author override doesn't affect the test
|
||||
PostProvider.edit({id: 1, 'author_id': 2})
|
||||
.then(function (updatedPost) {
|
||||
// Add user permissions
|
||||
return Models.User.read({id: 1})
|
||||
.then(function (foundUser) {
|
||||
var newPerm = new Models.Permission({
|
||||
name: "app test edit post",
|
||||
action_type: "edit",
|
||||
object_type: "post"
|
||||
});
|
||||
|
||||
return newPerm.save().then(function () {
|
||||
return foundUser.permissions().attach(newPerm).then(function () {
|
||||
return when.all([updatedPost, foundUser]);
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(function (results) {
|
||||
var updatedPost = results[0],
|
||||
updatedUser = results[1];
|
||||
|
||||
return permissions.canThis({ user: updatedUser.id })
|
||||
.edit
|
||||
.post(updatedPost.id)
|
||||
.then(function () {
|
||||
return results;
|
||||
})
|
||||
.otherwise(function (err) {
|
||||
done(new Error("Did not allow user 1 to edit post 1"));
|
||||
});
|
||||
})
|
||||
.then(function (results) {
|
||||
var updatedPost = results[0],
|
||||
updatedUser = results[1];
|
||||
|
||||
// Confirm app cannot edit it.
|
||||
return permissions.canThis({ app: 'Hemingway', user: updatedUser.id })
|
||||
.edit
|
||||
.post(updatedPost.id)
|
||||
.then(function () {
|
||||
done(new Error("Allowed an edit of post 1"));
|
||||
})
|
||||
.otherwise(function () {
|
||||
done();
|
||||
});
|
||||
}).otherwise(done);
|
||||
});
|
||||
it('allows an app to edit a post with permission', function (done) {
|
||||
permissions.canThis({ app: 'Kudos', user: 1 })
|
||||
.edit
|
||||
.post(1)
|
||||
.then(function () {
|
||||
done();
|
||||
})
|
||||
.otherwise(function () {
|
||||
done(new Error("Allowed an edit of post 1"));
|
||||
});
|
||||
});
|
||||
});
|
@ -101,6 +101,18 @@ DataGenerator.Content = {
|
||||
email: 'info@ghost.org',
|
||||
password: '$2a$10$.pZeeBE0gHXd0PTnbT/ph.GEKgd0Wd3q2pWna3ynTGBkPKnGIKZL6'
|
||||
}
|
||||
],
|
||||
|
||||
apps: [
|
||||
{
|
||||
name: 'Kudos'
|
||||
},
|
||||
{
|
||||
name: 'Importer'
|
||||
},
|
||||
{
|
||||
name: 'Hemingway'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@ -185,6 +197,14 @@ DataGenerator.forKnex = (function () {
|
||||
};
|
||||
}
|
||||
|
||||
function createApp(overrides) {
|
||||
return _.defaults(overrides, {
|
||||
uuid: uuid.v4(),
|
||||
created_by: 1,
|
||||
created_at: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
posts = [
|
||||
createPost(DataGenerator.Content.posts[0]),
|
||||
createPost(DataGenerator.Content.posts[1]),
|
||||
@ -219,7 +239,6 @@ DataGenerator.forKnex = (function () {
|
||||
createUser: createUser,
|
||||
createGenericUser: createGenericUser,
|
||||
createUserRole: createUserRole,
|
||||
createPostsTags: createPostsTags,
|
||||
|
||||
posts: posts,
|
||||
tags: tags,
|
||||
|
@ -91,9 +91,27 @@ function insertDefaultUser() {
|
||||
|
||||
users.push(DataGenerator.forKnex.createUser(DataGenerator.Content.users[0]));
|
||||
userRoles.push(DataGenerator.forKnex.createUserRole(1, 1));
|
||||
return when(knex('users').insert(users).then(function () {
|
||||
return knex('roles_users').insert(userRoles);
|
||||
}));
|
||||
return knex('users')
|
||||
.insert(users)
|
||||
.then(function () {
|
||||
return knex('roles_users').insert(userRoles);
|
||||
});
|
||||
}
|
||||
|
||||
function insertDefaultApp() {
|
||||
var apps = [];
|
||||
|
||||
apps.push(DataGenerator.forKnex.createApp(DataGenerator.Content.apps[0]));
|
||||
|
||||
return knex('apps')
|
||||
.insert(apps)
|
||||
.then(function () {
|
||||
return knex('permissions_apps')
|
||||
.insert({
|
||||
app_id: 1,
|
||||
permission_id: 1
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function insertDefaultFixtures() {
|
||||
@ -127,6 +145,7 @@ module.exports = {
|
||||
insertMorePosts: insertMorePosts,
|
||||
insertMorePostsTags: insertMorePostsTags,
|
||||
insertDefaultUser: insertDefaultUser,
|
||||
insertDefaultApp: insertDefaultApp,
|
||||
|
||||
loadExportFixture: loadExportFixture,
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user