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) {
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) {
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) {
var feed = new rss({
title: 'Recently Active Topics',

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

@ -1,19 +1,20 @@
var RDB = require('./redis.js'),
utils = require('./../public/src/utils.js'),
user = require('./user.js'),
topics = require('./topics.js'),
categories = require('./categories.js'),
favourites = require('./favourites.js'),
threadTools = require('./threadTools.js'),
var RDB = require('./redis'),
utils = require('./../public/src/utils'),
user = require('./user'),
topics = require('./topics'),
categories = require('./categories'),
favourites = require('./favourites'),
threadTools = require('./threadTools'),
postTools = require('./postTools'),
categories = require('./categories'),
feed = require('./feed.js'),
async = require('async'),
feed = require('./feed'),
plugins = require('./plugins'),
meta = require('./meta'),
async = require('async'),
reds = require('reds'),
postSearch = reds.createSearch('nodebbpostsearch'),
nconf = require('nconf'),
meta = require('./meta.js'),
validator = require('validator'),
winston = require('winston');
@ -125,60 +126,63 @@ var RDB = require('./redis.js'),
};
Posts.reply = function(tid, uid, content, callback) {
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);
threadTools.privileges(tid, uid, function(err, privileges) {
if (content) {
content = content.trim();
}
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);
}
if (!content || content.length < meta.config.minimumPostLength) {
return callback(new Error('content-too-short'));
} else if (!privileges.write) {
return callback(new Error('no-privileges'));
}
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) {
Posts.create(uid, tid, content, function(err, postData) {
if(err) {
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;
// 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() {
categories.getCategoryById(req.params.id, uid, function (err, data) {
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) {
var Utils = require('../../public/src/utils.js');

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

@ -2,16 +2,17 @@ var RDB = require('./redis'),
posts = require('./posts'),
utils = require('./../public/src/utils'),
user = require('./user'),
Groups = require('./groups')
categories = require('./categories'),
CategoryTools = require('./categoryTools'),
posts = require('./posts'),
threadTools = require('./threadTools'),
postTools = require('./postTools'),
notifications = require('./notifications'),
async = require('async'),
feed = require('./feed'),
favourites = require('./favourites'),
meta = require('./meta'),
async = require('async'),
reds = require('reds'),
topicSearch = reds.createSearch('nodebbtopicsearch'),
nconf = require('nconf'),
@ -20,98 +21,104 @@ var RDB = require('./redis'),
(function(Topics) {
Topics.post = function(uid, title, content, category_id, callback) {
if (!category_id)
throw new Error('Attempted to post without a category_id');
if (content)
content = content.trim();
if (title)
title = title.trim();
if (!uid) {
callback(new Error('not-logged-in'), null);
return;
} else if (!title || title.length < meta.config.minimumTitleLength) {
callback(new Error('title-too-short'), null);
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);
CategoryTools.privileges(category_id, uid, function(err, privileges) {
if (privileges.write) {
if (!category_id)
throw new Error('Attempted to post without a category_id');
if (content)
content = content.trim();
if (title)
title = title.trim();
if (!uid) {
callback(new Error('not-logged-in'), null);
return;
} else if (!title || title.length < meta.config.minimumTitleLength) {
callback(new Error('title-too-short'), null);
return;
} else if (!content || content.length < meta.config.miminumPostLength) {
callback(new Error('content-too-short'), null);
return;
}
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
});
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);
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
RDB.del('cid:' + category_id + ':read_by_uid', function(err, data) {
Topics.markAsRead(tid, uid);
});
// let everyone know that there is an unread topic in this category
RDB.del('cid:' + category_id + ':read_by_uid', function(err, data) {
Topics.markAsRead(tid, uid);
});
// 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.hincrby('category:' + category_id, 'topic_count', 1);
RDB.incr('totaltopiccount');
// 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.hincrby('category:' + category_id, 'topic_count', 1);
RDB.incr('totaltopiccount');
feed.updateCategory(category_id);
feed.updateCategory(category_id);
posts.create(uid, tid, content, function(err, postData) {
if(err) {
return callback(err, null);
} else if(!postData) {
return callback(new Error('invalid-post'), null);
}
posts.create(uid, tid, content, function(err, postData) {
if(err) {
return callback(err, null);
} else if(!postData) {
return callback(new Error('invalid-post'), null);
}
// Auto-subscribe the post creator to the newly created topic
threadTools.toggleFollow(tid, uid);
// Auto-subscribe the post creator to the newly created topic
threadTools.toggleFollow(tid, uid);
Topics.getTopicForCategoryView(tid, uid, function(topicData) {
topicData.unreplied = 1;
Topics.getTopicForCategoryView(tid, uid, function(topicData) {
topicData.unreplied = 1;
callback(null, {
topicData: topicData,
postData: postData
callback(null, {
topicData: topicData,
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
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);
});
}
@ -486,27 +493,25 @@ var RDB = require('./redis'),
function getTopicData(next) {
Topics.getTopicData(tid, 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);
});
}
};
function getPrivileges(next) {
threadTools.privileges(tid, current_user, function(privData) {
next(null, privData);
});
}
threadTools.privileges(tid, current_user, next);
};
function getCategoryData(next) {
Topics.getCategoryData(tid, next);
}
};
async.parallel([getTopicData, getTopicPosts, getPrivileges, getCategoryData], function(err, results) {
if (err) {
console.log(err.message);
winston.error('[Topics.getTopicWithPosts] Could not retrieve topic data: ', err.message);
callback(err, null);
return;
}

@ -723,14 +723,14 @@ var bcrypt = require('bcrypt'),
User.isModerator = function(uid, cid, callback) {
RDB.sismember('cid:' + cid + ':moderators', uid, function(err, exists) {
RDB.handle(err);
callback( !! exists);
callback(err, !! exists);
});
};
User.isAdministrator = function(uid, callback) {
Groups.getGidFromName('Administrators', function(err, gid) {
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) {
if(err) {
if(err.message === 'not-logged-in') {
if (err.message === 'not-logged-in') {
socket.emit('event:alert', {
title: 'Thank you for posting',
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);
} else if (err.message === 'too-many-posts') {
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 {
socket.emit('event:alert', {
title: 'Error',
@ -423,8 +430,7 @@ module.exports.init = function(io) {
posts.reply(data.topic_id, uid, data.content, function(err, postData) {
if(err) {
if(err.message === 'content-too-short') {
if (err.message === 'content-too-short') {
posts.emitContentTooShortAlert(socket);
} else if (err.message === 'too-many-posts') {
posts.emitTooManyPostsAlert(socket);
@ -435,6 +441,13 @@ module.exports.init = function(io) {
type: 'warning',
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;
}
@ -495,8 +508,8 @@ module.exports.init = function(io) {
});
socket.on('api:topic.delete', function(data) {
threadTools.privileges(data.tid, uid, function(privileges) {
if (privileges.editable) {
threadTools.privileges(data.tid, uid, function(err, privileges) {
if (!err && privileges.editable) {
threadTools.delete(data.tid, function(err) {
if (!err) {
emitTopicPostStats();
@ -511,8 +524,8 @@ module.exports.init = function(io) {
});
socket.on('api:topic.restore', function(data) {
threadTools.privileges(data.tid, uid, function(privileges) {
if (privileges.editable) {
threadTools.privileges(data.tid, uid, function(err, privileges) {
if (!err && privileges.editable) {
threadTools.restore(data.tid, socket, function(err) {
emitTopicPostStats();
@ -526,32 +539,32 @@ module.exports.init = function(io) {
});
socket.on('api:topic.lock', function(data) {
threadTools.privileges(data.tid, uid, function(privileges) {
if (privileges.editable) {
threadTools.privileges(data.tid, uid, function(err, privileges) {
if (!err && privileges.editable) {
threadTools.lock(data.tid, socket);
}
});
});
socket.on('api:topic.unlock', function(data) {
threadTools.privileges(data.tid, uid, function(privileges) {
if (privileges.editable) {
threadTools.privileges(data.tid, uid, function(err, privileges) {
if (!err && privileges.editable) {
threadTools.unlock(data.tid, socket);
}
});
});
socket.on('api:topic.pin', function(data) {
threadTools.privileges(data.tid, uid, function(privileges) {
if (privileges.editable) {
threadTools.privileges(data.tid, uid, function(err, privileges) {
if (!err && privileges.editable) {
threadTools.pin(data.tid, socket);
}
});
});
socket.on('api:topic.unpin', function(data) {
threadTools.privileges(data.tid, uid, function(privileges) {
if (privileges.editable) {
threadTools.privileges(data.tid, uid, function(err, privileges) {
if (!err && privileges.editable) {
threadTools.unpin(data.tid, socket);
}
});

Loading…
Cancel
Save