feat: category privilege API routes

closes #9342
v1.18.x
Julian Lam 4 years ago
parent 1e579428e7
commit c1b3079d93

@ -82,6 +82,10 @@ paths:
$ref: 'write/categories.yaml'
/categories/{cid}:
$ref: 'write/categories/cid.yaml'
/categories/{cid}/privileges:
$ref: 'write/categories/cid/privileges.yaml'
/categories/{cid}/privileges/{privilege}:
$ref: 'write/categories/cid/privileges/privilege.yaml'
/topics/:
$ref: 'write/topics.yaml'
/topics/{tid}:

@ -0,0 +1,62 @@
get:
tags:
- categories
summary: get a category's privilege set
description: This operation retrieves a category's privilege set.
parameters:
- in: path
name: cid
schema:
type: string
required: true
description: a valid category id, `0` for global privileges, `admin` for admin privileges
example: 1
responses:
'200':
description: Category privileges successfully retrieved
content:
application/json:
schema:
type: object
properties:
status:
$ref: ../../../components/schemas/Status.yaml#/Status
response:
type: object
properties:
users:
type: array
items:
type: object
properties:
name:
type: string
nameEscaped:
type: string
privileges:
type: object
additionalProperties:
type: boolean
description: A set of privileges with either true or false
isPrivate:
type: boolean
isSystem:
type: boolean
groups:
type: array
items:
type: object
properties:
name:
type: string
nameEscaped:
type: string
privileges:
type: object
additionalProperties:
type: boolean
description: A set of privileges with either true or false
isPrivate:
type: boolean
isSystem:
type: boolean

@ -0,0 +1,158 @@
put:
tags:
- categories
summary: Grant category privilege for user/group
description: This operation grants a category privilege for a specific user or group
parameters:
- in: path
name: cid
schema:
type: string
required: true
description: a valid category id, `0` for global privileges, `admin` for admin privileges
example: 1
- in: path
name: privilege
schema:
type: string
required: true
description: The specific privilege you would like to grant. Privileges for groups must be prefixed `group:`
example: 'groups:ban'
requestBody:
content:
application/json:
schema:
type: object
properties:
member:
type: string
description: A valid user id or group name
example: 'guests'
responses:
'200':
description: Privilege successfully granted
content:
application/json:
schema:
type: object
properties:
status:
$ref: ../../../../components/schemas/Status.yaml#/Status
response:
type: object
properties:
users:
type: array
items:
type: object
properties:
name:
type: string
nameEscaped:
type: string
privileges:
type: object
additionalProperties:
type: boolean
description: A set of privileges with either true or false
isPrivate:
type: boolean
isSystem:
type: boolean
groups:
type: array
items:
type: object
properties:
name:
type: string
nameEscaped:
type: string
privileges:
type: object
additionalProperties:
type: boolean
description: A set of privileges with either true or false
isPrivate:
type: boolean
isSystem:
type: boolean
delete:
tags:
- categories
summary: Resvinds category privilege for user/group
description: This operation rescinds a category privilege for a specific user or group
parameters:
- in: path
name: cid
schema:
type: string
required: true
description: a valid category id, `0` for global privileges, `admin` for admin privileges
example: 1
- in: path
name: privilege
schema:
type: string
required: true
description: The specific privilege you would like to rescind. Privileges for groups must be prefixed `group:`
example: 'groups:ban'
requestBody:
content:
application/json:
schema:
type: object
properties:
member:
type: string
description: A valid user id or group name
example: 'guests'
responses:
'200':
description: Privilege successfully rescinded
content:
application/json:
schema:
type: object
properties:
status:
$ref: ../../../../components/schemas/Status.yaml#/Status
response:
type: object
properties:
users:
type: array
items:
type: object
properties:
name:
type: string
nameEscaped:
type: string
privileges:
type: object
additionalProperties:
type: boolean
description: A set of privileges with either true or false
isPrivate:
type: boolean
isSystem:
type: boolean
groups:
type: array
items:
type: object
properties:
name:
type: string
nameEscaped:
type: string
privileges:
type: object
additionalProperties:
type: boolean
description: A set of privileges with either true or false
isPrivate:
type: boolean
isSystem:
type: boolean

