chat privilege

This commit is contained in:
Baris Usakli
2017-12-18 15:43:57 -05:00
parent 094bab24c9
commit 4a73621dca
18 changed files with 553 additions and 3 deletions

View File

@@ -0,0 +1,6 @@
{
"global": "Global",
"global.description": "You can configure the global privileges in this section. Privileges can be granted on a per-user or a per-group basis. You can add a new user to this table by searching for them in the form below.",
"global.warning": "<strong>Note</strong>: Privilege settings take effect immediately. It is not necessary to save after adjusting these settings.",
"global.no-users": "No user-specific global privileges."
}

View File

@@ -9,6 +9,7 @@
"section-manage": "Manage",
"manage/categories": "Categories",
"manage/privileges": "Privileges",
"manage/tags": "Tags",
"manage/users": "Users",
"manage/registration": "Registration Queue",

View File

@@ -20,7 +20,12 @@ define('admin/manage/category', [
});
$('#category-selector').on('change', function () {
var val = $(this).val();
if (val === 'global') {
ajaxify.go('admin/manage/privileges');
} else {
ajaxify.go('admin/manage/categories/' + $(this).val() + window.location.hash);
}
});
function enableColorPicker(idx, inputEl) {

View File

@@ -0,0 +1,158 @@
'use strict';
define('admin/manage/privileges', [
'autocomplete',
'translator',
'benchpress',
], function (autocomplete, translator, Benchpress) {
var Privileges = {};
Privileges.init = function () {
$('#category-selector').on('change', function () {
var val = $(this).val();
if (val !== 'global') {
ajaxify.go('admin/manage/categories/' + $(this).val() + '#privileges');
}
});
Privileges.setupPrivilegeTable();
};
Privileges.setupPrivilegeTable = function () {
$('.privilege-table-container').on('change', 'input[type="checkbox"]', function () {
var checkboxEl = $(this);
var privilege = checkboxEl.parent().attr('data-privilege');
var state = checkboxEl.prop('checked');
var rowEl = checkboxEl.parents('tr');
var member = rowEl.attr('data-group-name') || rowEl.attr('data-uid');
var isPrivate = parseInt(rowEl.attr('data-private') || 0, 10);
var isGroup = rowEl.attr('data-group-name') !== undefined;
if (member) {
Privileges.setPrivilege(member, privilege, state, checkboxEl);
} else {
app.alertError('[[error:invalid-data]]');
}
});
$('.privilege-table-container').on('click', '[data-action="search.user"]', Privileges.addUserToPrivilegeTable);
$('.privilege-table-container').on('click', '[data-action="search.group"]', Privileges.addGroupToPrivilegeTable);
Privileges.exposeAssumedPrivileges();
};
Privileges.refreshPrivilegeTable = function () {
socket.emit('admin.categories.getPrivilegeSettings', function (err, privileges) {
if (err) {
return app.alertError(err.message);
}
Benchpress.parse('admin/partials/global/privileges', {
privileges: privileges,
}, function (html) {
translator.translate(html, function (html) {
$('.privilege-table-container').html(html);
Privileges.exposeAssumedPrivileges();
});
});
});
};
Privileges.exposeAssumedPrivileges = function () {
/*
If registered-users has a privilege enabled, then all users and groups of that privilege
should be assumed to have that privilege as well, even if not set in the db, so reflect
this arrangement in the table
*/
var privs = [];
$('.privilege-table tr[data-group-name="registered-users"] td input[type="checkbox"]').parent().each(function (idx, el) {
if ($(el).find('input').prop('checked')) {
privs.push(el.getAttribute('data-privilege'));
}
});
for (var x = 0, numPrivs = privs.length; x < numPrivs; x += 1) {
var inputs = $('.privilege-table tr[data-group-name]:not([data-group-name="registered-users"],[data-group-name="guests"]) td[data-privilege="' + privs[x] + '"] input');
inputs.each(function (idx, el) {
if (!el.checked) {
el.indeterminate = true;
}
});
}
};
Privileges.setPrivilege = function (member, privilege, state, checkboxEl) {
socket.emit('admin.categories.setPrivilege', {
cid: 0,
privilege: privilege,
set: state,
member: member,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
checkboxEl.replaceWith('<i class="fa fa-spin fa-spinner"></i>');
Privileges.refreshPrivilegeTable();
});
};
Privileges.addUserToPrivilegeTable = function () {
var modal = bootbox.dialog({
title: '[[admin/manage/categories:alert.find-user]]',
message: '<input class="form-control input-lg" placeholder="[[admin/manage/categories:alert.user-search]]" />',
show: true,
});
modal.on('shown.bs.modal', function () {
var inputEl = modal.find('input');
autocomplete.user(inputEl, function (ev, ui) {
socket.emit('admin.categories.setPrivilege', {
cid: 0,
privilege: ['chat'],
set: true,
member: ui.item.user.uid,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
Privileges.refreshPrivilegeTable();
modal.modal('hide');
});
});
});
};
Privileges.addGroupToPrivilegeTable = function () {
var modal = bootbox.dialog({
title: '[[admin/manage/categories:alert.find-group]]',
message: '<input class="form-control input-lg" placeholder="[[admin/manage/categories:alert.group-search]]" />',
show: true,
});
modal.on('shown.bs.modal', function () {
var inputEl = modal.find('input');
autocomplete.group(inputEl, function (ev, ui) {
socket.emit('admin.categories.setPrivilege', {
cid: 0,
privilege: ['groups:chat'],
set: true,
member: ui.item.group.name,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
Privileges.refreshPrivilegeTable();
modal.modal('hide');
});
});
});
};
return Privileges;
});

View File

@@ -5,6 +5,7 @@ var async = require('async');
var messaging = require('../../messaging');
var meta = require('../../meta');
var user = require('../../user');
var privileges = require('../../privileges');
var helpers = require('../helpers');
var chatsController = module.exports;
@@ -19,6 +20,12 @@ chatsController.get = function (req, res, callback) {
async.waterfall([
function (next) {
privileges.global.can('chat', req.uid, next);
},
function (canChat, next) {
if (!canChat) {
return next(new Error('[[error:no-privileges]]'));
}
user.getUidByUserslug(req.params.userslug, next);
},
function (_uid, next) {

View File

@@ -3,6 +3,7 @@
var adminController = {
dashboard: require('./admin/dashboard'),
categories: require('./admin/categories'),
privileges: require('./admin/privileges'),
tags: require('./admin/tags'),
postQueue: require('./admin/postqueue'),
blacklist: require('./admin/blacklist'),

View File

@@ -0,0 +1,25 @@
'use strict';
var async = require('async');
var categories = require('../../categories');
var privileges = require('../../privileges');
var privilegesController = module.exports;
privilegesController.get = function (req, res, callback) {
async.waterfall([
function (next) {
async.parallel({
privileges: async.apply(privileges.global.list),
allCategories: async.apply(categories.buildForSelect, req.uid, 'read'),
}, next);
},
function (data) {
res.render('admin/manage/privileges', {
privileges: data.privileges,
allCategories: data.allCategories,
});
},
], callback);
};

View File

@@ -10,6 +10,7 @@ var meta = require('../meta');
var plugins = require('../plugins');
var navigation = require('../navigation');
var translator = require('../translator');
var privileges = require('../privileges');
var utils = require('../utils');
var controllers = {
@@ -75,6 +76,9 @@ module.exports = function (middleware) {
isModerator: function (next) {
user.isModeratorOfAnyCategory(req.uid, next);
},
canChat: function (next) {
privileges.global.can('chat', req.uid, next);
},
user: function (next) {
var userData = {
uid: 0,
@@ -124,6 +128,7 @@ module.exports = function (middleware) {
results.user.isAdmin = results.isAdmin;
results.user.isGlobalMod = results.isGlobalMod;
results.user.isMod = !!results.isModerator;
results.user.uid = parseInt(results.user.uid, 10);
results.user.email = String(results.user.email);
results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1;
@@ -138,6 +143,7 @@ module.exports = function (middleware) {
templateValues.isAdmin = results.user.isAdmin;
templateValues.isGlobalMod = results.user.isGlobalMod;
templateValues.showModMenu = results.user.isAdmin || results.user.isGlobalMod || results.user.isMod;
templateValues.canChat = results.canChat;
templateValues.user = results.user;
templateValues.userJSON = jsesc(JSON.stringify(results.user), { isScriptContext: true });
templateValues.useCustomCSS = parseInt(meta.config.useCustomCSS, 10) === 1 && meta.config.customCSS;

View File

@@ -40,6 +40,7 @@ privileges.groupPrivilegeList = privileges.userPrivilegeList.map(function (privi
privileges.privilegeList = privileges.userPrivilegeList.concat(privileges.groupPrivilegeList);
require('./privileges/global')(privileges);
require('./privileges/categories')(privileges);
require('./privileges/topics')(privileges);
require('./privileges/posts')(privileges);

171
src/privileges/global.js Normal file
View File

@@ -0,0 +1,171 @@
'use strict';
var async = require('async');
var _ = require('lodash');
var user = require('../user');
var groups = require('../groups');
var helpers = require('./helpers');
var plugins = require('../plugins');
module.exports = function (privileges) {
privileges.global = {};
privileges.global.privilegeLabels = [
{ name: 'Chat' },
];
privileges.global.userPrivilegeList = [
'chat',
];
privileges.global.groupPrivilegeList = privileges.global.userPrivilegeList.map(function (privilege) {
return 'groups:' + privilege;
});
privileges.global.list = function (callback) {
var privilegeLabels = privileges.global.privilegeLabels.slice();
var userPrivilegeList = privileges.global.userPrivilegeList.slice();
var groupPrivilegeList = privileges.global.groupPrivilegeList.slice();
async.waterfall([
function (next) {
async.parallel({
labels: function (next) {
async.parallel({
users: async.apply(plugins.fireHook, 'filter:privileges.global.list_human', privilegeLabels),
groups: async.apply(plugins.fireHook, 'filter:privileges.global.groups.list_human', privilegeLabels),
}, next);
},
users: function (next) {
var userPrivileges;
var memberSets;
async.waterfall([
async.apply(plugins.fireHook, 'filter:privileges.global.list', userPrivilegeList),
function (_privs, next) {
userPrivileges = _privs;
groups.getMembersOfGroups(userPrivileges.map(function (privilege) {
return 'cid:0:privileges:' + privilege;
}), next);
},
function (_memberSets, next) {
memberSets = _memberSets.map(function (set) {
return set.map(function (uid) {
return parseInt(uid, 10);
});
});
var members = _.uniq(_.flatten(memberSets));
user.getUsersFields(members, ['picture', 'username'], next);
},
function (memberData, next) {
memberData.forEach(function (member) {
member.privileges = {};
for (var x = 0, numPrivs = userPrivileges.length; x < numPrivs; x += 1) {
member.privileges[userPrivileges[x]] = memberSets[x].indexOf(parseInt(member.uid, 10)) !== -1;
}
});
next(null, memberData);
},
], next);
},
groups: function (next) {
var groupPrivileges;
async.waterfall([
async.apply(plugins.fireHook, 'filter:privileges.global.groups.list', groupPrivilegeList),
function (_privs, next) {
groupPrivileges = _privs;
async.parallel({
memberSets: function (next) {
groups.getMembersOfGroups(groupPrivileges.map(function (privilege) {
return 'cid:0:privileges:' + privilege;
}), next);
},
groupNames: function (next) {
groups.getGroups('groups:createtime', 0, -1, next);
},
}, next);
},
function (results, next) {
var memberSets = results.memberSets;
var uniqueGroups = _.uniq(_.flatten(memberSets));
var groupNames = results.groupNames.filter(function (groupName) {
return groupName.indexOf(':privileges:') === -1 && uniqueGroups.indexOf(groupName) !== -1;
});
var registeredUsersIndex = groupNames.indexOf('registered-users');
if (registeredUsersIndex !== -1) {
groupNames.splice(0, 0, groupNames.splice(registeredUsersIndex, 1)[0]);
} else {
groupNames = ['registered-users'].concat(groupNames);
}
var adminIndex = groupNames.indexOf('administrators');
if (adminIndex !== -1) {
groupNames.splice(adminIndex, 1);
}
var memberPrivs;
var memberData = groupNames.map(function (member) {
memberPrivs = {};
for (var x = 0, numPrivs = groupPrivileges.length; x < numPrivs; x += 1) {
memberPrivs[groupPrivileges[x]] = memberSets[x].indexOf(member) !== -1;
}
return {
name: member,
privileges: memberPrivs,
};
});
next(null, memberData);
},
function (memberData, next) {
// Grab privacy info for the groups as well
async.map(memberData, function (member, next) {
async.waterfall([
function (next) {
groups.isPrivate(member.name, next);
},
function (isPrivate, next) {
member.isPrivate = isPrivate;
next(null, member);
},
], next);
}, next);
},
], next);
},
}, next);
},
function (payload, next) {
// This is a hack because I can't do {labels.users.length} to echo the count in templates.js
payload.columnCount = payload.labels.users.length + 2;
next(null, payload);
},
], callback);
};
privileges.global.can = function (privilege, uid, callback) {
helpers.some([
function (next) {
helpers.isUserAllowedTo(privilege, uid, [0], function (err, results) {
next(err, Array.isArray(results) && results.length ? results[0] : false);
});
},
function (next) {
user.isGlobalModerator(uid, next);
},
function (next) {
user.isAdministrator(uid, next);
},
], callback);
};
};

View File

@@ -55,6 +55,7 @@ function addRoutes(router, middleware, controllers) {
router.get('/manage/categories/:category_id', middlewares, controllers.admin.categories.get);
router.get('/manage/categories/:category_id/analytics', middlewares, controllers.admin.categories.getAnalytics);
router.get('/manage/privileges', middlewares, controllers.admin.privileges.get);
router.get('/manage/tags', middlewares, controllers.admin.tags.get);
router.get('/manage/post-queue', middlewares, controllers.admin.postQueue.get);
router.get('/manage/ip-blacklist', middlewares, controllers.admin.blacklist.get);

View File

@@ -83,7 +83,11 @@ Categories.setPrivilege = function (socket, data, callback) {
};
Categories.getPrivilegeSettings = function (socket, cid, callback) {
if (!parseInt(cid, 10)) {
privileges.global.list(callback);
} else {
privileges.categories.list(cid, callback);
}
};
Categories.copyPrivilegesToChildren = function (socket, cid, callback) {

View File

@@ -11,6 +11,7 @@ var Messaging = require('../messaging');
var utils = require('../utils');
var server = require('./');
var user = require('../user');
var privileges = require('../privileges');
var SocketModules = module.exports;
@@ -73,6 +74,12 @@ SocketModules.chats.newRoom = function (socket, data, callback) {
async.waterfall([
function (next) {
privileges.global.can('chat', socket.uid, next);
},
function (canChat, next) {
if (!canChat) {
return next(new Error('[[error:no-privileges]]'));
}
Messaging.canMessageUser(socket.uid, data.touid, next);
},
function (next) {
@@ -92,6 +99,13 @@ SocketModules.chats.send = function (socket, data, callback) {
async.waterfall([
function (next) {
privileges.global.can('chat', socket.uid, next);
},
function (canChat, next) {
if (!canChat) {
return next(new Error('[[error:no-privileges]]'));
}
plugins.fireHook('filter:messaging.send', {
data: data,
uid: socket.uid,
@@ -133,6 +147,13 @@ SocketModules.chats.loadRoom = function (socket, data, callback) {
async.waterfall([
function (next) {
privileges.global.can('chat', socket.uid, next);
},
function (canChat, next) {
if (!canChat) {
return next(new Error('[[error:no-privileges]]'));
}
Messaging.isUserInRoom(socket.uid, data.roomId, next);
},
function (inRoom, next) {
@@ -174,6 +195,13 @@ SocketModules.chats.addUserToRoom = function (socket, data, callback) {
var uid;
async.waterfall([
function (next) {
privileges.global.can('chat', socket.uid, next);
},
function (canChat, next) {
if (!canChat) {
return next(new Error('[[error:no-privileges]]'));
}
Messaging.getUserCountInRoom(data.roomId, next);
},
function (userCount, next) {

View File

@@ -0,0 +1,12 @@
'use strict';
var groups = require('../../groups');
module.exports = {
name: 'Give chat privilege to registered-users',
timestamp: Date.UTC(2017, 11, 18),
method: function (callback) {
groups.join('cid:0:privileges:group:chat', 'registered-users', callback);
},
};

View File

@@ -12,6 +12,8 @@
</div>
<div class="col-md-3">
<select id="category-selector" class="form-control">
<option value="global" selected>[[admin/manage/privileges:global]]</option>
<option disabled>_____________</option>
<!-- BEGIN allCategories -->
<option value="{allCategories.value}" <!-- IF allCategories.selected -->selected<!-- ENDIF allCategories.selected -->>{allCategories.text}</option>
<!-- END allCategories -->

View File

@@ -0,0 +1,34 @@
<div class="row">
<form role="form" class="category">
<div class="row">
<div class="col-md-3 pull-right">
<select id="category-selector" class="form-control">
<option value="global" selected>[[admin/manage/privileges:global]]</option>
<option disabled>_____________</option>
<!-- BEGIN allCategories -->
<option value="{allCategories.value}">{allCategories.text}</option>
<!-- END allCategories -->
</select>
</div>
</div>
<br/>
<div class="">
<p>
[[admin/manage/privileges:global.description]]
</p>
<p class="text-warning">
[[admin/manage/privileges:global.warning]]
</p>
<hr />
<div class="privilege-table-container">
<!-- IMPORT admin/partials/global/privileges.tpl -->
</div>
</div>
</form>
</div>
<button id="save" class="floating-button mdl-button mdl-js-button mdl-button--fab mdl-js-ripple-effect mdl-button--colored">
<i class="material-icons">save</i>
</button>

View File

@@ -0,0 +1,86 @@
<table class="table table-striped privilege-table">
<thead>
<tr class="privilege-table-header">
<th colspan="3"></th>
</tr><tr><!-- zebrastripe reset --></tr>
<tr>
<th colspan="2">[[admin/manage/categories:privileges.section-user]]</th>
<!-- BEGIN privileges.labels.users -->
<th class="text-center">{privileges.labels.users.name}</th>
<!-- END privileges.labels.users -->
</tr>
</thead>
<tbody>
<!-- IF privileges.users.length -->
<!-- BEGIN privileges.users -->
<tr data-uid="{privileges.users.uid}">
<td>
<!-- IF ../picture -->
<img class="avatar avatar-sm" src="{privileges.users.picture}" title="{privileges.users.username}" />
<!-- ELSE -->
<div class="avatar avatar-sm" style="background-color: {../icon:bgColor};">{../icon:text}</div>
<!-- ENDIF ../picture -->
</td>
<td>{privileges.users.username}</td>
{function.spawnPrivilegeStates, privileges.users.username, ../privileges}
</tr>
<!-- END privileges.users -->
<tr>
<td colspan="{privileges.columnCount}">
<button type="button" class="btn btn-primary pull-right" data-ajaxify="false" data-action="search.user">
[[admin/manage/categories:privileges.search-user]]
</button>
</td>
</tr>
<!-- ELSE -->
<tr>
<td colspan="{privileges.columnCount}">
[[admin/manage/privileges:global.no-users]]
<button type="button" class="btn btn-primary pull-right" data-ajaxify="false" data-action="search.user">
[[admin/manage/categories:privileges.search-user]]
</button>
</td>
</tr>
<!-- ENDIF privileges.users.length -->
</tbody>
</table>
<table class="table table-striped privilege-table">
<thead>
<tr class="privilege-table-header">
<th colspan="3"></th>
</tr><tr><!-- zebrastripe reset --></tr>
<tr>
<th colspan="2">[[admin/manage/categories:privileges.section-group]]</th>
<!-- BEGIN privileges.labels.groups -->
<th class="text-center">{privileges.labels.groups.name}</th>
<!-- END privileges.labels.groups -->
</tr>
</thead>
<tbody>
<!-- BEGIN privileges.groups -->
<tr data-group-name="{privileges.groups.name}" data-private="<!-- IF privileges.groups.isPrivate -->1<!-- ELSE -->0<!-- ENDIF privileges.groups.isPrivate -->">
<td>
<!-- IF privileges.groups.isPrivate -->
<i class="fa fa-lock text-muted" title="[[admin/manage/categories:privileges.group-private]]"></i>
<!-- ENDIF privileges.groups.isPrivate -->
{privileges.groups.name}
</td>
<td></td>
{function.spawnPrivilegeStates, privileges.groups.name, ../privileges}
</tr>
<!-- END privileges.groups -->
<tr>
<td colspan="{privileges.columnCount}">
<div class="btn-toolbar">
<button type="button" class="btn btn-primary pull-right" data-ajaxify="false" data-action="search.group">
[[admin/manage/categories:privileges.search-group]]
</button>
</div>
</td>
</tr>
</tbody>
</table>
<div class="help-block">
[[admin/manage/categories:privileges.inherit]]
</div>

View File

@@ -15,6 +15,7 @@
<h3 class="menu-section-title">[[admin/menu:section-manage]]</h3>
<ul class="menu-section-list">
<li><a href="{relative_path}/admin/manage/categories">[[admin/menu:manage/categories]]</a></li>
<li><a href="{relative_path}/admin/manage/privileges">[[admin/menu:manage/privileges]]</a></li>
<li><a href="{relative_path}/admin/manage/users">[[admin/menu:manage/users]]</a></li>
<li><a href="{relative_path}/admin/manage/groups">[[admin/menu:manage/groups]]</a></li>
<li><a href="{relative_path}/admin/manage/tags">[[admin/menu:manage/tags]]</a></li>
@@ -188,6 +189,7 @@
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">[[admin/menu:section-manage]]</a>
<ul class="dropdown-menu" role="menu">
<li><a href="{relative_path}/admin/manage/categories">[[admin/menu:manage/categories]]</a></li>
<li><a href="{relative_path}/admin/manage/privileges">[[admin/menu:manage/privileges]]</a></li>
<li><a href="{relative_path}/admin/manage/users">[[admin/menu:manage/users]]</a></li>
<li><a href="{relative_path}/admin/manage/groups">[[admin/menu:manage/groups]]</a></li>
<li><a href="{relative_path}/admin/manage/tags">[[admin/menu:manage/tags]]</a></li>