diff --git a/public/language/en-GB/admin/manage/privileges.json b/public/language/en-GB/admin/manage/privileges.json
index faabb1a7b4..d85d1492b9 100644
--- a/public/language/en-GB/admin/manage/privileges.json
+++ b/public/language/en-GB/admin/manage/privileges.json
@@ -1,6 +1,7 @@
{
"global": "Global",
"global.no-users": "No user-specific global privileges.",
+ "admin": "Admin",
"group-privileges": "Group Privileges",
"user-privileges": "User Privileges",
"chat": "Chat",
@@ -31,5 +32,11 @@
"downvote-posts": "Downvote Posts",
"delete-topics": "Delete Topics",
"purge": "Purge",
- "moderate": "Moderate"
+ "moderate": "Moderate",
+
+ "admin-dashboard": "Dashboard",
+ "admin-categories": "Categories",
+ "admin-privileges": "Privileges",
+ "admin-users": "Users",
+ "admin-settings": "Settings"
}
\ No newline at end of file
diff --git a/public/openapi/read.yaml b/public/openapi/read.yaml
index 1baef2861d..6646149b46 100644
--- a/public/openapi/read.yaml
+++ b/public/openapi/read.yaml
@@ -354,6 +354,8 @@ paths:
timestampISO:
type: string
description: An ISO 8601 formatted date string (complementing `timestamp`)
+ showSystemControls:
+ type: boolean
- $ref: components/schemas/CommonProps.yaml#/CommonProps
/api/admin/settings/languages:
get:
diff --git a/public/src/admin/manage/privileges.js b/public/src/admin/manage/privileges.js
index 91813b4626..553e4427e9 100644
--- a/public/src/admin/manage/privileges.js
+++ b/public/src/admin/manage/privileges.js
@@ -11,12 +11,15 @@ define('admin/manage/privileges', [
var cid;
Privileges.init = function () {
- cid = ajaxify.data.cid || 0;
+ cid = ajaxify.data.cid || 'admin';
categorySelector.init($('[component="category-selector"]'), function (category) {
- var cid = parseInt(category.cid, 10);
- ajaxify.go('admin/manage/privileges/' + (cid || ''));
+ cid = parseInt(category.cid, 10);
+ cid = isNaN(cid) ? 'admin' : cid;
+ Privileges.refreshPrivilegeTable();
+ ajaxify.updateHistory('admin/manage/privileges/' + (cid || ''));
});
+
Privileges.setupPrivilegeTable();
};
@@ -81,7 +84,8 @@ define('admin/manage/privileges', [
if (err) {
return app.alertError(err.message);
}
- var tpl = cid ? 'admin/partials/categories/privileges' : 'admin/partials/global/privileges';
+
+ var tpl = parseInt(cid, 10) ? 'admin/partials/privileges/category' : 'admin/partials/privileges/global';
Benchpress.parse(tpl, {
privileges: privileges,
}, function (html) {
@@ -117,7 +121,7 @@ define('admin/manage/privileges', [
Privileges.setPrivilege = function (member, privilege, state, checkboxEl) {
socket.emit('admin.categories.setPrivilege', {
- cid: cid,
+ cid: isNaN(cid) ? 0 : cid,
privilege: privilege,
set: state,
member: member,
@@ -143,9 +147,14 @@ define('admin/manage/privileges', [
inputEl.focus();
autocomplete.user(inputEl, function (ev, ui) {
- var defaultPrivileges = cid ? ['find', 'read', 'topics:read'] : ['chat'];
+ var defaultPrivileges;
+ if (ajaxify.data.url === '/admin/manage/privileges/admin') {
+ defaultPrivileges = ['admin:dashboard'];
+ } else {
+ defaultPrivileges = cid ? ['find', 'read', 'topics:read'] : ['chat'];
+ }
socket.emit('admin.categories.setPrivilege', {
- cid: cid,
+ cid: isNaN(cid) ? 0 : cid,
privilege: defaultPrivileges,
set: true,
member: ui.item.user.uid,
@@ -170,11 +179,18 @@ define('admin/manage/privileges', [
modal.on('shown.bs.modal', function () {
var inputEl = modal.find('input');
+ inputEl.focus();
autocomplete.group(inputEl, function (ev, ui) {
- var defaultPrivileges = cid ? ['groups:find', 'groups:read', 'groups:topics:read'] : ['groups:chat'];
+ var defaultPrivileges;
+ if (ajaxify.data.url === '/admin/manage/privileges/admin') {
+ defaultPrivileges = ['groups:admin:dashboard'];
+ } else {
+ defaultPrivileges = cid ? ['groups:find', 'groups:read', 'groups:topics:read'] : ['groups:chat'];
+ }
+
socket.emit('admin.categories.setPrivilege', {
- cid: cid,
+ cid: isNaN(cid) ? 0 : cid,
privilege: defaultPrivileges,
set: true,
member: ui.item.group.name,
diff --git a/public/src/admin/modules/search.js b/public/src/admin/modules/search.js
index 77aafe7bfd..274d7a14b2 100644
--- a/public/src/admin/modules/search.js
+++ b/public/src/admin/modules/search.js
@@ -46,6 +46,10 @@ define('admin/modules/search', ['mousetrap'], function (mousetrap) {
}
search.init = function () {
+ if (!app.user.privileges['admin:settings']) {
+ return;
+ }
+
socket.emit('admin.getSearchDict', {}, function (err, dict) {
if (err) {
app.alertError(err);
diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js
index 485dd1a5bb..6592a1418b 100644
--- a/public/src/modules/helpers.js
+++ b/public/src/modules/helpers.js
@@ -188,7 +188,7 @@
var spidersEnabled = ['groups:find', 'groups:read', 'groups:topics:read', 'groups:view:users', 'groups:view:tags', 'groups:view:groups'];
var globalModDisabled = ['groups:moderate'];
var disabled =
- (member === 'guests' && guestDisabled.includes(priv.name)) ||
+ (member === 'guests' && (guestDisabled.includes(priv.name) || priv.name.startsWith('groups:admin:'))) ||
(member === 'spiders' && !spidersEnabled.includes(priv.name)) ||
(member === 'Global Moderators' && globalModDisabled.includes(priv.name));
diff --git a/src/controllers/admin.js b/src/controllers/admin.js
index 02e61463f5..b6412828a5 100644
--- a/src/controllers/admin.js
+++ b/src/controllers/admin.js
@@ -1,5 +1,8 @@
'use strict';
+const privileges = require('../privileges');
+const helpers = require('./helpers');
+
var adminController = {
dashboard: require('./admin/dashboard'),
categories: require('./admin/categories'),
@@ -28,5 +31,22 @@ var adminController = {
info: require('./admin/info'),
};
+adminController.routeIndex = async (req, res) => {
+ const privilegeSet = await privileges.admin.get(req.uid);
+
+ if (privilegeSet.superadmin || privilegeSet['admin:dashboard']) {
+ return adminController.dashboard.get(req, res);
+ } else if (privilegeSet['admin:categories']) {
+ return helpers.redirect(res, 'admin/manage/categories');
+ } else if (privilegeSet['admin:privileges']) {
+ return helpers.redirect(res, 'admin/manage/privileges');
+ } else if (privilegeSet['admin:users']) {
+ return helpers.redirect(res, 'admin/manage/users');
+ } else if (privilegeSet['admin:settings']) {
+ return helpers.redirect(res, 'admin/settings/general');
+ }
+
+ return helpers.notAllowed(req, res);
+};
module.exports = adminController;
diff --git a/src/controllers/admin/dashboard.js b/src/controllers/admin/dashboard.js
index 04a0b78949..73c50e468f 100644
--- a/src/controllers/admin/dashboard.js
+++ b/src/controllers/admin/dashboard.js
@@ -16,11 +16,12 @@ const utils = require('../../utils');
const dashboardController = module.exports;
dashboardController.get = async function (req, res) {
- const [stats, notices, latestVersion, lastrestart] = await Promise.all([
+ const [stats, notices, latestVersion, lastrestart, isAdmin] = await Promise.all([
getStats(),
getNotices(),
getLatestVersion(),
getLastRestart(),
+ user.isAdministrator(),
]);
const version = nconf.get('version');
@@ -34,6 +35,7 @@ dashboardController.get = async function (req, res) {
stats: stats,
canRestart: !!process.send,
lastrestart: lastrestart,
+ showSystemControls: isAdmin,
});
};
diff --git a/src/controllers/admin/privileges.js b/src/controllers/admin/privileges.js
index 687ae3864a..d15a0400ed 100644
--- a/src/controllers/admin/privileges.js
+++ b/src/controllers/admin/privileges.js
@@ -6,9 +6,18 @@ const privileges = require('../../privileges');
const privilegesController = module.exports;
privilegesController.get = async function (req, res) {
- const cid = req.params.cid ? parseInt(req.params.cid, 10) : 0;
+ const cid = req.params.cid ? parseInt(req.params.cid, 10) || 0 : 0;
+ const isAdminPriv = req.params.cid === 'admin';
+
+ let method;
+ if (cid > 0) {
+ method = privileges.categories.list.bind(null, cid);
+ } else if (cid === 0) {
+ method = isAdminPriv ? privileges.admin.list : privileges.global.list;
+ }
+
const [privilegesData, categoriesData] = await Promise.all([
- cid ? privileges.categories.list(cid) : privileges.global.list(),
+ method(),
categories.buildForSelectAll(),
]);
@@ -16,12 +25,16 @@ privilegesController.get = async function (req, res) {
cid: 0,
name: '[[admin/manage/privileges:global]]',
icon: 'fa-list',
+ }, {
+ cid: 'admin', // what do?
+ name: '[[admin/manage/privileges:admin]]',
+ icon: 'fa-lock',
});
let selectedCategory;
categoriesData.forEach(function (category) {
if (category) {
- category.selected = category.cid === cid;
+ category.selected = category.cid === (!isAdminPriv ? cid : 'admin');
if (category.selected) {
selectedCategory = category;
diff --git a/src/middleware/admin.js b/src/middleware/admin.js
index 33e8a45f49..945b2c17f2 100644
--- a/src/middleware/admin.js
+++ b/src/middleware/admin.js
@@ -8,6 +8,7 @@ var semver = require('semver');
var user = require('../user');
var meta = require('../meta');
var plugins = require('../plugins');
+var privileges = require('../privileges');
var utils = require('../../public/src/utils');
var versions = require('../admin/versions');
var helpers = require('./helpers');
@@ -43,11 +44,13 @@ module.exports = function (middleware) {
custom_header: plugins.fireHook('filter:admin.header.build', custom_header),
configs: meta.configs.list(),
latestVersion: getLatestVersion(),
+ privileges: privileges.admin.get(req.uid),
});
var userData = results.userData;
userData.uid = req.uid;
userData['email:confirmed'] = userData['email:confirmed'] === 1;
+ userData.privileges = results.privileges;
var acpPath = req.path.slice(1).split('/');
acpPath.forEach(function (path, i) {
@@ -103,4 +106,26 @@ module.exports = function (middleware) {
middleware.admin.renderFooter = async function (req, res, data) {
return await req.app.renderAsync('admin/footer', data);
};
+
+ middleware.admin.checkPrivileges = async (req, res, next) => {
+ // Kick out guests, obviously
+ if (!req.uid) {
+ return controllers.helpers.notAllowed(req, res);
+ }
+
+ // Users in "administrators" group are considered super admins
+ const isAdmin = await user.isAdministrator(req.uid);
+ if (isAdmin) {
+ return next();
+ }
+
+ // Otherwise, check for privilege based on page (if not in mapping, deny access)
+ const path = req.path.replace(/^(\/api)?\/admin\/?/g, '');
+ const privilege = privileges.admin.resolve(path);
+ if (!privilege || !await privileges.admin.can(privilege, req.uid)) {
+ return controllers.helpers.notAllowed(req, res);
+ }
+
+ return next();
+ };
};
diff --git a/src/privileges/admin.js b/src/privileges/admin.js
new file mode 100644
index 0000000000..5c654b0a39
--- /dev/null
+++ b/src/privileges/admin.js
@@ -0,0 +1,183 @@
+
+'use strict';
+
+const _ = require('lodash');
+
+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.admin = {};
+
+ 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-users]]' },
+ { name: '[[admin/manage/privileges:admin-settings]]' },
+ ];
+
+ privileges.admin.userPrivilegeList = [
+ 'admin:dashboard',
+ 'admin:categories',
+ 'admin:privileges',
+ 'admin:users',
+ 'admin:settings',
+ ];
+
+ privileges.admin.groupPrivilegeList = privileges.admin.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/users': 'admin:users',
+ 'extend/plugins': 'admin:settings',
+ 'extend/widgets': 'admin:settings',
+ 'extend/rewards': 'admin:settings',
+ };
+ privileges.admin.routeRegexpMap = {
+ '^manage/categories/\\d+': 'admin:categories',
+ '^manage/privileges/\\d+': 'admin:privileges',
+ '^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.categories.copyPrivilegesToChildren': 'admin:privileges',
+ 'admin.categories.copyPrivilegesFrom': 'admin:privileges',
+ 'admin.categories.copyPrivilegesToAllCategories': 'admin:privileges',
+
+ 'admin.user.loadGroups': 'admin:users',
+ 'admin.groups.join': 'admin:users',
+ 'admin.groups.leave': 'admin:users',
+ 'user.banUsers': 'admin:users',
+ 'user.unbanUsers': '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.search': 'admin:users',
+ 'admin.user.invite': 'admin:users',
+
+ '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',
+ };
+
+ privileges.admin.resolve = (path) => {
+ if (privileges.admin.routeMap[path]) {
+ return privileges.admin.routeMap[path];
+ } else if (path === '') {
+ return 'manage:dashboard';
+ }
+
+ 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 () {
+ async function getLabels() {
+ return await utils.promiseParallel({
+ users: plugins.fireHook('filter:privileges.admin.list_human', privileges.admin.privilegeLabels.slice()),
+ groups: plugins.fireHook('filter:privileges.admin.groups.list_human', privileges.admin.privilegeLabels.slice()),
+ });
+ }
+ const payload = await utils.promiseParallel({
+ labels: getLabels(),
+ users: helpers.getUserPrivileges(0, 'filter:privileges.admin.list', privileges.admin.userPrivilegeList),
+ groups: helpers.getGroupPrivileges(0, 'filter:privileges.admin.groups.list', privileges.admin.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.admin.get = async function (uid) {
+ const [userPrivileges, isAdministrator] = await Promise.all([
+ helpers.isUserAllowedTo(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.fireHook('filter:privileges.admin.get', privData);
+ };
+
+ privileges.admin.can = async function (privilege, uid) {
+ const isUserAllowedTo = await helpers.isUserAllowedTo(privilege, uid, [0]);
+ return 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.fireHook('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.fireHook('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);
+ // };
+};
diff --git a/src/privileges/categories.js b/src/privileges/categories.js
index 8fa6165dac..868ea065fc 100644
--- a/src/privileges/categories.js
+++ b/src/privileges/categories.js
@@ -45,14 +45,12 @@ module.exports = function (privileges) {
user.isModerator(uid, cid),
]);
- const privData = _.zipObject(privs, userPrivileges);
+ const combined = userPrivileges.map(allowed => allowed || isAdministrator);
+ const privData = _.zipObject(privs, combined);
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,
+ ...privData,
cid: cid,
uid: uid,
editable: isAdminOrMod,
diff --git a/src/privileges/global.js b/src/privileges/global.js
index 6738ff09c3..7b851e1c9a 100644
--- a/src/privileges/global.js
+++ b/src/privileges/global.js
@@ -71,20 +71,10 @@ module.exports = function (privileges) {
user.isAdministrator(uid),
]);
- const privData = _.zipObject(privileges.global.userPrivilegeList, userPrivileges);
+ const combined = userPrivileges.map(allowed => allowed || isAdministrator);
+ const privData = _.zipObject(privileges.global.userPrivilegeList, combined);
- 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,
- 'view:users:info': privData['view:users:info'] || isAdministrator,
- });
+ return await plugins.fireHook('filter:privileges.global.get', privData);
};
privileges.global.can = async function (privilege, uid) {
diff --git a/src/privileges/index.js b/src/privileges/index.js
index 1a17f59241..b1cb6c8e76 100644
--- a/src/privileges/index.js
+++ b/src/privileges/index.js
@@ -43,6 +43,7 @@ privileges.groupPrivilegeList = privileges.userPrivilegeList.map(privilege => 'g
privileges.privilegeList = privileges.userPrivilegeList.concat(privileges.groupPrivilegeList);
require('./global')(privileges);
+require('./admin')(privileges);
require('./categories')(privileges);
require('./topics')(privileges);
require('./posts')(privileges);
diff --git a/src/routes/admin.js b/src/routes/admin.js
index 087911abf1..708a66a485 100644
--- a/src/routes/admin.js
+++ b/src/routes/admin.js
@@ -5,7 +5,7 @@ const helpers = require('./helpers');
module.exports = function (app, middleware, controllers) {
const middlewares = [middleware.pluginHooks];
- helpers.setupAdminPageRoute(app, '/admin', middleware, middlewares, controllers.admin.dashboard.get);
+ helpers.setupAdminPageRoute(app, '/admin', middleware, middlewares, controllers.admin.routeIndex);
helpers.setupAdminPageRoute(app, '/admin/dashboard', middleware, middlewares, controllers.admin.dashboard.get);
diff --git a/src/routes/index.js b/src/routes/index.js
index 2274a7f418..fca5527047 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -98,8 +98,8 @@ module.exports = async function (app, middleware) {
var ensureLoggedIn = require('connect-ensure-login');
router.all('(/+api|/+api/*?)', middleware.prepareAPI);
- router.all('(/+api/admin|/+api/admin/*?)', middleware.isAdmin);
- router.all('(/+admin|/+admin/*?)', ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login?local=1'), middleware.applyCSRF, middleware.isAdmin);
+ router.all('(/+api/admin|/+api/admin/*?)', middleware.admin.checkPrivileges);
+ router.all('(/+admin|/+admin/*?)', ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login?local=1'), middleware.applyCSRF, middleware.admin.checkPrivileges);
app.use(middleware.stripLeadingSlashes);
diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index ee2c5dd52b..5848f106a4 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -6,6 +6,7 @@ const meta = require('../meta');
const user = require('../user');
const events = require('../events');
const db = require('../database');
+const privileges = require('../privileges');
const websockets = require('./index');
const index = require('./index');
const getAdminSearchDict = require('../admin/search').getDictionary;
@@ -37,6 +38,13 @@ SocketAdmin.before = async function (socket, method) {
if (isAdmin) {
return;
}
+
+ // Check admin privileges mapping (if not in mapping, deny access)
+ const privilege = privileges.admin.socketMap[method];
+ if (privilege && await privileges.admin.can(privilege, socket.uid)) {
+ return;
+ }
+
winston.warn('[socket.io] Call to admin method ( ' + method + ' ) blocked (accessed by uid ' + socket.uid + ')');
throw new Error('[[error:no-privileges]]');
};
diff --git a/src/socket.io/admin/categories.js b/src/socket.io/admin/categories.js
index c57eebc159..50e61f94bc 100644
--- a/src/socket.io/admin/categories.js
+++ b/src/socket.io/admin/categories.js
@@ -77,7 +77,9 @@ Categories.setPrivilege = async function (socket, data) {
};
Categories.getPrivilegeSettings = async function (socket, cid) {
- if (!parseInt(cid, 10)) {
+ if (cid === 'admin') {
+ return await privileges.admin.list();
+ } else if (!parseInt(cid, 10)) {
return await privileges.global.list();
}
return await privileges.categories.list(cid);
diff --git a/src/views/admin/dashboard.tpl b/src/views/admin/dashboard.tpl
index 781d4ef782..36180b2e2c 100644
--- a/src/views/admin/dashboard.tpl
+++ b/src/views/admin/dashboard.tpl
@@ -124,6 +124,7 @@
+ {{{ if showSystemControls }}}
[[admin/dashboard:control-panel]]
@@ -152,6 +153,7 @@
[[admin/dashboard:realtime-chart-updates]] OFF
+ {{{ end }}}
[[admin/dashboard:active-users]]
diff --git a/src/views/admin/manage/privileges.tpl b/src/views/admin/manage/privileges.tpl
index 527dd86607..9930a240cb 100644
--- a/src/views/admin/manage/privileges.tpl
+++ b/src/views/admin/manage/privileges.tpl
@@ -11,11 +11,11 @@
-
-
-
-
-
+ {{{ if cid }}}
+
+ {{{ else }}}
+
+ {{{ endif }}}
diff --git a/src/views/admin/partials/menu.tpl b/src/views/admin/partials/menu.tpl
index 653a0ef9a8..36f24f6a59 100644
--- a/src/views/admin/partials/menu.tpl
+++ b/src/views/admin/partials/menu.tpl
@@ -12,9 +12,10 @@
+ {{{ if user.privileges.admin:settings }}}
-
+ {{{ end }}}
+ {{{ if user.privileges.superadmin }}}
+ {{{ end }}}
@@ -127,6 +132,7 @@
+ {{{ if user.privileges.admin:settings }}}
+ {{{ end }}}
@@ -163,15 +170,19 @@
+ {{{ if user.privileges.admin:dashboard }}}
+ {{{ end }}}
+
+
+ {{{ if user.privileges.admin:settings }}}
-
+ {{{ end }}}
+
+ {{{ if user.privileges.superadmin }}}
+ {{{ end }}}
\ No newline at end of file
diff --git a/src/views/admin/partials/categories/privileges.tpl b/src/views/admin/partials/privileges/category.tpl
similarity index 100%
rename from src/views/admin/partials/categories/privileges.tpl
rename to src/views/admin/partials/privileges/category.tpl
diff --git a/src/views/admin/partials/global/privileges.tpl b/src/views/admin/partials/privileges/global.tpl
similarity index 96%
rename from src/views/admin/partials/global/privileges.tpl
rename to src/views/admin/partials/privileges/global.tpl
index 7c4fc0e5fa..314beeebe7 100644
--- a/src/views/admin/partials/global/privileges.tpl
+++ b/src/views/admin/partials/privileges/global.tpl
@@ -1,9 +1,6 @@
[[admin/manage/privileges:group-privileges]]
-
[[admin/manage/categories:privileges.section-group]]
diff --git a/src/views/admin/partials/quick_actions/buttons.tpl b/src/views/admin/partials/quick_actions/buttons.tpl
index 950283d69e..eed00da8f6 100644
--- a/src/views/admin/partials/quick_actions/buttons.tpl
+++ b/src/views/admin/partials/quick_actions/buttons.tpl
@@ -3,6 +3,8 @@
+
+{{{ if user.privileges.superadmin }}}
@@ -13,6 +15,7 @@
+{{{ end }}}