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

This commit is contained in:
JC Brand 2020-02-22 21:12:40 +01:00
parent e5341d54a9
commit 2af93f4492
6 changed files with 141 additions and 80 deletions

View File

@ -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)

View File

@ -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
--------------------------

View File

@ -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);

View File

@ -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') {

View File

@ -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>`;
}

View File

@ -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',