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:
Jacob Gable 2014-02-11 21:40:39 -06:00 committed by Sebastian Gierlinger
parent 9447b4ec2a
commit 9369dd3bf7
15 changed files with 396 additions and 136 deletions

View File

@ -1,7 +1,7 @@
{
"core": {
"databaseVersion": {
"defaultValue": "002"
"defaultValue": "003"
},
"dbHash": {
"defaultValue": null

View File

@ -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
View 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
};

View File

@ -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();

View File

@ -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);
}
});

View File

@ -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) {

View File

@ -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') +

View 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;

View File

@ -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 () {

View File

@ -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;

View File

@ -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]);

View File

@ -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');

View File

@ -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"));
});
});
});

View File

@ -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,

View File

@ -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,