mirror of https://github.com/zulip/zulip.git
parent
849c925dc9
commit
c96762b7a9
|
@ -59,6 +59,28 @@ run_test('can_edit', () => {
|
|||
return false;
|
||||
};
|
||||
assert(!settings_user_groups.can_edit(1));
|
||||
|
||||
page_params.realm_user_group_edit_policy = 2;
|
||||
page_params.is_admin = true;
|
||||
assert(settings_user_groups.can_edit(1));
|
||||
|
||||
page_params.is_admin = false;
|
||||
user_groups.is_member_of = (group_id, user_id) => {
|
||||
assert.equal(group_id, 1);
|
||||
assert.equal(user_id, undefined);
|
||||
return true;
|
||||
};
|
||||
assert(!settings_user_groups.can_edit(1));
|
||||
|
||||
page_params.realm_user_group_edit_policy = 1;
|
||||
page_params.is_admin = false;
|
||||
user_groups.is_member_of = (group_id, user_id) => {
|
||||
assert.equal(group_id, 1);
|
||||
assert.equal(user_id, undefined);
|
||||
return true;
|
||||
};
|
||||
assert(settings_user_groups.can_edit(1));
|
||||
|
||||
});
|
||||
|
||||
var user_group_selector = "#user-groups #1";
|
||||
|
|
|
@ -32,6 +32,8 @@ exports.build_page = function () {
|
|||
realm_authentication_methods: page_params.realm_authentication_methods,
|
||||
realm_create_stream_policy: page_params.realm_create_stream_policy,
|
||||
realm_invite_to_stream_policy: page_params.realm_invite_to_stream_policy,
|
||||
realm_user_group_edit_policy: page_params.realm_user_group_edit_policy,
|
||||
USER_GROUP_EDIT_POLICY_MEMBERS: 1,
|
||||
realm_name_changes_disabled: page_params.realm_name_changes_disabled,
|
||||
realm_email_changes_disabled: page_params.realm_email_changes_disabled,
|
||||
realm_avatar_changes_disabled: page_params.realm_avatar_changes_disabled,
|
||||
|
|
|
@ -88,6 +88,7 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) {
|
|||
allow_message_deleting: noop,
|
||||
allow_message_editing: noop,
|
||||
allow_community_topic_editing: noop,
|
||||
user_group_edit_policy: noop,
|
||||
avatar_changes_disabled: settings_account.update_avatar_change_display,
|
||||
bot_creation_policy: settings_bots.update_bot_permissions_ui,
|
||||
create_stream_policy: noop,
|
||||
|
|
|
@ -117,6 +117,15 @@ function get_property_value(property_name) {
|
|||
}
|
||||
}
|
||||
|
||||
if (property_name === 'realm_user_group_edit_policy') {
|
||||
if (page_params.realm_user_group_edit_policy === 1) {
|
||||
return "by_members";
|
||||
}
|
||||
if (page_params.realm_user_group_edit_policy === 2) {
|
||||
return "by_admins_only";
|
||||
}
|
||||
}
|
||||
|
||||
if (property_name === 'realm_add_emoji_by_admins_only') {
|
||||
if (page_params.realm_add_emoji_by_admins_only) {
|
||||
return "by_admins_only";
|
||||
|
@ -202,6 +211,11 @@ function set_invite_to_stream_policy_dropdown() {
|
|||
$("#id_realm_invite_to_stream_policy").val(value);
|
||||
}
|
||||
|
||||
function set_user_group_edit_policy_dropdown() {
|
||||
var value = get_property_value("realm_user_group_edit_policy");
|
||||
$("#id_realm_user_group_edit_policy").val(value);
|
||||
}
|
||||
|
||||
function set_add_emoji_permission_dropdown() {
|
||||
$("#id_realm_add_emoji_by_admins_only").val(get_property_value("realm_add_emoji_by_admins_only"));
|
||||
}
|
||||
|
@ -454,6 +468,8 @@ function update_dependent_subsettings(property_name) {
|
|||
set_create_stream_policy_dropdown();
|
||||
} else if (property_name === 'realm_invite_to_stream_policy') {
|
||||
set_invite_to_stream_policy_dropdown();
|
||||
} else if (property_name === 'realm_user_group_edit_policy') {
|
||||
set_user_group_edit_policy_dropdown();
|
||||
} else if (property_name === 'realm_video_chat_provider' ||
|
||||
property_name === 'realm_google_hangouts_domain' ||
|
||||
property_name.startsWith('realm_zoom')) {
|
||||
|
@ -618,6 +634,7 @@ exports.build_page = function () {
|
|||
set_user_invite_restriction_dropdown();
|
||||
set_message_content_in_email_notifications_visiblity();
|
||||
set_digest_emails_weekday_visibility();
|
||||
set_user_group_edit_policy_dropdown();
|
||||
|
||||
function get_auth_method_table_data() {
|
||||
const new_auth_methods = {};
|
||||
|
@ -752,6 +769,7 @@ exports.build_page = function () {
|
|||
const waiting_period_threshold = $("#id_realm_waiting_period_setting").val();
|
||||
const create_stream_policy = $("#id_realm_create_stream_policy").val();
|
||||
const invite_to_stream_policy = $("#id_realm_invite_to_stream_policy").val();
|
||||
const user_group_edit_policy = $("#id_realm_user_group_edit_policy").val();
|
||||
const add_emoji_permission = $("#id_realm_add_emoji_by_admins_only").val();
|
||||
|
||||
if (add_emoji_permission === "by_admins_only") {
|
||||
|
@ -776,6 +794,12 @@ exports.build_page = function () {
|
|||
data.invite_to_stream_policy = 3;
|
||||
}
|
||||
|
||||
if (user_group_edit_policy === "by_admins_only") {
|
||||
data.user_group_edit_policy = 2;
|
||||
} else if (user_group_edit_policy === "by_members") {
|
||||
data.user_group_edit_policy = 1;
|
||||
}
|
||||
|
||||
if (waiting_period_threshold === "none") {
|
||||
data.waiting_period_threshold = 0;
|
||||
} else if (waiting_period_threshold === "three_days") {
|
||||
|
|
|
@ -19,6 +19,8 @@ exports.reload = function () {
|
|||
exports.populate_user_groups();
|
||||
};
|
||||
|
||||
const USER_GROUP_EDIT_POLICY_MEMBERS = 1;
|
||||
|
||||
exports.can_edit = function (group_id) {
|
||||
if (page_params.is_admin) {
|
||||
return true;
|
||||
|
@ -28,6 +30,10 @@ exports.can_edit = function (group_id) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (page_params.realm_user_group_edit_policy !== USER_GROUP_EDIT_POLICY_MEMBERS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return user_groups.is_member_of(group_id, people.my_current_user_id());
|
||||
};
|
||||
|
||||
|
|
|
@ -114,6 +114,14 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="realm_user_group_edit_policy" class="dropdown-title">{{t "Who can create and manage user groups" }}</label>
|
||||
<select name="realm_user_group_edit_policy" id="id_realm_user_group_edit_policy" class="prop-element">
|
||||
<option value="by_admins_only">{{t "Admins" }}</option>
|
||||
<option value="by_members">{{t "Admins and members" }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="realm_add_emoji_by_admins_only" class="dropdown-title">{{t "Who can add custom emoji" }}</label>
|
||||
<select name="realm_add_emoji_by_admins_only" id="id_realm_add_emoji_by_admins_only" class="prop-element">
|
||||
|
|
|
@ -1,30 +1,37 @@
|
|||
<div id="user-groups-admin" class="settings-section" data-name="user-groups-admin">
|
||||
{{#unless is_admin}}
|
||||
<div class="tip">Only group members and organization administrators can modify a group.</div>
|
||||
{{#if (eq realm_user_group_edit_policy USER_GROUP_EDIT_POLICY_MEMBERS) }}
|
||||
<div class="tip">{{t 'Only group members and organization administrators can modify a group.' }}</div>
|
||||
{{else}}
|
||||
<div class="tip">{{t 'Only organization administrators can modify user groups in this organization.' }}</div>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
{{#unless is_guest}}
|
||||
<p>
|
||||
{{#tr this}}User groups allow you to <a href="/help/mention-a-user-or-group" target="_blank">mention</a> multiple users at once. When you mention a user group, everyone in the group is notified as if they were individually mentioned.{{/tr}}
|
||||
</p>
|
||||
<form class="form-horizontal admin-user-group-form">
|
||||
<div class="add-new-user-group-box grey-box">
|
||||
<div class="new-user-group-form">
|
||||
<div class="settings-section-title new-user-group-section-title no-padding">{{t "Add a new user group" }}</div>
|
||||
<div class="alert" id="admin-user-group-status"></div>
|
||||
<div class="inline-block">
|
||||
<label for="user_group_name">{{t "Name" }}</label>
|
||||
<input type="text" name="name" id="user_group_name" maxlength="100" placeholder="{{t 'marketing' }}" />
|
||||
<p>
|
||||
{{#tr this}}User groups allow you to <a href="/help/mention-a-user-or-group" target="_blank">mention</a> multiple users at once. When you mention a user group, everyone in the group is notified as if they were individually mentioned.{{/tr}}
|
||||
</p>
|
||||
{{#unless (and (not (eq realm_user_group_edit_policy USER_GROUP_EDIT_POLICY_MEMBERS) (not is_admin)))}}
|
||||
<form class="form-horizontal admin-user-group-form">
|
||||
<div class="add-new-user-group-box grey-box">
|
||||
<div class="new-user-group-form">
|
||||
<div class="settings-section-title new-user-group-section-title no-padding">{{t "Add a new user group" }}</div>
|
||||
<div class="alert" id="admin-user-group-status"></div>
|
||||
<div class="inline-block">
|
||||
<label for="user_group_name">{{t "Name" }}</label>
|
||||
<input type="text" name="name" id="user_group_name" maxlength="100" placeholder="{{t 'marketing' }}" />
|
||||
</div>
|
||||
<div class="inline-block">
|
||||
<label for="user_group_description">{{t "Description" }}</label>
|
||||
<input type="text" name="description" id="user_group_description" maxlength="300" placeholder="{{t 'Marketing team' }}" />
|
||||
</div>
|
||||
<button type="submit" class="button rounded sea-green">
|
||||
{{t 'Save' }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="inline-block">
|
||||
<label for="user_group_description">{{t "Description" }}</label>
|
||||
<input type="text" name="description" id="user_group_description" maxlength="300" placeholder="{{t 'Marketing team' }}" />
|
||||
</div>
|
||||
<button type="submit" class="button rounded sea-green">
|
||||
{{t 'Save' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
|
||||
<div id="user-groups" class="new-style"></div>
|
||||
|
|
|
@ -533,6 +533,17 @@ def require_member_or_admin(view_func: ViewFuncT) -> ViewFuncT:
|
|||
return view_func(request, user_profile, *args, **kwargs)
|
||||
return _wrapped_view_func # type: ignore # https://github.com/python/mypy/issues/1927
|
||||
|
||||
def require_user_group_edit_policy(view_func: ViewFuncT) -> ViewFuncT:
|
||||
@wraps(view_func)
|
||||
def _wrapped_view_func(request: HttpRequest, user_profile: UserProfile,
|
||||
*args: Any, **kwargs: Any) -> HttpResponse:
|
||||
realm = user_profile.realm
|
||||
if realm.user_group_edit_policy != Realm.USER_GROUP_EDIT_POLICY_MEMBERS and \
|
||||
not user_profile.is_realm_admin:
|
||||
raise JsonableError(_("Must be an organization administrator"))
|
||||
return view_func(request, user_profile, *args, **kwargs)
|
||||
return _wrapped_view_func # type: ignore # https://github.com/python/mypy/issues/1927
|
||||
|
||||
# This API endpoint is used only for the mobile apps. It is part of a
|
||||
# workaround for the fact that React Native doesn't support setting
|
||||
# HTTP basic authentication headers.
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.24 on 2019-10-16 22:48
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('zerver', '0251_prereg_user_add_full_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='realm',
|
||||
name='user_group_edit_policy',
|
||||
field=models.PositiveSmallIntegerField(default=1),
|
||||
),
|
||||
]
|
|
@ -197,6 +197,11 @@ class Realm(models.Model):
|
|||
invite_to_stream_policy = models.PositiveSmallIntegerField(
|
||||
default=INVITE_TO_STREAM_POLICY_MEMBERS) # type: int
|
||||
|
||||
USER_GROUP_EDIT_POLICY_MEMBERS = 1
|
||||
USER_GROUP_EDIT_POLICY_ADMINS = 2
|
||||
user_group_edit_policy = models.PositiveSmallIntegerField(
|
||||
default=INVITE_TO_STREAM_POLICY_MEMBERS) # type: int
|
||||
|
||||
# Who in the organization has access to users' actual email
|
||||
# addresses. Controls whether the UserProfile.email field is the
|
||||
# same as UserProfile.delivery_email, or is instead garbage.
|
||||
|
@ -330,6 +335,7 @@ class Realm(models.Model):
|
|||
video_chat_provider=int,
|
||||
waiting_period_threshold=int,
|
||||
digest_weekday=int,
|
||||
user_group_edit_policy=int,
|
||||
) # type: Dict[str, Union[type, Tuple[type, ...]]]
|
||||
|
||||
# Icon is the square mobile icon.
|
||||
|
|
|
@ -1588,6 +1588,7 @@ class EventsRegisterTest(ZulipTestCase):
|
|||
waiting_period_threshold=[10, 20],
|
||||
create_stream_policy=[3, 2, 1],
|
||||
invite_to_stream_policy=[3, 2, 1],
|
||||
user_group_edit_policy=[1, 2],
|
||||
email_address_visibility=[Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS],
|
||||
bot_creation_policy=[Realm.BOT_CREATION_EVERYONE],
|
||||
video_chat_provider=[
|
||||
|
|
|
@ -180,6 +180,7 @@ class HomeTest(ZulipTestCase):
|
|||
"realm_signup_notifications_stream_id",
|
||||
"realm_upload_quota",
|
||||
"realm_uri",
|
||||
"realm_user_group_edit_policy",
|
||||
"realm_user_groups",
|
||||
"realm_users",
|
||||
"realm_video_chat_provider",
|
||||
|
|
|
@ -591,6 +591,8 @@ class RealmAPITest(ZulipTestCase):
|
|||
create_stream_policy=[Realm.CREATE_STREAM_POLICY_ADMINS,
|
||||
Realm.CREATE_STREAM_POLICY_MEMBERS,
|
||||
Realm.CREATE_STREAM_POLICY_WAITING_PERIOD],
|
||||
user_group_edit_policy=[Realm.USER_GROUP_EDIT_POLICY_ADMINS,
|
||||
Realm.USER_GROUP_EDIT_POLICY_MEMBERS],
|
||||
invite_to_stream_policy=[Realm.INVITE_TO_STREAM_POLICY_ADMINS,
|
||||
Realm.INVITE_TO_STREAM_POLICY_MEMBERS,
|
||||
Realm.INVITE_TO_STREAM_POLICY_WAITING_PERIOD],
|
||||
|
|
|
@ -10,6 +10,7 @@ from zerver.lib.test_classes import ZulipTestCase
|
|||
from zerver.lib.test_helpers import (
|
||||
most_recent_usermessage,
|
||||
)
|
||||
from zerver.lib.actions import do_set_realm_property
|
||||
from zerver.lib.user_groups import (
|
||||
check_add_user_to_user_group,
|
||||
check_remove_user_from_user_group,
|
||||
|
@ -440,3 +441,81 @@ class UserGroupAPITestCase(ZulipTestCase):
|
|||
for user in other_users:
|
||||
um = most_recent_usermessage(user)
|
||||
self.assertFalse(um.flags.mentioned)
|
||||
|
||||
def test_only_admin_manage_groups(self) -> None:
|
||||
iago = self.example_user('iago')
|
||||
hamlet = self.example_user('hamlet')
|
||||
cordelia = self.example_user('cordelia')
|
||||
self.login(iago.email)
|
||||
do_set_realm_property(iago.realm, 'user_group_edit_policy',
|
||||
Realm.USER_GROUP_EDIT_POLICY_ADMINS)
|
||||
|
||||
params = {
|
||||
'name': 'support',
|
||||
'members': ujson.dumps([iago.id, hamlet.id]),
|
||||
'description': 'Support team',
|
||||
}
|
||||
|
||||
result = self.client_post('/json/user_groups/create', info=params)
|
||||
self.assert_json_success(result)
|
||||
user_group = UserGroup.objects.get(name='support')
|
||||
|
||||
# Test add member
|
||||
params = {'add': ujson.dumps([cordelia.id])}
|
||||
result = self.client_post('/json/user_groups/{}/members'.format(user_group.id),
|
||||
info=params)
|
||||
self.assert_json_success(result)
|
||||
|
||||
# Test remove member
|
||||
params = {'delete': ujson.dumps([cordelia.id])}
|
||||
result = self.client_post('/json/user_groups/{}/members'.format(user_group.id),
|
||||
info=params)
|
||||
self.assert_json_success(result)
|
||||
|
||||
# Test changing groups name
|
||||
params = {
|
||||
'name': 'help',
|
||||
'description': 'Troubleshooting',
|
||||
}
|
||||
result = self.client_patch('/json/user_groups/{}'.format(user_group.id), info=params)
|
||||
self.assert_json_success(result)
|
||||
|
||||
# Test delete a group
|
||||
result = self.client_delete('/json/user_groups/{}'.format(user_group.id))
|
||||
self.assert_json_success(result)
|
||||
|
||||
user_group = create_user_group(name='support',
|
||||
members=[hamlet, iago],
|
||||
realm=iago.realm,
|
||||
)
|
||||
|
||||
self.logout()
|
||||
|
||||
self.login(self.example_email("hamlet"))
|
||||
|
||||
# Test creating a group
|
||||
params = {
|
||||
'name': 'support2',
|
||||
'members': ujson.dumps([hamlet.id]),
|
||||
'description': 'Support team',
|
||||
}
|
||||
result = self.client_post('/json/user_groups/create', info=params)
|
||||
self.assert_json_error(result, "Must be an organization administrator")
|
||||
|
||||
# Test add member
|
||||
params = {'add': ujson.dumps([cordelia.id])}
|
||||
result = self.client_post('/json/user_groups/{}/members'.format(user_group.id),
|
||||
info=params)
|
||||
self.assert_json_error(result, "Must be an organization administrator")
|
||||
|
||||
# Test delete a group
|
||||
result = self.client_delete('/json/user_groups/{}'.format(user_group.id))
|
||||
self.assert_json_error(result, "Must be an organization administrator")
|
||||
|
||||
# Test changing groups name
|
||||
params = {
|
||||
'name': 'help',
|
||||
'description': 'Troubleshooting',
|
||||
}
|
||||
result = self.client_patch('/json/user_groups/{}'.format(user_group.id), info=params)
|
||||
self.assert_json_error(result, "Must be an organization administrator")
|
||||
|
|
|
@ -63,6 +63,7 @@ def update_realm(
|
|||
bot_creation_policy: Optional[int]=REQ(converter=to_not_negative_int_or_none, default=None),
|
||||
create_stream_policy: Optional[int]=REQ(validator=check_int, default=None),
|
||||
invite_to_stream_policy: Optional[int]=REQ(validator=check_int, default=None),
|
||||
user_group_edit_policy: Optional[int]=REQ(validator=check_int, default=None),
|
||||
email_address_visibility: Optional[int]=REQ(converter=to_not_negative_int_or_none, default=None),
|
||||
default_twenty_four_hour_time: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||
video_chat_provider: Optional[int]=REQ(validator=check_int, default=None),
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.utils.translation import ugettext as _
|
|||
|
||||
from typing import List
|
||||
|
||||
from zerver.decorator import require_member_or_admin
|
||||
from zerver.decorator import require_member_or_admin, require_user_group_edit_policy
|
||||
from zerver.lib.actions import check_add_user_group, do_update_user_group_name, \
|
||||
do_update_user_group_description, bulk_add_members_to_user_group, \
|
||||
remove_members_from_user_group, check_delete_user_group
|
||||
|
@ -18,6 +18,7 @@ from zerver.models import UserProfile
|
|||
from zerver.views.streams import compose_views, FuncKwargPair
|
||||
|
||||
@require_member_or_admin
|
||||
@require_user_group_edit_policy
|
||||
@has_request_variables
|
||||
def add_user_group(request: HttpRequest, user_profile: UserProfile,
|
||||
name: str=REQ(),
|
||||
|
@ -34,6 +35,7 @@ def get_user_group(request: HttpRequest, user_profile: UserProfile) -> HttpRespo
|
|||
return json_success({"user_groups": user_groups})
|
||||
|
||||
@require_member_or_admin
|
||||
@require_user_group_edit_policy
|
||||
@has_request_variables
|
||||
def edit_user_group(request: HttpRequest, user_profile: UserProfile,
|
||||
user_group_id: int=REQ(validator=check_int),
|
||||
|
@ -53,6 +55,7 @@ def edit_user_group(request: HttpRequest, user_profile: UserProfile,
|
|||
return json_success()
|
||||
|
||||
@require_member_or_admin
|
||||
@require_user_group_edit_policy
|
||||
@has_request_variables
|
||||
def delete_user_group(request: HttpRequest, user_profile: UserProfile,
|
||||
user_group_id: int=REQ(validator=check_int)) -> HttpResponse:
|
||||
|
@ -61,6 +64,7 @@ def delete_user_group(request: HttpRequest, user_profile: UserProfile,
|
|||
return json_success()
|
||||
|
||||
@require_member_or_admin
|
||||
@require_user_group_edit_policy
|
||||
@has_request_variables
|
||||
def update_user_group_backend(request: HttpRequest, user_profile: UserProfile,
|
||||
user_group_id: int=REQ(validator=check_int),
|
||||
|
|
Loading…
Reference in New Issue