From faccb191ec6ff63c549ef2ccc4e8860f56768f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 20 Jul 2019 22:12:22 -0400 Subject: [PATCH] feat: #7743, privileges --- src/privileges/categories.js | 286 +++++++++--------------- src/privileges/global.js | 149 +++++-------- src/privileges/helpers.js | 246 ++++++++------------- src/privileges/index.js | 4 +- src/privileges/posts.js | 413 ++++++++++++++--------------------- src/privileges/topics.js | 312 ++++++++++---------------- src/privileges/users.js | 241 +++++++------------- 7 files changed, 623 insertions(+), 1028 deletions(-) diff --git a/src/privileges/categories.js b/src/privileges/categories.js index 36d09dab25..b6917fa307 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -1,232 +1,164 @@ 'use strict'; -var async = require('async'); -var _ = require('lodash'); +const _ = require('lodash'); -var categories = require('../categories'); -var user = require('../user'); -var groups = require('../groups'); -var helpers = require('./helpers'); -var plugins = require('../plugins'); +const categories = require('../categories'); +const user = require('../user'); +const groups = require('../groups'); +const helpers = require('./helpers'); +const plugins = require('../plugins'); +const utils = require('../utils'); module.exports = function (privileges) { privileges.categories = {}; - privileges.categories.list = function (cid, callback) { - // Method used in admin/category controller to show all users/groups with privs in that given cid - async.waterfall([ - function (next) { - async.parallel({ - labels: function (next) { - async.parallel({ - users: async.apply(plugins.fireHook, 'filter:privileges.list_human', privileges.privilegeLabels.slice()), - groups: async.apply(plugins.fireHook, 'filter:privileges.groups.list_human', privileges.privilegeLabels.slice()), - }, next); - }, - users: function (next) { - helpers.getUserPrivileges(cid, 'filter:privileges.list', privileges.userPrivilegeList, next); - }, - groups: function (next) { - helpers.getGroupPrivileges(cid, 'filter:privileges.groups.list', privileges.groupPrivilegeList, next); - }, - }, next); - }, - function (payload, next) { - // This is a hack because I can't do {labels.users.length} to echo the count in templates.js - payload.columnCountUser = payload.labels.users.length + 2; - payload.columnCountUserOther = payload.labels.users.length - privileges.privilegeLabels.length; - payload.columnCountGroup = payload.labels.groups.length + 2; - payload.columnCountGroupOther = payload.labels.groups.length - privileges.privilegeLabels.length; - next(null, payload); - }, - ], callback); + // Method used in admin/category controller to show all users/groups with privs in that given cid + privileges.categories.list = async function (cid) { + async function getLabels() { + return await utils.promiseParallel({ + users: plugins.fireHook('filter:privileges.list_human', privileges.privilegeLabels.slice()), + groups: plugins.fireHook('filter:privileges.groups.list_human', privileges.privilegeLabels.slice()), + }); + } + + const payload = await utils.promiseParallel({ + labels: getLabels(), + users: helpers.getUserPrivileges(cid, 'filter:privileges.list', privileges.userPrivilegeList), + groups: helpers.getGroupPrivileges(cid, 'filter:privileges.groups.list', privileges.groupPrivilegeList), + }); + + // This is a hack because I can't do {labels.users.length} to echo the count in templates.js + payload.columnCountUser = payload.labels.users.length + 2; + payload.columnCountUserOther = payload.labels.users.length - privileges.privilegeLabels.length; + payload.columnCountGroup = payload.labels.groups.length + 2; + payload.columnCountGroupOther = payload.labels.groups.length - privileges.privilegeLabels.length; + return payload; }; - privileges.categories.get = function (cid, uid, callback) { - var privs = ['topics:create', 'topics:read', 'topics:tag', 'read']; - async.waterfall([ - function (next) { - async.parallel({ - privileges: function (next) { - helpers.isUserAllowedTo(privs, uid, cid, next); - }, - isAdministrator: function (next) { - user.isAdministrator(uid, next); - }, - isModerator: function (next) { - user.isModerator(uid, cid, next); - }, - }, next); - }, - function (results, next) { - var privData = _.zipObject(privs, results.privileges); - var isAdminOrMod = results.isAdministrator || results.isModerator; + privileges.categories.get = async function (cid, uid) { + const privs = ['topics:create', 'topics:read', 'topics:tag', 'read']; - plugins.fireHook('filter:privileges.categories.get', { - 'topics:create': privData['topics:create'] || results.isAdministrator, - 'topics:read': privData['topics:read'] || results.isAdministrator, - 'topics:tag': privData['topics:tag'] || results.isAdministrator, - read: privData.read || results.isAdministrator, - cid: cid, - uid: uid, - editable: isAdminOrMod, - view_deleted: isAdminOrMod, - isAdminOrMod: isAdminOrMod, - }, next); - }, - ], callback); + const [userPrivileges, isAdministrator, isModerator] = await Promise.all([ + helpers.isUserAllowedTo(privs, uid, cid), + user.isAdministrator(uid), + user.isModerator(uid, cid), + ]); + + const privData = _.zipObject(privs, userPrivileges); + const isAdminOrMod = isAdministrator || isModerator; + + return await plugins.fireHook('filter:privileges.categories.get', { + 'topics:create': privData['topics:create'] || isAdministrator, + 'topics:read': privData['topics:read'] || isAdministrator, + 'topics:tag': privData['topics:tag'] || isAdministrator, + read: privData.read || isAdministrator, + cid: cid, + uid: uid, + editable: isAdminOrMod, + view_deleted: isAdminOrMod, + isAdminOrMod: isAdminOrMod, + }); }; - privileges.categories.isAdminOrMod = function (cid, uid, callback) { + privileges.categories.isAdminOrMod = async function (cid, uid) { if (parseInt(uid, 10) <= 0) { - return setImmediate(callback, null, false); + return false; } - helpers.some([ - function (next) { - user.isModerator(uid, cid, next); - }, - function (next) { - user.isAdministrator(uid, next); - }, - ], callback); + const [isAdmin, isMod] = await Promise.all([ + user.isAdministrator(uid), + user.isModerator(uid, cid), + ]); + return isAdmin || isMod; }; - privileges.categories.isUserAllowedTo = function (privilege, cid, uid, callback) { + privileges.categories.isUserAllowedTo = async function (privilege, cid, uid) { if (!cid) { - return setImmediate(callback, null, false); + return false; } - if (Array.isArray(cid)) { - helpers.isUserAllowedTo(privilege, uid, cid, function (err, results) { - callback(err, Array.isArray(results) && results.length ? results : false); - }); - } else { - helpers.isUserAllowedTo(privilege, uid, [cid], function (err, results) { - callback(err, Array.isArray(results) && results.length ? results[0] : false); - }); + const results = await helpers.isUserAllowedTo(privilege, uid, Array.isArray(cid) ? cid : [cid]); + + if (Array.isArray(results) && results.length) { + return Array.isArray(cid) ? results : results[0]; } + return false; }; - privileges.categories.can = function (privilege, cid, uid, callback) { + privileges.categories.can = async function (privilege, cid, uid) { if (!cid) { - return setImmediate(callback, null, false); + return false; } - async.waterfall([ - function (next) { - async.parallel({ - disabled: async.apply(categories.getCategoryField, cid, 'disabled'), - isAdmin: async.apply(user.isAdministrator, uid), - isAllowed: async.apply(privileges.categories.isUserAllowedTo, privilege, cid, uid), - }, next); - }, - function (results, next) { - next(null, !results.disabled && (results.isAllowed || results.isAdmin)); - }, - ], callback); + const [disabled, isAdmin, isAllowed] = await Promise.all([ + categories.getCategoryField(cid, 'disabled'), + user.isAdministrator(uid), + privileges.categories.isUserAllowedTo(privilege, cid, uid), + ]); + return !disabled && (isAllowed || isAdmin); }; - privileges.categories.filterCids = function (privilege, cids, uid, callback) { + privileges.categories.filterCids = async function (privilege, cids, uid) { if (!Array.isArray(cids) || !cids.length) { - return callback(null, []); + return []; } cids = _.uniq(cids); - - async.waterfall([ - function (next) { - privileges.categories.getBase(privilege, cids, uid, next); - }, - function (results, next) { - cids = cids.filter(function (cid, index) { - return !results.categories[index].disabled && - (results.allowedTo[index] || results.isAdmin); - }); - - next(null, cids.filter(Boolean)); - }, - ], callback); + const results = await privileges.categories.getBase(privilege, cids, uid); + return cids.filter(function (cid, index) { + return !!cid && !results.categories[index].disabled && (results.allowedTo[index] || results.isAdmin); + }); }; - privileges.categories.getBase = function (privilege, cids, uid, callback) { - async.parallel({ - categories: function (next) { - categories.getCategoriesFields(cids, ['disabled'], next); - }, - allowedTo: function (next) { - helpers.isUserAllowedTo(privilege, uid, cids, next); - }, - isAdmin: function (next) { - user.isAdministrator(uid, next); - }, - }, callback); + privileges.categories.getBase = async function (privilege, cids, uid) { + return await utils.promiseParallel({ + categories: categories.getCategoriesFields(cids, ['disabled']), + allowedTo: helpers.isUserAllowedTo(privilege, uid, cids), + isAdmin: user.isAdministrator(uid), + }); }; - privileges.categories.filterUids = function (privilege, cid, uids, callback) { + privileges.categories.filterUids = async function (privilege, cid, uids) { if (!uids.length) { - return setImmediate(callback, null, []); + return []; } uids = _.uniq(uids); - async.waterfall([ - function (next) { - async.parallel({ - allowedTo: function (next) { - helpers.isUsersAllowedTo(privilege, uids, cid, next); - }, - isAdmins: function (next) { - user.isAdministrator(uids, next); - }, - }, next); - }, - function (results, next) { - uids = uids.filter(function (uid, index) { - return results.allowedTo[index] || results.isAdmins[index]; - }); - next(null, uids); - }, - ], callback); + const [allowedTo, isAdmins] = await Promise.all([ + helpers.isUsersAllowedTo(privilege, uids, cid), + user.isAdministrator(uids), + ]); + return uids.filter((uid, index) => allowedTo[index] || isAdmins[index]); }; - privileges.categories.give = function (privileges, cid, groupName, callback) { - helpers.giveOrRescind(groups.join, privileges, cid, groupName, callback); + privileges.categories.give = async function (privileges, cid, groupName) { + await helpers.giveOrRescind(groups.join, privileges, cid, groupName); }; - privileges.categories.rescind = function (privileges, cid, groupName, callback) { - helpers.giveOrRescind(groups.leave, privileges, cid, groupName, callback); + privileges.categories.rescind = async function (privileges, cid, groupName) { + await helpers.giveOrRescind(groups.leave, privileges, cid, groupName); }; - privileges.categories.canMoveAllTopics = function (currentCid, targetCid, uid, callback) { - async.waterfall([ - function (next) { - async.parallel({ - isAdmin: async.apply(user.isAdministrator, uid), - isModerators: async.apply(user.isModerator, uid, [currentCid, targetCid]), - }, next); - }, - function (results, next) { - next(null, results.isAdmin || !results.isModerators.includes(false)); - }, - ], callback); + privileges.categories.canMoveAllTopics = async function (currentCid, targetCid, uid) { + const [isAdmin, isModerators] = await Promise.all([ + user.isAdministrator(uid), + user.isModerator(uid, [currentCid, targetCid]), + ]); + return isAdmin || !isModerators.includes(false); }; - privileges.categories.userPrivileges = function (cid, uid, callback) { - var tasks = {}; - + privileges.categories.userPrivileges = async function (cid, uid) { + const tasks = {}; privileges.userPrivilegeList.forEach(function (privilege) { - tasks[privilege] = async.apply(groups.isMember, uid, 'cid:' + cid + ':privileges:' + privilege); + tasks[privilege] = groups.isMember(uid, 'cid:' + cid + ':privileges:' + privilege); }); - - async.parallel(tasks, callback); + return await utils.promiseParallel(tasks); }; - privileges.categories.groupPrivileges = function (cid, groupName, callback) { - var tasks = {}; - + privileges.categories.groupPrivileges = async function (cid, groupName) { + const tasks = {}; privileges.groupPrivilegeList.forEach(function (privilege) { - tasks[privilege] = async.apply(groups.isMember, groupName, 'cid:' + cid + ':privileges:' + privilege); + tasks[privilege] = groups.isMember(groupName, 'cid:' + cid + ':privileges:' + privilege); }); - - async.parallel(tasks, callback); + return await utils.promiseParallel(tasks); }; }; diff --git a/src/privileges/global.js b/src/privileges/global.js index 7edb32ef12..1c8e8c18e7 100644 --- a/src/privileges/global.js +++ b/src/privileges/global.js @@ -1,13 +1,13 @@ 'use strict'; -var async = require('async'); -var _ = require('lodash'); +const _ = require('lodash'); -var user = require('../user'); -var groups = require('../groups'); -var helpers = require('./helpers'); -var plugins = require('../plugins'); +const user = require('../user'); +const groups = require('../groups'); +const helpers = require('./helpers'); +const plugins = require('../plugins'); +const utils = require('../utils'); module.exports = function (privileges) { privileges.global = {}; @@ -44,108 +44,79 @@ module.exports = function (privileges) { 'group:create', ]; - privileges.global.groupPrivilegeList = privileges.global.userPrivilegeList.map(function (privilege) { - return 'groups:' + privilege; - }); + privileges.global.groupPrivilegeList = privileges.global.userPrivilegeList.map(privilege => 'groups:' + privilege); - privileges.global.list = function (callback) { - async.waterfall([ - function (next) { - async.parallel({ - labels: function (next) { - async.parallel({ - users: async.apply(plugins.fireHook, 'filter:privileges.global.list_human', privileges.global.privilegeLabels.slice()), - groups: async.apply(plugins.fireHook, 'filter:privileges.global.groups.list_human', privileges.global.privilegeLabels.slice()), - }, next); - }, - users: function (next) { - helpers.getUserPrivileges(0, 'filter:privileges.global.list', privileges.global.userPrivilegeList, next); - }, - groups: function (next) { - helpers.getGroupPrivileges(0, 'filter:privileges.global.groups.list', privileges.global.groupPrivilegeList, next); - }, - }, next); - }, - function (payload, next) { - // This is a hack because I can't do {labels.users.length} to echo the count in templates.js - payload.columnCount = payload.labels.users.length + 2; - next(null, payload); - }, - ], callback); + privileges.global.list = async function () { + async function getLabels() { + return await utils.promiseParallel({ + users: plugins.fireHook('filter:privileges.global.list_human', privileges.global.privilegeLabels.slice()), + groups: plugins.fireHook('filter:privileges.global.groups.list_human', privileges.global.privilegeLabels.slice()), + }); + } + const payload = await utils.promiseParallel({ + labels: getLabels(), + users: helpers.getUserPrivileges(0, 'filter:privileges.global.list', privileges.global.userPrivilegeList), + groups: helpers.getGroupPrivileges(0, 'filter:privileges.global.groups.list', privileges.global.groupPrivilegeList), + }); + // This is a hack because I can't do {labels.users.length} to echo the count in templates.js + payload.columnCount = payload.labels.users.length + 2; + return payload; }; - privileges.global.get = function (uid, callback) { - async.waterfall([ - function (next) { - async.parallel({ - privileges: function (next) { - helpers.isUserAllowedTo(privileges.global.userPrivilegeList, uid, 0, next); - }, - isAdministrator: function (next) { - user.isAdministrator(uid, next); - }, - }, next); - }, - function (results, next) { - var privData = _.zipObject(privileges.global.userPrivilegeList, results.privileges); + privileges.global.get = async function (uid) { + const [userPrivileges, isAdministrator] = await Promise.all([ + helpers.isUserAllowedTo(privileges.global.userPrivilegeList, uid, 0), + user.isAdministrator(uid), + ]); - plugins.fireHook('filter:privileges.global.get', { - chat: privData.chat || results.isAdministrator, - 'upload:post:image': privData['upload:post:image'] || results.isAdministrator, - 'upload:post:file': privData['upload:post:file'] || results.isAdministrator, - 'search:content': privData['search:content'] || results.isAdministrator, - 'search:users': privData['search:users'] || results.isAdministrator, - 'search:tags': privData['search:tags'] || results.isAdministrator, - 'view:users': privData['view:users'] || results.isAdministrator, - 'view:tags': privData['view:tags'] || results.isAdministrator, - 'view:groups': privData['view:groups'] || results.isAdministrator, - }, next); - }, - ], callback); + const privData = _.zipObject(privileges.global.userPrivilegeList, userPrivileges); + + return await plugins.fireHook('filter:privileges.global.get', { + chat: privData.chat || isAdministrator, + 'upload:post:image': privData['upload:post:image'] || isAdministrator, + 'upload:post:file': privData['upload:post:file'] || isAdministrator, + 'search:content': privData['search:content'] || isAdministrator, + 'search:users': privData['search:users'] || isAdministrator, + 'search:tags': privData['search:tags'] || isAdministrator, + 'view:users': privData['view:users'] || isAdministrator, + 'view:tags': privData['view:tags'] || isAdministrator, + 'view:groups': privData['view:groups'] || isAdministrator, + }); }; - privileges.global.can = function (privilege, uid, callback) { - helpers.some([ - function (next) { - helpers.isUserAllowedTo(privilege, uid, [0], function (err, results) { - next(err, Array.isArray(results) && results.length ? results[0] : false); - }); - }, - function (next) { - user.isAdministrator(uid, next); - }, - ], callback); + privileges.global.can = async function (privilege, uid) { + const [isAdministrator, isUserAllowedTo] = await Promise.all([ + user.isAdministrator(uid), + helpers.isUserAllowedTo(privilege, uid, [0]), + ]); + return isAdministrator || isUserAllowedTo[0]; }; - privileges.global.canGroup = function (privilege, groupName, callback) { - groups.isMember(groupName, 'cid:0:privileges:groups:' + privilege, callback); + privileges.global.canGroup = async function (privilege, groupName) { + return await groups.isMember(groupName, 'cid:0:privileges:groups:' + privilege); }; - privileges.global.give = function (privileges, groupName, callback) { - helpers.giveOrRescind(groups.join, privileges, 0, groupName, callback); + privileges.global.give = async function (privileges, groupName) { + await helpers.giveOrRescind(groups.join, privileges, 0, groupName); }; - privileges.global.rescind = function (privileges, groupName, callback) { - helpers.giveOrRescind(groups.leave, privileges, 0, groupName, callback); + privileges.global.rescind = async function (privileges, groupName) { + await helpers.giveOrRescind(groups.leave, privileges, 0, groupName); }; - privileges.global.userPrivileges = function (uid, callback) { - var tasks = {}; - + privileges.global.userPrivileges = async function (uid) { + const tasks = {}; privileges.global.userPrivilegeList.forEach(function (privilege) { - tasks[privilege] = async.apply(groups.isMember, uid, 'cid:0:privileges:' + privilege); + tasks[privilege] = groups.isMember(uid, 'cid:0:privileges:' + privilege); }); - - async.parallel(tasks, callback); + return await utils.promiseParallel(tasks); }; - privileges.global.groupPrivileges = function (groupName, callback) { - var tasks = {}; - + privileges.global.groupPrivileges = async function (groupName) { + const tasks = {}; privileges.global.groupPrivilegeList.forEach(function (privilege) { - tasks[privilege] = async.apply(groups.isMember, groupName, 'cid:0:privileges:' + privilege); + tasks[privilege] = groups.isMember(groupName, 'cid:0:privileges:' + privilege); }); - - async.parallel(tasks, callback); + return await utils.promiseParallel(tasks); }; }; diff --git a/src/privileges/helpers.js b/src/privileges/helpers.js index a0933c5c5e..81659d510d 100644 --- a/src/privileges/helpers.js +++ b/src/privileges/helpers.js @@ -1,212 +1,137 @@ 'use strict'; -var async = require('async'); -var _ = require('lodash'); +const _ = require('lodash'); -var groups = require('../groups'); -var user = require('../user'); -var plugins = require('../plugins'); +const groups = require('../groups'); +const user = require('../user'); +const plugins = require('../plugins'); -var helpers = module.exports; +const helpers = module.exports; -var uidToSystemGroup = { +const uidToSystemGroup = { 0: 'guests', '-1': 'spiders', }; -helpers.some = function (tasks, callback) { - async.some(tasks, function (task, next) { - task(next); - }, callback); -}; - -helpers.isUserAllowedTo = function (privilege, uid, cid, callback) { +helpers.isUserAllowedTo = async function (privilege, uid, cid) { if (Array.isArray(privilege) && !Array.isArray(cid)) { - isUserAllowedToPrivileges(privilege, uid, cid, callback); + return await isUserAllowedToPrivileges(privilege, uid, cid); } else if (Array.isArray(cid) && !Array.isArray(privilege)) { - isUserAllowedToCids(privilege, uid, cid, callback); - } else { - return callback(new Error('[[error:invalid-data]]')); + return await isUserAllowedToCids(privilege, uid, cid); } + throw new Error('[[error:invalid-data]]'); }; -function isUserAllowedToCids(privilege, uid, cids, callback) { +async function isUserAllowedToCids(privilege, uid, cids) { if (parseInt(uid, 10) <= 0) { - return isSystemGroupAllowedToCids(privilege, uid, cids, callback); + return await isSystemGroupAllowedToCids(privilege, uid, cids); } - var userKeys = []; - var groupKeys = []; + const userKeys = []; + const groupKeys = []; cids.forEach(function (cid) { userKeys.push('cid:' + cid + ':privileges:' + privilege); groupKeys.push('cid:' + cid + ':privileges:groups:' + privilege); }); - checkIfAllowed(uid, userKeys, groupKeys, callback); + return await checkIfAllowed(uid, userKeys, groupKeys); } -function isUserAllowedToPrivileges(privileges, uid, cid, callback) { +async function isUserAllowedToPrivileges(privileges, uid, cid) { if (parseInt(uid, 10) <= 0) { - return isSystemGroupAllowedToPrivileges(privileges, uid, cid, callback); + return await isSystemGroupAllowedToPrivileges(privileges, uid, cid); } - var userKeys = []; - var groupKeys = []; + const userKeys = []; + const groupKeys = []; privileges.forEach(function (privilege) { userKeys.push('cid:' + cid + ':privileges:' + privilege); groupKeys.push('cid:' + cid + ':privileges:groups:' + privilege); }); - checkIfAllowed(uid, userKeys, groupKeys, callback); + return await checkIfAllowed(uid, userKeys, groupKeys); } -function checkIfAllowed(uid, userKeys, groupKeys, callback) { - async.waterfall([ - function (next) { - async.parallel({ - hasUserPrivilege: function (next) { - groups.isMemberOfGroups(uid, userKeys, next); - }, - hasGroupPrivilege: function (next) { - groups.isMemberOfGroupsList(uid, groupKeys, next); - }, - }, next); - }, - function (results, next) { - var result = userKeys.map(function (key, index) { - return results.hasUserPrivilege[index] || results.hasGroupPrivilege[index]; - }); - - next(null, result); - }, - ], callback); +async function checkIfAllowed(uid, userKeys, groupKeys) { + const [hasUserPrivilege, hasGroupPrivilege] = await Promise.all([ + groups.isMemberOfGroups(uid, userKeys), + groups.isMemberOfGroupsList(uid, groupKeys), + ]); + return userKeys.map((key, index) => hasUserPrivilege[index] || hasGroupPrivilege[index]); } -helpers.isUsersAllowedTo = function (privilege, uids, cid, callback) { - async.waterfall([ - function (next) { - async.parallel({ - hasUserPrivilege: function (next) { - groups.isMembers(uids, 'cid:' + cid + ':privileges:' + privilege, next); - }, - hasGroupPrivilege: function (next) { - groups.isMembersOfGroupList(uids, 'cid:' + cid + ':privileges:groups:' + privilege, next); - }, - }, next); - }, - function (results, next) { - var result = uids.map(function (uid, index) { - return results.hasUserPrivilege[index] || results.hasGroupPrivilege[index]; - }); - - next(null, result); - }, - ], callback); +helpers.isUsersAllowedTo = async function (privilege, uids, cid) { + const [hasUserPrivilege, hasGroupPrivilege] = await Promise.all([ + groups.isMembers(uids, 'cid:' + cid + ':privileges:' + privilege), + groups.isMembersOfGroupList(uids, 'cid:' + cid + ':privileges:groups:' + privilege), + ]); + return uids.map((uid, index) => hasUserPrivilege[index] || hasGroupPrivilege[index]); }; -function isSystemGroupAllowedToCids(privilege, uid, cids, callback) { +async function isSystemGroupAllowedToCids(privilege, uid, cids) { const groupKeys = cids.map(cid => 'cid:' + cid + ':privileges:groups:' + privilege); - groups.isMemberOfGroups(uidToSystemGroup[uid], groupKeys, callback); + return await groups.isMemberOfGroups(uidToSystemGroup[uid], groupKeys); } -function isSystemGroupAllowedToPrivileges(privileges, uid, cid, callback) { +async function isSystemGroupAllowedToPrivileges(privileges, uid, cid) { const groupKeys = privileges.map(privilege => 'cid:' + cid + ':privileges:groups:' + privilege); - groups.isMemberOfGroups(uidToSystemGroup[uid], groupKeys, callback); + return await groups.isMemberOfGroups(uidToSystemGroup[uid], groupKeys); } -helpers.getUserPrivileges = function (cid, hookName, userPrivilegeList, callback) { - var userPrivileges; - var memberSets; - async.waterfall([ - async.apply(plugins.fireHook, hookName, userPrivilegeList.slice()), - function (_privs, next) { - userPrivileges = _privs; - groups.getMembersOfGroups(userPrivileges.map(privilege => 'cid:' + cid + ':privileges:' + privilege), next); - }, - function (_memberSets, next) { - memberSets = _memberSets.map(function (set) { - return set.map(uid => parseInt(uid, 10)); - }); +helpers.getUserPrivileges = async function (cid, hookName, userPrivilegeList) { + const userPrivileges = await plugins.fireHook(hookName, userPrivilegeList.slice()); + let memberSets = await groups.getMembersOfGroups(userPrivileges.map(privilege => 'cid:' + cid + ':privileges:' + privilege)); + memberSets = memberSets.map(function (set) { + return set.map(uid => parseInt(uid, 10)); + }); - var members = _.uniq(_.flatten(memberSets)); + const members = _.uniq(_.flatten(memberSets)); + const memberData = await user.getUsersFields(members, ['picture', 'username']); - user.getUsersFields(members, ['picture', 'username'], next); - }, - function (memberData, next) { - memberData.forEach(function (member) { - member.privileges = {}; - for (var x = 0, numPrivs = userPrivileges.length; x < numPrivs; x += 1) { - member.privileges[userPrivileges[x]] = memberSets[x].includes(parseInt(member.uid, 10)); - } - }); + memberData.forEach(function (member) { + member.privileges = {}; + for (var x = 0, numPrivs = userPrivileges.length; x < numPrivs; x += 1) { + member.privileges[userPrivileges[x]] = memberSets[x].includes(parseInt(member.uid, 10)); + } + }); - next(null, memberData); - }, - ], callback); + return memberData; }; -helpers.getGroupPrivileges = function (cid, hookName, groupPrivilegeList, callback) { - var groupPrivileges; - async.waterfall([ - async.apply(plugins.fireHook, hookName, groupPrivilegeList.slice()), - function (_privs, next) { - groupPrivileges = _privs; - async.parallel({ - memberSets: function (next) { - groups.getMembersOfGroups(groupPrivileges.map(privilege => 'cid:' + cid + ':privileges:' + privilege), next); - }, - groupNames: function (next) { - groups.getGroups('groups:createtime', 0, -1, next); - }, - }, next); - }, - function (results, next) { - var memberSets = results.memberSets; - var uniqueGroups = _.uniq(_.flatten(memberSets)); +helpers.getGroupPrivileges = async function (cid, hookName, groupPrivilegeList) { + const groupPrivileges = await plugins.fireHook(hookName, groupPrivilegeList.slice()); + const [memberSets, allGroupNames] = await Promise.all([ + groups.getMembersOfGroups(groupPrivileges.map(privilege => 'cid:' + cid + ':privileges:' + privilege)), + groups.getGroups('groups:createtime', 0, -1), + ]); - var groupNames = results.groupNames.filter(groupName => !groupName.includes(':privileges:') && uniqueGroups.includes(groupName)); + const uniqueGroups = _.uniq(_.flatten(memberSets)); - groupNames = groups.ephemeralGroups.concat(groupNames); - moveToFront(groupNames, 'Global Moderators'); - moveToFront(groupNames, 'registered-users'); + let groupNames = allGroupNames.filter(groupName => !groupName.includes(':privileges:') && uniqueGroups.includes(groupName)); - var adminIndex = groupNames.indexOf('administrators'); - if (adminIndex !== -1) { - groupNames.splice(adminIndex, 1); - } + groupNames = groups.ephemeralGroups.concat(groupNames); + moveToFront(groupNames, 'Global Moderators'); + moveToFront(groupNames, 'registered-users'); - var memberPrivs; + const adminIndex = groupNames.indexOf('administrators'); + if (adminIndex !== -1) { + groupNames.splice(adminIndex, 1); + } + const groupData = await groups.getGroupsFields(groupNames, ['private']); + const memberData = groupNames.map(function (member, index) { + const memberPrivs = {}; - var memberData = groupNames.map(function (member) { - memberPrivs = {}; - - for (var x = 0, numPrivs = groupPrivileges.length; x < numPrivs; x += 1) { - memberPrivs[groupPrivileges[x]] = memberSets[x].includes(member); - } - return { - name: member, - privileges: memberPrivs, - }; - }); - - next(null, memberData); - }, - function (memberData, next) { - // Grab privacy info for the groups as well - async.map(memberData, function (member, next) { - async.waterfall([ - function (next) { - groups.isPrivate(member.name, next); - }, - function (isPrivate, next) { - member.isPrivate = isPrivate; - next(null, member); - }, - ], next); - }, next); - }, - ], callback); + for (var x = 0, numPrivs = groupPrivileges.length; x < numPrivs; x += 1) { + memberPrivs[groupPrivileges[x]] = memberSets[x].includes(member); + } + return { + name: member, + privileges: memberPrivs, + isPrivate: groupData[index] && !!groupData[index].private, + }; + }); + return memberData; }; function moveToFront(groupNames, groupToMove) { @@ -218,16 +143,19 @@ function moveToFront(groupNames, groupToMove) { } } -helpers.giveOrRescind = function (method, privileges, cids, groupNames, callback) { +helpers.giveOrRescind = async function (method, privileges, cids, groupNames) { groupNames = Array.isArray(groupNames) ? groupNames : [groupNames]; cids = Array.isArray(cids) ? cids : [cids]; - async.eachSeries(groupNames, function (groupName, next) { - var groupKeys = []; + for (const groupName of groupNames) { + const groupKeys = []; cids.forEach((cid) => { privileges.forEach((privilege) => { groupKeys.push('cid:' + cid + ':privileges:groups:' + privilege); }); }); - method(groupKeys, groupName, next); - }, callback); + /* eslint-disable no-await-in-loop */ + await method(groupKeys, groupName); + } }; + +require('../promisify')(helpers); diff --git a/src/privileges/index.js b/src/privileges/index.js index d8eff842e3..1a17f59241 100644 --- a/src/privileges/index.js +++ b/src/privileges/index.js @@ -38,9 +38,7 @@ privileges.userPrivilegeList = [ 'moderate', ]; -privileges.groupPrivilegeList = privileges.userPrivilegeList.map(function (privilege) { - return 'groups:' + privilege; -}); +privileges.groupPrivilegeList = privileges.userPrivilegeList.map(privilege => 'groups:' + privilege); privileges.privilegeList = privileges.userPrivilegeList.concat(privileges.groupPrivilegeList); diff --git a/src/privileges/posts.js b/src/privileges/posts.js index 1af55abac3..f8d824f943 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -1,300 +1,213 @@ 'use strict'; -var async = require('async'); -var _ = require('lodash'); +const _ = require('lodash'); -var meta = require('../meta'); -var posts = require('../posts'); -var topics = require('../topics'); -var user = require('../user'); -var helpers = require('./helpers'); -var plugins = require('../plugins'); +const meta = require('../meta'); +const posts = require('../posts'); +const topics = require('../topics'); +const user = require('../user'); +const helpers = require('./helpers'); +const plugins = require('../plugins'); +const utils = require('../utils'); module.exports = function (privileges) { privileges.posts = {}; - privileges.posts.get = function (pids, uid, callback) { + privileges.posts.get = async function (pids, uid) { if (!Array.isArray(pids) || !pids.length) { - return setImmediate(callback, null, []); + return []; } - let uniqueCids; - let cids; - async.waterfall([ - function (next) { - posts.getCidsByPids(pids, next); - }, - function (_cids, next) { - cids = _cids; - uniqueCids = _.uniq(cids); - async.parallel({ - isAdmin: async.apply(user.isAdministrator, uid), - isModerator: async.apply(user.isModerator, uid, uniqueCids), - isOwner: async.apply(posts.isOwner, pids, uid), - 'topics:read': async.apply(helpers.isUserAllowedTo, 'topics:read', uid, uniqueCids), - read: async.apply(helpers.isUserAllowedTo, 'read', uid, uniqueCids), - 'posts:edit': async.apply(helpers.isUserAllowedTo, 'posts:edit', uid, uniqueCids), - 'posts:history': async.apply(helpers.isUserAllowedTo, 'posts:history', uid, uniqueCids), - 'posts:view_deleted': async.apply(helpers.isUserAllowedTo, 'posts:view_deleted', uid, uniqueCids), - }, next); - }, - function (results, next) { - const isModerator = _.zipObject(uniqueCids, results.isModerator); - const privData = {}; - privData['topics:read'] = _.zipObject(uniqueCids, results['topics:read']); - privData.read = _.zipObject(uniqueCids, results.read); - privData['posts:edit'] = _.zipObject(uniqueCids, results['posts:edit']); - privData['posts:history'] = _.zipObject(uniqueCids, results['posts:history']); - privData['posts:view_deleted'] = _.zipObject(uniqueCids, results['posts:view_deleted']); + const cids = await posts.getCidsByPids(pids); + const uniqueCids = _.uniq(cids); - var privileges = cids.map(function (cid, i) { - var isAdminOrMod = results.isAdmin || isModerator[cid]; - var editable = (privData['posts:edit'][cid] && (results.isOwner[i] || results.isModerator)) || results.isAdmin; - var viewDeletedPosts = results.isOwner[i] || privData['posts:view_deleted'][cid] || results.isAdmin; - var viewHistory = results.isOwner[i] || privData['posts:history'][cid] || results.isAdmin; + const results = await utils.promiseParallel({ + isAdmin: user.isAdministrator(uid), + isModerator: user.isModerator(uid, uniqueCids), + isOwner: posts.isOwner(pids, uid), + 'topics:read': helpers.isUserAllowedTo('topics:read', uid, uniqueCids), + read: helpers.isUserAllowedTo('read', uid, uniqueCids), + 'posts:edit': helpers.isUserAllowedTo('posts:edit', uid, uniqueCids), + 'posts:history': helpers.isUserAllowedTo('posts:history', uid, uniqueCids), + 'posts:view_deleted': helpers.isUserAllowedTo('posts:view_deleted', uid, uniqueCids), + }); - return { - editable: editable, - move: isAdminOrMod, - isAdminOrMod: isAdminOrMod, - 'topics:read': privData['topics:read'][cid] || results.isAdmin, - read: privData.read[cid] || results.isAdmin, - 'posts:history': viewHistory, - 'posts:view_deleted': viewDeletedPosts, - }; - }); + const isModerator = _.zipObject(uniqueCids, results.isModerator); + const privData = {}; + privData['topics:read'] = _.zipObject(uniqueCids, results['topics:read']); + privData.read = _.zipObject(uniqueCids, results.read); + privData['posts:edit'] = _.zipObject(uniqueCids, results['posts:edit']); + privData['posts:history'] = _.zipObject(uniqueCids, results['posts:history']); + privData['posts:view_deleted'] = _.zipObject(uniqueCids, results['posts:view_deleted']); - next(null, privileges); - }, - ], callback); + const privileges = cids.map(function (cid, i) { + const isAdminOrMod = results.isAdmin || isModerator[cid]; + const editable = (privData['posts:edit'][cid] && (results.isOwner[i] || results.isModerator)) || results.isAdmin; + const viewDeletedPosts = results.isOwner[i] || privData['posts:view_deleted'][cid] || results.isAdmin; + const viewHistory = results.isOwner[i] || privData['posts:history'][cid] || results.isAdmin; + + return { + editable: editable, + move: isAdminOrMod, + isAdminOrMod: isAdminOrMod, + 'topics:read': privData['topics:read'][cid] || results.isAdmin, + read: privData.read[cid] || results.isAdmin, + 'posts:history': viewHistory, + 'posts:view_deleted': viewDeletedPosts, + }; + }); + + return privileges; }; - privileges.posts.can = function (privilege, pid, uid, callback) { - async.waterfall([ - function (next) { - posts.getCidByPid(pid, next); - }, - function (cid, next) { - privileges.categories.can(privilege, cid, uid, next); - }, - ], callback); + privileges.posts.can = async function (privilege, pid, uid) { + const cid = await posts.getCidByPid(pid); + return await privileges.categories.can(privilege, cid, uid); }; - privileges.posts.filter = function (privilege, pids, uid, callback) { + privileges.posts.filter = async function (privilege, pids, uid) { if (!Array.isArray(pids) || !pids.length) { - return setImmediate(callback, null, []); + return []; } - var cids; - var postData; - var tids; - var tidToTopic = {}; pids = _.uniq(pids); + const postData = await posts.getPostsFields(pids, ['uid', 'tid', 'deleted']); + const tids = _.uniq(postData.map(post => post && post.tid).filter(Boolean)); + const topicData = await topics.getTopicsFields(tids, ['deleted', 'cid']); - async.waterfall([ - function (next) { - posts.getPostsFields(pids, ['uid', 'tid', 'deleted'], next); - }, - function (_posts, next) { - postData = _posts; + const tidToTopic = _.zipObject(tids, topicData); - tids = _.uniq(_posts.map(post => post && post.tid).filter(Boolean)); + let cids = postData.map(function (post, index) { + if (post) { + post.pid = pids[index]; + post.topic = tidToTopic[post.tid]; + } + return tidToTopic[post.tid] && tidToTopic[post.tid].cid; + }).filter(cid => parseInt(cid, 10)); - topics.getTopicsFields(tids, ['deleted', 'cid'], next); - }, - function (topicData, next) { - topicData.forEach(function (topic, index) { - if (topic) { - tidToTopic[tids[index]] = topic; - } - }); + cids = _.uniq(cids); - cids = postData.map(function (post, index) { - if (post) { - post.pid = pids[index]; - post.topic = tidToTopic[post.tid]; - } - return tidToTopic[post.tid] && tidToTopic[post.tid].cid; - }).filter(cid => parseInt(cid, 10)); + const results = await privileges.categories.getBase(privilege, cids, uid); + cids = cids.filter(function (cid, index) { + return !results.categories[index].disabled && + (results.allowedTo[index] || results.isAdmin); + }); - cids = _.uniq(cids); + const cidsSet = new Set(cids); - privileges.categories.getBase(privilege, cids, uid, next); - }, - function (results, next) { - cids = cids.filter(function (cid, index) { - return !results.categories[index].disabled && - (results.allowedTo[index] || results.isAdmin); - }); + pids = postData.filter(function (post) { + return post.topic && cidsSet.has(post.topic.cid) && + ((!post.topic.deleted && !post.deleted) || results.isAdmin); + }).map(post => post.pid); - const cidsSet = new Set(cids); + const data = await plugins.fireHook('filter:privileges.posts.filter', { + privilege: privilege, + uid: uid, + pids: pids, + }); - pids = postData.filter(function (post) { - return post.topic && cidsSet.has(post.topic.cid) && - ((!post.topic.deleted && !post.deleted) || results.isAdmin); - }).map(post => post.pid); - - plugins.fireHook('filter:privileges.posts.filter', { - privilege: privilege, - uid: uid, - pids: pids, - }, function (err, data) { - next(err, data ? data.pids : null); - }); - }, - ], callback); + return data ? data.pids : null; }; - privileges.posts.canEdit = function (pid, uid, callback) { - let results; - async.waterfall([ - function (next) { - async.parallel({ - isAdmin: async.apply(privileges.users.isAdministrator, uid), - isMod: async.apply(posts.isModerator, [pid], uid), - owner: async.apply(posts.isOwner, pid, uid), - edit: async.apply(privileges.posts.can, 'posts:edit', pid, uid), - postData: async.apply(posts.getPostFields, pid, ['tid', 'timestamp', 'deleted', 'deleterUid']), - userData: async.apply(user.getUserFields, uid, ['reputation']), - }, next); - }, - function (_results, next) { - results = _results; - results.isMod = results.isMod[0]; - if (results.isAdmin) { - return callback(null, { flag: true }); - } + privileges.posts.canEdit = async function (pid, uid) { + const results = await utils.promiseParallel({ + isAdmin: privileges.users.isAdministrator(uid), + isMod: posts.isModerator([pid], uid), + owner: posts.isOwner(pid, uid), + edit: privileges.posts.can('posts:edit', pid, uid), + postData: posts.getPostFields(pid, ['tid', 'timestamp', 'deleted', 'deleterUid']), + userData: user.getUserFields(uid, ['reputation']), + }); - if (!results.isMod && meta.config.postEditDuration && (Date.now() - results.postData.timestamp > meta.config.postEditDuration * 1000)) { - return callback(null, { flag: false, message: '[[error:post-edit-duration-expired, ' + meta.config.postEditDuration + ']]' }); - } - if (!results.isMod && meta.config.newbiePostEditDuration > 0 && meta.config.newbiePostDelayThreshold > _results.userData.reputation && Date.now() - _results.postData.timestamp > meta.config.newbiePostEditDuration * 1000) { - return callback(null, { flag: false, message: '[[error:post-edit-duration-expired, ' + meta.config.newbiePostEditDuration + ']]' }); - } + results.isMod = results.isMod[0]; + if (results.isAdmin) { + return { flag: true }; + } - topics.isLocked(results.postData.tid, next); - }, - function (isLocked, next) { - if (!results.isMod && isLocked) { - return callback(null, { flag: false, message: '[[error:topic-locked]]' }); - } + if (!results.isMod && meta.config.postEditDuration && (Date.now() - results.postData.timestamp > meta.config.postEditDuration * 1000)) { + return { flag: false, message: '[[error:post-edit-duration-expired, ' + meta.config.postEditDuration + ']]' }; + } + if (!results.isMod && meta.config.newbiePostEditDuration > 0 && meta.config.newbiePostDelayThreshold > results.userData.reputation && Date.now() - results.postData.timestamp > meta.config.newbiePostEditDuration * 1000) { + return { flag: false, message: '[[error:post-edit-duration-expired, ' + meta.config.newbiePostEditDuration + ']]' }; + } - if (!results.isMod && results.postData.deleted && parseInt(uid, 10) !== parseInt(results.postData.deleterUid, 10)) { - return callback(null, { flag: false, message: '[[error:post-deleted]]' }); - } + const isLocked = await topics.isLocked(results.postData.tid); + if (!results.isMod && isLocked) { + return { flag: false, message: '[[error:topic-locked]]' }; + } - results.pid = parseInt(pid, 10); - results.uid = uid; + if (!results.isMod && results.postData.deleted && parseInt(uid, 10) !== parseInt(results.postData.deleterUid, 10)) { + return { flag: false, message: '[[error:post-deleted]]' }; + } - plugins.fireHook('filter:privileges.posts.edit', results, next); - }, - function (result, next) { - next(null, { flag: result.edit && (result.owner || result.isMod), message: '[[error:no-privileges]]' }); - }, - ], callback); + results.pid = parseInt(pid, 10); + results.uid = uid; + + const result = await plugins.fireHook('filter:privileges.posts.edit', results); + return { flag: result.edit && (result.owner || result.isMod), message: '[[error:no-privileges]]' }; }; - privileges.posts.canDelete = function (pid, uid, callback) { - var postData; - async.waterfall([ - function (next) { - posts.getPostFields(pid, ['uid', 'tid', 'timestamp', 'deleterUid'], next); - }, - function (_postData, next) { - postData = _postData; - async.parallel({ - isAdmin: async.apply(privileges.users.isAdministrator, uid), - isMod: async.apply(posts.isModerator, [pid], uid), - isLocked: async.apply(topics.isLocked, postData.tid), - isOwner: async.apply(posts.isOwner, pid, uid), - 'posts:delete': async.apply(privileges.posts.can, 'posts:delete', pid, uid), - }, next); - }, - function (results, next) { - results.isMod = results.isMod[0]; - if (results.isAdmin) { - return next(null, { flag: true }); - } + privileges.posts.canDelete = async function (pid, uid) { + const postData = await posts.getPostFields(pid, ['uid', 'tid', 'timestamp', 'deleterUid']); + const results = await utils.promiseParallel({ + isAdmin: privileges.users.isAdministrator(uid), + isMod: posts.isModerator([pid], uid), + isLocked: topics.isLocked(postData.tid), + isOwner: posts.isOwner(pid, uid), + 'posts:delete': privileges.posts.can('posts:delete', pid, uid), + }); + results.isMod = results.isMod[0]; + if (results.isAdmin) { + return { flag: true }; + } - if (!results.isMod && results.isLocked) { - return next(null, { flag: false, message: '[[error:topic-locked]]' }); - } + if (!results.isMod && results.isLocked) { + return { flag: false, message: '[[error:topic-locked]]' }; + } - var postDeleteDuration = meta.config.postDeleteDuration; - if (!results.isMod && postDeleteDuration && (Date.now() - postData.timestamp > postDeleteDuration * 1000)) { - return next(null, { flag: false, message: '[[error:post-delete-duration-expired, ' + meta.config.postDeleteDuration + ']]' }); - } - var deleterUid = postData.deleterUid; - var flag = results['posts:delete'] && ((results.isOwner && (deleterUid === 0 || deleterUid === postData.uid)) || results.isMod); - next(null, { flag: flag, message: '[[error:no-privileges]]' }); - }, - ], callback); + var postDeleteDuration = meta.config.postDeleteDuration; + if (!results.isMod && postDeleteDuration && (Date.now() - postData.timestamp > postDeleteDuration * 1000)) { + return { flag: false, message: '[[error:post-delete-duration-expired, ' + meta.config.postDeleteDuration + ']]' }; + } + var deleterUid = postData.deleterUid; + var flag = results['posts:delete'] && ((results.isOwner && (deleterUid === 0 || deleterUid === postData.uid)) || results.isMod); + return { flag: flag, message: '[[error:no-privileges]]' }; }; - privileges.posts.canFlag = function (pid, uid, callback) { - async.waterfall([ - function (next) { - async.parallel({ - userReputation: async.apply(user.getUserField, uid, 'reputation'), - isAdminOrMod: async.apply(isAdminOrMod, pid, uid), - }, next); - }, - function (results, next) { - var minimumReputation = meta.config['min:rep:flag']; - var canFlag = results.isAdminOrMod || (results.userReputation >= minimumReputation); - next(null, { flag: canFlag }); - }, - ], callback); + privileges.posts.canFlag = async function (pid, uid) { + const [userReputation, isAdminOrModerator] = await Promise.all([ + user.getUserField(uid, 'reputation'), + isAdminOrMod(pid, uid), + ]); + const minimumReputation = meta.config['min:rep:flag']; + const canFlag = isAdminOrModerator || (userReputation >= minimumReputation); + return { flag: canFlag }; }; - privileges.posts.canMove = function (pid, uid, callback) { - async.waterfall([ - function (next) { - posts.isMain(pid, next); - }, - function (isMain, next) { - if (isMain) { - return next(new Error('[[error:cant-move-mainpost]]')); - } - isAdminOrMod(pid, uid, next); - }, - ], callback); + privileges.posts.canMove = async function (pid, uid) { + const isMain = await posts.isMain(pid); + if (isMain) { + throw new Error('[[error:cant-move-mainpost]]'); + } + return await isAdminOrMod(pid, uid); }; - privileges.posts.canPurge = function (pid, uid, callback) { - async.waterfall([ - function (next) { - posts.getCidByPid(pid, next); - }, - function (cid, next) { - async.parallel({ - purge: async.apply(privileges.categories.isUserAllowedTo, 'purge', cid, uid), - owner: async.apply(posts.isOwner, pid, uid), - isAdmin: async.apply(privileges.users.isAdministrator, uid), - isModerator: async.apply(privileges.users.isModerator, uid, cid), - }, next); - }, - function (results, next) { - next(null, (results.purge && (results.owner || results.isModerator)) || results.isAdmin); - }, - ], callback); + privileges.posts.canPurge = async function (pid, uid) { + const cid = await posts.getCidByPid(pid); + const results = await utils.promiseParallel({ + purge: privileges.categories.isUserAllowedTo('purge', cid, uid), + owner: posts.isOwner(pid, uid), + isAdmin: privileges.users.isAdministrator(uid), + isModerator: privileges.users.isModerator(uid, cid), + }); + return (results.purge && (results.owner || results.isModerator)) || results.isAdmin; }; - function isAdminOrMod(pid, uid, callback) { - helpers.some([ - function (next) { - async.waterfall([ - function (next) { - posts.getCidByPid(pid, next); - }, - function (cid, next) { - user.isModerator(uid, cid, next); - }, - ], next); - }, - function (next) { - user.isAdministrator(uid, next); - }, - ], callback); + async function isAdminOrMod(pid, uid) { + if (parseInt(uid, 10) <= 0) { + return false; + } + const cid = await posts.getCidByPid(pid); + return await privileges.categories.isAdminOrMod(cid, uid); } }; diff --git a/src/privileges/topics.js b/src/privileges/topics.js index 247447b031..f13dbb1f4a 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -1,239 +1,163 @@ 'use strict'; -var async = require('async'); -var _ = require('lodash'); +const _ = require('lodash'); -var meta = require('../meta'); -var topics = require('../topics'); -var user = require('../user'); -var helpers = require('./helpers'); -var categories = require('../categories'); -var plugins = require('../plugins'); +const meta = require('../meta'); +const topics = require('../topics'); +const user = require('../user'); +const helpers = require('./helpers'); +const categories = require('../categories'); +const plugins = require('../plugins'); module.exports = function (privileges) { privileges.topics = {}; - privileges.topics.get = function (tid, uid, callback) { + privileges.topics.get = async function (tid, uid) { uid = parseInt(uid, 10); - var topic; - var privs = [ + + const privs = [ 'topics:reply', 'topics:read', 'topics:tag', 'topics:delete', 'posts:edit', 'posts:history', 'posts:delete', 'posts:view_deleted', 'read', 'purge', ]; - async.waterfall([ - async.apply(topics.getTopicFields, tid, ['cid', 'uid', 'locked', 'deleted']), - function (_topic, next) { - topic = _topic; - async.parallel({ - privileges: async.apply(helpers.isUserAllowedTo, privs, uid, topic.cid), - isAdministrator: async.apply(user.isAdministrator, uid), - isModerator: async.apply(user.isModerator, uid, topic.cid), - disabled: async.apply(categories.getCategoryField, topic.cid, 'disabled'), - }, next); - }, - function (results, next) { - var privData = _.zipObject(privs, results.privileges); - var isOwner = uid > 0 && uid === topic.uid; - var isAdminOrMod = results.isAdministrator || results.isModerator; - var editable = isAdminOrMod; - var deletable = (privData['topics:delete'] && (isOwner || results.isModerator)) || results.isAdministrator; + const topicData = await topics.getTopicFields(tid, ['cid', 'uid', 'locked', 'deleted']); + const [userPrivileges, isAdministrator, isModerator, disabled] = await Promise.all([ + helpers.isUserAllowedTo(privs, uid, topicData.cid), + user.isAdministrator(uid), + user.isModerator(uid, topicData.cid), + categories.getCategoryField(topicData.cid, 'disabled'), + ]); + const privData = _.zipObject(privs, userPrivileges); + const isOwner = uid > 0 && uid === topicData.uid; + const isAdminOrMod = isAdministrator || isModerator; + const editable = isAdminOrMod; + const deletable = (privData['topics:delete'] && (isOwner || isModerator)) || isAdministrator; - plugins.fireHook('filter:privileges.topics.get', { - 'topics:reply': (privData['topics:reply'] && ((!topic.locked && !topic.deleted) || results.isModerator)) || results.isAdministrator, - 'topics:read': privData['topics:read'] || results.isAdministrator, - 'topics:tag': privData['topics:tag'] || results.isAdministrator, - 'topics:delete': (privData['topics:delete'] && (isOwner || results.isModerator)) || results.isAdministrator, - 'posts:edit': (privData['posts:edit'] && (!topic.locked || results.isModerator)) || results.isAdministrator, - 'posts:history': privData['posts:history'] || results.isAdministrator, - 'posts:delete': (privData['posts:delete'] && (!topic.locked || results.isModerator)) || results.isAdministrator, - 'posts:view_deleted': privData['posts:view_deleted'] || results.isAdministrator, - read: privData.read || results.isAdministrator, - purge: (privData.purge && (isOwner || results.isModerator)) || results.isAdministrator, + return await plugins.fireHook('filter:privileges.topics.get', { + 'topics:reply': (privData['topics:reply'] && ((!topicData.locked && !topicData.deleted) || isModerator)) || isAdministrator, + 'topics:read': privData['topics:read'] || isAdministrator, + 'topics:tag': privData['topics:tag'] || isAdministrator, + 'topics:delete': (privData['topics:delete'] && (isOwner || isModerator)) || isAdministrator, + 'posts:edit': (privData['posts:edit'] && (!topicData.locked || isModerator)) || isAdministrator, + 'posts:history': privData['posts:history'] || isAdministrator, + 'posts:delete': (privData['posts:delete'] && (!topicData.locked || isModerator)) || isAdministrator, + 'posts:view_deleted': privData['posts:view_deleted'] || isAdministrator, + read: privData.read || isAdministrator, + purge: (privData.purge && (isOwner || isModerator)) || isAdministrator, - view_thread_tools: editable || deletable, - editable: editable, - deletable: deletable, - view_deleted: isAdminOrMod || isOwner, - isAdminOrMod: isAdminOrMod, - disabled: results.disabled, - tid: tid, - uid: uid, - }, next); - }, - ], callback); + view_thread_tools: editable || deletable, + editable: editable, + deletable: deletable, + view_deleted: isAdminOrMod || isOwner, + isAdminOrMod: isAdminOrMod, + disabled: disabled, + tid: tid, + uid: uid, + }); }; - privileges.topics.can = function (privilege, tid, uid, callback) { - async.waterfall([ - function (next) { - topics.getTopicField(tid, 'cid', next); - }, - function (cid, next) { - privileges.categories.can(privilege, cid, uid, next); - }, - ], callback); + privileges.topics.can = async function (privilege, tid, uid) { + const cid = await topics.getTopicField(tid, 'cid'); + return await privileges.categories.can(privilege, cid, uid); }; - privileges.topics.filterTids = function (privilege, tids, uid, callback) { + privileges.topics.filterTids = async function (privilege, tids, uid) { if (!Array.isArray(tids) || !tids.length) { - return callback(null, []); + return []; } - var cids; - var topicsData; - async.waterfall([ - function (next) { - topics.getTopicsFields(tids, ['tid', 'cid', 'deleted'], next); - }, - function (_topicsData, next) { - topicsData = _topicsData; - cids = _.uniq(topicsData.map(topic => topic.cid)); - privileges.categories.getBase(privilege, cids, uid, next); - }, - function (results, next) { - cids = cids.filter(function (cid, index) { - return !results.categories[index].disabled && - (results.allowedTo[index] || results.isAdmin); - }); + const topicsData = await topics.getTopicsFields(tids, ['tid', 'cid', 'deleted']); + let cids = _.uniq(topicsData.map(topic => topic.cid)); + const results = await privileges.categories.getBase(privilege, cids, uid); - const cidsSet = new Set(cids); + cids = cids.filter(function (cid, index) { + return !results.categories[index].disabled && + (results.allowedTo[index] || results.isAdmin); + }); - tids = topicsData.filter(function (topic) { - return cidsSet.has(topic.cid) && - (!topic.deleted || results.isAdmin); - }).map(topic => topic.tid); + const cidsSet = new Set(cids); - plugins.fireHook('filter:privileges.topics.filter', { - privilege: privilege, - uid: uid, - tids: tids, - }, function (err, data) { - next(err, data ? data.tids : null); - }); - }, - ], callback); + tids = topicsData.filter(t => cidsSet.has(t.cid) && (!t.deleted || results.isAdmin)).map(t => t.tid); + + const data = await plugins.fireHook('filter:privileges.topics.filter', { + privilege: privilege, + uid: uid, + tids: tids, + }); + return data ? data.tids : []; }; - privileges.topics.filterUids = function (privilege, tid, uids, callback) { + privileges.topics.filterUids = async function (privilege, tid, uids) { if (!Array.isArray(uids) || !uids.length) { - return setImmediate(callback, null, []); + return []; } uids = _.uniq(uids); - var topicData; - async.waterfall([ - function (next) { - topics.getTopicFields(tid, ['tid', 'cid', 'deleted'], next); - }, - function (_topicData, next) { - topicData = _topicData; - async.parallel({ - disabled: function (next) { - categories.getCategoryField(topicData.cid, 'disabled', next); - }, - allowedTo: function (next) { - helpers.isUsersAllowedTo(privilege, uids, topicData.cid, next); - }, - isAdmins: function (next) { - user.isAdministrator(uids, next); - }, - }, next); - }, - function (results, next) { - uids = uids.filter(function (uid, index) { - return !results.disabled && - ((results.allowedTo[index] && !topicData.deleted) || results.isAdmins[index]); - }); - - next(null, uids); - }, - ], callback); + const topicData = await topics.getTopicFields(tid, ['tid', 'cid', 'deleted']); + const [disabled, allowedTo, isAdmins] = await Promise.all([ + categories.getCategoryField(topicData.cid, 'disabled'), + helpers.isUsersAllowedTo(privilege, uids, topicData.cid), + user.isAdministrator(uids), + ]); + return uids.filter(function (uid, index) { + return !disabled && + ((allowedTo[index] && !topicData.deleted) || isAdmins[index]); + }); }; - privileges.topics.canPurge = function (tid, uid, callback) { - async.waterfall([ - function (next) { - topics.getTopicField(tid, 'cid', next); - }, - function (cid, next) { - async.parallel({ - purge: async.apply(privileges.categories.isUserAllowedTo, 'purge', cid, uid), - owner: async.apply(topics.isOwner, tid, uid), - isAdmin: async.apply(privileges.users.isAdministrator, uid), - isModerator: async.apply(privileges.users.isModerator, uid, cid), - }, next); - }, - function (results, next) { - next(null, (results.purge && (results.owner || results.isModerator)) || results.isAdmin); - }, - ], callback); + privileges.topics.canPurge = async function (tid, uid) { + const cid = await topics.getTopicField(tid, 'cid'); + const [purge, owner, isAdmin, isModerator] = await Promise.all([ + privileges.categories.isUserAllowedTo('purge', cid, uid), + topics.isOwner(tid, uid), + privileges.users.isAdministrator(uid), + privileges.users.isModerator(uid, cid), + ]); + return (purge && (owner || isModerator)) || isAdmin; }; - privileges.topics.canDelete = function (tid, uid, callback) { - var topicData; - async.waterfall([ - function (next) { - topics.getTopicFields(tid, ['cid', 'postcount'], next); - }, - function (_topicData, next) { - topicData = _topicData; - async.parallel({ - isModerator: async.apply(user.isModerator, uid, topicData.cid), - isAdministrator: async.apply(user.isAdministrator, uid), - isOwner: async.apply(topics.isOwner, tid, uid), - 'topics:delete': async.apply(helpers.isUserAllowedTo, 'topics:delete', uid, [topicData.cid]), - }, next); - }, - function (results, next) { - if (results.isAdministrator) { - return next(null, true); - } + privileges.topics.canDelete = async function (tid, uid) { + const topicData = await topics.getTopicFields(tid, ['cid', 'postcount']); + const [isModerator, isAdministrator, isOwner, allowedTo] = await Promise.all([ + user.isModerator(uid, topicData.cid), + user.isAdministrator(uid), + topics.isOwner(tid, uid), + helpers.isUserAllowedTo('topics:delete', uid, [topicData.cid]), + ]); - var preventTopicDeleteAfterReplies = meta.config.preventTopicDeleteAfterReplies; - if (!results.isModerator && preventTopicDeleteAfterReplies && (topicData.postcount - 1) >= preventTopicDeleteAfterReplies) { - var langKey = preventTopicDeleteAfterReplies > 1 ? - '[[error:cant-delete-topic-has-replies, ' + meta.config.preventTopicDeleteAfterReplies + ']]' : - '[[error:cant-delete-topic-has-reply]]'; - return next(new Error(langKey)); - } + if (isAdministrator) { + return true; + } - next(null, results['topics:delete'][0] && (results.isOwner || results.isModerator)); - }, - ], callback); + const preventTopicDeleteAfterReplies = meta.config.preventTopicDeleteAfterReplies; + if (!isModerator && preventTopicDeleteAfterReplies && (topicData.postcount - 1) >= preventTopicDeleteAfterReplies) { + const langKey = preventTopicDeleteAfterReplies > 1 ? + '[[error:cant-delete-topic-has-replies, ' + meta.config.preventTopicDeleteAfterReplies + ']]' : + '[[error:cant-delete-topic-has-reply]]'; + throw new Error(langKey); + } + + return allowedTo[0] && (isOwner || isModerator); }; - privileges.topics.canEdit = function (tid, uid, callback) { - privileges.topics.isOwnerOrAdminOrMod(tid, uid, callback); + privileges.topics.canEdit = async function (tid, uid) { + return await privileges.topics.isOwnerOrAdminOrMod(tid, uid); }; - privileges.topics.isOwnerOrAdminOrMod = function (tid, uid, callback) { - helpers.some([ - function (next) { - topics.isOwner(tid, uid, next); - }, - function (next) { - privileges.topics.isAdminOrMod(tid, uid, next); - }, - ], callback); + privileges.topics.isOwnerOrAdminOrMod = async function (tid, uid) { + const [isOwner, isAdminOrMod] = await Promise.all([ + topics.isOwner(tid, uid), + privileges.topics.isAdminOrMod(tid, uid), + ]); + return isOwner || isAdminOrMod; }; - privileges.topics.isAdminOrMod = function (tid, uid, callback) { - helpers.some([ - function (next) { - async.waterfall([ - function (next) { - topics.getTopicField(tid, 'cid', next); - }, - function (cid, next) { - user.isModerator(uid, cid, next); - }, - ], next); - }, - function (next) { - user.isAdministrator(uid, next); - }, - ], callback); + privileges.topics.isAdminOrMod = async function (tid, uid) { + if (parseInt(uid, 10) <= 0) { + return false; + } + const cid = await topics.getTopicField(tid, 'cid'); + return await privileges.categories.isAdminOrMod(cid, uid); }; }; diff --git a/src/privileges/users.js b/src/privileges/users.js index b73c1dd033..1cf9441aee 100644 --- a/src/privileges/users.js +++ b/src/privileges/users.js @@ -1,189 +1,118 @@ 'use strict'; -var async = require('async'); -var _ = require('lodash'); +const _ = require('lodash'); -var groups = require('../groups'); -var plugins = require('../plugins'); -var helpers = require('./helpers'); +const groups = require('../groups'); +const plugins = require('../plugins'); +const helpers = require('./helpers'); module.exports = function (privileges) { privileges.users = {}; - privileges.users.isAdministrator = function (uid, callback) { - if (Array.isArray(uid)) { - groups.isMembers(uid, 'administrators', callback); - } else { - groups.isMember(uid, 'administrators', callback); - } + privileges.users.isAdministrator = async function (uid) { + return await isGroupMember(uid, 'administrators'); }; - privileges.users.isGlobalModerator = function (uid, callback) { - if (Array.isArray(uid)) { - groups.isMembers(uid, 'Global Moderators', callback); - } else { - groups.isMember(uid, 'Global Moderators', callback); - } + privileges.users.isGlobalModerator = async function (uid) { + return await isGroupMember(uid, 'Global Moderators'); }; - privileges.users.isModerator = function (uid, cid, callback) { + async function isGroupMember(uid, groupName) { + return await groups[Array.isArray(uid) ? 'isMembers' : 'isMember'](uid, groupName); + } + + privileges.users.isModerator = async function (uid, cid) { if (Array.isArray(cid)) { - isModeratorOfCategories(cid, uid, callback); + return await isModeratorOfCategories(cid, uid); } else if (Array.isArray(uid)) { - isModeratorsOfCategory(cid, uid, callback); - } else { - isModeratorOfCategory(cid, uid, callback); + return await isModeratorsOfCategory(cid, uid); } + return await isModeratorOfCategory(cid, uid); }; - function isModeratorOfCategories(cids, uid, callback) { + async function isModeratorOfCategories(cids, uid) { if (parseInt(uid, 10) <= 0) { - return filterIsModerator(cids, uid, cids.map(() => false), callback); + return await filterIsModerator(cids, uid, cids.map(() => false)); } - var uniqueCids; - async.waterfall([ - function (next) { - privileges.users.isGlobalModerator(uid, next); - }, - function (isGlobalModerator, next) { - if (isGlobalModerator) { - return filterIsModerator(cids, uid, cids.map(() => true), callback); - } - uniqueCids = _.uniq(cids); - - helpers.isUserAllowedTo('moderate', uid, uniqueCids, next); - }, - function (isAllowed, next) { - const map = _.zipObject(uniqueCids, isAllowed); - const isModerator = cids.map(cid => map[cid]); - filterIsModerator(cids, uid, isModerator, next); - }, - ], callback); - } - - function isModeratorsOfCategory(cid, uids, callback) { - async.waterfall([ - function (next) { - async.parallel([ - async.apply(privileges.users.isGlobalModerator, uids), - async.apply(groups.isMembers, uids, 'cid:' + cid + ':privileges:moderate'), - async.apply(groups.isMembersOfGroupList, uids, 'cid:' + cid + ':privileges:groups:moderate'), - ], next); - }, - function (checks, next) { - var isModerator = checks[0].map(function (isMember, idx) { - return isMember || checks[1][idx] || checks[2][idx]; - }); - - filterIsModerator(cid, uids, isModerator, next); - }, - ], callback); - } - - function isModeratorOfCategory(cid, uid, callback) { - if (parseInt(uid, 10) <= 0) { - return filterIsModerator(cid, uid, false, callback); + const isGlobalModerator = await privileges.users.isGlobalModerator(uid); + if (isGlobalModerator) { + return await filterIsModerator(cids, uid, cids.map(() => true)); } - async.waterfall([ - function (next) { - async.parallel([ - async.apply(privileges.users.isGlobalModerator, uid), - async.apply(groups.isMember, uid, 'cid:' + cid + ':privileges:moderate'), - async.apply(groups.isMemberOfGroupList, uid, 'cid:' + cid + ':privileges:groups:moderate'), - ], next); - }, - function (checks, next) { - var isModerator = checks[0] || checks[1] || checks[2]; - filterIsModerator(cid, uid, isModerator, next); - }, - ], callback); + const uniqueCids = _.uniq(cids); + const isAllowed = await helpers.isUserAllowedTo('moderate', uid, uniqueCids); + + const cidToIsAllowed = _.zipObject(uniqueCids, isAllowed); + const isModerator = cids.map(cid => cidToIsAllowed[cid]); + return await filterIsModerator(cids, uid, isModerator); } - function filterIsModerator(cid, uid, isModerator, callback) { - async.waterfall([ - function (next) { - plugins.fireHook('filter:user.isModerator', { uid: uid, cid: cid, isModerator: isModerator }, next); - }, - function (data, next) { - if ((Array.isArray(uid) || Array.isArray(cid)) && !Array.isArray(data.isModerator)) { - return callback(new Error('filter:user.isModerator - i/o mismatch')); - } - - next(null, data.isModerator); - }, - ], callback); + async function isModeratorsOfCategory(cid, uids) { + const [check1, check2, check3] = await Promise.all([ + privileges.users.isGlobalModerator(uids), + groups.isMembers(uids, 'cid:' + cid + ':privileges:moderate'), + groups.isMembersOfGroupList(uids, 'cid:' + cid + ':privileges:groups:moderate'), + ]); + const isModerator = uids.map((uid, idx) => check1[idx] || check2[idx] || check3[idx]); + return await filterIsModerator(cid, uids, isModerator); } - privileges.users.canEdit = function (callerUid, uid, callback) { + async function isModeratorOfCategory(cid, uid) { + const result = await isModeratorOfCategories([cid], uid); + return result ? result[0] : false; + } + + async function filterIsModerator(cid, uid, isModerator) { + const data = await plugins.fireHook('filter:user.isModerator', { uid: uid, cid: cid, isModerator: isModerator }); + if ((Array.isArray(uid) || Array.isArray(cid)) && !Array.isArray(data.isModerator)) { + throw new Error('filter:user.isModerator - i/o mismatch'); + } + + return data.isModerator; + } + + privileges.users.canEdit = async function (callerUid, uid) { if (parseInt(callerUid, 10) === parseInt(uid, 10)) { - return process.nextTick(callback, null, true); + return true; } - async.waterfall([ - function (next) { - async.parallel({ - isAdmin: function (next) { - privileges.users.isAdministrator(callerUid, next); - }, - isGlobalMod: function (next) { - privileges.users.isGlobalModerator(callerUid, next); - }, - isTargetAdmin: function (next) { - privileges.users.isAdministrator(uid, next); - }, - }, next); - }, - function (results, next) { - results.canEdit = results.isAdmin || (results.isGlobalMod && !results.isTargetAdmin); - results.callerUid = callerUid; - results.uid = uid; - plugins.fireHook('filter:user.canEdit', results, next); - }, - function (data, next) { - next(null, data.canEdit); - }, - ], callback); + const [isAdmin, isGlobalMod, isTargetAdmin] = await Promise.all([ + privileges.users.isAdministrator(callerUid), + privileges.users.isGlobalModerator(callerUid), + privileges.users.isAdministrator(uid), + ]); + + const data = await plugins.fireHook('filter:user.canEdit', { + isAdmin: isAdmin, + isGlobalMod: isGlobalMod, + isTargetAdmin: isTargetAdmin, + canEdit: isAdmin || (isGlobalMod && !isTargetAdmin), + callerUid: callerUid, + uid: uid, + }); + return data.canEdit; }; - privileges.users.canBanUser = function (callerUid, uid, callback) { - async.waterfall([ - function (next) { - async.parallel({ - canBan: function (next) { - privileges.global.can('ban', callerUid, next); - }, - isTargetAdmin: function (next) { - privileges.users.isAdministrator(uid, next); - }, - }, next); - }, - function (results, next) { - results.canBan = !results.isTargetAdmin && results.canBan; - results.callerUid = callerUid; - results.uid = uid; - plugins.fireHook('filter:user.canBanUser', results, next); - }, - function (data, next) { - next(null, data.canBan); - }, - ], callback); + privileges.users.canBanUser = async function (callerUid, uid) { + const [canBan, isTargetAdmin] = await Promise.all([ + privileges.global.can('ban', callerUid), + privileges.users.isAdministrator(uid), + ]); + + const data = await plugins.fireHook('filter:user.canBanUser', { + canBan: canBan && !isTargetAdmin, + callerUid: callerUid, + uid: uid, + }); + return data.canBan; }; - privileges.users.hasBanPrivilege = function (uid, callback) { - async.waterfall([ - function (next) { - privileges.global.can('ban', uid, next); - }, - function (canBan, next) { - plugins.fireHook('filter:user.hasBanPrivilege', { - uid: uid, - canBan: canBan, - }, next); - }, - function (data, next) { - next(null, data.canBan); - }, - ], callback); + privileges.users.hasBanPrivilege = async function (uid) { + const canBan = await privileges.global.can('ban', uid); + const data = await plugins.fireHook('filter:user.hasBanPrivilege', { + uid: uid, + canBan: canBan, + }); + return data.canBan; }; };