From 293b7c2650f4f3a9ad1c71bad09dc457ca2bf599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 24 Feb 2021 18:10:34 -0500 Subject: [PATCH] refactor: privileges, export modules directly (#9325) fix unused/commented out methods in admin privileges --- src/categories/create.js | 4 +- src/categories/delete.js | 2 +- src/controllers/admin/admins-mods.js | 2 +- src/privileges/admin.js | 400 ++++++++++++------------- src/privileges/categories.js | 356 ++++++++++++---------- src/privileges/global.js | 216 +++++++------- src/privileges/helpers.js | 9 + src/privileges/index.js | 70 ++--- src/privileges/posts.js | 427 +++++++++++++-------------- src/privileges/topics.js | 297 ++++++++++--------- src/privileges/users.js | 250 ++++++++-------- test/posts.js | 2 +- 12 files changed, 1015 insertions(+), 1020 deletions(-) diff --git a/src/categories/create.js b/src/categories/create.js index a27f0954aa..eeb2b87f28 100644 --- a/src/categories/create.js +++ b/src/categories/create.js @@ -190,7 +190,9 @@ module.exports = function (Categories) { group = group || ''; const data = await plugins.hooks.fire('filter:categories.copyPrivilegesFrom', { - privileges: group ? privileges.groupPrivilegeList.slice() : privileges.privilegeList.slice(), + privileges: group ? + privileges.categories.groupPrivilegeList.slice() : + privileges.categories.privilegeList.slice(), fromCid: fromCid, toCid: toCid, group: group, diff --git a/src/categories/delete.js b/src/categories/delete.js index d2a19e6270..dc3cba36a5 100644 --- a/src/categories/delete.js +++ b/src/categories/delete.js @@ -48,7 +48,7 @@ module.exports = function (Categories) { `cid:${cid}:tag:whitelist`, `category:${cid}`, ]); - await groups.destroy(privileges.privilegeList.map(privilege => `cid:${cid}:privileges:${privilege}`)); + await groups.destroy(privileges.categories.privilegeList.map(privilege => `cid:${cid}:privileges:${privilege}`)); } async function removeFromParent(cid) { diff --git a/src/controllers/admin/admins-mods.js b/src/controllers/admin/admins-mods.js index 6333364e37..7aaf7558e6 100644 --- a/src/controllers/admin/admins-mods.js +++ b/src/controllers/admin/admins-mods.js @@ -30,7 +30,7 @@ AdminsMods.get = async function (req, res, next) { globalMods: globalMods, categoryMods: [moderators], selectedCategory: selectedCategory, - allPrivileges: privileges.userPrivilegeList, + allPrivileges: privileges.categories.userPrivilegeList, }); }; diff --git a/src/privileges/admin.js b/src/privileges/admin.js index 268b338985..5b0f6c440d 100644 --- a/src/privileges/admin.js +++ b/src/privileges/admin.js @@ -9,213 +9,203 @@ const helpers = require('./helpers'); const plugins = require('../plugins'); const utils = require('../utils'); -module.exports = function (privileges) { - privileges.admin = {}; +const privsAdmin = module.exports; - privileges.admin.privilegeLabels = [ - { name: '[[admin/manage/privileges:admin-dashboard]]' }, - { name: '[[admin/manage/privileges:admin-categories]]' }, - { name: '[[admin/manage/privileges:admin-privileges]]' }, - { name: '[[admin/manage/privileges:admin-admins-mods]]' }, - { name: '[[admin/manage/privileges:admin-users]]' }, - { name: '[[admin/manage/privileges:admin-groups]]' }, - { name: '[[admin/manage/privileges:admin-tags]]' }, - { name: '[[admin/manage/privileges:admin-settings]]' }, - ]; +privsAdmin.privilegeLabels = [ + { name: '[[admin/manage/privileges:admin-dashboard]]' }, + { name: '[[admin/manage/privileges:admin-categories]]' }, + { name: '[[admin/manage/privileges:admin-privileges]]' }, + { name: '[[admin/manage/privileges:admin-admins-mods]]' }, + { name: '[[admin/manage/privileges:admin-users]]' }, + { name: '[[admin/manage/privileges:admin-groups]]' }, + { name: '[[admin/manage/privileges:admin-tags]]' }, + { name: '[[admin/manage/privileges:admin-settings]]' }, +]; - privileges.admin.userPrivilegeList = [ - 'admin:dashboard', - 'admin:categories', - 'admin:privileges', - 'admin:admins-mods', - 'admin:users', - 'admin:groups', - 'admin:tags', - 'admin:settings', - ]; +privsAdmin.userPrivilegeList = [ + 'admin:dashboard', + 'admin:categories', + 'admin:privileges', + 'admin:admins-mods', + 'admin:users', + 'admin:groups', + 'admin:tags', + 'admin:settings', +]; - privileges.admin.groupPrivilegeList = privileges.admin.userPrivilegeList.map(privilege => `groups:${privilege}`); +privsAdmin.groupPrivilegeList = privsAdmin.userPrivilegeList.map(privilege => `groups:${privilege}`); - // Mapping for a page route (via direct match or regexp) to a privilege - privileges.admin.routeMap = { - dashboard: 'admin:dashboard', - 'manage/categories': 'admin:categories', - 'manage/privileges': 'admin:privileges', - 'manage/admins-mods': 'admin:admins-mods', - 'manage/users': 'admin:users', - 'manage/groups': 'admin:groups', - 'manage/tags': 'admin:tags', - 'settings/tags': 'admin:tags', - 'extend/plugins': 'admin:settings', - 'extend/widgets': 'admin:settings', - 'extend/rewards': 'admin:settings', - }; - privileges.admin.routeRegexpMap = { - '^manage/categories/\\d+': 'admin:categories', - '^manage/privileges/(\\d+|admin)': 'admin:privileges', - '^manage/groups/.+$': 'admin:groups', - '^settings/[\\w\\-]+$': 'admin:settings', - '^appearance/[\\w]+$': 'admin:settings', - '^plugins/[\\w\\-]+$': 'admin:settings', - }; - - // Mapping for socket call methods to a privilege - // In NodeBB v2, these socket calls will be removed in favour of xhr calls - privileges.admin.socketMap = { - 'admin.rooms.getAll': 'admin:dashboard', - 'admin.analytics.get': 'admin:dashboard', - - 'admin.categories.getAll': 'admin:categories', - 'admin.categories.create': 'admin:categories', - 'admin.categories.update': 'admin:categories', - 'admin.categories.purge': 'admin:categories', - 'admin.categories.copySettingsFrom': 'admin:categories', - - 'admin.categories.getPrivilegeSettings': 'admin:privileges', - 'admin.categories.setPrivilege': 'admin:privileges;admin:admins-mods', - 'admin.categories.copyPrivilegesToChildren': 'admin:privileges', - 'admin.categories.copyPrivilegesFrom': 'admin:privileges', - 'admin.categories.copyPrivilegesToAllCategories': 'admin:privileges', - - 'admin.user.makeAdmins': 'admin:admins-mods', - 'admin.user.removeAdmins': 'admin:admins-mods', - - 'admin.user.loadGroups': 'admin:users', - 'admin.groups.join': 'admin:users', - 'admin.groups.leave': 'admin:users', - 'admin.user.resetLockouts': 'admin:users', - 'admin.user.validateEmail': 'admin:users', - 'admin.user.sendValidationEmail': 'admin:users', - 'admin.user.sendPasswordResetEmail': 'admin:users', - 'admin.user.forcePasswordReset': 'admin:users', - 'admin.user.deleteUsers': 'admin:users', - 'admin.user.deleteUsersAndContent': 'admin:users', - 'admin.user.createUser': 'admin:users', - 'admin.user.invite': 'admin:users', - - 'admin.tags.create': 'admin:tags', - 'admin.tags.update': 'admin:tags', - 'admin.tags.rename': 'admin:tags', - 'admin.tags.deleteTags': 'admin:tags', - - 'admin.getSearchDict': 'admin:settings', - 'admin.config.setMultiple': 'admin:settings', - 'admin.config.remove': 'admin:settings', - 'admin.themes.getInstalled': 'admin:settings', - 'admin.themes.set': 'admin:settings', - 'admin.reloadAllSessions': 'admin:settings', - 'admin.settings.get': 'admin:settings', - 'admin.settings.set': 'admin:settings', - }; - - privileges.admin.resolve = (path) => { - if (privileges.admin.routeMap[path]) { - return privileges.admin.routeMap[path]; - } - - let privilege; - Object.keys(privileges.admin.routeRegexpMap).forEach((regexp) => { - if (!privilege) { - if (new RegExp(regexp).test(path)) { - privilege = privileges.admin.routeRegexpMap[regexp]; - } - } - }); - - return privilege; - }; - - privileges.admin.list = async function (uid) { - const privilegeLabels = privileges.admin.privilegeLabels.slice(); - const userPrivilegeList = privileges.admin.userPrivilegeList.slice(); - const groupPrivilegeList = privileges.admin.groupPrivilegeList.slice(); - - // Restrict privileges column to superadmins - if (!(await user.isAdministrator(uid))) { - const idx = privileges.admin.userPrivilegeList.indexOf('admin:privileges'); - privilegeLabels.splice(idx, 1); - userPrivilegeList.splice(idx, 1); - groupPrivilegeList.splice(idx, 1); - } - - async function getLabels() { - return await utils.promiseParallel({ - users: plugins.hooks.fire('filter:privileges.admin.list_human', privilegeLabels.slice()), - groups: plugins.hooks.fire('filter:privileges.admin.groups.list_human', privilegeLabels.slice()), - }); - } - - const keys = await utils.promiseParallel({ - users: plugins.hooks.fire('filter:privileges.admin.list', userPrivilegeList.slice()), - groups: plugins.hooks.fire('filter:privileges.admin.groups.list', groupPrivilegeList.slice()), - }); - - const payload = await utils.promiseParallel({ - labels: getLabels(), - users: helpers.getUserPrivileges(0, keys.users), - groups: helpers.getGroupPrivileges(0, keys.groups), - }); - payload.keys = keys; - - // 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 + 3; - return payload; - }; - - privileges.admin.get = async function (uid) { - const [userPrivileges, isAdministrator] = await Promise.all([ - helpers.isAllowedTo(privileges.admin.userPrivilegeList, uid, 0), - user.isAdministrator(uid), - ]); - - const combined = userPrivileges.map(allowed => allowed || isAdministrator); - const privData = _.zipObject(privileges.admin.userPrivilegeList, combined); - - privData.superadmin = isAdministrator; - return await plugins.hooks.fire('filter:privileges.admin.get', privData); - }; - - privileges.admin.can = async function (privilege, uid) { - const [isUserAllowedTo, isAdministrator] = await Promise.all([ - helpers.isAllowedTo(privilege, uid, [0]), - user.isAdministrator(uid), - ]); - return isAdministrator || isUserAllowedTo[0]; - }; - - // privileges.admin.canGroup = async function (privilege, groupName) { - // return await groups.isMember(groupName, 'cid:0:privileges:groups:' + privilege); - // }; - - privileges.admin.give = async function (privileges, groupName) { - await helpers.giveOrRescind(groups.join, privileges, 'admin', groupName); - plugins.hooks.fire('action:privileges.admin.give', { - privileges: privileges, - groupNames: Array.isArray(groupName) ? groupName : [groupName], - }); - }; - - privileges.admin.rescind = async function (privileges, groupName) { - await helpers.giveOrRescind(groups.leave, privileges, 'admin', groupName); - plugins.hooks.fire('action:privileges.admin.rescind', { - privileges: privileges, - groupNames: Array.isArray(groupName) ? groupName : [groupName], - }); - }; - - // privileges.admin.userPrivileges = async function (uid) { - // const tasks = {}; - // privileges.admin.userPrivilegeList.forEach(function (privilege) { - // tasks[privilege] = groups.isMember(uid, 'cid:0:privileges:' + privilege); - // }); - // return await utils.promiseParallel(tasks); - // }; - - // privileges.admin.groupPrivileges = async function (groupName) { - // const tasks = {}; - // privileges.admin.groupPrivilegeList.forEach(function (privilege) { - // tasks[privilege] = groups.isMember(groupName, 'cid:0:privileges:' + privilege); - // }); - // return await utils.promiseParallel(tasks); - // }; +// Mapping for a page route (via direct match or regexp) to a privilege +privsAdmin.routeMap = { + dashboard: 'admin:dashboard', + 'manage/categories': 'admin:categories', + 'manage/privileges': 'admin:privileges', + 'manage/admins-mods': 'admin:admins-mods', + 'manage/users': 'admin:users', + 'manage/groups': 'admin:groups', + 'manage/tags': 'admin:tags', + 'settings/tags': 'admin:tags', + 'extend/plugins': 'admin:settings', + 'extend/widgets': 'admin:settings', + 'extend/rewards': 'admin:settings', +}; +privsAdmin.routeRegexpMap = { + '^manage/categories/\\d+': 'admin:categories', + '^manage/privileges/(\\d+|admin)': 'admin:privileges', + '^manage/groups/.+$': 'admin:groups', + '^settings/[\\w\\-]+$': 'admin:settings', + '^appearance/[\\w]+$': 'admin:settings', + '^plugins/[\\w\\-]+$': 'admin:settings', +}; + +// Mapping for socket call methods to a privilege +// In NodeBB v2, these socket calls will be removed in favour of xhr calls +privsAdmin.socketMap = { + 'admin.rooms.getAll': 'admin:dashboard', + 'admin.analytics.get': 'admin:dashboard', + + 'admin.categories.getAll': 'admin:categories', + 'admin.categories.create': 'admin:categories', + 'admin.categories.update': 'admin:categories', + 'admin.categories.purge': 'admin:categories', + 'admin.categories.copySettingsFrom': 'admin:categories', + + 'admin.categories.getPrivilegeSettings': 'admin:privileges', + 'admin.categories.setPrivilege': 'admin:privileges;admin:admins-mods', + 'admin.categories.copyPrivilegesToChildren': 'admin:privileges', + 'admin.categories.copyPrivilegesFrom': 'admin:privileges', + 'admin.categories.copyPrivilegesToAllCategories': 'admin:privileges', + + 'admin.user.makeAdmins': 'admin:admins-mods', + 'admin.user.removeAdmins': 'admin:admins-mods', + + 'admin.user.loadGroups': 'admin:users', + 'admin.groups.join': 'admin:users', + 'admin.groups.leave': 'admin:users', + 'admin.user.resetLockouts': 'admin:users', + 'admin.user.validateEmail': 'admin:users', + 'admin.user.sendValidationEmail': 'admin:users', + 'admin.user.sendPasswordResetEmail': 'admin:users', + 'admin.user.forcePasswordReset': 'admin:users', + 'admin.user.deleteUsers': 'admin:users', + 'admin.user.deleteUsersAndContent': 'admin:users', + 'admin.user.createUser': 'admin:users', + 'admin.user.invite': 'admin:users', + + 'admin.tags.create': 'admin:tags', + 'admin.tags.update': 'admin:tags', + 'admin.tags.rename': 'admin:tags', + 'admin.tags.deleteTags': 'admin:tags', + + 'admin.getSearchDict': 'admin:settings', + 'admin.config.setMultiple': 'admin:settings', + 'admin.config.remove': 'admin:settings', + 'admin.themes.getInstalled': 'admin:settings', + 'admin.themes.set': 'admin:settings', + 'admin.reloadAllSessions': 'admin:settings', + 'admin.settings.get': 'admin:settings', + 'admin.settings.set': 'admin:settings', +}; + +privsAdmin.resolve = (path) => { + if (privsAdmin.routeMap[path]) { + return privsAdmin.routeMap[path]; + } + + let privilege; + Object.keys(privsAdmin.routeRegexpMap).forEach((regexp) => { + if (!privilege) { + if (new RegExp(regexp).test(path)) { + privilege = privsAdmin.routeRegexpMap[regexp]; + } + } + }); + + return privilege; +}; + +privsAdmin.list = async function (uid) { + const privilegeLabels = privsAdmin.privilegeLabels.slice(); + const userPrivilegeList = privsAdmin.userPrivilegeList.slice(); + const groupPrivilegeList = privsAdmin.groupPrivilegeList.slice(); + + // Restrict privileges column to superadmins + if (!(await user.isAdministrator(uid))) { + const idx = privsAdmin.userPrivilegeList.indexOf('admin:privileges'); + privilegeLabels.splice(idx, 1); + userPrivilegeList.splice(idx, 1); + groupPrivilegeList.splice(idx, 1); + } + + async function getLabels() { + return await utils.promiseParallel({ + users: plugins.hooks.fire('filter:privileges.admin.list_human', privilegeLabels.slice()), + groups: plugins.hooks.fire('filter:privileges.admin.groups.list_human', privilegeLabels.slice()), + }); + } + + const keys = await utils.promiseParallel({ + users: plugins.hooks.fire('filter:privileges.admin.list', userPrivilegeList.slice()), + groups: plugins.hooks.fire('filter:privileges.admin.groups.list', groupPrivilegeList.slice()), + }); + + const payload = await utils.promiseParallel({ + labels: getLabels(), + users: helpers.getUserPrivileges(0, keys.users), + groups: helpers.getGroupPrivileges(0, keys.groups), + }); + payload.keys = keys; + + // 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 + 3; + return payload; +}; + +privsAdmin.get = async function (uid) { + const [userPrivileges, isAdministrator] = await Promise.all([ + helpers.isAllowedTo(privsAdmin.userPrivilegeList, uid, 0), + user.isAdministrator(uid), + ]); + + const combined = userPrivileges.map(allowed => allowed || isAdministrator); + const privData = _.zipObject(privsAdmin.userPrivilegeList, combined); + + privData.superadmin = isAdministrator; + return await plugins.hooks.fire('filter:privileges.admin.get', privData); +}; + +privsAdmin.can = async function (privilege, uid) { + const [isUserAllowedTo, isAdministrator] = await Promise.all([ + helpers.isAllowedTo(privilege, uid, [0]), + user.isAdministrator(uid), + ]); + return isAdministrator || isUserAllowedTo[0]; +}; + +privsAdmin.canGroup = async function (privilege, groupName) { + return await groups.isMember(groupName, `cid:0:privileges:groups:${privilege}`); +}; + +privsAdmin.give = async function (privileges, groupName) { + await helpers.giveOrRescind(groups.join, privileges, 0, groupName); + plugins.hooks.fire('action:privileges.admin.give', { + privileges: privileges, + groupNames: Array.isArray(groupName) ? groupName : [groupName], + }); +}; + +privsAdmin.rescind = async function (privileges, groupName) { + await helpers.giveOrRescind(groups.leave, privileges, 0, groupName); + plugins.hooks.fire('action:privileges.admin.rescind', { + privileges: privileges, + groupNames: Array.isArray(groupName) ? groupName : [groupName], + }); +}; + +privsAdmin.userPrivileges = async function (uid) { + return await helpers.userOrGroupPrivileges(0, uid, privsAdmin.userPrivilegeList); +}; + +privsAdmin.groupPrivileges = async function (groupName) { + return await helpers.userOrGroupPrivileges(0, groupName, privsAdmin.groupPrivilegeList); }; diff --git a/src/privileges/categories.js b/src/privileges/categories.js index b322b3bfa0..e8cc21d489 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -10,177 +10,207 @@ const helpers = require('./helpers'); const plugins = require('../plugins'); const utils = require('../utils'); -module.exports = function (privileges) { - privileges.categories = {}; +const privsCategories = module.exports; - // 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.hooks.fire('filter:privileges.list_human', privileges.privilegeLabels.slice()), - groups: plugins.hooks.fire('filter:privileges.groups.list_human', privileges.privilegeLabels.slice()), - }); - } +privsCategories.privilegeLabels = [ + { name: '[[admin/manage/privileges:find-category]]' }, + { name: '[[admin/manage/privileges:access-category]]' }, + { name: '[[admin/manage/privileges:access-topics]]' }, + { name: '[[admin/manage/privileges:create-topics]]' }, + { name: '[[admin/manage/privileges:reply-to-topics]]' }, + { name: '[[admin/manage/privileges:tag-topics]]' }, + { name: '[[admin/manage/privileges:edit-posts]]' }, + { name: '[[admin/manage/privileges:view-edit-history]]' }, + { name: '[[admin/manage/privileges:delete-posts]]' }, + { name: '[[admin/manage/privileges:upvote-posts]]' }, + { name: '[[admin/manage/privileges:downvote-posts]]' }, + { name: '[[admin/manage/privileges:delete-topics]]' }, + { name: '[[admin/manage/privileges:view_deleted]]' }, + { name: '[[admin/manage/privileges:purge]]' }, + { name: '[[admin/manage/privileges:moderate]]' }, +]; - const keys = await utils.promiseParallel({ - users: plugins.hooks.fire('filter:privileges.list', privileges.userPrivilegeList.slice()), - groups: plugins.hooks.fire('filter:privileges.groups.list', privileges.groupPrivilegeList.slice()), - }); +privsCategories.userPrivilegeList = [ + 'find', + 'read', + 'topics:read', + 'topics:create', + 'topics:reply', + 'topics:tag', + 'posts:edit', + 'posts:history', + 'posts:delete', + 'posts:upvote', + 'posts:downvote', + 'topics:delete', + 'posts:view_deleted', + 'purge', + 'moderate', +]; - const payload = await utils.promiseParallel({ - labels: getLabels(), - users: helpers.getUserPrivileges(cid, keys.users), - groups: helpers.getGroupPrivileges(cid, keys.groups), - }); - payload.keys = keys; +privsCategories.groupPrivilegeList = privsCategories.userPrivilegeList.map(privilege => `groups:${privilege}`); - // 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 + 3; - payload.columnCountUserOther = payload.labels.users.length - privileges.privilegeLabels.length; - payload.columnCountGroup = payload.labels.groups.length + 3; - payload.columnCountGroupOther = payload.labels.groups.length - privileges.privilegeLabels.length; - return payload; - }; +privsCategories.privilegeList = privsCategories.userPrivilegeList.concat(privsCategories.groupPrivilegeList); - privileges.categories.get = async function (cid, uid) { - const privs = ['topics:create', 'topics:read', 'topics:tag', 'read']; - - const [userPrivileges, isAdministrator, isModerator] = await Promise.all([ - helpers.isAllowedTo(privs, uid, cid), - user.isAdministrator(uid), - user.isModerator(uid, cid), - ]); - - const combined = userPrivileges.map(allowed => allowed || isAdministrator); - const privData = _.zipObject(privs, combined); - const isAdminOrMod = isAdministrator || isModerator; - - return await plugins.hooks.fire('filter:privileges.categories.get', { - ...privData, - cid: cid, - uid: uid, - editable: isAdminOrMod, - view_deleted: isAdminOrMod, - isAdminOrMod: isAdminOrMod, - }); - }; - - privileges.categories.isAdminOrMod = async function (cid, uid) { - if (parseInt(uid, 10) <= 0) { - return false; - } - const [isAdmin, isMod] = await Promise.all([ - user.isAdministrator(uid), - user.isModerator(uid, cid), - ]); - return isAdmin || isMod; - }; - - privileges.categories.isUserAllowedTo = async function (privilege, cid, uid) { - if ((Array.isArray(privilege) && !privilege.length) || (Array.isArray(cid) && !cid.length)) { - return []; - } - if (!cid) { - return false; - } - const results = await helpers.isAllowedTo(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 = async function (privilege, cid, uid) { - if (!cid) { - return false; - } - 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 = async function (privilege, cids, uid) { - if (!Array.isArray(cids) || !cids.length) { - return []; - } - - cids = _.uniq(cids); - const [categoryData, allowedTo, isAdmin] = await Promise.all([ - categories.getCategoriesFields(cids, ['disabled']), - helpers.isAllowedTo(privilege, uid, cids), - user.isAdministrator(uid), - ]); - return cids.filter( - (cid, index) => !!cid && !categoryData[index].disabled && (allowedTo[index] || isAdmin) - ); - }; - - privileges.categories.getBase = async function (privilege, cids, uid) { +// Method used in admin/category controller to show all users/groups with privs in that given cid +privsCategories.list = async function (cid) { + async function getLabels() { return await utils.promiseParallel({ - categories: categories.getCategoriesFields(cids, ['disabled']), - allowedTo: helpers.isAllowedTo(privilege, uid, cids), - view_deleted: helpers.isAllowedTo('posts:view_deleted', uid, cids), - isAdmin: user.isAdministrator(uid), + users: plugins.hooks.fire('filter:privileges.list_human', privsCategories.privilegeLabels.slice()), + groups: plugins.hooks.fire('filter:privileges.groups.list_human', privsCategories.privilegeLabels.slice()), }); - }; + } - privileges.categories.filterUids = async function (privilege, cid, uids) { - if (!uids.length) { - return []; - } + const keys = await utils.promiseParallel({ + users: plugins.hooks.fire('filter:privileges.list', privsCategories.userPrivilegeList.slice()), + groups: plugins.hooks.fire('filter:privileges.groups.list', privsCategories.groupPrivilegeList.slice()), + }); - uids = _.uniq(uids); + const payload = await utils.promiseParallel({ + labels: getLabels(), + users: helpers.getUserPrivileges(cid, keys.users), + groups: helpers.getGroupPrivileges(cid, keys.groups), + }); + payload.keys = keys; - 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 = async function (privileges, cid, members) { - await helpers.giveOrRescind(groups.join, privileges, cid, members); - plugins.hooks.fire('action:privileges.categories.give', { - privileges: privileges, - cids: Array.isArray(cid) ? cid : [cid], - members: Array.isArray(members) ? members : [members], - }); - }; - - privileges.categories.rescind = async function (privileges, cid, members) { - await helpers.giveOrRescind(groups.leave, privileges, cid, members); - plugins.hooks.fire('action:privileges.categories.rescind', { - privileges: privileges, - cids: Array.isArray(cid) ? cid : [cid], - members: Array.isArray(members) ? members : [members], - }); - }; - - 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 = async function (cid, uid) { - const tasks = {}; - privileges.userPrivilegeList.forEach((privilege) => { - tasks[privilege] = groups.isMember(uid, `cid:${cid}:privileges:${privilege}`); - }); - return await utils.promiseParallel(tasks); - }; - - privileges.categories.groupPrivileges = async function (cid, groupName) { - const tasks = {}; - privileges.groupPrivilegeList.forEach((privilege) => { - tasks[privilege] = groups.isMember(groupName, `cid:${cid}:privileges:${privilege}`); - }); - return await utils.promiseParallel(tasks); - }; + // 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 + 3; + payload.columnCountUserOther = payload.labels.users.length - privsCategories.privilegeLabels.length; + payload.columnCountGroup = payload.labels.groups.length + 3; + payload.columnCountGroupOther = payload.labels.groups.length - privsCategories.privilegeLabels.length; + return payload; +}; + +privsCategories.get = async function (cid, uid) { + const privs = ['topics:create', 'topics:read', 'topics:tag', 'read']; + + const [userPrivileges, isAdministrator, isModerator] = await Promise.all([ + helpers.isAllowedTo(privs, uid, cid), + user.isAdministrator(uid), + user.isModerator(uid, cid), + ]); + + const combined = userPrivileges.map(allowed => allowed || isAdministrator); + const privData = _.zipObject(privs, combined); + const isAdminOrMod = isAdministrator || isModerator; + + return await plugins.hooks.fire('filter:privileges.categories.get', { + ...privData, + cid: cid, + uid: uid, + editable: isAdminOrMod, + view_deleted: isAdminOrMod, + isAdminOrMod: isAdminOrMod, + }); +}; + +privsCategories.isAdminOrMod = async function (cid, uid) { + if (parseInt(uid, 10) <= 0) { + return false; + } + const [isAdmin, isMod] = await Promise.all([ + user.isAdministrator(uid), + user.isModerator(uid, cid), + ]); + return isAdmin || isMod; +}; + +privsCategories.isUserAllowedTo = async function (privilege, cid, uid) { + if ((Array.isArray(privilege) && !privilege.length) || (Array.isArray(cid) && !cid.length)) { + return []; + } + if (!cid) { + return false; + } + const results = await helpers.isAllowedTo(privilege, uid, Array.isArray(cid) ? cid : [cid]); + + if (Array.isArray(results) && results.length) { + return Array.isArray(cid) ? results : results[0]; + } + return false; +}; + +privsCategories.can = async function (privilege, cid, uid) { + if (!cid) { + return false; + } + const [disabled, isAdmin, isAllowed] = await Promise.all([ + categories.getCategoryField(cid, 'disabled'), + user.isAdministrator(uid), + privsCategories.isUserAllowedTo(privilege, cid, uid), + ]); + return !disabled && (isAllowed || isAdmin); +}; + +privsCategories.filterCids = async function (privilege, cids, uid) { + if (!Array.isArray(cids) || !cids.length) { + return []; + } + + cids = _.uniq(cids); + const [categoryData, allowedTo, isAdmin] = await Promise.all([ + categories.getCategoriesFields(cids, ['disabled']), + helpers.isAllowedTo(privilege, uid, cids), + user.isAdministrator(uid), + ]); + return cids.filter( + (cid, index) => !!cid && !categoryData[index].disabled && (allowedTo[index] || isAdmin) + ); +}; + +privsCategories.getBase = async function (privilege, cids, uid) { + return await utils.promiseParallel({ + categories: categories.getCategoriesFields(cids, ['disabled']), + allowedTo: helpers.isAllowedTo(privilege, uid, cids), + view_deleted: helpers.isAllowedTo('posts:view_deleted', uid, cids), + isAdmin: user.isAdministrator(uid), + }); +}; + +privsCategories.filterUids = async function (privilege, cid, uids) { + if (!uids.length) { + return []; + } + + uids = _.uniq(uids); + + const [allowedTo, isAdmins] = await Promise.all([ + helpers.isUsersAllowedTo(privilege, uids, cid), + user.isAdministrator(uids), + ]); + return uids.filter((uid, index) => allowedTo[index] || isAdmins[index]); +}; + +privsCategories.give = async function (privileges, cid, members) { + await helpers.giveOrRescind(groups.join, privileges, cid, members); + plugins.hooks.fire('action:privileges.categories.give', { + privileges: privileges, + cids: Array.isArray(cid) ? cid : [cid], + members: Array.isArray(members) ? members : [members], + }); +}; + +privsCategories.rescind = async function (privileges, cid, members) { + await helpers.giveOrRescind(groups.leave, privileges, cid, members); + plugins.hooks.fire('action:privileges.categories.rescind', { + privileges: privileges, + cids: Array.isArray(cid) ? cid : [cid], + members: Array.isArray(members) ? members : [members], + }); +}; + +privsCategories.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); +}; + +privsCategories.userPrivileges = async function (cid, uid) { + return await helpers.userOrGroupPrivileges(cid, uid, privsCategories.userPrivilegeList); +}; + +privsCategories.groupPrivileges = async function (cid, groupName) { + return await helpers.userOrGroupPrivileges(cid, groupName, privsCategories.groupPrivilegeList); }; diff --git a/src/privileges/global.js b/src/privileges/global.js index 7c0e1b369b..f8e8fe894f 100644 --- a/src/privileges/global.js +++ b/src/privileges/global.js @@ -9,125 +9,115 @@ const helpers = require('./helpers'); const plugins = require('../plugins'); const utils = require('../utils'); -module.exports = function (privileges) { - privileges.global = {}; +const privsGlobal = module.exports; - privileges.global.privilegeLabels = [ - { name: '[[admin/manage/privileges:chat]]' }, - { name: '[[admin/manage/privileges:upload-images]]' }, - { name: '[[admin/manage/privileges:upload-files]]' }, - { name: '[[admin/manage/privileges:signature]]' }, - { name: '[[admin/manage/privileges:ban]]' }, - { name: '[[admin/manage/privileges:invite]]' }, - { name: '[[admin/manage/privileges:search-content]]' }, - { name: '[[admin/manage/privileges:search-users]]' }, - { name: '[[admin/manage/privileges:search-tags]]' }, - { name: '[[admin/manage/privileges:view-users]]' }, - { name: '[[admin/manage/privileges:view-tags]]' }, - { name: '[[admin/manage/privileges:view-groups]]' }, - { name: '[[admin/manage/privileges:allow-local-login]]' }, - { name: '[[admin/manage/privileges:allow-group-creation]]' }, - { name: '[[admin/manage/privileges:view-users-info]]' }, - ]; +privsGlobal.privilegeLabels = [ + { name: '[[admin/manage/privileges:chat]]' }, + { name: '[[admin/manage/privileges:upload-images]]' }, + { name: '[[admin/manage/privileges:upload-files]]' }, + { name: '[[admin/manage/privileges:signature]]' }, + { name: '[[admin/manage/privileges:ban]]' }, + { name: '[[admin/manage/privileges:invite]]' }, + { name: '[[admin/manage/privileges:search-content]]' }, + { name: '[[admin/manage/privileges:search-users]]' }, + { name: '[[admin/manage/privileges:search-tags]]' }, + { name: '[[admin/manage/privileges:view-users]]' }, + { name: '[[admin/manage/privileges:view-tags]]' }, + { name: '[[admin/manage/privileges:view-groups]]' }, + { name: '[[admin/manage/privileges:allow-local-login]]' }, + { name: '[[admin/manage/privileges:allow-group-creation]]' }, + { name: '[[admin/manage/privileges:view-users-info]]' }, +]; - privileges.global.userPrivilegeList = [ - 'chat', - 'upload:post:image', - 'upload:post:file', - 'signature', - 'ban', - 'invite', - 'search:content', - 'search:users', - 'search:tags', - 'view:users', - 'view:tags', - 'view:groups', - 'local:login', - 'group:create', - 'view:users:info', - ]; +privsGlobal.userPrivilegeList = [ + 'chat', + 'upload:post:image', + 'upload:post:file', + 'signature', + 'ban', + 'invite', + 'search:content', + 'search:users', + 'search:tags', + 'view:users', + 'view:tags', + 'view:groups', + 'local:login', + 'group:create', + 'view:users:info', +]; - privileges.global.groupPrivilegeList = privileges.global.userPrivilegeList.map(privilege => `groups:${privilege}`); +privsGlobal.groupPrivilegeList = privsGlobal.userPrivilegeList.map(privilege => `groups:${privilege}`); - privileges.global.list = async function () { - async function getLabels() { - return await utils.promiseParallel({ - users: plugins.hooks.fire('filter:privileges.global.list_human', privileges.global.privilegeLabels.slice()), - groups: plugins.hooks.fire('filter:privileges.global.groups.list_human', privileges.global.privilegeLabels.slice()), - }); - } - - const keys = await utils.promiseParallel({ - users: plugins.hooks.fire('filter:privileges.global.list', privileges.global.userPrivilegeList.slice()), - groups: plugins.hooks.fire('filter:privileges.global.groups.list', privileges.global.groupPrivilegeList.slice()), +privsGlobal.list = async function () { + async function getLabels() { + return await utils.promiseParallel({ + users: plugins.hooks.fire('filter:privileges.global.list_human', privsGlobal.privilegeLabels.slice()), + groups: plugins.hooks.fire('filter:privileges.global.groups.list_human', privsGlobal.privilegeLabels.slice()), }); + } - const payload = await utils.promiseParallel({ - labels: getLabels(), - users: helpers.getUserPrivileges(0, keys.users), - groups: helpers.getGroupPrivileges(0, keys.groups), - }); - payload.keys = keys; + const keys = await utils.promiseParallel({ + users: plugins.hooks.fire('filter:privileges.global.list', privsGlobal.userPrivilegeList.slice()), + groups: plugins.hooks.fire('filter:privileges.global.groups.list', privsGlobal.groupPrivilegeList.slice()), + }); - // 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 + 3; - return payload; - }; + const payload = await utils.promiseParallel({ + labels: getLabels(), + users: helpers.getUserPrivileges(0, keys.users), + groups: helpers.getGroupPrivileges(0, keys.groups), + }); + payload.keys = keys; - privileges.global.get = async function (uid) { - const [userPrivileges, isAdministrator] = await Promise.all([ - helpers.isAllowedTo(privileges.global.userPrivilegeList, uid, 0), - user.isAdministrator(uid), - ]); - - const combined = userPrivileges.map(allowed => allowed || isAdministrator); - const privData = _.zipObject(privileges.global.userPrivilegeList, combined); - - return await plugins.hooks.fire('filter:privileges.global.get', privData); - }; - - privileges.global.can = async function (privilege, uid) { - const [isAdministrator, isUserAllowedTo] = await Promise.all([ - user.isAdministrator(uid), - helpers.isAllowedTo(privilege, uid, [0]), - ]); - return isAdministrator || isUserAllowedTo[0]; - }; - - privileges.global.canGroup = async function (privilege, groupName) { - return await groups.isMember(groupName, `cid:0:privileges:groups:${privilege}`); - }; - - privileges.global.give = async function (privileges, groupName) { - await helpers.giveOrRescind(groups.join, privileges, 0, groupName); - plugins.hooks.fire('action:privileges.global.give', { - privileges: privileges, - groupNames: Array.isArray(groupName) ? groupName : [groupName], - }); - }; - - privileges.global.rescind = async function (privileges, groupName) { - await helpers.giveOrRescind(groups.leave, privileges, 0, groupName); - plugins.hooks.fire('action:privileges.global.rescind', { - privileges: privileges, - groupNames: Array.isArray(groupName) ? groupName : [groupName], - }); - }; - - privileges.global.userPrivileges = async function (uid) { - const tasks = {}; - privileges.global.userPrivilegeList.forEach((privilege) => { - tasks[privilege] = groups.isMember(uid, `cid:0:privileges:${privilege}`); - }); - return await utils.promiseParallel(tasks); - }; - - privileges.global.groupPrivileges = async function (groupName) { - const tasks = {}; - privileges.global.groupPrivilegeList.forEach((privilege) => { - tasks[privilege] = groups.isMember(groupName, `cid:0:privileges:${privilege}`); - }); - return await utils.promiseParallel(tasks); - }; + // 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 + 3; + return payload; +}; + +privsGlobal.get = async function (uid) { + const [userPrivileges, isAdministrator] = await Promise.all([ + helpers.isAllowedTo(privsGlobal.userPrivilegeList, uid, 0), + user.isAdministrator(uid), + ]); + + const combined = userPrivileges.map(allowed => allowed || isAdministrator); + const privData = _.zipObject(privsGlobal.userPrivilegeList, combined); + + return await plugins.hooks.fire('filter:privileges.global.get', privData); +}; + +privsGlobal.can = async function (privilege, uid) { + const [isAdministrator, isUserAllowedTo] = await Promise.all([ + user.isAdministrator(uid), + helpers.isAllowedTo(privilege, uid, [0]), + ]); + return isAdministrator || isUserAllowedTo[0]; +}; + +privsGlobal.canGroup = async function (privilege, groupName) { + return await groups.isMember(groupName, `cid:0:privileges:groups:${privilege}`); +}; + +privsGlobal.give = async function (privileges, groupName) { + await helpers.giveOrRescind(groups.join, privileges, 0, groupName); + plugins.hooks.fire('action:privileges.global.give', { + privileges: privileges, + groupNames: Array.isArray(groupName) ? groupName : [groupName], + }); +}; + +privsGlobal.rescind = async function (privileges, groupName) { + await helpers.giveOrRescind(groups.leave, privileges, 0, groupName); + plugins.hooks.fire('action:privileges.global.rescind', { + privileges: privileges, + groupNames: Array.isArray(groupName) ? groupName : [groupName], + }); +}; + +privsGlobal.userPrivileges = async function (uid) { + return await helpers.userOrGroupPrivileges(0, uid, privsGlobal.userPrivilegeList); +}; + +privsGlobal.groupPrivileges = async function (groupName) { + return await helpers.userOrGroupPrivileges(0, groupName, privsGlobal.groupPrivilegeList); }; diff --git a/src/privileges/helpers.js b/src/privileges/helpers.js index 61198b0015..aedc9a7cee 100644 --- a/src/privileges/helpers.js +++ b/src/privileges/helpers.js @@ -8,6 +8,7 @@ const groups = require('../groups'); const user = require('../user'); const plugins = require('../plugins'); const translator = require('../translator'); +const utils = require('../utils'); const helpers = module.exports; @@ -183,4 +184,12 @@ helpers.giveOrRescind = async function (method, privileges, cids, members) { } }; +helpers.userOrGroupPrivileges = async function (cid, uidOrGroup, privilegeList) { + const tasks = {}; + privilegeList.forEach((privilege) => { + tasks[privilege] = groups.isMember(uidOrGroup, `cid:${cid}:privileges:${privilege}`); + }); + return await utils.promiseParallel(tasks); +}; + require('../promisify')(helpers); diff --git a/src/privileges/index.js b/src/privileges/index.js index 4f9a482815..4a9c4d2096 100644 --- a/src/privileges/index.js +++ b/src/privileges/index.js @@ -1,52 +1,28 @@ 'use strict'; const privileges = module.exports; - -privileges.privilegeLabels = [ - { name: '[[admin/manage/privileges:find-category]]' }, - { name: '[[admin/manage/privileges:access-category]]' }, - { name: '[[admin/manage/privileges:access-topics]]' }, - { name: '[[admin/manage/privileges:create-topics]]' }, - { name: '[[admin/manage/privileges:reply-to-topics]]' }, - { name: '[[admin/manage/privileges:tag-topics]]' }, - { name: '[[admin/manage/privileges:edit-posts]]' }, - { name: '[[admin/manage/privileges:view-edit-history]]' }, - { name: '[[admin/manage/privileges:delete-posts]]' }, - { name: '[[admin/manage/privileges:upvote-posts]]' }, - { name: '[[admin/manage/privileges:downvote-posts]]' }, - { name: '[[admin/manage/privileges:delete-topics]]' }, - { name: '[[admin/manage/privileges:view_deleted]]' }, - { name: '[[admin/manage/privileges:purge]]' }, - { name: '[[admin/manage/privileges:moderate]]' }, -]; - -privileges.userPrivilegeList = [ - 'find', - 'read', - 'topics:read', - 'topics:create', - 'topics:reply', - 'topics:tag', - 'posts:edit', - 'posts:history', - 'posts:delete', - 'posts:upvote', - 'posts:downvote', - 'topics:delete', - 'posts:view_deleted', - 'purge', - 'moderate', -]; - -privileges.groupPrivilegeList = privileges.userPrivilegeList.map(privilege => `groups:${privilege}`); - -privileges.privilegeList = privileges.userPrivilegeList.concat(privileges.groupPrivilegeList); - -require('./global')(privileges); -require('./admin')(privileges); -require('./categories')(privileges); -require('./topics')(privileges); -require('./posts')(privileges); -require('./users')(privileges); +privileges.global = require('./global'); +privileges.admin = require('./admin'); +privileges.categories = require('./categories'); +privileges.topics = require('./topics'); +privileges.posts = require('./posts'); +privileges.users = require('./users'); require('../promisify')(privileges); + +// TODO: backwards compatibility remove in 1.18.0 +[ + 'privilegeLabels', + 'userPrivilegeList', + 'groupPrivilegeList', + 'privilegeList', +].forEach((fieldName) => { + Object.defineProperty(privileges, fieldName, { + configurable: true, + enumerable: true, + get: function () { + console.warn(`[deprecated] privileges.${fieldName} is deprecated. Use privileges.categories.${fieldName}`); + return privileges.categories[fieldName]; + }, + }); +}); diff --git a/src/privileges/posts.js b/src/privileges/posts.js index 68842a3992..7252c98185 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -10,221 +10,220 @@ const user = require('../user'); const helpers = require('./helpers'); const plugins = require('../plugins'); const utils = require('../utils'); +const privsCategories = require('./categories'); -module.exports = function (privileges) { - privileges.posts = {}; +const privsPosts = module.exports; - privileges.posts.get = async function (pids, uid) { - if (!Array.isArray(pids) || !pids.length) { - return []; - } - const cids = await posts.getCidsByPids(pids); - const uniqueCids = _.uniq(cids); - - const results = await utils.promiseParallel({ - isAdmin: user.isAdministrator(uid), - isModerator: user.isModerator(uid, uniqueCids), - isOwner: posts.isOwner(pids, uid), - 'topics:read': helpers.isAllowedTo('topics:read', uid, uniqueCids), - read: helpers.isAllowedTo('read', uid, uniqueCids), - 'posts:edit': helpers.isAllowedTo('posts:edit', uid, uniqueCids), - 'posts:history': helpers.isAllowedTo('posts:history', uid, uniqueCids), - 'posts:view_deleted': helpers.isAllowedTo('posts:view_deleted', uid, uniqueCids), - }); - - 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 privileges = cids.map((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 = async function (privilege, pid, uid) { - const cid = await posts.getCidByPid(pid); - return await privileges.categories.can(privilege, cid, uid); - }; - - privileges.posts.filter = async function (privilege, pids, uid) { - if (!Array.isArray(pids) || !pids.length) { - return []; - } - - 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']); - - const tidToTopic = _.zipObject(tids, topicData); - - let cids = postData.map((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)); - - cids = _.uniq(cids); - - const results = await privileges.categories.getBase(privilege, cids, uid); - const allowedCids = cids.filter((cid, index) => !results.categories[index].disabled && - (results.allowedTo[index] || results.isAdmin)); - - const cidsSet = new Set(allowedCids); - const canViewDeleted = _.zipObject(cids, results.view_deleted); - - pids = postData.filter(post => ( - post.topic && - cidsSet.has(post.topic.cid) && - ((!post.topic.deleted && !post.deleted) || canViewDeleted[post.topic.cid] || results.isAdmin) - )).map(post => post.pid); - - const data = await plugins.hooks.fire('filter:privileges.posts.filter', { - privilege: privilege, - uid: uid, - pids: pids, - }); - - return data ? data.pids : null; - }; - - 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']), - }); - - results.isMod = results.isMod[0]; - if (results.isAdmin) { - return { flag: true }; - } - - 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}]]` }; - } - - const isLocked = await topics.isLocked(results.postData.tid); - if (!results.isMod && isLocked) { - return { flag: false, message: '[[error:topic-locked]]' }; - } - - if (!results.isMod && results.postData.deleted && parseInt(uid, 10) !== parseInt(results.postData.deleterUid, 10)) { - return { flag: false, message: '[[error:post-deleted]]' }; - } - - results.pid = parseInt(pid, 10); - results.uid = uid; - - const result = await plugins.hooks.fire('filter:privileges.posts.edit', results); - return { flag: result.edit && (result.owner || result.isMod), message: '[[error:no-privileges]]' }; - }; - - 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 { flag: false, message: '[[error:topic-locked]]' }; - } - - const { postDeleteDuration } = meta.config; - if (!results.isMod && postDeleteDuration && (Date.now() - postData.timestamp > postDeleteDuration * 1000)) { - return { flag: false, message: `[[error:post-delete-duration-expired, ${meta.config.postDeleteDuration}]]` }; - } - const { deleterUid } = postData; - const flag = results['posts:delete'] && ((results.isOwner && (deleterUid === 0 || deleterUid === postData.uid)) || results.isMod); - return { flag: flag, message: '[[error:no-privileges]]' }; - }; - - privileges.posts.canFlag = async function (pid, uid) { - const targetUid = await posts.getPostField(pid, 'uid'); - const [userReputation, isAdminOrModerator, targetPrivileged, reporterPrivileged] = await Promise.all([ - user.getUserField(uid, 'reputation'), - isAdminOrMod(pid, uid), - user.isPrivileged(targetUid), - user.isPrivileged(uid), - ]); - const minimumReputation = meta.config['min:rep:flag']; - let canFlag = isAdminOrModerator || (userReputation >= minimumReputation); - - if (targetPrivileged && !reporterPrivileged) { - canFlag = false; - } - - return { flag: canFlag }; - }; - - 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 = 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; - }; - - 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); +privsPosts.get = async function (pids, uid) { + if (!Array.isArray(pids) || !pids.length) { + return []; } + const cids = await posts.getCidsByPids(pids); + const uniqueCids = _.uniq(cids); + + const results = await utils.promiseParallel({ + isAdmin: user.isAdministrator(uid), + isModerator: user.isModerator(uid, uniqueCids), + isOwner: posts.isOwner(pids, uid), + 'topics:read': helpers.isAllowedTo('topics:read', uid, uniqueCids), + read: helpers.isAllowedTo('read', uid, uniqueCids), + 'posts:edit': helpers.isAllowedTo('posts:edit', uid, uniqueCids), + 'posts:history': helpers.isAllowedTo('posts:history', uid, uniqueCids), + 'posts:view_deleted': helpers.isAllowedTo('posts:view_deleted', uid, uniqueCids), + }); + + 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 privileges = cids.map((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; }; + +privsPosts.can = async function (privilege, pid, uid) { + const cid = await posts.getCidByPid(pid); + return await privsCategories.can(privilege, cid, uid); +}; + +privsPosts.filter = async function (privilege, pids, uid) { + if (!Array.isArray(pids) || !pids.length) { + return []; + } + + 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']); + + const tidToTopic = _.zipObject(tids, topicData); + + let cids = postData.map((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)); + + cids = _.uniq(cids); + + const results = await privsCategories.getBase(privilege, cids, uid); + const allowedCids = cids.filter((cid, index) => !results.categories[index].disabled && + (results.allowedTo[index] || results.isAdmin)); + + const cidsSet = new Set(allowedCids); + const canViewDeleted = _.zipObject(cids, results.view_deleted); + + pids = postData.filter(post => ( + post.topic && + cidsSet.has(post.topic.cid) && + ((!post.topic.deleted && !post.deleted) || canViewDeleted[post.topic.cid] || results.isAdmin) + )).map(post => post.pid); + + const data = await plugins.hooks.fire('filter:privileges.posts.filter', { + privilege: privilege, + uid: uid, + pids: pids, + }); + + return data ? data.pids : null; +}; + +privsPosts.canEdit = async function (pid, uid) { + const results = await utils.promiseParallel({ + isAdmin: user.isAdministrator(uid), + isMod: posts.isModerator([pid], uid), + owner: posts.isOwner(pid, uid), + edit: privsPosts.can('posts:edit', pid, uid), + postData: posts.getPostFields(pid, ['tid', 'timestamp', 'deleted', 'deleterUid']), + userData: user.getUserFields(uid, ['reputation']), + }); + + results.isMod = results.isMod[0]; + if (results.isAdmin) { + return { flag: true }; + } + + 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}]]` }; + } + + const isLocked = await topics.isLocked(results.postData.tid); + if (!results.isMod && isLocked) { + return { flag: false, message: '[[error:topic-locked]]' }; + } + + if (!results.isMod && results.postData.deleted && parseInt(uid, 10) !== parseInt(results.postData.deleterUid, 10)) { + return { flag: false, message: '[[error:post-deleted]]' }; + } + + results.pid = parseInt(pid, 10); + results.uid = uid; + + const result = await plugins.hooks.fire('filter:privileges.posts.edit', results); + return { flag: result.edit && (result.owner || result.isMod), message: '[[error:no-privileges]]' }; +}; + +privsPosts.canDelete = async function (pid, uid) { + const postData = await posts.getPostFields(pid, ['uid', 'tid', 'timestamp', 'deleterUid']); + const results = await utils.promiseParallel({ + isAdmin: user.isAdministrator(uid), + isMod: posts.isModerator([pid], uid), + isLocked: topics.isLocked(postData.tid), + isOwner: posts.isOwner(pid, uid), + 'posts:delete': privsPosts.can('posts:delete', pid, uid), + }); + results.isMod = results.isMod[0]; + if (results.isAdmin) { + return { flag: true }; + } + + if (!results.isMod && results.isLocked) { + return { flag: false, message: '[[error:topic-locked]]' }; + } + + const { postDeleteDuration } = meta.config; + if (!results.isMod && postDeleteDuration && (Date.now() - postData.timestamp > postDeleteDuration * 1000)) { + return { flag: false, message: `[[error:post-delete-duration-expired, ${meta.config.postDeleteDuration}]]` }; + } + const { deleterUid } = postData; + const flag = results['posts:delete'] && ((results.isOwner && (deleterUid === 0 || deleterUid === postData.uid)) || results.isMod); + return { flag: flag, message: '[[error:no-privileges]]' }; +}; + +privsPosts.canFlag = async function (pid, uid) { + const targetUid = await posts.getPostField(pid, 'uid'); + const [userReputation, isAdminOrModerator, targetPrivileged, reporterPrivileged] = await Promise.all([ + user.getUserField(uid, 'reputation'), + isAdminOrMod(pid, uid), + user.isPrivileged(targetUid), + user.isPrivileged(uid), + ]); + const minimumReputation = meta.config['min:rep:flag']; + let canFlag = isAdminOrModerator || (userReputation >= minimumReputation); + + if (targetPrivileged && !reporterPrivileged) { + canFlag = false; + } + + return { flag: canFlag }; +}; + +privsPosts.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); +}; + +privsPosts.canPurge = async function (pid, uid) { + const cid = await posts.getCidByPid(pid); + const results = await utils.promiseParallel({ + purge: privsCategories.isUserAllowedTo('purge', cid, uid), + owner: posts.isOwner(pid, uid), + isAdmin: user.isAdministrator(uid), + isModerator: user.isModerator(uid, cid), + }); + return (results.purge && (results.owner || results.isModerator)) || results.isAdmin; +}; + +async function isAdminOrMod(pid, uid) { + if (parseInt(uid, 10) <= 0) { + return false; + } + const cid = await posts.getCidByPid(pid); + return await privsCategories.isAdminOrMod(cid, uid); +} diff --git a/src/privileges/topics.js b/src/privileges/topics.js index 5524b12bb0..52a4af2c31 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -9,158 +9,157 @@ const user = require('../user'); const helpers = require('./helpers'); const categories = require('../categories'); const plugins = require('../plugins'); +const privsCategories = require('./categories'); -module.exports = function (privileges) { - privileges.topics = {}; +const privsTopics = module.exports; - privileges.topics.get = async function (tid, uid) { - uid = parseInt(uid, 10); +privsTopics.get = async function (tid, uid) { + uid = parseInt(uid, 10); - const privs = [ - 'topics:reply', 'topics:read', 'topics:tag', - 'topics:delete', 'posts:edit', 'posts:history', - 'posts:delete', 'posts:view_deleted', 'read', 'purge', - ]; - const topicData = await topics.getTopicFields(tid, ['cid', 'uid', 'locked', 'deleted']); - const [userPrivileges, isAdministrator, isModerator, disabled] = await Promise.all([ - helpers.isAllowedTo(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; + const privs = [ + 'topics:reply', 'topics:read', 'topics:tag', + 'topics:delete', 'posts:edit', 'posts:history', + 'posts:delete', 'posts:view_deleted', 'read', 'purge', + ]; + const topicData = await topics.getTopicFields(tid, ['cid', 'uid', 'locked', 'deleted']); + const [userPrivileges, isAdministrator, isModerator, disabled] = await Promise.all([ + helpers.isAllowedTo(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; - return await plugins.hooks.fire('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, + return await plugins.hooks.fire('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: disabled, - tid: tid, - uid: uid, - }); - }; - - 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 = async function (privilege, tids, uid) { - if (!Array.isArray(tids) || !tids.length) { - return []; - } - - const topicsData = await topics.getTopicsFields(tids, ['tid', 'cid', 'deleted']); - const cids = _.uniq(topicsData.map(topic => topic.cid)); - const results = await privileges.categories.getBase(privilege, cids, uid); - - const allowedCids = cids.filter((cid, index) => ( - !results.categories[index].disabled && - (results.allowedTo[index] || results.isAdmin) - )); - - const cidsSet = new Set(allowedCids); - const canViewDeleted = _.zipObject(cids, results.view_deleted); - - tids = topicsData.filter(t => ( - cidsSet.has(t.cid) && - (!t.deleted || canViewDeleted[t.cid] || results.isAdmin) - )).map(t => t.tid); - - const data = await plugins.hooks.fire('filter:privileges.topics.filter', { - privilege: privilege, - uid: uid, - tids: tids, - }); - return data ? data.tids : []; - }; - - privileges.topics.filterUids = async function (privilege, tid, uids) { - if (!Array.isArray(uids) || !uids.length) { - return []; - } - - uids = _.uniq(uids); - 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((uid, index) => !disabled && - ((allowedTo[index] && !topicData.deleted) || isAdmins[index])); - }; - - 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 = async function (tid, uid) { - const topicData = await topics.getTopicFields(tid, ['uid', 'cid', 'postcount', 'deleterUid']); - const [isModerator, isAdministrator, isOwner, allowedTo] = await Promise.all([ - user.isModerator(uid, topicData.cid), - user.isAdministrator(uid), - topics.isOwner(tid, uid), - helpers.isAllowedTo('topics:delete', uid, [topicData.cid]), - ]); - - if (isAdministrator) { - return true; - } - - const { preventTopicDeleteAfterReplies } = meta.config; - 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); - } - - const { deleterUid } = topicData; - return allowedTo[0] && ((isOwner && (deleterUid === 0 || deleterUid === topicData.uid)) || isModerator); - }; - - privileges.topics.canEdit = async function (tid, uid) { - return await privileges.topics.isOwnerOrAdminOrMod(tid, uid); - }; - - 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 = 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); - }; + view_thread_tools: editable || deletable, + editable: editable, + deletable: deletable, + view_deleted: isAdminOrMod || isOwner, + isAdminOrMod: isAdminOrMod, + disabled: disabled, + tid: tid, + uid: uid, + }); +}; + +privsTopics.can = async function (privilege, tid, uid) { + const cid = await topics.getTopicField(tid, 'cid'); + return await privsCategories.can(privilege, cid, uid); +}; + +privsTopics.filterTids = async function (privilege, tids, uid) { + if (!Array.isArray(tids) || !tids.length) { + return []; + } + + const topicsData = await topics.getTopicsFields(tids, ['tid', 'cid', 'deleted']); + const cids = _.uniq(topicsData.map(topic => topic.cid)); + const results = await privsCategories.getBase(privilege, cids, uid); + + const allowedCids = cids.filter((cid, index) => ( + !results.categories[index].disabled && + (results.allowedTo[index] || results.isAdmin) + )); + + const cidsSet = new Set(allowedCids); + const canViewDeleted = _.zipObject(cids, results.view_deleted); + + tids = topicsData.filter(t => ( + cidsSet.has(t.cid) && + (!t.deleted || canViewDeleted[t.cid] || results.isAdmin) + )).map(t => t.tid); + + const data = await plugins.hooks.fire('filter:privileges.topics.filter', { + privilege: privilege, + uid: uid, + tids: tids, + }); + return data ? data.tids : []; +}; + +privsTopics.filterUids = async function (privilege, tid, uids) { + if (!Array.isArray(uids) || !uids.length) { + return []; + } + + uids = _.uniq(uids); + 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((uid, index) => !disabled && + ((allowedTo[index] && !topicData.deleted) || isAdmins[index])); +}; + +privsTopics.canPurge = async function (tid, uid) { + const cid = await topics.getTopicField(tid, 'cid'); + const [purge, owner, isAdmin, isModerator] = await Promise.all([ + privsCategories.isUserAllowedTo('purge', cid, uid), + topics.isOwner(tid, uid), + user.isAdministrator(uid), + user.isModerator(uid, cid), + ]); + return (purge && (owner || isModerator)) || isAdmin; +}; + +privsTopics.canDelete = async function (tid, uid) { + const topicData = await topics.getTopicFields(tid, ['uid', 'cid', 'postcount', 'deleterUid']); + const [isModerator, isAdministrator, isOwner, allowedTo] = await Promise.all([ + user.isModerator(uid, topicData.cid), + user.isAdministrator(uid), + topics.isOwner(tid, uid), + helpers.isAllowedTo('topics:delete', uid, [topicData.cid]), + ]); + + if (isAdministrator) { + return true; + } + + const { preventTopicDeleteAfterReplies } = meta.config; + 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); + } + + const { deleterUid } = topicData; + return allowedTo[0] && ((isOwner && (deleterUid === 0 || deleterUid === topicData.uid)) || isModerator); +}; + +privsTopics.canEdit = async function (tid, uid) { + return await privsTopics.isOwnerOrAdminOrMod(tid, uid); +}; + +privsTopics.isOwnerOrAdminOrMod = async function (tid, uid) { + const [isOwner, isAdminOrMod] = await Promise.all([ + topics.isOwner(tid, uid), + privsTopics.isAdminOrMod(tid, uid), + ]); + return isOwner || isAdminOrMod; +}; + +privsTopics.isAdminOrMod = async function (tid, uid) { + if (parseInt(uid, 10) <= 0) { + return false; + } + const cid = await topics.getTopicField(tid, 'cid'); + return await privsCategories.isAdminOrMod(cid, uid); }; diff --git a/src/privileges/users.js b/src/privileges/users.js index 7a1488ebfc..88f28ea5c7 100644 --- a/src/privileges/users.js +++ b/src/privileges/users.js @@ -9,130 +9,130 @@ const groups = require('../groups'); const plugins = require('../plugins'); const helpers = require('./helpers'); -module.exports = function (privileges) { - privileges.users = {}; +const privsUsers = module.exports; - privileges.users.isAdministrator = async function (uid) { - return await isGroupMember(uid, 'administrators'); - }; - - privileges.users.isGlobalModerator = async function (uid) { - return await isGroupMember(uid, 'Global Moderators'); - }; - - 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)) { - return await isModeratorOfCategories(cid, uid); - } else if (Array.isArray(uid)) { - return await isModeratorsOfCategory(cid, uid); - } - return await isModeratorOfCategory(cid, uid); - }; - - async function isModeratorOfCategories(cids, uid) { - if (parseInt(uid, 10) <= 0) { - return await filterIsModerator(cids, uid, cids.map(() => false)); - } - - const isGlobalModerator = await privileges.users.isGlobalModerator(uid); - if (isGlobalModerator) { - return await filterIsModerator(cids, uid, cids.map(() => true)); - } - const uniqueCids = _.uniq(cids); - const isAllowed = await helpers.isAllowedTo('moderate', uid, uniqueCids); - - const cidToIsAllowed = _.zipObject(uniqueCids, isAllowed); - const isModerator = cids.map(cid => cidToIsAllowed[cid]); - return await filterIsModerator(cids, uid, isModerator); - } - - 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); - } - - 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.hooks.fire('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 true; - } - const [isAdmin, isGlobalMod, isTargetAdmin] = await Promise.all([ - privileges.users.isAdministrator(callerUid), - privileges.users.isGlobalModerator(callerUid), - privileges.users.isAdministrator(uid), - ]); - - const data = await plugins.hooks.fire('filter:user.canEdit', { - isAdmin: isAdmin, - isGlobalMod: isGlobalMod, - isTargetAdmin: isTargetAdmin, - canEdit: isAdmin || (isGlobalMod && !isTargetAdmin), - callerUid: callerUid, - uid: uid, - }); - return data.canEdit; - }; - - 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.hooks.fire('filter:user.canBanUser', { - canBan: canBan && !isTargetAdmin, - callerUid: callerUid, - uid: uid, - }); - return data.canBan; - }; - - privileges.users.canFlag = async function (callerUid, uid) { - const [userReputation, targetPrivileged, reporterPrivileged] = await Promise.all([ - user.getUserField(callerUid, 'reputation'), - user.isPrivileged(uid), - user.isPrivileged(callerUid), - ]); - const minimumReputation = meta.config['min:rep:flag']; - let canFlag = reporterPrivileged || (userReputation >= minimumReputation); - - if (targetPrivileged && !reporterPrivileged) { - canFlag = false; - } - - return { flag: canFlag }; - }; - - privileges.users.hasBanPrivilege = async uid => await hasGlobalPrivilege('ban', uid); - privileges.users.hasInvitePrivilege = async uid => await hasGlobalPrivilege('invite', uid); - - async function hasGlobalPrivilege(privilege, uid) { - const privilegeName = privilege.split('-').map(word => word.slice(0, 1).toUpperCase() + word.slice(1)).join(''); - let payload = { uid }; - payload[`can${privilegeName}`] = await privileges.global.can(privilege, uid); - payload = await plugins.hooks.fire(`filter:user.has${privilegeName}Privilege`, payload); - return payload[`can${privilegeName}`]; - } +privsUsers.isAdministrator = async function (uid) { + return await isGroupMember(uid, 'administrators'); }; + +privsUsers.isGlobalModerator = async function (uid) { + return await isGroupMember(uid, 'Global Moderators'); +}; + +async function isGroupMember(uid, groupName) { + return await groups[Array.isArray(uid) ? 'isMembers' : 'isMember'](uid, groupName); +} + +privsUsers.isModerator = async function (uid, cid) { + if (Array.isArray(cid)) { + return await isModeratorOfCategories(cid, uid); + } else if (Array.isArray(uid)) { + return await isModeratorsOfCategory(cid, uid); + } + return await isModeratorOfCategory(cid, uid); +}; + +async function isModeratorOfCategories(cids, uid) { + if (parseInt(uid, 10) <= 0) { + return await filterIsModerator(cids, uid, cids.map(() => false)); + } + + const isGlobalModerator = await privsUsers.isGlobalModerator(uid); + if (isGlobalModerator) { + return await filterIsModerator(cids, uid, cids.map(() => true)); + } + const uniqueCids = _.uniq(cids); + const isAllowed = await helpers.isAllowedTo('moderate', uid, uniqueCids); + + const cidToIsAllowed = _.zipObject(uniqueCids, isAllowed); + const isModerator = cids.map(cid => cidToIsAllowed[cid]); + return await filterIsModerator(cids, uid, isModerator); +} + +async function isModeratorsOfCategory(cid, uids) { + const [check1, check2, check3] = await Promise.all([ + privsUsers.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); +} + +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.hooks.fire('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; +} + +privsUsers.canEdit = async function (callerUid, uid) { + if (parseInt(callerUid, 10) === parseInt(uid, 10)) { + return true; + } + const [isAdmin, isGlobalMod, isTargetAdmin] = await Promise.all([ + privsUsers.isAdministrator(callerUid), + privsUsers.isGlobalModerator(callerUid), + privsUsers.isAdministrator(uid), + ]); + + const data = await plugins.hooks.fire('filter:user.canEdit', { + isAdmin: isAdmin, + isGlobalMod: isGlobalMod, + isTargetAdmin: isTargetAdmin, + canEdit: isAdmin || (isGlobalMod && !isTargetAdmin), + callerUid: callerUid, + uid: uid, + }); + return data.canEdit; +}; + +privsUsers.canBanUser = async function (callerUid, uid) { + const privsGlobal = require('./global'); + const [canBan, isTargetAdmin] = await Promise.all([ + privsGlobal.can('ban', callerUid), + privsUsers.isAdministrator(uid), + ]); + + const data = await plugins.hooks.fire('filter:user.canBanUser', { + canBan: canBan && !isTargetAdmin, + callerUid: callerUid, + uid: uid, + }); + return data.canBan; +}; + +privsUsers.canFlag = async function (callerUid, uid) { + const [userReputation, targetPrivileged, reporterPrivileged] = await Promise.all([ + user.getUserField(callerUid, 'reputation'), + user.isPrivileged(uid), + user.isPrivileged(callerUid), + ]); + const minimumReputation = meta.config['min:rep:flag']; + let canFlag = reporterPrivileged || (userReputation >= minimumReputation); + + if (targetPrivileged && !reporterPrivileged) { + canFlag = false; + } + + return { flag: canFlag }; +}; + +privsUsers.hasBanPrivilege = async uid => await hasGlobalPrivilege('ban', uid); +privsUsers.hasInvitePrivilege = async uid => await hasGlobalPrivilege('invite', uid); + +async function hasGlobalPrivilege(privilege, uid) { + const privsGlobal = require('./global'); + const privilegeName = privilege.split('-').map(word => word.slice(0, 1).toUpperCase() + word.slice(1)).join(''); + let payload = { uid }; + payload[`can${privilegeName}`] = await privsGlobal.can(privilege, uid); + payload = await plugins.hooks.fire(`filter:user.has${privilegeName}Privilege`, payload); + return payload[`can${privilegeName}`]; +} diff --git a/test/posts.js b/test/posts.js index a8504b0b50..fe94f3ba12 100644 --- a/test/posts.js +++ b/test/posts.js @@ -753,7 +753,7 @@ describe('Post\'s', () => { const cat2 = await categories.create({ name: 'Test Category', description: 'Test category created by testing script' }); const result = await socketTopics.post({ uid: globalModUid }, { title: 'target topic', content: 'queued topic', cid: cat2.cid }); const modUid = await user.create({ username: 'modofcat1' }); - await privileges.categories.give(privileges.userPrivilegeList, cat1.cid, modUid); + await privileges.categories.give(privileges.categories.userPrivilegeList, cat1.cid, modUid); let err; try { await socketPosts.movePost({ uid: modUid }, { pid: replyPid, tid: result.tid });