diff --git a/public/language/en-GB/admin/manage/privileges.json b/public/language/en-GB/admin/manage/privileges.json index ca4ad9fc36..faabb1a7b4 100644 --- a/public/language/en-GB/admin/manage/privileges.json +++ b/public/language/en-GB/admin/manage/privileges.json @@ -16,7 +16,7 @@ "view-groups": "View Groups", "allow-local-login": "Local Login", "allow-group-creation": "Group Create", - + "view-users-info": "View Users Info", "find-category": "Find Category", "access-category": "Access Category", "access-topics": "Access Topics", diff --git a/src/controllers/accounts/chats.js b/src/controllers/accounts/chats.js index 0bfad61b81..d470445264 100644 --- a/src/controllers/accounts/chats.js +++ b/src/controllers/accounts/chats.js @@ -46,6 +46,9 @@ chatsController.get = async function (req, res, next) { room.title = room.roomName || room.usernames || '[[pages:chats]]'; room.uid = uid; room.userslug = req.params.userslug; + + room.canViewInfo = await privileges.global.can('view:users:info', uid); + res.render('chats', room); }; diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index 3975a5c914..466f190b92 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -31,6 +31,7 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID) { const isAdmin = results.isAdmin; const isGlobalModerator = results.isGlobalModerator; const isModerator = results.isModerator; + const canViewInfo = results.canViewInfo; const isSelf = parseInt(callerUID, 10) === parseInt(userData.uid, 10); userData.age = Math.max(0, userData.birthday ? Math.floor((new Date().getTime() - new Date(userData.birthday).getTime()) / 31536000000) : 0); @@ -47,7 +48,7 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID) { userData.fullname = ''; } - if (isAdmin || isSelf || ((isGlobalModerator || isModerator) && !results.isTargetAdmin)) { + if (isAdmin || isSelf || (canViewInfo && !results.isTargetAdmin)) { userData.ips = results.ips; } @@ -86,6 +87,7 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID) { moderator: isModerator, globalMod: isGlobalModerator, admin: isAdmin, + canViewInfo: canViewInfo, }); userData.sso = results.sso.associations; @@ -129,6 +131,7 @@ async function getAllData(uid, callerUID) { canEdit: privileges.users.canEdit(callerUID, uid), canBanUser: privileges.users.canBanUser(callerUID, uid), isBlocked: user.blocks.is(uid, callerUID), + canViewInfo: privileges.global.can('view:users:info', callerUID), }); } @@ -140,9 +143,10 @@ async function getProfileMenu(uid, callerUID) { visibility: { self: false, other: false, - moderator: true, - globalMod: true, + moderator: false, + globalMod: false, admin: true, + canViewInfo: true, }, }, { id: 'sessions', @@ -154,6 +158,7 @@ async function getProfileMenu(uid, callerUID) { moderator: false, globalMod: false, admin: false, + canViewInfo: false, }, }]; @@ -168,6 +173,7 @@ async function getProfileMenu(uid, callerUID) { moderator: false, globalMod: false, admin: false, + canViewInfo: false, }, }); } @@ -202,6 +208,7 @@ function filterLinks(links, states) { moderator: true, globalMod: true, admin: true, + canViewInfo: true, ...link.visibility }; var permit = Object.keys(states).some(function (state) { diff --git a/src/install.js b/src/install.js index 64f354521f..29a4e2bba1 100644 --- a/src/install.js +++ b/src/install.js @@ -411,7 +411,7 @@ function giveGlobalPrivileges(next) { privileges.global.give(defaultPrivileges, 'registered-users', next); }, function (next) { - privileges.global.give(defaultPrivileges.concat(['ban', 'upload:post:file']), 'Global Moderators', next); + privileges.global.give(defaultPrivileges.concat(['ban', 'upload:post:file', 'view:users:info']), 'Global Moderators', next); }, function (next) { privileges.global.give(['view:users', 'view:tags', 'view:groups'], 'guests', next); diff --git a/src/middleware/user.js b/src/middleware/user.js index 07e65cec4b..141c574d5e 100644 --- a/src/middleware/user.js +++ b/src/middleware/user.js @@ -132,7 +132,7 @@ module.exports = function (middleware) { // For the account/info page only, allow plain moderators through if (/user\/.+\/info$/.test(req.path)) { - user.isModeratorOfAnyCategory(req.uid, next); + privileges.global.can('view:users:info', req.uid, next); } else { next(null, false); } diff --git a/src/privileges/global.js b/src/privileges/global.js index 1c8e8c18e7..807159d135 100644 --- a/src/privileges/global.js +++ b/src/privileges/global.js @@ -26,6 +26,7 @@ module.exports = function (privileges) { { 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 = [ @@ -42,6 +43,7 @@ module.exports = function (privileges) { 'view:groups', 'local:login', 'group:create', + 'view:users:info', ]; privileges.global.groupPrivilegeList = privileges.global.userPrivilegeList.map(privilege => 'groups:' + privilege); @@ -81,6 +83,7 @@ module.exports = function (privileges) { 'view:users': privData['view:users'] || isAdministrator, 'view:tags': privData['view:tags'] || isAdministrator, 'view:groups': privData['view:groups'] || isAdministrator, + 'view:users:info': privData['view:users:info'] || isAdministrator, }); }; diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 8a76eb74d8..38814e49a1 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -280,7 +280,7 @@ SocketModules.chats.getMessages = async function (socket, data) { }; SocketModules.chats.getIP = async function (socket, mid) { - const allowed = await user.isAdminOrGlobalMod(socket.uid); + const allowed = await privileges.global.can('view:users:info', socket.uid); if (!allowed) { throw new Error('[[error:no-privilege]]'); } diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js index 3aa10116d9..7723320d5a 100644 --- a/src/socket.io/posts/tools.js +++ b/src/socket.io/posts/tools.js @@ -31,6 +31,7 @@ module.exports = function (SocketPosts) { tools: plugins.fireHook('filter:post.tools', { pid: data.pid, uid: socket.uid, tools: [] }), postSharing: social.getActivePostSharing(), history: posts.diffs.exists(data.pid), + canViewInfo: privileges.global.can('view:users:info', socket.uid), }); const postData = results.posts; @@ -48,7 +49,7 @@ module.exports = function (SocketPosts) { postData.display_history = results.history; postData.toolsVisible = postData.tools.length || postData.display_moderator_tools; - if (!results.isAdmin && !results.isGlobalMod && !results.isModerator) { + if (!results.isAdmin && !results.canViewInfo) { postData.ip = undefined; } return results; diff --git a/src/upgrades/1.12.3/give_mod_info_privilege.js b/src/upgrades/1.12.3/give_mod_info_privilege.js new file mode 100644 index 0000000000..8d8fe32e86 --- /dev/null +++ b/src/upgrades/1.12.3/give_mod_info_privilege.js @@ -0,0 +1,45 @@ +'use strict'; + +var async = require('async'); +var db = require('../../database'); +var privileges = require('../../privileges'); +var groups = require('../../groups'); + +module.exports = { + name: 'give mod info privilege', + timestamp: Date.UTC(2019, 9, 8), + method: function (callback) { + async.waterfall([ + function (next) { + db.getSortedSetRevRange('categories:cid', 0, -1, next); + }, + function (cids, next) { + async.eachSeries(cids, function (cid, next) { + async.waterfall([ + function (next) { + givePrivsToModerators(cid, '', next); + }, + function (next) { + givePrivsToModerators(cid, 'groups:', next); + }, + ], next); + }, next); + }, + function (next) { + privileges.global.give(['view:users:info'], 'Global Moderators', next); + }, + ], callback); + function givePrivsToModerators(cid, groupPrefix, callback) { + async.waterfall([ + function (next) { + db.getSortedSetRevRange('group:cid:' + cid + ':privileges:' + groupPrefix + 'moderate:members', 0, -1, next); + }, + function (members, next) { + async.eachSeries(members, function (member, next) { + groups.join(['cid:0:privileges:view:users:info'], member, next); + }, next); + }, + ], callback); + } + }, +}; diff --git a/test/categories.js b/test/categories.js index 26f6c4157d..a0d3f778d2 100644 --- a/test/categories.js +++ b/test/categories.js @@ -767,6 +767,7 @@ describe('Categories', function () { 'search:content': false, 'search:users': false, 'search:tags': false, + 'view:users:info': false, 'upload:post:image': false, 'upload:post:file': false, signature: false, @@ -816,6 +817,7 @@ describe('Categories', function () { 'groups:search:users': true, 'groups:search:tags': true, 'groups:view:users': true, + 'groups:view:users:info': false, 'groups:view:tags': true, 'groups:view:groups': true, 'groups:upload:post:image': true,