Commit 2af93f44 authored by JC Brand's avatar JC Brand

modtools: settings for which roles/affiliations may be queried or assigned

parent e5341d54
......@@ -18,6 +18,8 @@
- #1839: Headline messages are shown in controlbox
- Allow ignore bootstrap modules at build using environment variable: BOOTSTRAP_IGNORE_MODULES="Modal,Dropdown".
example: export BOOTSTRAP_IGNORE_MODULES="Modal,Dropdown" && make dist
- New config option [modtools_disable_query](https://conversejs.org/docs/html/configuration.html#modtools-disable-query)
- New config option [modtools_disable_assign](https://conversejs.org/docs/html/configuration.html#modtools-disable-assign)
## 6.0.0 (2020-01-09)
......
......@@ -1039,6 +1039,26 @@ and it's trivial for an attacker to bypass this restriction.
You should therefore also configure your XMPP server to limit message sizes.
modtools_disable_assign
-----------------------
* Default: ``false``
* Possible Values: ``true``, ``false``, ``['owner', 'admin', 'member', 'outcast', 'none', 'moderator', 'participant', 'visitor']``
This setting allows you to disable (either completely, or fine-grained) which affiliations and or roles
may be assigned in the moderator tools modal.
modtools_disable_query
----------------------
* Default: ``[]``
* Possible Values: ``['owner', 'admin', 'member', 'outcast', 'none', 'moderator', 'participant', 'visitor']``
This setting allows you to disable which affiliations or roles may be queried in the moderator tools modal.
If all roles or all affiliations are disabled, then the relevant tab won't be
showed at all.
muc_disable_slash_commands
--------------------------
......
......@@ -41,7 +41,8 @@
_converse.connection.IQ_stanzas = [];
tab.click();
let select = modal.el.querySelector('.select-affiliation');
expect(select.value).toBe('admin');
expect(select.value).toBe('owner');
select.value = 'admin';
let button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
button.click();
await u.waitUntil(() => !modal.loading_users_with_affiliation);
......
......@@ -40,7 +40,7 @@ const { Strophe, sizzle, $iq, $pres } = converse.env;
const u = converse.env.utils;
const ROLES = ['moderator', 'participant', 'visitor'];
const AFFILIATIONS = ['admin', 'member', 'outcast', 'owner'];
const AFFILIATIONS = ['owner', 'admin', 'member', 'outcast', 'none'];
const OWNER_COMMANDS = ['owner'];
const ADMIN_COMMANDS = ['admin', 'ban', 'deop', 'destroy', 'member', 'op', 'revoke'];
const MODERATOR_COMMANDS = ['kick', 'mute', 'voice', 'modtools'];
......@@ -101,16 +101,18 @@ converse.plugins.add('converse-muc-views', {
'auto_list_rooms': false,
'cache_muc_messages': true,
'locked_muc_nickname': false,
'show_retraction_warning': true,
'modtools_disable_query': [],
'modtools_disable_assign': false,
'muc_disable_slash_commands': false,
'muc_show_join_leave': true,
'muc_show_join_leave_status': true,
'muc_mention_autocomplete_min_chars': 0,
'muc_mention_autocomplete_filter': 'contains',
'muc_mention_autocomplete_min_chars': 0,
'muc_mention_autocomplete_show_avatar': true,
'roomconfig_whitelist': [],
'muc_roomid_policy': null,
'muc_roomid_policy_hint': null,
'muc_show_join_leave': true,
'muc_show_join_leave_status': true,
'roomconfig_whitelist': [],
'show_retraction_warning': true,
'visible_toolbar_buttons': {
'toggle_occupants': true
}
......@@ -248,26 +250,17 @@ converse.plugins.add('converse-muc-views', {
},
toHTML () {
const allowed_commands = this.chatroomview.getAllowedCommands();
const allowed_affiliations = allowed_commands.map(c => COMMAND_TO_AFFILIATION[c]).filter(c => c);
const allowed_roles = [...new Set(allowed_commands
.filter((value, i, list) => list.indexOf(value) == i)
.map(c => COMMAND_TO_ROLE[c])
.filter(c => c))];
allowed_affiliations.sort();
allowed_roles.sort();
const occupant = this.chatroomview.model.occupants.findWhere({'jid': _converse.bare_jid});
return tpl_moderator_tools_modal(Object.assign(this.model.toJSON(), {
allowed_affiliations,
allowed_roles,
'affiliations': [...AFFILIATIONS, 'none'],
'assignAffiliation': ev => this.assignAffiliation(ev),
'assignRole': ev => this.assignRole(ev),
'loading_users_with_affiliation': this.loading_users_with_affiliation,
'queryAffiliation': ev => this.queryAffiliation(ev),
'queryRole': ev => this.queryRole(ev),
'roles': ROLES,
'queryable_affiliations': AFFILIATIONS.filter(a => !_converse.modtools_disable_query.includes(a)),
'queryable_roles': ROLES.filter(a => !_converse.modtools_disable_query.includes(a)),
'assignable_affiliations': this.getAssignableAffiliations(occupant),
'assignable_roles': this.getAssignableRoles(occupant),
'switchTab': ev => this.switchTab(ev),
'toggleForm': ev => this.toggleForm(ev),
'users_with_affiliation': this.users_with_affiliation,
......@@ -275,6 +268,30 @@ converse.plugins.add('converse-muc-views', {
}));
},
getAssignableAffiliations (occupant) {
const disabled = _converse.modtools_disable_assign;
if (!Array.isArray(disabled)) {
return disabled ? [] : AFFILIATIONS;
} else if (occupant.get('affiliation') === 'owner') {
return AFFILIATIONS.filter(a => !disabled.includes(a));
} else if (occupant.get('affiliation') === 'admin') {
return AFFILIATIONS.filter(a => !['owner', ...disabled].includes(a));
} else {
return [];
}
},
getAssignableRoles (occupant) {
const disabled = _converse.modtools_disable_assign;
if (!Array.isArray(disabled)) {
return disabled ? [] : ROLES;
} else if (occupant.get('role') === 'moderator') {
return ROLES.filter(r => !disabled.includes(r));
} else {
return [];
}
},
shouldFetchAffiliationsList () {
const affiliation = this.model.get('affiliation');
if (affiliation === 'none') {
......
......@@ -64,6 +64,31 @@ const affiliation_option = (o) => html`
`;
const tpl_set_role_form = (o) => html`
<form class="role-form hidden" @submit=${o.assignRole}>
<div class="form-group">
<input type="hidden" name="jid" value="${o.item.jid}"/>
<input type="hidden" name="nick" value="${o.item.nick}"/>
<div class="row">
<div class="col">
<label><strong>${i18n_new_role}:</strong></label>
<select class="custom-select select-role" name="role">
${ o.assignable_roles.map(role => html`<option value="${role}" ?selected=${role === o.item.role}>${role}</option>`) }
</select>
</div>
<div class="col">
<label><strong>${i18n_reason}:</strong></label>
<input class="form-control" type="text" name="reason"/>
</div>
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="${i18n_change_role}"/>
</div>
</form>
`;
const role_list_item = (o) => html`
<li class="list-group-item">
<ul class="list-group">
......@@ -74,34 +99,39 @@ const role_list_item = (o) => html`
<div><strong>Nickname:</strong> ${o.item.nick}</div>
</li>
<li class="list-group-item">
<div><strong>Role:</strong> ${o.item.role}<a href="#" data-form="role-form" class="toggle-form right fa fa-wrench" @click=${o.toggleForm}></a></div>
<form class="role-form hidden" @submit=${o.assignRole}>
<div class="form-group">
<input type="hidden" name="jid" value="${o.item.jid}"/>
<input type="hidden" name="nick" value="${o.item.nick}"/>
<div class="row">
<div class="col">
<label><strong>${i18n_new_role}:</strong></label>
<select class="custom-select select-role" name="role">
${ o.allowed_roles.map(role => html`<option value="${role}" ?selected=${role === o.item.role}>${role}</option>`) }
</select>
</div>
<div class="col">
<label><strong>${i18n_reason}:</strong></label>
<input class="form-control" type="text" name="reason"/>
</div>
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="${i18n_change_role}"/>
</div>
</form>
<div><strong>Role:</strong> ${o.item.role} ${o.assignable_roles.length ? html`<a href="#" data-form="role-form" class="toggle-form right fa fa-wrench" @click=${o.toggleForm}></a>` : ''}</div>
${o.assignable_roles.length ? tpl_set_role_form(o) : ''}
</li>
</ul>
</li>
`;
const tpl_set_affiliation_form = (o) => html`
<form class="affiliation-form hidden" @submit=${o.assignAffiliation}>
<div class="form-group">
<input type="hidden" name="jid" value="${o.item.jid}"/>
<input type="hidden" name="nick" value="${o.item.nick}"/>
<div class="row">
<div class="col">
<label><strong>${i18n_new_affiliation}:</strong></label>
<select class="custom-select select-affiliation" name="affiliation">
${ o.assignable_affiliations.map(aff => html`<option value="${aff}" ?selected=${aff === o.item.affiliation}>${aff}</option>`) }
</select>
</div>
<div class="col">
<label><strong>${i18n_reason}:</strong></label>
<input class="form-control" type="text" name="reason"/>
</div>
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" name="change" value="${i18n_change_affiliation}"/>
</div>
</form>
`;
const affiliation_list_item = (o) => html`
<li class="list-group-item">
<ul class="list-group">
......@@ -112,35 +142,30 @@ const affiliation_list_item = (o) => html`
<div><strong>Nickname:</strong> ${o.item.nick}</div>
</li>
<li class="list-group-item">
<div><strong>Affiliation:</strong> ${o.item.affiliation} <a href="#" data-form="affiliation-form" class="toggle-form right fa fa-wrench" @click=${o.toggleForm}></a></div>
<form class="affiliation-form hidden" @submit=${o.assignAffiliation}>
<div class="form-group">
<input type="hidden" name="jid" value="${o.item.jid}"/>
<input type="hidden" name="nick" value="${o.item.nick}"/>
<div class="row">
<div class="col">
<label><strong>${i18n_new_affiliation}:</strong></label>
<select class="custom-select select-affiliation" name="affiliation">
${ o.allowed_affiliations.map(aff => html`<option value="${aff}" ?selected=${aff === o.item.affiliation}>${aff}</option>`) }
</select>
</div>
<div class="col">
<label><strong>${i18n_reason}:</strong></label>
<input class="form-control" type="text" name="reason"/>
</div>
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" name="change" value="${i18n_change_affiliation}"/>
</div>
</form>
<div><strong>Affiliation:</strong> ${o.item.affiliation} ${o.assignable_affiliations.length ? html`<a href="#" data-form="affiliation-form" class="toggle-form right fa fa-wrench" @click=${o.toggleForm}></a>` : ''}</div>
${o.assignable_affiliations.length ? tpl_set_affiliation_form(o) : ''}
</li>
</ul>
</li>
`;
export default (o) => html`
const tpl_navigation = (o) => html`
<ul class="nav nav-pills justify-content-center">
<li role="presentation" class="nav-item">
<a class="nav-link active" id="affiliations-tab" href="#affiliations-tabpanel" aria-controls="affiliations-tabpanel" role="tab" data-toggle="tab" @click=${o.switchTab}>Affiliations</a>
</li>
<li role="presentation" class="nav-item">
<a class="nav-link" id="roles-tab" href="#roles-tabpanel" aria-controls="roles-tabpanel" role="tab" data-toggle="tab" @click=${o.switchTab}>Roles</a>
</li>
</ul>
`;
export default (o) => {
const show_both_tabs = o.queryable_roles.length && o.queryable_affiliations.length;
return html`
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
......@@ -150,17 +175,11 @@ export default (o) => html`
<div class="modal-body d-flex flex-column">
<span class="modal-alert"></span>
<ul class="nav nav-pills justify-content-center">
<li role="presentation" class="nav-item">
<a class="nav-link active" id="affiliations-tab" href="#affiliations-tabpanel" aria-controls="affiliations-tabpanel" role="tab" data-toggle="tab" @click=${o.switchTab}>Affiliations</a>
</li>
<li role="presentation" class="nav-item">
<a class="nav-link" id="roles-tab" href="#roles-tabpanel" aria-controls="roles-tabpanel" role="tab" data-toggle="tab" @click=${o.switchTab}>Roles</a>
</li>
</ul>
${ show_both_tabs ? tpl_navigation(o) : '' }
<div class="tab-content">
<div class="tab-pane tab-pane--columns active" id="affiliations-tabpanel" role="tabpanel" aria-labelledby="affiliations-tab">
<div class="tab-pane tab-pane--columns ${ o.queryable_affiliations.length ? 'active' : ''}" id="affiliations-tabpanel" role="tabpanel" aria-labelledby="affiliations-tab">
<form class="converse-form query-affiliation" @submit=${o.queryAffiliation}>
<p class="helptext pb-3">${i18n_helptext_affiliation}</p>
<div class="form-group">
......@@ -170,7 +189,7 @@ export default (o) => html`
<div class="row">
<div class="col">
<select class="custom-select select-affiliation" name="affiliation">
${o.affiliations.map(item => affiliation_option(Object.assign({item}, o)))}
${o.queryable_affiliations.map(item => affiliation_option(Object.assign({item}, o)))}
</select>
</div>
<div class="col">
......@@ -193,7 +212,7 @@ export default (o) => html`
</div>
</div>
<div class="tab-pane tab-pane--columns" id="roles-tabpanel" role="tabpanel" aria-labelledby="roles-tab">
<div class="tab-pane tab-pane--columns ${ !show_both_tabs && o.queryable_roles.length ? 'active' : ''}" id="roles-tabpanel" role="tabpanel" aria-labelledby="roles-tab">
<form class="converse-form query-role" @submit=${o.queryRole}>
<p class="helptext pb-3">${i18n_helptext_role}</p>
<div class="form-group">
......@@ -201,7 +220,7 @@ export default (o) => html`
<div class="row">
<div class="col">
<select class="custom-select select-role" name="role">
${o.roles.map(item => role_option(Object.assign({item}, o)))}
${o.queryable_roles.map(item => role_option(Object.assign({item}, o)))}
</select>
</div>
<div class="col">
......@@ -224,5 +243,5 @@ export default (o) => html`
</div>
</div>
</div>
</div>
`;
</div>`;
}
......@@ -23,6 +23,8 @@
auto_away: 300,
auto_register_muc_nickname: true,
loglevel: 'debug',
modtools_disable_assign: ['owner', 'moderator', 'participant', 'visitor'],
modtools_disable_query: ['moderator', 'participant', 'visitor'],
enable_smacks: true,
i18n: 'en',
message_archiving: 'always',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment