Browse Source

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

JC Brand 5 years ago
parent
commit
2af93f4492
6 changed files with 141 additions and 80 deletions
  1. 2 0
      CHANGES.md
  2. 20 0
      docs/source/configuration.rst
  3. 2 1
      spec/modtools.js
  4. 37 20
      src/converse-muc-views.js
  5. 78 59
      src/templates/moderator_tools_modal.js
  6. 2 0
      webpack.html

+ 2 - 0
CHANGES.md

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

+ 20 - 0
docs/source/configuration.rst

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

+ 2 - 1
spec/modtools.js

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

+ 37 - 20
src/converse-muc-views.js

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

+ 78 - 59
src/templates/moderator_tools_modal.js

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

+ 2 - 0
webpack.html

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