Skip to content

Commit

Permalink
Wire permmissions for notifications, mail and tags
Browse files Browse the repository at this point in the history
closes TryGhost#2739

- wraps the api endpoints for mail, notifications, and tags in a canThis
  check
- add internal context to internal calls
- updates tests
  • Loading branch information
ErisDS committed Jul 17, 2014
1 parent 6ab8a37 commit cfaa6f0
Show file tree
Hide file tree
Showing 14 changed files with 255 additions and 180 deletions.
4 changes: 2 additions & 2 deletions core/server/api/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ authentication = {
}]
};

return mail.send(payload);
return mail.send(payload, {context: {internal: true}});
}).then(function () {
return when.resolve({passwordreset: [{message: 'Check your email for further instructions.'}]});
}).otherwise(function (error) {
Expand Down Expand Up @@ -188,7 +188,7 @@ authentication = {
}]
};

return mail.send(payload).otherwise(function (error) {
return mail.send(payload, {context: {internal: true}}).otherwise(function (error) {
errors.logError(
error.message,
"Unable to send welcome email, your blog will continue to function.",
Expand Down
39 changes: 22 additions & 17 deletions core/server/api/mail.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// API for sending Mail
var when = require('when'),
config = require('../config'),
canThis = require('../permissions').canThis,
errors = require('../errors'),
mail;

Expand All @@ -21,23 +22,27 @@ mail = {
* @param {Mail} object details of the email to send
* @returns {Promise}
*/
send: function (object) {
send: function (object, options) {
var mailer = require('../mail');

// TODO: permissions
return mailer.send(object.mail[0].message)
.then(function (data) {
delete object.mail[0].options;
// Sendmail returns extra details we don't need and that don't convert to JSON
delete object.mail[0].message.transport;
object.mail[0].status = {
message: data.message
};
return object;
})
.otherwise(function (error) {
return when.reject(new errors.EmailError(error.message));
});
return canThis(options.context).send.mail().then(function () {
return mailer.send(object.mail[0].message)
.then(function (data) {
delete object.mail[0].options;
// Sendmail returns extra details we don't need and that don't convert to JSON
delete object.mail[0].message.transport;
object.mail[0].status = {
message: data.message
};
return object;
})
.otherwise(function (error) {
return when.reject(new errors.EmailError(error.message));
});

}, function () {
return when.reject(new errors.NoPermissionError('You do not have permission to send mail.'));
});
},

/**
Expand All @@ -48,7 +53,7 @@ mail = {
* @param {Object} required property 'to' which contains the recipient address
* @returns {Promise}
*/
sendTest: function (object) {
sendTest: function (object, options) {
var html = '<p><strong>Hello there!</strong></p>' +
'<p>Excellent!' +
' You\'ve successfully setup your email config for your Ghost blog over on ' + config().url + '</p>' +
Expand All @@ -65,7 +70,7 @@ mail = {
}
}]};

return mail.send(payload);
return mail.send(payload, options);
}
};

Expand Down
130 changes: 73 additions & 57 deletions core/server/api/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// RESTful API for creating notifications
var when = require('when'),
_ = require('lodash'),
canThis = require('../permissions').canThis,
errors = require('../errors'),
utils = require('./utils'),

Expand All @@ -23,49 +24,12 @@ notifications = {
* Fetch all notifications
* @returns {Promise(Notifications)}
*/
browse: function browse() {
return when({ 'notifications': notificationsStore });
},

/**
* ### Destroy
* Remove a specific notification
*
* @param {{id (required), context}} options
* @returns {Promise(Notifications)}
*/
destroy: function destroy(options) {
var notification = _.find(notificationsStore, function (element) {
return element.id === parseInt(options.id, 10);
});

if (notification && !notification.dismissible) {
return when.reject(
new errors.NoPermissionError('You do not have permission to dismiss this notification.')
);
}

if (!notification) {
return when.reject(new errors.NotFoundError('Notification does not exist.'));
}

notificationsStore = _.reject(notificationsStore, function (element) {
return element.id === parseInt(options.id, 10);
browse: function browse(options) {
return canThis(options.context).browse.notification().then(function () {
return when({ 'notifications': notificationsStore });
}, function () {
return when.reject(new errors.NoPermissionError('You do not have permission to browse notifications.'));
});
return when({notifications: [notification]});
},

/**
* ### DestroyAll
* Clear all notifications, used for tests
*
* @private Not exposed over HTTP
* @returns {Promise}
*/
destroyAll: function destroyAll() {
notificationsStore = [];
notificationCounter = 0;
return when(notificationsStore);
},

/**
Expand All @@ -75,14 +39,14 @@ notifications = {
* **takes:** a notification object of the form
* ```
* msg = { notifications: [{
* type: 'error', // this can be 'error', 'success', 'warn' and 'info'
* message: 'This is an error', // A string. Should fit in one line.
* location: 'bottom', // A string where this notification should appear. can be 'bottom' or 'top'
* dismissible: true // A Boolean. Whether the notification is dismissible or not.
* }] };
* type: 'error', // this can be 'error', 'success', 'warn' and 'info'
* message: 'This is an error', // A string. Should fit in one line.
* location: 'bottom', // A string where this notification should appear. can be 'bottom' or 'top'
* dismissible: true // A Boolean. Whether the notification is dismissible or not.
* }] };
* ```
*/
add: function add(object) {
add: function add(object, options) {

var defaults = {
dismissible: true,
Expand All @@ -91,21 +55,73 @@ notifications = {
},
addedNotifications = [];

return canThis(options.context).add.notification().then(function () {
return utils.checkObject(object, 'notifications').then(function (checkedNotificationData) {
_.each(checkedNotificationData.notifications, function (notification) {
notificationCounter = notificationCounter + 1;

return utils.checkObject(object, 'notifications').then(function (checkedNotificationData) {
_.each(checkedNotificationData.notifications, function (notification) {
notificationCounter = notificationCounter + 1;
notification = _.assign(defaults, notification, {
id: notificationCounter
//status: 'persistent'
});

notification = _.assign(defaults, notification, {
id: notificationCounter
//status: 'persistent'
notificationsStore.push(notification);
addedNotifications.push(notification);
});

notificationsStore.push(notification);
addedNotifications.push(notification);
return when({ notifications: addedNotifications});
});
}, function () {
return when.reject(new errors.NoPermissionError('You do not have permission to add notifications.'));
});
},

/**
* ### Destroy
* Remove a specific notification
*
* @param {{id (required), context}} options
* @returns {Promise(Notifications)}
*/
destroy: function destroy(options) {
return canThis(options.context).destroy.notification().then(function () {
var notification = _.find(notificationsStore, function (element) {
return element.id === parseInt(options.id, 10);
});

if (notification && !notification.dismissible) {
return when.reject(
new errors.NoPermissionError('You do not have permission to dismiss this notification.')
);
}

if (!notification) {
return when.reject(new errors.NotFoundError('Notification does not exist.'));
}

notificationsStore = _.reject(notificationsStore, function (element) {
return element.id === parseInt(options.id, 10);
});
return when({notifications: [notification]});
}, function () {
return when.reject(new errors.NoPermissionError('You do not have permission to destroy notifications.'));
});
},

return when({ notifications: addedNotifications});
/**
* ### DestroyAll
* Clear all notifications, used for tests
*
* @private Not exposed over HTTP
* @returns {Promise}
*/
destroyAll: function destroyAll(options) {
return canThis(options.context).destroy.notification().then(function () {
notificationsStore = [];
notificationCounter = 0;
return when(notificationsStore);
}, function () {
return when.reject(new errors.NoPermissionError('You do not have permission to destroy notifications.'));
});
}
};
Expand Down
14 changes: 11 additions & 3 deletions core/server/api/tags.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// # Tag API
// RESTful API for the Tag resource
var dataProvider = require('../models'),
var when = require('when'),
canThis = require('../permissions').canThis,
dataProvider = require('../models'),
errors = require('../errors'),
tags;

/**
Expand All @@ -15,8 +18,13 @@ tags = {
* @returns {Promise(Tags)}
*/
browse: function browse(options) {
return dataProvider.Tag.findAll(options).then(function (result) {
return { tags: result.toJSON() };
return canThis(options.context).browse.tag().then(function () {
return dataProvider.Tag.findAll(options).then(function (result) {
return { tags: result.toJSON() };
});

}, function () {
return when.reject(new errors.NoPermissionError('You do not have permission to browse tags.'));
});
}
};
Expand Down
4 changes: 2 additions & 2 deletions core/server/api/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ users = {
options: {}
}]
};
return mail.send(payload).then(function () {
return mail.send(payload, {context: {internal: true}}).then(function () {
// If status was invited-pending and sending the invitation succeeded, set status to invited.
if (user.status === 'invited-pending') {
return dataProvider.User.edit({status: 'invited'}, {id: user.id});
Expand All @@ -211,7 +211,7 @@ users = {
return when.reject(error);
});
}, function () {
return when.reject(new errors.NoPermissionError('You do not have permission to add a users.'));
return when.reject(new errors.NoPermissionError('You do not have permission to add a user.'));
});
},

Expand Down
2 changes: 1 addition & 1 deletion core/server/controllers/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ adminControllers = {

return api.notifications.browse().then(function (results) {
if (!_.some(results.notifications, { message: notification.message })) {
return api.notifications.add({ notifications: [notification] });
return api.notifications.add({ notifications: [notification] }, {context: {internal: true}});
}
});
}).finally(function () {
Expand Down
6 changes: 3 additions & 3 deletions core/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function doFirstRun() {
return api.notifications.add({ notifications: [{
type: 'info',
message: firstRunMessage.join(' ')
}] });
}] }, {context: {internal: true}});
}

function initDbHashAndFirstRun() {
Expand Down Expand Up @@ -176,7 +176,7 @@ function initNotifications() {
"It is recommended that you explicitly configure an e-mail service,",
"See <a href=\"http://docs.ghost.org/mail\">http://docs.ghost.org/mail</a> for instructions"
].join(' ')
}] });
}] }, {context: {internal: true}});
}
if (mailer.state && mailer.state.emailDisabled) {
api.notifications.add({ notifications: [{
Expand All @@ -185,7 +185,7 @@ function initNotifications() {
"Ghost is currently unable to send e-mail.",
"See <a href=\"http://docs.ghost.org/mail\">http://docs.ghost.org/mail</a> for instructions"
].join(' ')
}] });
}] }, {context: {internal: true}});
}
}

Expand Down
4 changes: 0 additions & 4 deletions core/server/middleware/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,6 @@ setupMiddleware = function (server) {
// local data
expressServer.use(ghostLocals);

// So on every request we actually clean out redundant passive notifications from the server side
// ToDo: Remove when ember handles passive notifications.
expressServer.use(middleware.cleanNotifications);

// ### Routing
// Set up API routes
expressServer.use(subdir + routes.apiBaseUri, routes.api(middleware));
Expand Down
17 changes: 0 additions & 17 deletions core/server/middleware/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,6 @@ var middleware = {
next();
},

// While we're here, let's clean up on aisle 5
// That being ghost.notifications, and let's remove the passives from there
// plus the local messages, as they have already been added at this point
// otherwise they'd appear one too many times
// ToDo: Remove once ember handles passive notifications.
cleanNotifications: function (req, res, next) {
/*jslint unparam:true*/
api.notifications.browse().then(function (notifications) {
_.each(notifications.notifications, function (notification) {
if (notification.status === 'passive') {
api.notifications.destroy(notification);
}
});
next();
});
},

// ### CacheControl Middleware
// provide sensible cache control headers
cacheControl: function (options) {
Expand Down
Loading

0 comments on commit cfaa6f0

Please sign in to comment.