0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-06 22:40:14 -05:00

Merge pull request #3312 from ErisDS/issue-2739-2

Wire permmissions for notifications, mail and tags
This commit is contained in:
Hannah Wolfe 2014-07-17 18:04:38 +01:00
commit 487844122d
14 changed files with 265 additions and 190 deletions

View file

@ -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) {
@ -197,7 +197,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.",

View file

@ -2,6 +2,7 @@
// API for sending Mail
var when = require('when'),
config = require('../config'),
canThis = require('../permissions').canThis,
errors = require('../errors'),
mail;
@ -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.'));
});
},
/**
@ -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>' +
@ -65,7 +70,7 @@ mail = {
}
}]};
return mail.send(payload);
return mail.send(payload, options);
}
};

View file

@ -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'),
@ -23,8 +24,56 @@ notifications = {
* Fetch all notifications
* @returns {Promise(Notifications)}
*/
browse: function browse() {
return when({ 'notifications': notificationsStore });
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.'));
});
},
/**
* ### Add
*
*
* **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.
* }] };
* ```
*/
add: function add(object, options) {
var defaults = {
dismissible: true,
location: 'bottom',
status: 'persistent'
},
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;
notification = _.assign(defaults, notification, {
id: notificationCounter
//status: 'persistent'
});
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.'));
});
},
/**
@ -35,24 +84,28 @@ notifications = {
* @returns {Promise(Notifications)}
*/
destroy: function destroy(options) {
var notification = _.find(notificationsStore, function (element) {
return element.id === parseInt(options.id, 10);
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.'));
});
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]});
},
/**
@ -62,50 +115,13 @@ notifications = {
* @private Not exposed over HTTP
* @returns {Promise}
*/
destroyAll: function destroyAll() {
notificationsStore = [];
notificationCounter = 0;
return when(notificationsStore);
},
/**
* ### Add
*
*
* **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.
* }] };
* ```
*/
add: function add(object) {
var defaults = {
dismissible: true,
location: 'bottom',
status: 'persistent'
},
addedNotifications = [];
return utils.checkObject(object, 'notifications').then(function (checkedNotificationData) {
_.each(checkedNotificationData.notifications, function (notification) {
notificationCounter = notificationCounter + 1;
notification = _.assign(defaults, notification, {
id: notificationCounter
//status: 'persistent'
});
notificationsStore.push(notification);
addedNotifications.push(notification);
});
return when({ notifications: addedNotifications});
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.'));
});
}
};

View file