@ -1,13 +1,14 @@
'use strict';
define('admin/manage/privileges', [
'api',
'autocomplete',
'bootbox',
'translator',
'categorySelector',
'mousetrap',
'admin/modules/checkboxRowSelector',
], function (autocomplete, bootbox, translator, categorySelector, mousetrap, checkboxRowSelector) {
], function (api, autocomplete, bootbox, translator, categorySelector, mousetrap, checkboxRowSelector) {
var Privileges = {};
var cid;
@ -141,9 +142,17 @@ define('admin/manage/privileges', [
return Privileges.setPrivilege(member, privilege, state);
});
Promise.allSettled(requests).then(function () {
Promise.allSettled(requests).then((results) => {
Privileges.refreshPrivilegeTable();
app.alertSuccess('[[admin/manage/privileges:alert.saved]]');
const rejects = results.filter(r => r.status === 'rejected');
if (rejects.length) {
rejects.forEach((result) => {
app.alertError(result.reason);
});
} else {
app.alertSuccess('[[admin/manage/privileges:alert.saved]]');
}
});
};
@ -153,23 +162,21 @@ define('admin/manage/privileges', [
};
Privileges.refreshPrivilegeTable = function (groupToHighlight) {
socket.emit('admin.categories.getPrivilegeSettings', cid, function (err, privileges) {
if (err) {
return app.alertError(err.message);
}
ajaxify.data.privileges = privileges;
api.get(`/categories/${cid}/privileges`, {}).then((privileges) => {
ajaxify.data.privileges = { ...ajaxify.data.privileges, ...privileges };
var tpl = parseInt(cid, 10) ? 'admin/partials/privileges/category' : 'admin/partials/privileges/global';
app.parseAndTranslate(tpl, {
privileges: privileges,
}, function (html) {
$('.privilege-table-container').html(html);
Promise.all([
app.parseAndTranslate(tpl, 'privileges.groups', { privileges }),
app.parseAndTranslate(tpl, 'privileges.users', { privileges }),
]).then((html) => {
$('.privilege-table-container tbody').first().html(html[0]);
$('.privilege-table-container tbody').last().html(html[1]);
Privileges.exposeAssumedPrivileges();
checkboxRowSelector.updateAll();
hightlightRowByDataAttr('data-group-name', groupToHighlight);
});
});
}).catch(app.alertError);
};
Privileges.exposeAssumedPrivileges = function (isBanned) {
@ -195,23 +202,7 @@ define('admin/manage/privileges', [
applyPrivileges(registeredUsersPrivs, getRegisteredUsersInputSelector);
};
Privileges.setPrivilege = function (member, privilege, state) {
return new Promise(function (resolve, reject) {
socket.emit('admin.categories.setPrivilege', {
cid: isNaN(cid) ? 0 : cid,
privilege: privilege,
set: state,
member: member,
}, function (err) {
if (err) {
reject(err);
return app.alertError(err.message);
}
resolve();
});
});
};
Privileges.setPrivilege = (member, privilege, state) => api[state ? 'put' : 'delete'](`/categories/${isNaN(cid) ? 0 : cid}/privileges/${privilege}`, { member });
Privileges.addUserToPrivilegeTable = function () {
var modal = bootbox.dialog({

@ -2,6 +2,8 @@
const categories = require('../categories');
const events = require('../events');
const user = require('../user');
const groups = require('../groups');
const privileges = require('../privileges');
const categoriesAPI = module.exports;
@ -39,3 +41,50 @@ categoriesAPI.delete = async function (caller, data) {
name: name,
});
};
categoriesAPI.getPrivileges = async (caller, cid) => {
let responsePayload;
if (cid === 'admin') {
responsePayload = await privileges.admin.list(caller.uid);
} else if (!parseInt(cid, 10)) {
responsePayload = await privileges.global.list();
} else {
responsePayload = await privileges.categories.list(cid);
}
// The various privilege .list() methods return superfluous data for the template, return only a minimal set
const validKeys = ['users', 'groups'];
Object.keys(responsePayload).forEach((key) => {
if (!validKeys.includes(key)) {
delete responsePayload[key];
}
});
return responsePayload;
};
categoriesAPI.setPrivilege = async (caller, data) => {
const [userExists, groupExists] = await Promise.all([
user.exists(data.member),
groups.exists(data.member),
]);
if (!userExists && !groupExists) {
throw new Error('[[error:no-user-or-group]]');
}
await privileges.categories[data.set ? 'give' : 'rescind'](
Array.isArray(data.privilege) ? data.privilege : [data.privilege], data.cid, data.member
);
await events.log({
uid: caller.uid,
type: 'privilege-change',
ip: caller.ip,
privilege: data.privilege.toString(),
cid: data.cid,
action: data.set ? 'grant' : 'rescind',
target: data.member,
});
};

@ -42,3 +42,27 @@ Categories.delete = async (req, res) => {
await api.categories.delete(req, { cid: req.params.cid });
helpers.formatApiResponse(200, res);
};
Categories.getPrivileges = async (req, res) => {
if (!await privileges.admin.can('admin:privileges', req.uid)) {
throw new Error('[[error:no-privileges]]');
}
const privilegeSet = await api.categories.getPrivileges(req, req.params.cid);
helpers.formatApiResponse(200, res, privilegeSet);
};
Categories.setPrivilege = async (req, res) => {
if (!await privileges.admin.can('admin:privileges', req.uid)) {
throw new Error('[[error:no-privileges]]');
}
await api.categories.setPrivilege(req, {
...req.params,
member: req.body.member,
set: req.method === 'PUT',
});
const privilegeSet = await api.categories.getPrivileges(req, req.params.cid);
helpers.formatApiResponse(200, res, privilegeSet);
};

@ -15,5 +15,9 @@ module.exports = function () {
setupApiRoute(router, 'put', '/:cid', [...middlewares], controllers.write.categories.update);
setupApiRoute(router, 'delete', '/:cid', [...middlewares], controllers.write.categories.delete);
setupApiRoute(router, 'get', '/:cid/privileges', [...middlewares], controllers.write.categories.getPrivileges);
setupApiRoute(router, 'put', '/:cid/privileges/:privilege', [...middlewares, middleware.checkRequired.bind(null, ['member'])], controllers.write.categories.setPrivilege);
setupApiRoute(router, 'delete', '/:cid/privileges/:privilege', [...middlewares, middleware.checkRequired.bind(null, ['member'])], controllers.write.categories.setPrivilege);
return router;
};

@ -2,12 +2,8 @@
const winston = require('winston');
const groups = require('../../groups');
const user = require('../../user');
const categories = require('../../categories');
const privileges = require('../../privileges');
const plugins = require('../../plugins');
const events = require('../../events');
const api = require('../../api');
const sockets = require('..');
@ -55,40 +51,21 @@ Categories.update = async function (socket, data) {
};
Categories.setPrivilege = async function (socket, data) {
sockets.warnDeprecated(socket, 'PUT /api/v3/categories/:cid/privileges/:privilege');
if (!data) {
throw new Error('[[error:invalid-data]]');
}
const [userExists, groupExists] = await Promise.all([
user.exists(data.member),
groups.exists(data.member),
]);
if (!userExists && !groupExists) {
throw new Error('[[error:no-user-or-group]]');
}
await privileges.categories[data.set ? 'give' : 'rescind'](
Array.isArray(data.privilege) ? data.privilege : [data.privilege], data.cid, data.member
);
await events.log({
uid: socket.uid,
type: 'privilege-change',
ip: socket.ip,
privilege: data.privilege.toString(),
cid: data.cid,
action: data.set ? 'grant' : 'rescind',
target: data.member,
});
return await api.categories.setPrivilege(socket, data);
};
Categories.getPrivilegeSettings = async function (socket, cid) {
if (cid === 'admin') {
return await privileges.admin.list(socket.uid);
} else if (!parseInt(cid, 10)) {
return await privileges.global.list();
sockets.warnDeprecated(socket, 'GET /api/v3/categories/:cid/privileges');
if (!isFinite(cid) && cid !== 'admin') {
throw new Error('[[error:invalid-data]]');
}
return await privileges.categories.list(cid);
return await api.categories.getPrivileges(socket, cid);
};
Categories.copyPrivilegesToChildren = async function (socket, data) {

@ -57,6 +57,8 @@
{function.spawnPrivilegeStates, privileges.groups.name, ../privileges}
</tr>
<!-- END privileges.groups -->
</tbody>
<tfoot>
<tr>
<td colspan="{privileges.columnCountGroup}">
<div class="btn-toolbar">
@ -79,7 +81,7 @@
</div>
</td>
</tr>
</tbody>
</tfoot>
</table>
<div class="help-block">
[[admin/manage/categories:privileges.inherit]]

@ -29,6 +29,8 @@
{function.spawnPrivilegeStates, privileges.groups.name, ../privileges}
</tr>
<!-- END privileges.groups -->
</tbody>
<tfoot>
<tr>
<td colspan="{privileges.columnCount}">
<div class="btn-toolbar">
@ -39,7 +41,7 @@
</div>
</td>
</tr>
</tbody>
</tfoot>
</table>
<div class="help-block">
[[admin/manage/categories:privileges.inherit]]

Loading…
Cancel
Save