category whitelist for replying to posts, lots of refactoring, too

v1.18.x
Julian Lam 11 years ago
parent 182659d0e1
commit 5ee5c8179a

@ -171,28 +171,6 @@ var RDB = require('./redis.js'),
}); });
}; };
Categories.privileges = function(cid, uid, callback) {
function isModerator(next) {
user.isModerator(uid, cid, function(isMod) {
next(null, isMod);
});
}
function isAdministrator(next) {
user.isAdministrator(uid, function(isAdmin) {
next(null, isAdmin);
});
}
async.parallel([isModerator, isAdministrator], function(err, results) {
callback({
editable: results.indexOf(true) !== -1 ? true : false,
view_deleted: results.indexOf(true) !== -1 ? true : false
});
});
};
Categories.isTopicsRead = function(cid, uid, callback) { Categories.isTopicsRead = function(cid, uid, callback) {
RDB.zrange('categories:' + cid + ':tid', 0, -1, function(err, tids) { RDB.zrange('categories:' + cid + ':tid', 0, -1, function(err, tids) {

@ -0,0 +1,32 @@
var Groups = require('./groups'),
User = require('./user'),
async = require('async'),
CategoryTools = {};
CategoryTools.privileges = function(cid, uid, callback) {
async.parallel({
"+r": function(next) {
Groups.isMemberByGroupName(uid, 'cid:' + cid + ':privileges:+r', next);
},
"+w": function(next) {
Groups.isMemberByGroupName(uid, 'cid:' + cid + ':privileges:+w', next);
},
moderator: function(next) {
User.isModerator(uid, cid, next);
},
admin: function(next) {
User.isAdministrator(uid, next);
}
}, function(err, privileges) {
callback(err, !privileges ? null : {
read: privileges['+r'] || privileges.moderator || privileges.admin,
write: privileges['+w'] || privileges.moderator || privileges.admin,
editable: privileges.moderator || privileges.admin,
view_deleted: privileges.moderator || privileges.admin
});
});
};
module.exports = CategoryTools;

@ -108,8 +108,6 @@
}; };
Feed.updateRecent = function(callback) { Feed.updateRecent = function(callback) {
console.log('entered');
if (process.env.NODE_ENV === 'development') winston.info('[rss] Updating Recent Posts RSS feed');
topics.getLatestTopics(0, 0, 19, undefined, function (err, recentData) { topics.getLatestTopics(0, 0, 19, undefined, function (err, recentData) {
var feed = new rss({ var feed = new rss({
title: 'Recently Active Topics', title: 'Recently Active Topics',

@ -36,9 +36,7 @@ var RDB = require('./redis'),
function getThreadPrivileges(next) { function getThreadPrivileges(next) {
posts.getPostField(pid, 'tid', function(err, tid) { posts.getPostField(pid, 'tid', function(err, tid) {
threadTools.privileges(tid, uid, function(privileges) { threadTools.privileges(tid, uid, next);
next(null, privileges);
});
}); });
} }

@ -1,19 +1,20 @@
var RDB = require('./redis.js'), var RDB = require('./redis'),
utils = require('./../public/src/utils.js'), utils = require('./../public/src/utils'),
user = require('./user.js'), user = require('./user'),
topics = require('./topics.js'), topics = require('./topics'),
categories = require('./categories.js'), categories = require('./categories'),
favourites = require('./favourites.js'), favourites = require('./favourites'),
threadTools = require('./threadTools.js'), threadTools = require('./threadTools'),
postTools = require('./postTools'), postTools = require('./postTools'),
categories = require('./categories'), categories = require('./categories'),
feed = require('./feed.js'), feed = require('./feed'),
async = require('async'),
plugins = require('./plugins'), plugins = require('./plugins'),
meta = require('./meta'),
async = require('async'),
reds = require('reds'), reds = require('reds'),
postSearch = reds.createSearch('nodebbpostsearch'), postSearch = reds.createSearch('nodebbpostsearch'),
nconf = require('nconf'), nconf = require('nconf'),
meta = require('./meta.js'),
validator = require('validator'), validator = require('validator'),
winston = require('winston'); winston = require('winston');
@ -125,60 +126,63 @@ var RDB = require('./redis.js'),
}; };
Posts.reply = function(tid, uid, content, callback) { Posts.reply = function(tid, uid, content, callback) {
if(content) { threadTools.privileges(tid, uid, function(err, privileges) {
content = content.trim(); if (content) {
} content = content.trim();
if (!content || content.length < meta.config.minimumPostLength) {
callback(new Error('content-too-short'), null);
return;
}
Posts.create(uid, tid, content, function(err, postData) {
if(err) {
return callback(err, null);
} else if(!postData) {
callback(new Error('reply-error'), null);
} }
async.parallel([ if (!content || content.length < meta.config.minimumPostLength) {
function(next) { return callback(new Error('content-too-short'));
topics.markUnRead(tid, function(err) { } else if (!privileges.write) {
if(err) { return callback(new Error('no-privileges'));
return next(err); }
}
topics.markAsRead(tid, uid);
next();
});
},
function(next) {
Posts.getCidByPid(postData.pid, function(err, cid) {
if(err) {
return next(err);
}
RDB.del('cid:' + cid + ':read_by_uid'); Posts.create(uid, tid, content, function(err, postData) {
next();
});
},
function(next) {
threadTools.notifyFollowers(tid, uid);
next();
},
function(next) {
Posts.addUserInfoToPost(postData, function(err) {
if(err) {
return next(err);
}
next();
});
}
], function(err, results) {
if(err) { if(err) {
return callback(err, null); return callback(err, null);
} else if(!postData) {
callback(new Error('reply-error'), null);
} }
callback(null, postData); async.parallel([
function(next) {
topics.markUnRead(tid, function(err) {
if(err) {
return next(err);
}
topics.markAsRead(tid, uid);
next();
});
},
function(next) {
Posts.getCidByPid(postData.pid, function(err, cid) {
if(err) {
return next(err);
}
RDB.del('cid:' + cid + ':read_by_uid');
next();
});
},
function(next) {
threadTools.notifyFollowers(tid, uid);
next();
},
function(next) {
Posts.addUserInfoToPost(postData, function(err) {
if(err) {
return next(err);
}
next();
});
}
], function(err, results) {
if(err) {
return callback(err, null);
}
callback(null, postData);
});
}); });
}); });
} }

@ -130,7 +130,7 @@ var user = require('./../user.js'),
var uid = (req.user) ? req.user.uid : 0; var uid = (req.user) ? req.user.uid : 0;
// Category Whitelisting (support for "-r" to come later) // Category Whitelisting (support for "-r" to come later)
var whitelistReadKey = 'cid:' + req.params.id + ':permissions:+r', var whitelistReadKey = 'cid:' + req.params.id + ':privileges:+r',
success = function() { success = function() {
categories.getCategoryById(req.params.id, uid, function (err, data) { categories.getCategoryById(req.params.id, uid, function (err, data) {
if (!err && data && data.disabled === "0") if (!err && data && data.disabled === "0")

@ -55,15 +55,6 @@ var DebugRoute = function(app) {
}); });
}); });
app.get('/prune', function(req, res) {
var Notifications = require('../notifications');
Notifications.prune(new Date(), function() {
console.log('done');
});
res.send();
});
app.get('/uuidtest', function(req, res) { app.get('/uuidtest', function(req, res) {
var Utils = require('../../public/src/utils.js'); var Utils = require('../../public/src/utils.js');

@ -1,16 +1,18 @@
var RDB = require('./redis.js'), var RDB = require('./redis'),
topics = require('./topics.js'), topics = require('./topics'),
categories = require('./categories.js'), categories = require('./categories'),
user = require('./user.js'), CategoryTools = require('./categoryTools'),
user = require('./user'),
async = require('async'), async = require('async'),
notifications = require('./notifications.js'), notifications = require('./notifications'),
posts = require('./posts'), posts = require('./posts'),
meta = require('./meta'),
websockets = require('./websockets');
reds = require('reds'), reds = require('reds'),
topicSearch = reds.createSearch('nodebbtopicsearch'), topicSearch = reds.createSearch('nodebbtopicsearch'),
winston = require('winston'), winston = require('winston'),
meta = require('./meta'),
nconf = require('nconf'), nconf = require('nconf'),
websockets = require('./websockets');
(function(ThreadTools) { (function(ThreadTools) {
@ -22,28 +24,24 @@ var RDB = require('./redis.js'),
} }
ThreadTools.privileges = function(tid, uid, callback) { ThreadTools.privileges = function(tid, uid, callback) {
//todo: break early if one condition is true async.parallel({
categoryPrivs: function(next) {
function getCategoryPrivileges(next) { topics.getTopicField(tid, 'cid', function(err, cid) {
topics.getTopicField(tid, 'cid', function(err, cid) { CategoryTools.privileges(cid, uid, next);
categories.privileges(cid, uid, function(privileges) {
next(null, privileges);
}); });
}); },
} hasEnoughRep: function(next) {
user.getUserField(uid, 'reputation', function(err, reputation) {
function hasEnoughRep(next) { if (err) return next(null, false);
user.getUserField(uid, 'reputation', function(err, reputation) { next(null, parseInt(reputation, 10) >= parseInt(meta.config['privileges:manage_topic'], 10));
if (err) return next(null, false); });
next(null, parseInt(reputation, 10) >= parseInt(meta.config['privileges:manage_topic'], 10)); }
}); }, function(err, results) {
} callback(err, !results ? undefined : {
read: results.categoryPrivs.read,
write: results.categoryPrivs.write,
async.parallel([getCategoryPrivileges, hasEnoughRep], function(err, results) { editable: results.categoryPrivs.editable || results.hasEnoughRep,
callback({ view_deleted: results.categoryPrivs.view_deleted || results.hasEnoughRep
editable: results[0].editable || results[1],
view_deleted: results[0].view_deleted || results[1]
}); });
}); });
} }

@ -2,16 +2,17 @@ var RDB = require('./redis'),
posts = require('./posts'), posts = require('./posts'),
utils = require('./../public/src/utils'), utils = require('./../public/src/utils'),
user = require('./user'), user = require('./user'),
Groups = require('./groups')
categories = require('./categories'), categories = require('./categories'),
CategoryTools = require('./categoryTools'),
posts = require('./posts'), posts = require('./posts'),
threadTools = require('./threadTools'), threadTools = require('./threadTools'),
postTools = require('./postTools'), postTools = require('./postTools'),
notifications = require('./notifications'), notifications = require('./notifications'),
async = require('async'),
feed = require('./feed'), feed = require('./feed'),
favourites = require('./favourites'), favourites = require('./favourites'),
meta = require('./meta'), meta = require('./meta'),
async = require('async'),
reds = require('reds'), reds = require('reds'),
topicSearch = reds.createSearch('nodebbtopicsearch'), topicSearch = reds.createSearch('nodebbtopicsearch'),
nconf = require('nconf'), nconf = require('nconf'),
@ -20,98 +21,104 @@ var RDB = require('./redis'),
(function(Topics) { (function(Topics) {
Topics.post = function(uid, title, content, category_id, callback) { Topics.post = function(uid, title, content, category_id, callback) {
if (!category_id) CategoryTools.privileges(category_id, uid, function(err, privileges) {
throw new Error('Attempted to post without a category_id'); if (privileges.write) {
if (!category_id)
if (content) throw new Error('Attempted to post without a category_id');
content = content.trim();
if (title) if (content)
title = title.trim(); content = content.trim();
if (title)
if (!uid) { title = title.trim();
callback(new Error('not-logged-in'), null);
return; if (!uid) {
} else if (!title || title.length < meta.config.minimumTitleLength) { callback(new Error('not-logged-in'), null);
callback(new Error('title-too-short'), null); return;
return; } else if (!title || title.length < meta.config.minimumTitleLength) {
} else if (!content || content.length < meta.config.miminumPostLength) { callback(new Error('title-too-short'), null);
callback(new Error('content-too-short'), null); return;
return; } else if (!content || content.length < meta.config.miminumPostLength) {
} callback(new Error('content-too-short'), null);
return;
user.getUserField(uid, 'lastposttime', function(err, lastposttime) {
if (err) lastposttime = 0;
if (Date.now() - lastposttime < meta.config.postDelay * 1000) {
callback(new Error('too-many-posts'), null);
return;
}
RDB.incr('next_topic_id', function(err, tid) {
RDB.handle(err);
// Global Topics
if (uid == null) uid = 0;
if (uid !== null) {
RDB.sadd('topics:tid', tid);
} else {
// need to add some unique key sent by client so we can update this with the real uid later
RDB.lpush('topics:queued:tid', tid);
} }
var slug = tid + '/' + utils.slugify(title); user.getUserField(uid, 'lastposttime', function(err, lastposttime) {
var timestamp = Date.now(); if (err) lastposttime = 0;
RDB.hmset('topic:' + tid, { if (Date.now() - lastposttime < meta.config.postDelay * 1000) {
'tid': tid, callback(new Error('too-many-posts'), null);
'uid': uid, return;
'cid': category_id, }
'title': title,
'slug': slug, RDB.incr('next_topic_id', function(err, tid) {
'timestamp': timestamp, RDB.handle(err);
'lastposttime': 0,
'postcount': 0, // Global Topics
'viewcount': 0, if (uid == null) uid = 0;
'locked': 0, if (uid !== null) {
'deleted': 0, RDB.sadd('topics:tid', tid);
'pinned': 0 } else {
}); // need to add some unique key sent by client so we can update this with the real uid later
RDB.lpush('topics:queued:tid', tid);
}
var slug = tid + '/' + utils.slugify(title);
var timestamp = Date.now();
RDB.hmset('topic:' + tid, {
'tid': tid,
'uid': uid,
'cid': category_id,
'title': title,
'slug': slug,
'timestamp': timestamp,
'lastposttime': 0,
'postcount': 0,
'viewcount': 0,
'locked': 0,
'deleted': 0,
'pinned': 0
});
topicSearch.index(title, tid); topicSearch.index(title, tid);
user.addTopicIdToUser(uid, tid); user.addTopicIdToUser(uid, tid);
// let everyone know that there is an unread topic in this category // let everyone know that there is an unread topic in this category
RDB.del('cid:' + category_id + ':read_by_uid', function(err, data) { RDB.del('cid:' + category_id + ':read_by_uid', function(err, data) {
Topics.markAsRead(tid, uid); Topics.markAsRead(tid, uid);
}); });
// in future it may be possible to add topics to several categories, so leaving the door open here. // in future it may be possible to add topics to several categories, so leaving the door open here.
RDB.zadd('categories:' + category_id + ':tid', timestamp, tid); RDB.zadd('categories:' + category_id + ':tid', timestamp, tid);
RDB.hincrby('category:' + category_id, 'topic_count', 1); RDB.hincrby('category:' + category_id, 'topic_count', 1);
RDB.incr('totaltopiccount'); RDB.incr('totaltopiccount');
feed.updateCategory(category_id); feed.updateCategory(category_id);
posts.create(uid, tid, content, function(err, postData) { posts.create(uid, tid, content, function(err, postData) {
if(err) { if(err) {
return callback(err, null); return callback(err, null);
} else if(!postData) { } else if(!postData) {
return callback(new Error('invalid-post'), null); return callback(new Error('invalid-post'), null);
} }
// Auto-subscribe the post creator to the newly created topic // Auto-subscribe the post creator to the newly created topic
threadTools.toggleFollow(tid, uid); threadTools.toggleFollow(tid, uid);
Topics.getTopicForCategoryView(tid, uid, function(topicData) { Topics.getTopicForCategoryView(tid, uid, function(topicData) {
topicData.unreplied = 1; topicData.unreplied = 1;
callback(null, { callback(null, {
topicData: topicData, topicData: topicData,
postData: postData postData: postData
});
});
}); });
}); });
}); });
}); } else {
callback(new Error('no-privileges'));
}
}); });
}; };
@ -403,7 +410,7 @@ var RDB = require('./redis'),
// temporary. I don't think this call should belong here // temporary. I don't think this call should belong here
function getPrivileges(next) { function getPrivileges(next) {
categories.privileges(category_id, current_user, function(user_privs) { CategoryTools.privileges(category_id, current_user, function(user_privs) {
next(null, user_privs); next(null, user_privs);
}); });
} }
@ -486,27 +493,25 @@ var RDB = require('./redis'),
function getTopicData(next) { function getTopicData(next) {
Topics.getTopicData(tid, next); Topics.getTopicData(tid, next);
} };
function getTopicPosts(next) { function getTopicPosts(next) {
Topics.getTopicPosts(tid, start, end, current_user, function(topicPosts, privileges) { Topics.getTopicPosts(tid, start, end, current_user, function(topicPosts) {
next(null, topicPosts); next(null, topicPosts);
}); });
} };
function getPrivileges(next) { function getPrivileges(next) {
threadTools.privileges(tid, current_user, function(privData) { threadTools.privileges(tid, current_user, next);
next(null, privData); };
});
}
function getCategoryData(next) { function getCategoryData(next) {
Topics.getCategoryData(tid, next); Topics.getCategoryData(tid, next);
} };
async.parallel([getTopicData, getTopicPosts, getPrivileges, getCategoryData], function(err, results) { async.parallel([getTopicData, getTopicPosts, getPrivileges, getCategoryData], function(err, results) {
if (err) { if (err) {
console.log(err.message); winston.error('[Topics.getTopicWithPosts] Could not retrieve topic data: ', err.message);
callback(err, null); callback(err, null);
return; return;
} }

@ -723,14 +723,14 @@ var bcrypt = require('bcrypt'),
User.isModerator = function(uid, cid, callback) { User.isModerator = function(uid, cid, callback) {
RDB.sismember('cid:' + cid + ':moderators', uid, function(err, exists) { RDB.sismember('cid:' + cid + ':moderators', uid, function(err, exists) {
RDB.handle(err); RDB.handle(err);
callback( !! exists); callback(err, !! exists);
}); });
}; };
User.isAdministrator = function(uid, callback) { User.isAdministrator = function(uid, callback) {
Groups.getGidFromName('Administrators', function(err, gid) { Groups.getGidFromName('Administrators', function(err, gid) {
Groups.isMember(uid, gid, function(err, isAdmin) { Groups.isMember(uid, gid, function(err, isAdmin) {
callback( !! isAdmin); callback(err, !! isAdmin);
}); });
}); });
}; };

@ -348,7 +348,7 @@ module.exports.init = function(io) {
topics.post(uid, data.title, data.content, data.category_id, function(err, result) { topics.post(uid, data.title, data.content, data.category_id, function(err, result) {
if(err) { if(err) {
if(err.message === 'not-logged-in') { if (err.message === 'not-logged-in') {
socket.emit('event:alert', { socket.emit('event:alert', {
title: 'Thank you for posting', title: 'Thank you for posting',
message: 'Since you are unregistered, your post is awaiting approval. Click here to register now.', message: 'Since you are unregistered, your post is awaiting approval. Click here to register now.',
@ -364,6 +364,13 @@ module.exports.init = function(io) {
posts.emitContentTooShortAlert(socket); posts.emitContentTooShortAlert(socket);
} else if (err.message === 'too-many-posts') { } else if (err.message === 'too-many-posts') {
posts.emitTooManyPostsAlert(socket); posts.emitTooManyPostsAlert(socket);
} else if (err.message === 'no-privileges') {
socket.emit('event:alert', {
title: 'Unable to post',
message: 'You do not have posting privileges in this category.',
type: 'danger',
timeout: 7500
});
} else { } else {
socket.emit('event:alert', { socket.emit('event:alert', {
title: 'Error', title: 'Error',
@ -423,8 +430,7 @@ module.exports.init = function(io) {
posts.reply(data.topic_id, uid, data.content, function(err, postData) { posts.reply(data.topic_id, uid, data.content, function(err, postData) {
if(err) { if(err) {
if (err.message === 'content-too-short') {
if(err.message === 'content-too-short') {
posts.emitContentTooShortAlert(socket); posts.emitContentTooShortAlert(socket);
} else if (err.message === 'too-many-posts') { } else if (err.message === 'too-many-posts') {
posts.emitTooManyPostsAlert(socket); posts.emitTooManyPostsAlert(socket);
@ -435,6 +441,13 @@ module.exports.init = function(io) {
type: 'warning', type: 'warning',
timeout: 2000 timeout: 2000
}); });
} else if (err.message === 'no-privileges') {
socket.emit('event:alert', {
title: 'Unable to post',
message: 'You do not have posting privileges in this category.',
type: 'danger',
timeout: 7500
});
} }
return; return;
} }
@ -495,8 +508,8 @@ module.exports.init = function(io) {
}); });
socket.on('api:topic.delete', function(data) { socket.on('api:topic.delete', function(data) {
threadTools.privileges(data.tid, uid, function(privileges) { threadTools.privileges(data.tid, uid, function(err, privileges) {
if (privileges.editable) { if (!err && privileges.editable) {
threadTools.delete(data.tid, function(err) { threadTools.delete(data.tid, function(err) {
if (!err) { if (!err) {
emitTopicPostStats(); emitTopicPostStats();
@ -511,8 +524,8 @@ module.exports.init = function(io) {
}); });
socket.on('api:topic.restore', function(data) { socket.on('api:topic.restore', function(data) {
threadTools.privileges(data.tid, uid, function(privileges) { threadTools.privileges(data.tid, uid, function(err, privileges) {
if (privileges.editable) { if (!err && privileges.editable) {
threadTools.restore(data.tid, socket, function(err) { threadTools.restore(data.tid, socket, function(err) {
emitTopicPostStats(); emitTopicPostStats();
@ -526,32 +539,32 @@ module.exports.init = function(io) {
}); });
socket.on('api:topic.lock', function(data) { socket.on('api:topic.lock', function(data) {
threadTools.privileges(data.tid, uid, function(privileges) { threadTools.privileges(data.tid, uid, function(err, privileges) {
if (privileges.editable) { if (!err && privileges.editable) {
threadTools.lock(data.tid, socket); threadTools.lock(data.tid, socket);
} }
}); });
}); });
socket.on('api:topic.unlock', function(data) { socket.on('api:topic.unlock', function(data) {
threadTools.privileges(data.tid, uid, function(privileges) { threadTools.privileges(data.tid, uid, function(err, privileges) {
if (privileges.editable) { if (!err && privileges.editable) {
threadTools.unlock(data.tid, socket); threadTools.unlock(data.tid, socket);
} }
}); });
}); });
socket.on('api:topic.pin', function(data) { socket.on('api:topic.pin', function(data) {
threadTools.privileges(data.tid, uid, function(privileges) { threadTools.privileges(data.tid, uid, function(err, privileges) {
if (privileges.editable) { if (!err && privileges.editable) {
threadTools.pin(data.tid, socket); threadTools.pin(data.tid, socket);
} }
}); });
}); });
socket.on('api:topic.unpin', function(data) { socket.on('api:topic.unpin', function(data) {
threadTools.privileges(data.tid, uid, function(privileges) { threadTools.privileges(data.tid, uid, function(err, privileges) {
if (privileges.editable) { if (!err && privileges.editable) {
threadTools.unpin(data.tid, socket); threadTools.unpin(data.tid, socket);
} }
}); });

Loading…
Cancel
Save