@ -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;
/**
@ -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.'));
});
}
};

View file

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

View file

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

View file

@ -48,7 +48,7 @@ function doFirstRun() {
return api.notifications.add({ notifications: [{
type: 'info',
message: firstRunMessage.join(' ')
}] });
}] }, {context: {internal: true}});
}
function initDbHashAndFirstRun() {
@ -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: [{
@ -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}});
}
}

View file

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

View file

@ -79,23 +79,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) {

View file

@ -36,6 +36,7 @@ describe('DB API', function () {
});
it('delete all content', function (done) {
var options = {context: {user: 1}};
permissions.init().then(function () {
return dbAPI.deleteAllContent({context: {user: 1}});
}).then(function (result) {
@ -43,13 +44,13 @@ describe('DB API', function () {
result.db.should.be.instanceof(Array);
result.db.should.be.empty;
}).then(function () {
return TagsAPI.browse().then(function (results) {
return TagsAPI.browse(options).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.equal(0);
});
}).then(function () {
return PostAPI.browse().then(function (results) {
return PostAPI.browse(options).then(function (results) {
should.exist(results);
results.posts.length.should.equal(0);
done();
@ -102,17 +103,17 @@ describe('DB API', function () {
it('import content is denied', function (done) {
permissions.init().then(function () {
return dbAPI.importContent({context: {user: 2}});
}).then(function (result){
}).then(function (result) {
done(new Error("Import content is not denied for editor."));
}, function (error) {
error.type.should.eql('NoPermissionError');
return dbAPI.importContent({context: {user: 3}});
}).then(function (result){
}).then(function (result) {
done(new Error("Import content is not denied for author."));
}, function (error) {
error.type.should.eql('NoPermissionError');
return dbAPI.importContent();
}).then(function (result){
}).then(function (result) {
done(new Error("Import content is not denied without authentication."));
}).catch(function (error) {
error.type.should.eql('NoPermissionError');

View file

@ -3,6 +3,7 @@ var testUtils = require('../../utils'),
should = require('should'),
// Stuff we are testing
permissions = require('../../../server/permissions'),
MailAPI = require('../../../server/api/mail');
@ -22,11 +23,11 @@ describe('Mail API', function () {
testUtils.clearData()
.then(function () {
return testUtils.initData();
})
.then(function () {
}).then(function () {
return testUtils.insertDefaultFixtures();
})
.then(function () {
}).then(function () {
return permissions.init();
}).then(function () {
done();
}).catch(done);
});
@ -40,7 +41,8 @@ describe('Mail API', function () {
it('return correct failure message', function (done) {
MailAPI.send(mailData).then(function (response) {
MailAPI.send(mailData, {context: {internal: true}}).then(function (response) {
/*jshint unused:false */
done();
}).catch(function (error) {
error.type.should.eql('EmailError');

View file

@ -3,6 +3,7 @@ var testUtils = require('../../utils'),
should = require('should'),
// Stuff we are testing
permissions = require('../../../server/permissions'),
DataGenerator = require('../../utils/fixtures/data-generator'),
NotificationsAPI = require('../../../server/api/notifications');
@ -18,8 +19,9 @@ describe('Notifications API', function () {
testUtils.initData()
.then(function () {
return testUtils.insertDefaultFixtures();
})
.then(function () {
}).then(function () {
return permissions.init();
}).then(function () {
done();
}).catch(done);
});
@ -30,29 +32,13 @@ describe('Notifications API', function () {
}).catch(done);
});
it('can browse', function (done) {
var msg = {
type: 'error', // this can be 'error', 'success', 'warn' and 'info'
message: 'This is an error', // A string. Should fit in one line.
};
NotificationsAPI.add({ notifications: [msg] }).then(function (notification) {
NotificationsAPI.browse().then(function (results) {
should.exist(results);
should.exist(results.notifications);
results.notifications.length.should.be.above(0);
testUtils.API.checkResponse(results.notifications[0], 'notification');
done();
}).catch(done);
});
});
it('can add, adds defaults', function (done) {
it('can add, adds defaults (internal)', function (done) {
var msg = {
type: 'info',
message: 'Hello, this is dog'
};
NotificationsAPI.add({ notifications: [msg] }).then(function (result) {
NotificationsAPI.add({ notifications: [msg] }, {context: {internal: true}}).then(function (result) {
var notification;
should.exist(result);
@ -67,14 +53,35 @@ describe('Notifications API', function () {
}).catch(done);
});
it('can add, adds id and status', function (done) {
it('can add, adds defaults (owner)', function (done) {
var msg = {
type: 'info',
message: 'Hello, this is dog'
};
NotificationsAPI.add({ notifications: [msg] }, {context: {user: 1}}).then(function (result) {
var notification;
should.exist(result);
should.exist(result.notifications);
notification = result.notifications[0];
notification.dismissible.should.be.true;
should.exist(notification.location);
notification.location.should.equal('bottom');
done();
}).catch(done);
});
it('can add, adds id and status (internal)', function (done) {
var msg = {
type: 'info',
message: 'Hello, this is dog',
id: 99
};
NotificationsAPI.add({ notifications: [msg] }).then(function (result) {
NotificationsAPI.add({ notifications: [msg] }, {context: {internal: true}}).then(function (result) {
var notification;
should.exist(result);
@ -84,22 +91,56 @@ describe('Notifications API', function () {
notification.id.should.be.a.Number;
notification.id.should.not.equal(99);
should.exist(notification.status);
notification.status.should.equal('persistent')
notification.status.should.equal('persistent');
done();
}).catch(done);
});
it('can destroy', function (done) {
it('can browse (internal)', function (done) {
var msg = {
type: 'error', // this can be 'error', 'success', 'warn' and 'info'
message: 'This is an error' // A string. Should fit in one line.
};
NotificationsAPI.add({ notifications: [msg] }, {context: {internal: true}}).then(function (notification) {
NotificationsAPI.browse({context: {internal: true}}).then(function (results) {
should.exist(results);
should.exist(results.notifications);
results.notifications.length.should.be.above(0);
testUtils.API.checkResponse(results.notifications[0], 'notification');
done();
}).catch(done);
});
});
it('can browse (owner)', function (done) {
var msg = {
type: 'error', // this can be 'error', 'success', 'warn' and 'info'
message: 'This is an error' // A string. Should fit in one line.
};
NotificationsAPI.add({ notifications: [msg] }, {context: {internal: true}}).then(function (notification) {
NotificationsAPI.browse({context: {user: 1}}).then(function (results) {
should.exist(results);
should.exist(results.notifications);
results.notifications.length.should.be.above(0);
testUtils.API.checkResponse(results.notifications[0], 'notification');
done();
}).catch(done);
});
});
it('can destroy (internal)', function (done) {
var msg = {
type: 'error',
message: 'Goodbye, cruel world!'
};
NotificationsAPI.add({ notifications: [msg] }).then(function (result) {
NotificationsAPI.add({ notifications: [msg] }, {context: {internal: true}}).then(function (result) {
var notification = result.notifications[0];
NotificationsAPI.destroy({ id: notification.id }).then(function (result) {
NotificationsAPI.destroy({ id: notification.id, context: {internal: true}}).then(function (result) {
should.exist(result);
should.exist(result.notifications);
result.notifications[0].id.should.equal(notification.id);
@ -108,4 +149,23 @@ describe('Notifications API', function () {
}).catch(done);
});
});
it('can destroy (owner)', function (done) {
var msg = {
type: 'error',
message: 'Goodbye, cruel world!'
};
NotificationsAPI.add({ notifications: [msg] }, {context: {internal: true}}).then(function (result) {
var notification = result.notifications[0];
NotificationsAPI.destroy({ id: notification.id, context: {user: 1}}).then(function (result) {
should.exist(result);
should.exist(result.notifications);
result.notifications[0].id.should.equal(notification.id);
done();
}).catch(done);
});
});
});

View file

@ -3,6 +3,7 @@ var testUtils = require('../../utils'),
should = require('should'),
// Stuff we are testing
permissions = require('../../../server/permissions'),
DataGenerator = require('../../utils/fixtures/data-generator'),
TagsAPI = require('../../../server/api/tags');
@ -18,8 +19,13 @@ describe('Tags API', function () {
testUtils.initData()
.then(function () {
return testUtils.insertDefaultFixtures();
})
.then(function () {
}).then(function () {
return testUtils.insertEditorUser();
}).then(function () {
return testUtils.insertAuthorUser();
}).then(function () {
return permissions.init();
}).then(function () {
done();
}).catch(done);
});
@ -30,8 +36,44 @@ describe('Tags API', function () {
}).catch(done);
});
it('can browse', function (done) {
TagsAPI.browse().then(function (results) {
it('can browse (internal)', function (done) {
TagsAPI.browse({context: {internal: true}}).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag');
results.tags[0].created_at.should.be.an.instanceof(Date);
done();
}).catch(done);
});
it('can browse (admin)', function (done) {
TagsAPI.browse({context: {user: 1}}).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag');
results.tags[0].created_at.should.be.an.instanceof(Date);
done();
}).catch(done);
});
it('can browse (editor)', function (done) {
TagsAPI.browse({context: {user: 2}}).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag');
results.tags[0].created_at.should.be.an.instanceof(Date);
done();
}).catch(done);
});
it('can browse (author)', function (done) {
TagsAPI.browse({context: {user: 3}}).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);

View file

@ -39,44 +39,6 @@ describe('Middleware', function () {
// });
// });
describe('cleanNotifications', function () {
beforeEach(function (done) {
api.notifications.add({ notifications: [{
id: 0,
status: 'passive',
message: 'passive-one'
}] }).then(function () {
return api.notifications.add({ notifications: [{
id: 1,
status: 'passive',
message: 'passive-two'
}] });
}).then(function () {
return api.notifications.add({ notifications: [{
id: 2,
status: 'aggressive',
message: 'aggressive'
}] });
}).then(function () {
done();
}).catch(done);
});
it('should clean all passive messages', function (done) {
middleware.cleanNotifications(null, null, function () {
api.notifications.browse().then(function (notifications) {
should(notifications.notifications.length).eql(1);
var passiveMsgs = _.filter(notifications.notifications, function (notification) {
return notification.status === 'passive';
});
assert.equal(passiveMsgs.length, 0);
done();
}).catch(done);
});
});
});
describe('cacheControl', function () {
var res;