Add setting to only allow admins create new streams.

Fixes: #691.

Thanks to Preston Hansen for work on this feature!
This commit is contained in:
Aristeidis Fkiaras
2016-05-12 11:28:00 +03:00
committed by Tim Abbott
parent e781136132
commit 3ee210d9e8
10 changed files with 151 additions and 12 deletions

View File

@@ -10,6 +10,45 @@ casper.then(function () {
casper.test.assertExists('#administration.tab-pane.active', 'Administration page is active'); casper.test.assertExists('#administration.tab-pane.active', 'Administration page is active');
}); });
// Test only admins may create streams Setting
casper.waitForSelector('input[type="checkbox"][id="id_realm_create_stream_by_admins_only"]', function () {
casper.click('input[type="checkbox"][id="id_realm_create_stream_by_admins_only"]');
casper.click('form.admin-realm-form input.btn');
// Test setting was activated
casper.waitUntilVisible('#admin-realm-create-stream-by-admins-only-status', function () {
casper.test.assertSelectorHasText('#admin-realm-create-stream-by-admins-only-status', 'Only Admins may now create new streams!');
casper.test.assertEval(function () {
return document.querySelector('input[type="checkbox"][id="id_realm_create_stream_by_admins_only"]').checked;
}, 'Only admins may create streams Setting activated');
});
});
casper.then(function () {
// Leave the page and return
casper.click('#settings-dropdown');
casper.click('a[href^="#subscriptions"]');
casper.click('#settings-dropdown');
casper.click('a[href^="#administration"]');
casper.waitForSelector('input[type="checkbox"][id="id_realm_create_stream_by_admins_only"]', function () {
// Test Setting was saved
casper.test.assertEval(function () {
return document.querySelector('input[type="checkbox"][id="id_realm_create_stream_by_admins_only"]').checked;
}, 'Only admins may create streams Setting saved');
// Deactivate setting
casper.click('input[type="checkbox"][id="id_realm_create_stream_by_admins_only"]');
casper.click('form.admin-realm-form input.btn');
casper.waitUntilVisible('#admin-realm-create-stream-by-admins-only-status', function () {
casper.test.assertSelectorHasText('#admin-realm-create-stream-by-admins-only-status', 'Any user may now create new streams!');
casper.test.assertEval(function () {
return !(document.querySelector('input[type="checkbox"][id="id_realm_create_stream_by_admins_only"]').checked);
}, 'Only admins may create streams Setting deactivated');
});
});
});
// Test user deactivation and reactivation // Test user deactivation and reactivation
casper.waitForSelector('.user_row[id="user_cordelia@zulip.com"]', function () { casper.waitForSelector('.user_row[id="user_cordelia@zulip.com"]', function () {
casper.test.assertSelectorHasText('.user_row[id="user_cordelia@zulip.com"]', 'Deactivate'); casper.test.assertSelectorHasText('.user_row[id="user_cordelia@zulip.com"]', 'Deactivate');

View File

@@ -90,7 +90,8 @@ exports.setup_page = function () {
domain: page_params.domain, domain: page_params.domain,
realm_restricted_to_domain: page_params.realm_restricted_to_domain, realm_restricted_to_domain: page_params.realm_restricted_to_domain,
realm_invite_required: page_params.realm_invite_required, realm_invite_required: page_params.realm_invite_required,
realm_invite_by_admins_only: page_params.realm_invite_by_admins_only realm_invite_by_admins_only: page_params.realm_invite_by_admins_only,
realm_create_stream_by_admins_only: page_params.realm_create_stream_by_admins_only
}; };
var admin_tab = templates.render('admin_tab', options); var admin_tab = templates.render('admin_tab', options);
$("#administration").html(admin_tab); $("#administration").html(admin_tab);
@@ -99,6 +100,7 @@ exports.setup_page = function () {
$("#admin-realm-restricted-to-domain-status").expectOne().hide(); $("#admin-realm-restricted-to-domain-status").expectOne().hide();
$("#admin-realm-invite-required-status").expectOne().hide(); $("#admin-realm-invite-required-status").expectOne().hide();
$("#admin-realm-invite-by-admins-only-status").expectOne().hide(); $("#admin-realm-invite-by-admins-only-status").expectOne().hide();
$("#admin-realm-create-stream-by-admins-only-status").expectOne().hide();
$("#admin-emoji-status").expectOne().hide(); $("#admin-emoji-status").expectOne().hide();
$("#admin-emoji-name-status").expectOne().hide(); $("#admin-emoji-name-status").expectOne().hide();
$("#admin-emoji-url-status").expectOne().hide(); $("#admin-emoji-url-status").expectOne().hide();
@@ -244,10 +246,12 @@ exports.setup_page = function () {
var restricted_to_domain_status = $("#admin-realm-restricted-to-domain-status").expectOne(); var restricted_to_domain_status = $("#admin-realm-restricted-to-domain-status").expectOne();
var invite_required_status = $("#admin-realm-invite-required-status").expectOne(); var invite_required_status = $("#admin-realm-invite-required-status").expectOne();
var invite_by_admins_only_status = $("#admin-realm-invite-by-admins-only-status").expectOne(); var invite_by_admins_only_status = $("#admin-realm-invite-by-admins-only-status").expectOne();
var create_stream_by_admins_only_status = $("#admin-realm-create-stream-by-admins-only-status").expectOne();
name_status.hide(); name_status.hide();
restricted_to_domain_status.hide(); restricted_to_domain_status.hide();
invite_required_status.hide(); invite_required_status.hide();
invite_by_admins_only_status.hide(); invite_by_admins_only_status.hide();
create_stream_by_admins_only_status.hide();
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -256,13 +260,15 @@ exports.setup_page = function () {
var new_restricted = $("#id_realm_restricted_to_domain").prop("checked"); var new_restricted = $("#id_realm_restricted_to_domain").prop("checked");
var new_invite = $("#id_realm_invite_required").prop("checked"); var new_invite = $("#id_realm_invite_required").prop("checked");
var new_invite_by_admins_only = $("#id_realm_invite_by_admins_only").prop("checked"); var new_invite_by_admins_only = $("#id_realm_invite_by_admins_only").prop("checked");
var new_create_stream_by_admins_only = $("#id_realm_create_stream_by_admins_only").prop("checked");
var url = "/json/realm"; var url = "/json/realm";
var data = { var data = {
name: JSON.stringify(new_name), name: JSON.stringify(new_name),
restricted_to_domain: JSON.stringify(new_restricted), restricted_to_domain: JSON.stringify(new_restricted),
invite_required: JSON.stringify(new_invite), invite_required: JSON.stringify(new_invite),
invite_by_admins_only: JSON.stringify(new_invite_by_admins_only) invite_by_admins_only: JSON.stringify(new_invite_by_admins_only),
create_stream_by_admins_only: JSON.stringify(new_create_stream_by_admins_only)
}; };
channel.patch({ channel.patch({
@@ -296,6 +302,14 @@ exports.setup_page = function () {
ui.report_success("Any user may now invite new users!", invite_by_admins_only_status); ui.report_success("Any user may now invite new users!", invite_by_admins_only_status);
} }
} }
if (data.create_stream_by_admins_only !== undefined) {
if (data.create_stream_by_admins_only) {
ui.report_success("Only Admins may now create new streams!", create_stream_by_admins_only_status);
}
else {
ui.report_success("Any user may now create new streams!", create_stream_by_admins_only_status);
}
}
}, },
error: function (xhr, error) { error: function (xhr, error) {
ui.report_error("Failed!", xhr, name_status); ui.report_error("Failed!", xhr, name_status);

View File

@@ -88,6 +88,11 @@ function get_events_success(events) {
page_params.realm_invite_required = event.value; page_params.realm_invite_required = event.value;
} else if (event.op === 'update' && event.property === 'invite_by_admins_only') { } else if (event.op === 'update' && event.property === 'invite_by_admins_only') {
page_params.realm_invite_by_admins_only = event.value; page_params.realm_invite_by_admins_only = event.value;
} else if (event.op === 'update' && event.property === 'create_stream_by_admins_only') {
page_params.realm_create_stream_by_admins_only = event.value;
if (!page_params.is_admin) {
page_params.can_create_streams = !page_params.realm_create_stream_by_admins_only;
}
} else if (event.op === 'update' && event.property === 'restricted_to_domain') { } else if (event.op === 'update' && event.property === 'restricted_to_domain') {
page_params.realm_restricted_to_domain = event.value; page_params.realm_restricted_to_domain = event.value;
} }

View File

@@ -31,6 +31,7 @@
<div class="alert" id="admin-realm-restricted-to-domain-status"></div> <div class="alert" id="admin-realm-restricted-to-domain-status"></div>
<div class="alert" id="admin-realm-invite-required-status"></div> <div class="alert" id="admin-realm-invite-required-status"></div>
<div class="alert" id="admin-realm-invite-by-admins-only-status"></div> <div class="alert" id="admin-realm-invite-by-admins-only-status"></div>
<div class="alert" id="admin-realm-create-stream-by-admins-only-status"></div>
<label for="realm_name" class="control-label">Your organization's name</label> <label for="realm_name" class="control-label">Your organization's name</label>
<div class="controls"> <div class="controls">
<input type="text" id="id_realm_name" name="realm_name" class="admin-realm-name" <input type="text" id="id_realm_name" name="realm_name" class="admin-realm-name"
@@ -55,6 +56,12 @@
<input type="checkbox" id="id_realm_invite_by_admins_only" name="realm_invite_by_admins_only" {{#unless realm_invite_required}}disabled="disabled"{{/unless}} {{#if realm_invite_by_admins_only}}checked="checked"{{/if}} /> <input type="checkbox" id="id_realm_invite_by_admins_only" name="realm_invite_by_admins_only" {{#unless realm_invite_required}}disabled="disabled"{{/unless}} {{#if realm_invite_by_admins_only}}checked="checked"{{/if}} />
</div> </div>
</div> </div>
<div class="control-group">
<label for="realm_create_stream_by_admins_only" id="id_realm_create_stream_by_admins_only_label" title="If checked, only administrators may create new streams." class="control-label">Only admins may create streams</label>
<div class="controls">
<input type="checkbox" id="id_realm_create_stream_by_admins_only" name="realm_create_stream_by_admins_only" {{#if realm_create_stream_by_admins_only}}checked="checked"{{/if}} />
</div>
</div>
<div class="controls organization-submission"> <div class="controls organization-submission">
<input type="submit" class="btn btn-big btn-primary" value="Save changes" /> <input type="submit" class="btn btn-big btn-primary" value="Save changes" />
</div> </div>

View File

@@ -368,6 +368,18 @@ def do_set_realm_invite_by_admins_only(realm, invite_by_admins_only):
send_event(event, active_user_ids(realm)) send_event(event, active_user_ids(realm))
return {} return {}
def do_set_realm_create_stream_by_admins_only(realm, create_stream_by_admins_only):
realm.create_stream_by_admins_only = create_stream_by_admins_only
realm.save(update_fields=['create_stream_by_admins_only'])
event = dict(
type="realm",
op="update",
property='create_stream_by_admins_only',
value=create_stream_by_admins_only,
)
send_event(event, active_user_ids(realm))
return {}
def do_deactivate_realm(realm): def do_deactivate_realm(realm):
""" """
Deactivate this realm. Do NOT deactivate the users -- we need to be able to Deactivate this realm. Do NOT deactivate the users -- we need to be able to
@@ -2500,6 +2512,7 @@ def fetch_initial_state_data(user_profile, event_types, queue_id):
state['realm_restricted_to_domain'] = user_profile.realm.restricted_to_domain state['realm_restricted_to_domain'] = user_profile.realm.restricted_to_domain
state['realm_invite_required'] = user_profile.realm.invite_required state['realm_invite_required'] = user_profile.realm.invite_required
state['realm_invite_by_admins_only'] = user_profile.realm.invite_by_admins_only state['realm_invite_by_admins_only'] = user_profile.realm.invite_by_admins_only
state['realm_create_stream_by_admins_only'] = user_profile.realm.create_stream_by_admins_only
if want('realm_domain'): if want('realm_domain'):
state['realm_domain'] = user_profile.realm.domain state['realm_domain'] = user_profile.realm.domain

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('zerver', '0015_attachment'),
]
operations = [
migrations.AddField(
model_name='realm',
name='create_stream_by_admins_only',
field=models.BooleanField(default=False),
),
]

View File

@@ -121,6 +121,7 @@ class Realm(models.Model):
restricted_to_domain = models.BooleanField(default=True) restricted_to_domain = models.BooleanField(default=True)
invite_required = models.BooleanField(default=False) invite_required = models.BooleanField(default=False)
invite_by_admins_only = models.BooleanField(default=False) invite_by_admins_only = models.BooleanField(default=False)
create_stream_by_admins_only = models.BooleanField(default=False)
mandatory_topics = models.BooleanField(default=False) mandatory_topics = models.BooleanField(default=False)
show_digest_email = models.BooleanField(default=True) show_digest_email = models.BooleanField(default=True)
name_changes_disabled = models.BooleanField(default=False) name_changes_disabled = models.BooleanField(default=False)
@@ -427,10 +428,10 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
return {row['id']: row['email'] for row in rows} return {row['id']: row['email'] for row in rows}
def can_create_streams(self): def can_create_streams(self):
# Long term this might be an actual DB attribute. Short term and long term, we want if self.is_realm_admin or not self.realm.create_stream_by_admins_only:
# this to be conceptually a property of the user, although it may actually be administered return True
# in a more complicated way, like certain realms may allow only admins to create streams. else:
return True return False
def receives_offline_notifications(user_profile): def receives_offline_notifications(user_profile):
return ((user_profile.enable_offline_email_notifications or return ((user_profile.enable_offline_email_notifications or

View File

@@ -29,6 +29,7 @@ from zerver.lib.actions import (
do_remove_subscription, do_remove_subscription,
do_rename_stream, do_rename_stream,
do_set_muted_topics, do_set_muted_topics,
do_set_realm_create_stream_by_admins_only,
do_set_realm_name, do_set_realm_name,
do_set_realm_restricted_to_domain, do_set_realm_restricted_to_domain,
do_set_realm_invite_required, do_set_realm_invite_required,
@@ -433,6 +434,20 @@ class EventsRegisterTest(AuthedTestCase):
error = schema_checker('events[0]', events[0]) error = schema_checker('events[0]', events[0])
self.assert_on_error(error) self.assert_on_error(error)
def test_change_realm_create_stream_by_admins_only(self):
schema_checker = check_dict([
('type', equals('realm')),
('op', equals('update')),
('property', equals('create_stream_by_admins_only')),
('value', check_bool),
])
# The first False is probably a noop, then we get transitions in both directions.
for create_stream_by_admins_only in (False, True, False):
events = self.do_test(lambda: do_set_realm_create_stream_by_admins_only(self.user_profile.realm,
create_stream_by_admins_only))
error = schema_checker('events[0]', events[0])
self.assert_on_error(error)
def test_change_is_admin(self): def test_change_is_admin(self):
schema_checker = check_dict([ schema_checker = check_dict([
('type', equals('realm_user')), ('type', equals('realm_user')),

View File

@@ -21,9 +21,9 @@ from zerver.models import (
) )
from zerver.lib.actions import ( from zerver.lib.actions import (
create_stream_if_needed, do_add_default_stream, do_add_subscription, create_stream_if_needed, do_add_default_stream, do_add_subscription, do_change_is_admin,
do_change_is_admin, do_create_realm, do_remove_default_stream, gather_subscriptions, do_create_realm, do_remove_default_stream, do_set_realm_create_stream_by_admins_only,
get_default_streams_for_realm, get_realm, get_stream, gather_subscriptions, get_default_streams_for_realm, get_realm, get_stream,
get_user_profile_by_email, set_default_streams, get_user_profile_by_email, set_default_streams,
) )
@@ -379,6 +379,27 @@ class StreamAdminTest(AuthedTestCase):
self.assert_json_error( self.assert_json_error(
result, "Cannot administer invite-only streams this way") result, "Cannot administer invite-only streams this way")
def test_create_stream_by_admins_only_setting(self):
"""
When realm.create_stream_by_admins_only setting is active,
non admin users shouldn't be able to create new streams.
"""
email = 'hamlet@zulip.com'
self.login(email)
user_profile = get_user_profile_by_email(email)
do_change_is_admin(user_profile, False)
do_set_realm_create_stream_by_admins_only(user_profile.realm, True)
stream_name = ['adminsonlysetting']
result = self.common_subscribe_to_streams(
email,
stream_name
)
self.assert_json_error(result, 'User cannot create streams.')
# Change setting back to default
do_set_realm_create_stream_by_admins_only(user_profile.realm, False)
def test_remove_already_not_subbed(self): def test_remove_already_not_subbed(self):
""" """
Trying to unsubscribe someone who already isn't subscribed to a stream Trying to unsubscribe someone who already isn't subscribed to a stream
@@ -1517,4 +1538,3 @@ class GetSubscribersTest(AuthedTestCase):
result = self.make_subscriber_request(stream_name, email=other_email) result = self.make_subscriber_request(stream_name, email=other_email)
self.assert_json_error(result, self.assert_json_error(result,
"Unable to retrieve subscribers for invite-only stream") "Unable to retrieve subscribers for invite-only stream")

View File

@@ -28,7 +28,8 @@ from zerver.lib.actions import do_change_password, do_change_full_name, do_chang
internal_send_message, update_user_presence, do_events_register, \ internal_send_message, update_user_presence, do_events_register, \
get_status_dict, do_change_enable_offline_email_notifications, \ get_status_dict, do_change_enable_offline_email_notifications, \
do_change_enable_digest_emails, do_set_realm_name, do_set_realm_restricted_to_domain, \ do_change_enable_digest_emails, do_set_realm_name, do_set_realm_restricted_to_domain, \
do_set_realm_invite_required, do_set_realm_invite_by_admins_only, get_default_subs, \ do_set_realm_invite_required, do_set_realm_invite_by_admins_only, \
do_set_realm_create_stream_by_admins_only, get_default_subs, \
user_email_is_unique, do_invite_users, do_refer_friend, compute_mit_user_fullname, \ user_email_is_unique, do_invite_users, do_refer_friend, compute_mit_user_fullname, \
do_set_muted_topics, clear_followup_emails_queue, do_update_pointer, realm_user_count do_set_muted_topics, clear_followup_emails_queue, do_update_pointer, realm_user_count
from zerver.lib.push_notifications import num_push_devices_for_user from zerver.lib.push_notifications import num_push_devices_for_user
@@ -798,6 +799,7 @@ def home(request):
realm_name = register_ret['realm_name'], realm_name = register_ret['realm_name'],
realm_invite_required = register_ret['realm_invite_required'], realm_invite_required = register_ret['realm_invite_required'],
realm_invite_by_admins_only = register_ret['realm_invite_by_admins_only'], realm_invite_by_admins_only = register_ret['realm_invite_by_admins_only'],
realm_create_stream_by_admins_only = register_ret['realm_create_stream_by_admins_only'],
realm_restricted_to_domain = register_ret['realm_restricted_to_domain'], realm_restricted_to_domain = register_ret['realm_restricted_to_domain'],
enter_sends = user_profile.enter_sends, enter_sends = user_profile.enter_sends,
left_side_userlist = register_ret['left_side_userlist'], left_side_userlist = register_ret['left_side_userlist'],
@@ -1012,7 +1014,8 @@ def get_profile_backend(request, user_profile):
def update_realm(request, user_profile, name=REQ(validator=check_string, default=None), def update_realm(request, user_profile, name=REQ(validator=check_string, default=None),
restricted_to_domain=REQ(validator=check_bool, default=None), restricted_to_domain=REQ(validator=check_bool, default=None),
invite_required=REQ(validator=check_bool, default=None), invite_required=REQ(validator=check_bool, default=None),
invite_by_admins_only=REQ(validator=check_bool, default=None)): invite_by_admins_only=REQ(validator=check_bool, default=None),
create_stream_by_admins_only=REQ(validator=check_bool, default=None)):
realm = user_profile.realm realm = user_profile.realm
data = {} data = {}
if name is not None and realm.name != name: if name is not None and realm.name != name:
@@ -1027,6 +1030,9 @@ def update_realm(request, user_profile, name=REQ(validator=check_string, default
if invite_by_admins_only is not None and realm.invite_by_admins_only != invite_by_admins_only: if invite_by_admins_only is not None and realm.invite_by_admins_only != invite_by_admins_only:
do_set_realm_invite_by_admins_only(realm, invite_by_admins_only) do_set_realm_invite_by_admins_only(realm, invite_by_admins_only)
data['invite_by_admins_only'] = invite_by_admins_only data['invite_by_admins_only'] = invite_by_admins_only
if create_stream_by_admins_only is not None and realm.create_stream_by_admins_only != create_stream_by_admins_only:
do_set_realm_create_stream_by_admins_only(realm, create_stream_by_admins_only)
data['create_stream_by_admins_only'] = create_stream_by_admins_only
return json_success(data) return json_success(data)
@authenticated_json_post_view @authenticated_json_post_view