mirror of https://github.com/zulip/zulip.git
org settings: Add setting to prevent users from adding bots.
Fixes: #7908.
This commit is contained in:
parent
7c830c5767
commit
777b6de689
|
@ -54,6 +54,10 @@ set_global('message_edit', {
|
||||||
update_message_topic_editing_pencil: noop,
|
update_message_topic_editing_pencil: noop,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
set_global('settings_bots', {
|
||||||
|
update_bot_permissions_ui: noop,
|
||||||
|
});
|
||||||
|
|
||||||
// page_params is highly coupled to dispatching now
|
// page_params is highly coupled to dispatching now
|
||||||
set_global('page_params', {test_suite: false});
|
set_global('page_params', {test_suite: false});
|
||||||
var page_params = global.page_params;
|
var page_params = global.page_params;
|
||||||
|
@ -195,11 +199,11 @@ var event_fixtures = {
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
realm__update__create_generic_bot_by_admins_only: {
|
realm__update__bot_creation_policy: {
|
||||||
type: 'realm',
|
type: 'realm',
|
||||||
op: 'update',
|
op: 'update',
|
||||||
property: 'create_generic_bot_by_admins_only',
|
property: 'bot_creation_policy',
|
||||||
value: false,
|
value: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
realm__update_dict__default: {
|
realm__update_dict__default: {
|
||||||
|
|
|
@ -41,8 +41,6 @@ function _setup_page() {
|
||||||
realm_email_changes_disabled: page_params.realm_email_changes_disabled,
|
realm_email_changes_disabled: page_params.realm_email_changes_disabled,
|
||||||
realm_add_emoji_by_admins_only: page_params.realm_add_emoji_by_admins_only,
|
realm_add_emoji_by_admins_only: page_params.realm_add_emoji_by_admins_only,
|
||||||
can_admin_emojis: page_params.is_admin || !page_params.realm_add_emoji_by_admins_only,
|
can_admin_emojis: page_params.is_admin || !page_params.realm_add_emoji_by_admins_only,
|
||||||
realm_create_generic_bot_by_admins_only:
|
|
||||||
page_params.realm_create_generic_bot_by_admins_only,
|
|
||||||
realm_allow_message_deleting: page_params.realm_allow_message_deleting,
|
realm_allow_message_deleting: page_params.realm_allow_message_deleting,
|
||||||
realm_allow_message_editing: page_params.realm_allow_message_editing,
|
realm_allow_message_editing: page_params.realm_allow_message_editing,
|
||||||
realm_message_content_edit_limit_minutes:
|
realm_message_content_edit_limit_minutes:
|
||||||
|
@ -62,10 +60,14 @@ function _setup_page() {
|
||||||
realm_send_welcome_emails: page_params.realm_send_welcome_emails,
|
realm_send_welcome_emails: page_params.realm_send_welcome_emails,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
options.bot_creation_policy_values = settings_bots.bot_creation_policy_values;
|
||||||
var admin_tab = templates.render('admin_tab', options);
|
var admin_tab = templates.render('admin_tab', options);
|
||||||
$("#settings_content .organization-box").html(admin_tab);
|
$("#settings_content .organization-box").html(admin_tab);
|
||||||
$("#settings_content .alert").removeClass("show");
|
$("#settings_content .alert").removeClass("show");
|
||||||
|
|
||||||
|
settings_bots.update_bot_settings_tip();
|
||||||
|
$("#id_realm_bot_creation_policy").val(page_params.realm_bot_creation_policy);
|
||||||
|
|
||||||
// Since we just swapped in a whole new page, we need to
|
// Since we just swapped in a whole new page, we need to
|
||||||
// tell admin_sections nothing is loaded.
|
// tell admin_sections nothing is loaded.
|
||||||
admin_sections.reset_sections();
|
admin_sections.reset_sections();
|
||||||
|
|
|
@ -56,7 +56,7 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) {
|
||||||
allow_edit_history: noop,
|
allow_edit_history: noop,
|
||||||
allow_message_deleting: noop,
|
allow_message_deleting: noop,
|
||||||
allow_message_editing: noop,
|
allow_message_editing: noop,
|
||||||
create_generic_bot_by_admins_only: noop,
|
bot_creation_policy: settings_bots.update_bot_permissions_ui,
|
||||||
create_stream_by_admins_only: noop,
|
create_stream_by_admins_only: noop,
|
||||||
default_language: settings_org.reset_realm_default_language,
|
default_language: settings_org.reset_realm_default_language,
|
||||||
description: settings_org.update_realm_description,
|
description: settings_org.update_realm_description,
|
||||||
|
|
|
@ -99,12 +99,17 @@ function _setup_page() {
|
||||||
return tab;
|
return tab;
|
||||||
}());
|
}());
|
||||||
|
|
||||||
|
settings_bots.setup_bot_creation_policy_values();
|
||||||
|
|
||||||
var settings_tab = templates.render('settings_tab', {
|
var settings_tab = templates.render('settings_tab', {
|
||||||
full_name: people.my_full_name(),
|
full_name: people.my_full_name(),
|
||||||
page_params: page_params,
|
page_params: page_params,
|
||||||
zuliprc: 'zuliprc',
|
zuliprc: 'zuliprc',
|
||||||
flaskbotrc: 'flaskbotrc',
|
flaskbotrc: 'flaskbotrc',
|
||||||
timezones: moment.tz.names(),
|
timezones: moment.tz.names(),
|
||||||
|
admin_only_bot_creation: page_params.is_admin ||
|
||||||
|
page_params.realm_bot_creation_policy !==
|
||||||
|
settings_bots.bot_creation_policy_values.admins_only.code,
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".settings-box").html(settings_tab);
|
$(".settings-box").html(settings_tab);
|
||||||
|
|
|
@ -83,6 +83,57 @@ exports.generate_flaskbotrc_content = function (email, api_key) {
|
||||||
"\n";
|
"\n";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.bot_creation_policy_values = {};
|
||||||
|
|
||||||
|
exports.setup_bot_creation_policy_values = function () {
|
||||||
|
exports.bot_creation_policy_values = {
|
||||||
|
everyone: {
|
||||||
|
code: 1,
|
||||||
|
description: i18n.t("Everyone"),
|
||||||
|
},
|
||||||
|
admins_only: {
|
||||||
|
code: 3,
|
||||||
|
description: i18n.t("Admins only"),
|
||||||
|
},
|
||||||
|
restricted: {
|
||||||
|
code: 2,
|
||||||
|
description: i18n.t("Everyone, but only admins can add generic bots"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.update_bot_settings_tip = function () {
|
||||||
|
var permission_type = exports.bot_creation_policy_values;
|
||||||
|
var current_permission = page_params.realm_bot_creation_policy;
|
||||||
|
var tip_text;
|
||||||
|
if (current_permission === permission_type.admins_only.code) {
|
||||||
|
tip_text = i18n.t("Only organization administrators can add bots to this organization");
|
||||||
|
} else if (current_permission === permission_type.restricted.code) {
|
||||||
|
tip_text = i18n.t("Only orgainzation administrators can add generic bots");
|
||||||
|
} else {
|
||||||
|
tip_text = i18n.t("Anyone in this organization can add bots");
|
||||||
|
}
|
||||||
|
$(".bot-settings-tip").text(tip_text);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.update_bot_permissions_ui = function () {
|
||||||
|
exports.update_bot_settings_tip();
|
||||||
|
$('#bot_table_error').hide();
|
||||||
|
$("#id_realm_bot_creation_policy").val(page_params.realm_bot_creation_policy);
|
||||||
|
if (page_params.realm_bot_creation_policy ===
|
||||||
|
exports.bot_creation_policy_values.admins_only.code &&
|
||||||
|
!page_params.is_admin) {
|
||||||
|
$('#create_bot_form').hide();
|
||||||
|
$('.add-a-new-bot-tab').hide();
|
||||||
|
$('.account-api-key-section').hide();
|
||||||
|
$("#bots_lists_navbar .active-bots-tab").click();
|
||||||
|
} else {
|
||||||
|
$('#create_bot_form').show();
|
||||||
|
$('.add-a-new-bot-tab').show();
|
||||||
|
$('.account-api-key-section').show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
exports.set_up = function () {
|
exports.set_up = function () {
|
||||||
$('#payload_url_inputbox').hide();
|
$('#payload_url_inputbox').hide();
|
||||||
$('#create_payload_url').val('');
|
$('#create_payload_url').val('');
|
||||||
|
@ -393,6 +444,7 @@ exports.set_up = function () {
|
||||||
$("#add-a-new-bot-form").hide();
|
$("#add-a-new-bot-form").hide();
|
||||||
$("#active_bots_list").show();
|
$("#active_bots_list").show();
|
||||||
$("#inactive_bots_list").hide();
|
$("#inactive_bots_list").hide();
|
||||||
|
$('#bot_table_error').hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#bots_lists_navbar .inactive-bots-tab").click(function (e) {
|
$("#bots_lists_navbar .inactive-bots-tab").click(function (e) {
|
||||||
|
|
|
@ -309,10 +309,9 @@ function _set_up() {
|
||||||
checked_msg: i18n.t("Only administrators may now add new emoji!"),
|
checked_msg: i18n.t("Only administrators may now add new emoji!"),
|
||||||
unchecked_msg: i18n.t("Any user may now add new emoji!"),
|
unchecked_msg: i18n.t("Any user may now add new emoji!"),
|
||||||
},
|
},
|
||||||
create_generic_bot_by_admins_only: {
|
bot_creation_policy: {
|
||||||
type: 'bool',
|
type: 'integer',
|
||||||
checked_msg: i18n.t("Only administrators may now create new generic bots!"),
|
msg: i18n.t("Permissions changed"),
|
||||||
unchecked_msg: i18n.t("Any user may now create new generic bots!"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -361,7 +360,7 @@ function _set_up() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setting_type === 'text') {
|
if (setting_type === 'text' || setting_type === 'integer') {
|
||||||
ui_report.success(field_info.msg,
|
ui_report.success(field_info.msg,
|
||||||
property_type_status_element(key));
|
property_type_status_element(key));
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -353,7 +353,7 @@ input[type=checkbox] + .inline-block {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.organization-submission {
|
.org-settings-form .organization-submission {
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1192,7 +1192,8 @@ input[type=checkbox].inline-block {
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#id_realm_create_stream_permission {
|
#id_realm_create_stream_permission,
|
||||||
|
#id_realm_bot_creation_policy {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<div id="admin-bot-list" class="settings-section" data-name="bot-list-admin">
|
<div id="admin-bot-list" class="settings-section" data-name="bot-list-admin">
|
||||||
|
<div class="tip bot-settings-tip"></div>
|
||||||
<input type="text" class="search" placeholder="{{t 'Filter bots' }}" aria-label="{{t 'Filter bots' }}"/>
|
<input type="text" class="search" placeholder="{{t 'Filter bots' }}" aria-label="{{t 'Filter bots' }}"/>
|
||||||
<div class="clear-float"></div>
|
<div class="clear-float"></div>
|
||||||
<table class="table table-condensed table-striped wrapped-table">
|
<table class="table table-condensed table-striped wrapped-table">
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<div class="tip">
|
<div class="tip">
|
||||||
{{#tr this}}Looking for our <a href="/integrations" target="_blank">Integrations</a> or <a href="/api" target="_blank">API</a> documentation?{{/tr}}
|
{{#tr this}}Looking for our <a href="/integrations" target="_blank">Integrations</a> or <a href="/api" target="_blank">API</a> documentation?{{/tr}}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tip bot-settings-tip"></div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span>{{t 'Download config of all active outgoing webhook bots in Zulip Botserver format.' }}</span>
|
<span>{{t 'Download config of all active outgoing webhook bots in Zulip Botserver format.' }}</span>
|
||||||
|
@ -14,7 +15,7 @@
|
||||||
<ul class="nav nav-tabs nav-justified" id="bots_lists_navbar">
|
<ul class="nav nav-tabs nav-justified" id="bots_lists_navbar">
|
||||||
<li class="active active-bots-tab"><a>{{t "Active bots" }}</a></li>
|
<li class="active active-bots-tab"><a>{{t "Active bots" }}</a></li>
|
||||||
<li class="inactive-bots-tab"><a>{{t "Inactive bots" }}</a></li>
|
<li class="inactive-bots-tab"><a>{{t "Inactive bots" }}</a></li>
|
||||||
<li class="add-a-new-bot-tab"><a>{{t "Add a new bot" }}</a></li>
|
<li class="add-a-new-bot-tab {{#unless admin_only_bot_creation}}hide{{/unless}}"><a>{{t "Add a new bot" }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ol class="bots_list required-text" id="active_bots_list" data-empty="{{t 'You have no active bots.' }}">
|
<ol class="bots_list required-text" id="active_bots_list" data-empty="{{t 'You have no active bots.' }}">
|
||||||
|
@ -26,7 +27,8 @@
|
||||||
<div id="bot_table_error" class="alert alert-error hide"></div>
|
<div id="bot_table_error" class="alert alert-error hide"></div>
|
||||||
|
|
||||||
<div id="add-a-new-bot-form">
|
<div id="add-a-new-bot-form">
|
||||||
<form id="create_bot_form" class="form-horizontal no-padding">
|
<form id="create_bot_form"
|
||||||
|
class="form-horizontal no-padding {{#unless admin_only_bot_creation}}hide{{/unless}}">
|
||||||
<div class="new-bot-form">
|
<div class="new-bot-form">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="bot_type">
|
<label for="bot_type">
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="alert" id="admin-realm-name-changes-disabled-status"></div>
|
<div class="alert" id="admin-realm-name-changes-disabled-status"></div>
|
||||||
<div class="alert" id="admin-realm-add-emoji-by-admins-only-status"></div>
|
<div class="alert" id="admin-realm-add-emoji-by-admins-only-status"></div>
|
||||||
<div class="alert" id="admin-realm-create-stream-by-admins-only-status"></div>
|
<div class="alert" id="admin-realm-create-stream-by-admins-only-status"></div>
|
||||||
<div class="alert" id="admin-realm-create-generic-bot-by-admins-only-status"></div>
|
<div class="alert" id="admin-realm-bot-creation-policy-status"></div>
|
||||||
|
|
||||||
<div class="inline-block organization-permissions-parent">
|
<div class="inline-block organization-permissions-parent">
|
||||||
<div class="input-group admin-restricted-to-domain">
|
<div class="input-group admin-restricted-to-domain">
|
||||||
|
@ -74,8 +74,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>{{t "Other permissions" }}</h3>
|
<h3>{{t "Other permissions" }}</h3>
|
||||||
<div class="organization-permissions-parent">
|
<div class="m-10 inline-block organization-permissions-parent">
|
||||||
<div class="inline-block create-stream-dropdown">
|
<div class="input-group create-stream-dropdown">
|
||||||
<label for="realm_create_stream_permission" class="dropdown-title">{{t "Who can create streams" }}</label>
|
<label for="realm_create_stream_permission" class="dropdown-title">{{t "Who can create streams" }}</label>
|
||||||
<select name="realm_create_stream_permission" id="id_realm_create_stream_permission">
|
<select name="realm_create_stream_permission" id="id_realm_create_stream_permission">
|
||||||
<option value="by_anyone">{{t "Everyone" }}</option>
|
<option value="by_anyone">{{t "Everyone" }}</option>
|
||||||
|
@ -91,8 +91,16 @@
|
||||||
class="admin-realm-message-content-edit-limit-minutes"
|
class="admin-realm-message-content-edit-limit-minutes"
|
||||||
value="{{ realm_waiting_period_threshold }}"/>
|
value="{{ realm_waiting_period_threshold }}"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="realm_bot_creation_policy">{{t "Who can add bots" }}</label>
|
||||||
|
<select name="realm_bot_creation_policy" id="id_realm_bot_creation_policy">
|
||||||
|
{{#each bot_creation_policy_values}}
|
||||||
|
<option value='{{this.code}}'>{{this.description}}</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-t-10 inline-block organization-permissions-parent">
|
|
||||||
<div class="inline-block">
|
<div class="inline-block">
|
||||||
<label for="realm_add_emoji_by_admins_only" class="dropdown-title">{{t "Who can add emoji" }}</label>
|
<label for="realm_add_emoji_by_admins_only" class="dropdown-title">{{t "Who can add emoji" }}</label>
|
||||||
<select name="realm_add_emoji_by_admins_only" id="id_realm_add_emoji_by_admins_only">
|
<select name="realm_add_emoji_by_admins_only" id="id_realm_add_emoji_by_admins_only">
|
||||||
|
@ -102,21 +110,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>{{t "Interactive bots" }}</h3>
|
|
||||||
<div class="inline-block organization-permissions-parent">
|
|
||||||
<div class="input-group">
|
|
||||||
<label class="checkbox">
|
|
||||||
<input type="checkbox" id="id_realm_create_generic_bot_by_admins_only" name="realm_create_generic_bot_by_admins_only"
|
|
||||||
{{#if realm_create_generic_bot_by_admins_only}}checked="checked"{{/if}} />
|
|
||||||
<span></span>
|
|
||||||
</label>
|
|
||||||
<label for="id_realm_create_generic_bot_by_admins_only" id="id_realm_create_generic_bot_by_admins_only_label" class="inline-block"
|
|
||||||
title="{{t 'If checked, only administrators may create new generic bots.' }}">
|
|
||||||
{{t "Prevent users from creating generic bots" }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if is_admin }}
|
{{#if is_admin }}
|
||||||
<div class="input-group organization-submission">
|
<div class="input-group organization-submission">
|
||||||
<button type="submit" class="button rounded sea-green">
|
<button type="submit" class="button rounded sea-green">
|
||||||
|
|
|
@ -40,6 +40,19 @@ def check_valid_bot_config(service_name: str, config_data: Dict[str, str]) -> No
|
||||||
# error message.
|
# error message.
|
||||||
raise JsonableError(_("Invalid configuration data!"))
|
raise JsonableError(_("Invalid configuration data!"))
|
||||||
|
|
||||||
|
def check_bot_creation_policy(user_profile: UserProfile, bot_type: int) -> None:
|
||||||
|
# Realm administrators can always add bot
|
||||||
|
if user_profile.is_realm_admin:
|
||||||
|
return
|
||||||
|
|
||||||
|
if user_profile.realm.bot_creation_policy == Realm.BOT_CREATION_EVERYONE:
|
||||||
|
return
|
||||||
|
if user_profile.realm.bot_creation_policy == Realm.BOT_CREATION_ADMINS_ONLY:
|
||||||
|
raise JsonableError(_("Must be an organization administrator"))
|
||||||
|
if user_profile.realm.bot_creation_policy == Realm.BOT_CREATION_LIMIT_GENERIC_BOTS and \
|
||||||
|
bot_type == UserProfile.DEFAULT_BOT:
|
||||||
|
raise JsonableError(_("Must be an organization administrator"))
|
||||||
|
|
||||||
def check_valid_bot_type(user_profile: UserProfile, bot_type: int) -> None:
|
def check_valid_bot_type(user_profile: UserProfile, bot_type: int) -> None:
|
||||||
if bot_type not in user_profile.allowed_bot_types:
|
if bot_type not in user_profile.allowed_bot_types:
|
||||||
raise JsonableError(_('Invalid bot type'))
|
raise JsonableError(_('Invalid bot type'))
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.6 on 2018-03-09 18:00
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
|
||||||
|
from django.db.migrations.state import StateApps
|
||||||
|
|
||||||
|
def set_initial_value_for_bot_creation_policy(apps: StateApps, schema_editor: DatabaseSchemaEditor) -> None:
|
||||||
|
Realm = apps.get_model("zerver", "Realm")
|
||||||
|
for realm in Realm.objects.all():
|
||||||
|
if realm.create_generic_bot_by_admins_only:
|
||||||
|
realm.bot_creation_policy = 2 # BOT_CREATION_LIMIT_GENERIC_BOTS
|
||||||
|
else:
|
||||||
|
realm.bot_creation_policy = 1 # BOT_CREATION_EVERYONE
|
||||||
|
realm.save(update_fields=["bot_creation_policy"])
|
||||||
|
|
||||||
|
def reverse_code(apps: StateApps, schema_editor: DatabaseSchemaEditor) -> None:
|
||||||
|
Realm = apps.get_model("zerver", "Realm")
|
||||||
|
for realm in Realm.objects.all():
|
||||||
|
if realm.bot_creation_policy == 1: # BOT_CREATION_EVERYONE
|
||||||
|
realm.create_generic_bot_by_admins_only = False
|
||||||
|
else:
|
||||||
|
realm.create_generic_bot_by_admins_only = True
|
||||||
|
realm.save(update_fields=["create_generic_bot_by_admins_only"])
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('zerver', '0142_userprofile_translate_emoticons'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='realm',
|
||||||
|
name='bot_creation_policy',
|
||||||
|
field=models.PositiveSmallIntegerField(default=1),
|
||||||
|
),
|
||||||
|
migrations.RunPython(set_initial_value_for_bot_creation_policy,
|
||||||
|
reverse_code=reverse_code),
|
||||||
|
]
|
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.6 on 2018-03-09 21:21
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('zerver', '0143_realm_bot_creation_policy'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='realm',
|
||||||
|
name='create_generic_bot_by_admins_only',
|
||||||
|
),
|
||||||
|
]
|
|
@ -144,7 +144,6 @@ class Realm(models.Model):
|
||||||
inline_url_embed_preview = models.BooleanField(default=True) # type: bool
|
inline_url_embed_preview = models.BooleanField(default=True) # type: bool
|
||||||
create_stream_by_admins_only = models.BooleanField(default=False) # type: bool
|
create_stream_by_admins_only = models.BooleanField(default=False) # type: bool
|
||||||
add_emoji_by_admins_only = models.BooleanField(default=False) # type: bool
|
add_emoji_by_admins_only = models.BooleanField(default=False) # type: bool
|
||||||
create_generic_bot_by_admins_only = models.BooleanField(default=False) # type: bool
|
|
||||||
mandatory_topics = models.BooleanField(default=False) # type: bool
|
mandatory_topics = models.BooleanField(default=False) # type: bool
|
||||||
show_digest_email = models.BooleanField(default=True) # type: bool
|
show_digest_email = models.BooleanField(default=True) # type: bool
|
||||||
name_changes_disabled = models.BooleanField(default=False) # type: bool
|
name_changes_disabled = models.BooleanField(default=False) # type: bool
|
||||||
|
@ -164,6 +163,13 @@ class Realm(models.Model):
|
||||||
COMMUNITY = 2
|
COMMUNITY = 2
|
||||||
org_type = models.PositiveSmallIntegerField(default=CORPORATE) # type: int
|
org_type = models.PositiveSmallIntegerField(default=CORPORATE) # type: int
|
||||||
|
|
||||||
|
# This value is also being used in static/js/settings_bots.bot_creation_policy_values.
|
||||||
|
# On updating it here, update it there as well.
|
||||||
|
BOT_CREATION_EVERYONE = 1
|
||||||
|
BOT_CREATION_LIMIT_GENERIC_BOTS = 2
|
||||||
|
BOT_CREATION_ADMINS_ONLY = 3
|
||||||
|
bot_creation_policy = models.PositiveSmallIntegerField(default=BOT_CREATION_EVERYONE) # type: int
|
||||||
|
|
||||||
date_created = models.DateTimeField(default=timezone_now) # type: datetime.datetime
|
date_created = models.DateTimeField(default=timezone_now) # type: datetime.datetime
|
||||||
notifications_stream = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE) # type: Optional[Stream]
|
notifications_stream = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE) # type: Optional[Stream]
|
||||||
signup_notifications_stream = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE) # type: Optional[Stream]
|
signup_notifications_stream = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE) # type: Optional[Stream]
|
||||||
|
@ -183,7 +189,7 @@ class Realm(models.Model):
|
||||||
add_emoji_by_admins_only=bool,
|
add_emoji_by_admins_only=bool,
|
||||||
allow_edit_history=bool,
|
allow_edit_history=bool,
|
||||||
allow_message_deleting=bool,
|
allow_message_deleting=bool,
|
||||||
create_generic_bot_by_admins_only=bool,
|
bot_creation_policy=int,
|
||||||
create_stream_by_admins_only=bool,
|
create_stream_by_admins_only=bool,
|
||||||
default_language=Text,
|
default_language=Text,
|
||||||
description=Text,
|
description=Text,
|
||||||
|
@ -214,6 +220,12 @@ class Realm(models.Model):
|
||||||
DEFAULT_NOTIFICATION_STREAM_NAME = u'announce'
|
DEFAULT_NOTIFICATION_STREAM_NAME = u'announce'
|
||||||
INITIAL_PRIVATE_STREAM_NAME = u'core team'
|
INITIAL_PRIVATE_STREAM_NAME = u'core team'
|
||||||
|
|
||||||
|
BOT_CREATION_POLICY_TYPES = [
|
||||||
|
BOT_CREATION_EVERYONE,
|
||||||
|
BOT_CREATION_LIMIT_GENERIC_BOTS,
|
||||||
|
BOT_CREATION_ADMINS_ONLY,
|
||||||
|
]
|
||||||
|
|
||||||
def authentication_methods_dict(self) -> Dict[Text, bool]:
|
def authentication_methods_dict(self) -> Dict[Text, bool]:
|
||||||
"""Returns the a mapping from authentication flags to their status,
|
"""Returns the a mapping from authentication flags to their status,
|
||||||
showing only those authentication flags that are supported on
|
showing only those authentication flags that are supported on
|
||||||
|
@ -724,7 +736,8 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
|
||||||
def allowed_bot_types(self):
|
def allowed_bot_types(self):
|
||||||
# type: () -> List[int]
|
# type: () -> List[int]
|
||||||
allowed_bot_types = []
|
allowed_bot_types = []
|
||||||
if self.is_realm_admin or not self.realm.create_generic_bot_by_admins_only:
|
if self.is_realm_admin or \
|
||||||
|
not self.realm.bot_creation_policy == Realm.BOT_CREATION_LIMIT_GENERIC_BOTS:
|
||||||
allowed_bot_types.append(UserProfile.DEFAULT_BOT)
|
allowed_bot_types.append(UserProfile.DEFAULT_BOT)
|
||||||
allowed_bot_types += [
|
allowed_bot_types += [
|
||||||
UserProfile.INCOMING_WEBHOOK_BOT,
|
UserProfile.INCOMING_WEBHOOK_BOT,
|
||||||
|
|
|
@ -570,7 +570,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
|
||||||
self.assert_num_bots_equal(0)
|
self.assert_num_bots_equal(0)
|
||||||
self.assert_json_error(result, 'Invalid bot type')
|
self.assert_json_error(result, 'Invalid bot type')
|
||||||
|
|
||||||
def test_add_bot_with_bot_type_not_allowed(self) -> None:
|
def test_no_generic_bots_allowed_for_non_admins(self) -> None:
|
||||||
bot_info = {
|
bot_info = {
|
||||||
'full_name': 'The Bot of Hamlet',
|
'full_name': 'The Bot of Hamlet',
|
||||||
'short_name': 'hambot',
|
'short_name': 'hambot',
|
||||||
|
@ -578,15 +578,15 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
|
||||||
}
|
}
|
||||||
bot_email = 'hambot-bot@zulip.testserver'
|
bot_email = 'hambot-bot@zulip.testserver'
|
||||||
bot_realm = get_realm('zulip')
|
bot_realm = get_realm('zulip')
|
||||||
bot_realm.create_generic_bot_by_admins_only = True
|
bot_realm.bot_creation_policy = Realm.BOT_CREATION_LIMIT_GENERIC_BOTS
|
||||||
bot_realm.save(update_fields=['create_generic_bot_by_admins_only'])
|
bot_realm.save(update_fields=['bot_creation_policy'])
|
||||||
|
|
||||||
# A regular user cannot create a generic bot
|
# A regular user cannot create a generic bot
|
||||||
self.login(self.example_email('hamlet'))
|
self.login(self.example_email('hamlet'))
|
||||||
self.assert_num_bots_equal(0)
|
self.assert_num_bots_equal(0)
|
||||||
result = self.client_post("/json/bots", bot_info)
|
result = self.client_post("/json/bots", bot_info)
|
||||||
self.assert_num_bots_equal(0)
|
self.assert_num_bots_equal(0)
|
||||||
self.assert_json_error(result, 'Invalid bot type')
|
self.assert_json_error(result, 'Must be an organization administrator')
|
||||||
|
|
||||||
# But can create an incoming webhook
|
# But can create an incoming webhook
|
||||||
self.assert_num_bots_equal(0)
|
self.assert_num_bots_equal(0)
|
||||||
|
@ -595,11 +595,50 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
|
||||||
profile = get_user(bot_email, bot_realm)
|
profile = get_user(bot_email, bot_realm)
|
||||||
self.assertEqual(profile.bot_type, UserProfile.INCOMING_WEBHOOK_BOT)
|
self.assertEqual(profile.bot_type, UserProfile.INCOMING_WEBHOOK_BOT)
|
||||||
|
|
||||||
def test_add_bot_with_bot_type_not_allowed_admin(self) -> None:
|
def test_no_generic_bots_allowed_for_admins(self) -> None:
|
||||||
bot_email = 'hambot-bot@zulip.testserver'
|
bot_email = 'hambot-bot@zulip.testserver'
|
||||||
bot_realm = get_realm('zulip')
|
bot_realm = get_realm('zulip')
|
||||||
bot_realm.create_generic_bot_by_admins_only = True
|
bot_realm.bot_creation_policy = Realm.BOT_CREATION_LIMIT_GENERIC_BOTS
|
||||||
bot_realm.save(update_fields=['create_generic_bot_by_admins_only'])
|
bot_realm.save(update_fields=['bot_creation_policy'])
|
||||||
|
|
||||||
|
# An administrator can create any type of bot
|
||||||
|
self.login(self.example_email('iago'))
|
||||||
|
self.assert_num_bots_equal(0)
|
||||||
|
self.create_bot(bot_type=UserProfile.DEFAULT_BOT)
|
||||||
|
self.assert_num_bots_equal(1)
|
||||||
|
profile = get_user(bot_email, bot_realm)
|
||||||
|
self.assertEqual(profile.bot_type, UserProfile.DEFAULT_BOT)
|
||||||
|
|
||||||
|
def test_no_bots_allowed_for_non_admins(self) -> None:
|
||||||
|
bot_info = {
|
||||||
|
'full_name': 'The Bot of Hamlet',
|
||||||
|
'short_name': 'hambot',
|
||||||
|
'bot_type': 1,
|
||||||
|
}
|
||||||
|
bot_realm = get_realm('zulip')
|
||||||
|
bot_realm.bot_creation_policy = Realm.BOT_CREATION_ADMINS_ONLY
|
||||||
|
bot_realm.save(update_fields=['bot_creation_policy'])
|
||||||
|
|
||||||
|
# A regular user cannot create a generic bot
|
||||||
|
self.login(self.example_email('hamlet'))
|
||||||
|
self.assert_num_bots_equal(0)
|
||||||
|
result = self.client_post("/json/bots", bot_info)
|
||||||
|
self.assert_num_bots_equal(0)
|
||||||
|
self.assert_json_error(result, 'Must be an organization administrator')
|
||||||
|
|
||||||
|
# Also, a regular user cannot create a incoming bot
|
||||||
|
bot_info['bot_type'] = 2
|
||||||
|
self.login(self.example_email('hamlet'))
|
||||||
|
self.assert_num_bots_equal(0)
|
||||||
|
result = self.client_post("/json/bots", bot_info)
|
||||||
|
self.assert_num_bots_equal(0)
|
||||||
|
self.assert_json_error(result, 'Must be an organization administrator')
|
||||||
|
|
||||||
|
def test_no_bots_allowed_for_admins(self) -> None:
|
||||||
|
bot_email = 'hambot-bot@zulip.testserver'
|
||||||
|
bot_realm = get_realm('zulip')
|
||||||
|
bot_realm.bot_creation_policy = Realm.BOT_CREATION_ADMINS_ONLY
|
||||||
|
bot_realm.save(update_fields=['bot_creation_policy'])
|
||||||
|
|
||||||
# An administrator can create any type of bot
|
# An administrator can create any type of bot
|
||||||
self.login(self.example_email('iago'))
|
self.login(self.example_email('iago'))
|
||||||
|
|
|
@ -1231,6 +1231,7 @@ class EventsRegisterTest(ZulipTestCase):
|
||||||
message_retention_days=[10, 20],
|
message_retention_days=[10, 20],
|
||||||
name=[u'Zulip', u'New Name'],
|
name=[u'Zulip', u'New Name'],
|
||||||
waiting_period_threshold=[10, 20],
|
waiting_period_threshold=[10, 20],
|
||||||
|
bot_creation_policy=[Realm.BOT_CREATION_EVERYONE],
|
||||||
) # type: Dict[str, Any]
|
) # type: Dict[str, Any]
|
||||||
|
|
||||||
vals = test_values.get(name)
|
vals = test_values.get(name)
|
||||||
|
|
|
@ -109,9 +109,9 @@ class HomeTest(ZulipTestCase):
|
||||||
"realm_allow_message_deleting",
|
"realm_allow_message_deleting",
|
||||||
"realm_allow_message_editing",
|
"realm_allow_message_editing",
|
||||||
"realm_authentication_methods",
|
"realm_authentication_methods",
|
||||||
|
"realm_bot_creation_policy",
|
||||||
"realm_bot_domain",
|
"realm_bot_domain",
|
||||||
"realm_bots",
|
"realm_bots",
|
||||||
"realm_create_generic_bot_by_admins_only",
|
|
||||||
"realm_create_stream_by_admins_only",
|
"realm_create_stream_by_admins_only",
|
||||||
"realm_default_language",
|
"realm_default_language",
|
||||||
"realm_default_stream_groups",
|
"realm_default_stream_groups",
|
||||||
|
|
|
@ -295,6 +295,19 @@ class RealmTest(ZulipTestCase):
|
||||||
realm = get_realm('zulip')
|
realm = get_realm('zulip')
|
||||||
self.assertFalse(realm.deactivated)
|
self.assertFalse(realm.deactivated)
|
||||||
|
|
||||||
|
def test_change_bot_creation_policy(self) -> None:
|
||||||
|
# We need an admin user.
|
||||||
|
email = 'iago@zulip.com'
|
||||||
|
self.login(email)
|
||||||
|
req = dict(bot_creation_policy = ujson.dumps(Realm.BOT_CREATION_LIMIT_GENERIC_BOTS))
|
||||||
|
result = self.client_patch('/json/realm', req)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
|
||||||
|
invalid_add_bot_permission = 4
|
||||||
|
req = dict(bot_creation_policy = ujson.dumps(invalid_add_bot_permission))
|
||||||
|
result = self.client_patch('/json/realm', req)
|
||||||
|
self.assert_json_error(result, 'Invalid bot creation policy')
|
||||||
|
|
||||||
|
|
||||||
class RealmAPITest(ZulipTestCase):
|
class RealmAPITest(ZulipTestCase):
|
||||||
|
|
||||||
|
@ -329,6 +342,7 @@ class RealmAPITest(ZulipTestCase):
|
||||||
message_retention_days=[10, 20],
|
message_retention_days=[10, 20],
|
||||||
name=[u'Zulip', u'New Name'],
|
name=[u'Zulip', u'New Name'],
|
||||||
waiting_period_threshold=[10, 20],
|
waiting_period_threshold=[10, 20],
|
||||||
|
bot_creation_policy=[1, 2],
|
||||||
) # type: Dict[str, Any]
|
) # type: Dict[str, Any]
|
||||||
vals = test_values.get(name)
|
vals = test_values.get(name)
|
||||||
if Realm.property_types[name] is bool:
|
if Realm.property_types[name] is bool:
|
||||||
|
|
|
@ -38,7 +38,6 @@ def update_realm(
|
||||||
inline_url_embed_preview: Optional[bool]=REQ(validator=check_bool, default=None),
|
inline_url_embed_preview: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
create_stream_by_admins_only: Optional[bool]=REQ(validator=check_bool, default=None),
|
create_stream_by_admins_only: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
add_emoji_by_admins_only: Optional[bool]=REQ(validator=check_bool, default=None),
|
add_emoji_by_admins_only: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
create_generic_bot_by_admins_only: Optional[bool]=REQ(validator=check_bool, default=None),
|
|
||||||
allow_message_deleting: Optional[bool]=REQ(validator=check_bool, default=None),
|
allow_message_deleting: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
allow_message_editing: Optional[bool]=REQ(validator=check_bool, default=None),
|
allow_message_editing: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
mandatory_topics: Optional[bool]=REQ(validator=check_bool, default=None),
|
mandatory_topics: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
|
@ -50,7 +49,8 @@ def update_realm(
|
||||||
notifications_stream_id: Optional[int]=REQ(validator=check_int, default=None),
|
notifications_stream_id: Optional[int]=REQ(validator=check_int, default=None),
|
||||||
signup_notifications_stream_id: Optional[int]=REQ(validator=check_int, default=None),
|
signup_notifications_stream_id: Optional[int]=REQ(validator=check_int, default=None),
|
||||||
message_retention_days: Optional[int]=REQ(converter=to_not_negative_int_or_none, default=None),
|
message_retention_days: Optional[int]=REQ(converter=to_not_negative_int_or_none, default=None),
|
||||||
send_welcome_emails: Optional[bool]=REQ(validator=check_bool, default=None)
|
send_welcome_emails: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
|
bot_creation_policy: Optional[int]=REQ(converter=to_not_negative_int_or_none, default=None)
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
realm = user_profile.realm
|
realm = user_profile.realm
|
||||||
|
|
||||||
|
@ -67,6 +67,9 @@ def update_realm(
|
||||||
if signup_notifications_stream_id is not None and settings.NEW_USER_BOT is None:
|
if signup_notifications_stream_id is not None and settings.NEW_USER_BOT is None:
|
||||||
return json_error(_("NEW_USER_BOT must configured first."))
|
return json_error(_("NEW_USER_BOT must configured first."))
|
||||||
|
|
||||||
|
# Additional validation of permissions values to add new bot
|
||||||
|
if bot_creation_policy is not None and bot_creation_policy not in Realm.BOT_CREATION_POLICY_TYPES:
|
||||||
|
return json_error(_("Invalid bot creation policy"))
|
||||||
# The user of `locals()` here is a bit of a code smell, but it's
|
# The user of `locals()` here is a bit of a code smell, but it's
|
||||||
# restricted to the elements present in realm.property_types.
|
# restricted to the elements present in realm.property_types.
|
||||||
#
|
#
|
||||||
|
|
|
@ -26,7 +26,7 @@ from zerver.lib.response import json_error, json_success
|
||||||
from zerver.lib.streams import access_stream_by_name
|
from zerver.lib.streams import access_stream_by_name
|
||||||
from zerver.lib.upload import upload_avatar_image
|
from zerver.lib.upload import upload_avatar_image
|
||||||
from zerver.lib.validator import check_bool, check_string, check_int, check_url, check_dict
|
from zerver.lib.validator import check_bool, check_string, check_int, check_url, check_dict
|
||||||
from zerver.lib.users import check_valid_bot_type, \
|
from zerver.lib.users import check_valid_bot_type, check_bot_creation_policy, \
|
||||||
check_full_name, check_short_name, check_valid_interface_type, check_valid_bot_config
|
check_full_name, check_short_name, check_valid_interface_type, check_valid_bot_config
|
||||||
from zerver.lib.utils import generate_random_token
|
from zerver.lib.utils import generate_random_token
|
||||||
from zerver.models import UserProfile, Stream, Message, email_allowed_for_realm, \
|
from zerver.models import UserProfile, Stream, Message, email_allowed_for_realm, \
|
||||||
|
@ -299,6 +299,7 @@ def add_bot_backend(
|
||||||
return json_error(_("Username already in use"))
|
return json_error(_("Username already in use"))
|
||||||
except UserProfile.DoesNotExist:
|
except UserProfile.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
check_bot_creation_policy(user_profile, bot_type)
|
||||||
check_valid_bot_type(user_profile, bot_type)
|
check_valid_bot_type(user_profile, bot_type)
|
||||||
check_valid_interface_type(interface_type)
|
check_valid_interface_type(interface_type)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue