mirror of
https://github.com/zulip/zulip.git
synced 2025-10-28 18:43:52 +00:00
settings: Add two new realm settings to restrict bot creation.
Added `can_create_bots_group` setting which controls who can create any type of bots in the organization. Added `can_create_write_only_bots_group` setting which controls who can create incoming webhooks in the organization in additon to those who are in `can_create_bots_group`.
This commit is contained in:
@@ -20,6 +20,18 @@ format used by the Zulip server that they are interacting with.
|
|||||||
|
|
||||||
## Changes in Zulip 10.0
|
## Changes in Zulip 10.0
|
||||||
|
|
||||||
|
**Feature level 344**
|
||||||
|
|
||||||
|
* `PATCH /realm`, [`GET /events`](/api/get-events),
|
||||||
|
[`POST /register`](/api/register-queue):
|
||||||
|
Added two new realm settings, `can_create_bots_group` which is a
|
||||||
|
[group-setting value](/api/group-setting-values) describing the set of users
|
||||||
|
with permission to create bot users in the organization, and
|
||||||
|
`can_create_write_only_bots_group` which is a
|
||||||
|
[group-setting value](/api/group-setting-values) describing the set of users
|
||||||
|
with permission to create bot users who can only send messages in the organization
|
||||||
|
in addition to the users who are in `can_create_bots_group`.
|
||||||
|
|
||||||
**Feature level 343**
|
**Feature level 343**
|
||||||
|
|
||||||
* [`GET /events`](/api/get-events): Added a new field `stream_ids` to replace
|
* [`GET /events`](/api/get-events): Added a new field `stream_ids` to replace
|
||||||
|
|||||||
@@ -6,13 +6,25 @@ By default, anyone other than guests can [add a bot](/help/add-a-bot-or-integrat
|
|||||||
or integration to the Zulip organization. Organization administrators can
|
or integration to the Zulip organization. Organization administrators can
|
||||||
change who is allowed to add bots.
|
change who is allowed to add bots.
|
||||||
|
|
||||||
## Configure who can add bots
|
## Configure who can create any type of bot
|
||||||
|
|
||||||
{start_tabs}
|
{start_tabs}
|
||||||
|
|
||||||
{settings_tab|organization-permissions}
|
{settings_tab|organization-permissions}
|
||||||
|
|
||||||
2. Under **Other permissions**, configure **Who can add bots**.
|
2. Under **Other permissions**, configure **Who can create any bot**.
|
||||||
|
|
||||||
|
{!save-changes.md!}
|
||||||
|
|
||||||
|
{end_tabs}
|
||||||
|
|
||||||
|
## Configure who can create bots that can only send messages
|
||||||
|
|
||||||
|
{start_tabs}
|
||||||
|
|
||||||
|
{settings_tab|organization-permissions}
|
||||||
|
|
||||||
|
2. Under **Other permissions**, configure **Who can create bots that send messages into Zulip**.
|
||||||
|
|
||||||
{!save-changes.md!}
|
{!save-changes.md!}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
|||||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
|
|
||||||
API_FEATURE_LEVEL = 343 # Last bumped for `stream_ids` field in stream deletion events.
|
API_FEATURE_LEVEL = 344 # Last bumped for `can_create_bots_group`.
|
||||||
|
|
||||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||||
# only when going from an old version of the code to a newer version. Bump
|
# only when going from an old version of the code to a newer version. Bump
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ export function build_page(): void {
|
|||||||
realm_email_changes_disabled: realm.realm_email_changes_disabled,
|
realm_email_changes_disabled: realm.realm_email_changes_disabled,
|
||||||
realm_avatar_changes_disabled: realm.realm_avatar_changes_disabled,
|
realm_avatar_changes_disabled: realm.realm_avatar_changes_disabled,
|
||||||
can_add_emojis: settings_data.user_can_add_custom_emoji(),
|
can_add_emojis: settings_data.user_can_add_custom_emoji(),
|
||||||
can_create_new_bots: settings_bots.can_create_new_bots(),
|
can_create_new_bots: settings_bots.can_create_incoming_webhooks(),
|
||||||
realm_message_content_edit_limit_minutes:
|
realm_message_content_edit_limit_minutes:
|
||||||
settings_components.get_realm_time_limits_in_minutes(
|
settings_components.get_realm_time_limits_in_minutes(
|
||||||
"realm_message_content_edit_limit_seconds",
|
"realm_message_content_edit_limit_seconds",
|
||||||
@@ -194,7 +194,6 @@ export function build_page(): void {
|
|||||||
msg_edit_limit_dropdown_values: settings_config.msg_edit_limit_dropdown_values,
|
msg_edit_limit_dropdown_values: settings_config.msg_edit_limit_dropdown_values,
|
||||||
msg_delete_limit_dropdown_values: settings_config.msg_delete_limit_dropdown_values,
|
msg_delete_limit_dropdown_values: settings_config.msg_delete_limit_dropdown_values,
|
||||||
msg_move_limit_dropdown_values: settings_config.msg_move_limit_dropdown_values,
|
msg_move_limit_dropdown_values: settings_config.msg_move_limit_dropdown_values,
|
||||||
bot_creation_policy_values: settings_bots.bot_creation_policy_values,
|
|
||||||
email_address_visibility_values: settings_config.email_address_visibility_values,
|
email_address_visibility_values: settings_config.email_address_visibility_values,
|
||||||
waiting_period_threshold_dropdown_values:
|
waiting_period_threshold_dropdown_values:
|
||||||
settings_config.waiting_period_threshold_dropdown_values,
|
settings_config.waiting_period_threshold_dropdown_values,
|
||||||
@@ -259,7 +258,7 @@ export function build_page(): void {
|
|||||||
$("#settings_content .organization-box").html(rendered_admin_tab);
|
$("#settings_content .organization-box").html(rendered_admin_tab);
|
||||||
$("#settings_content .alert").removeClass("show");
|
$("#settings_content .alert").removeClass("show");
|
||||||
|
|
||||||
settings_bots.update_bot_settings_tip($("#admin-bot-settings-tip"), true);
|
settings_bots.update_bot_settings_tip($("#admin-bot-settings-tip"));
|
||||||
settings_invites.update_invite_user_panel();
|
settings_invites.update_invite_user_panel();
|
||||||
insert_tip_box();
|
insert_tip_box();
|
||||||
|
|
||||||
@@ -268,8 +267,6 @@ export function build_page(): void {
|
|||||||
demo_organizations_ui.handle_demo_organization_conversion();
|
demo_organizations_ui.handle_demo_organization_conversion();
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#id_realm_bot_creation_policy").val(realm.realm_bot_creation_policy);
|
|
||||||
|
|
||||||
$("#id_realm_digest_weekday").val(realm.realm_digest_weekday);
|
$("#id_realm_digest_weekday").val(realm.realm_digest_weekday);
|
||||||
|
|
||||||
const is_plan_plus = realm.realm_plan_type === 10;
|
const is_plan_plus = realm.realm_plan_type === 10;
|
||||||
|
|||||||
@@ -44,9 +44,11 @@ export const realm_group_setting_name_schema = z.enum([
|
|||||||
"can_add_custom_emoji_group",
|
"can_add_custom_emoji_group",
|
||||||
"can_add_subscribers_group",
|
"can_add_subscribers_group",
|
||||||
"can_create_groups",
|
"can_create_groups",
|
||||||
|
"can_create_bots_group",
|
||||||
"can_create_public_channel_group",
|
"can_create_public_channel_group",
|
||||||
"can_create_private_channel_group",
|
"can_create_private_channel_group",
|
||||||
"can_create_web_public_channel_group",
|
"can_create_web_public_channel_group",
|
||||||
|
"can_create_write_only_bots_group",
|
||||||
"can_delete_any_message_group",
|
"can_delete_any_message_group",
|
||||||
"can_delete_own_message_group",
|
"can_delete_own_message_group",
|
||||||
"can_invite_users_group",
|
"can_invite_users_group",
|
||||||
|
|||||||
@@ -211,12 +211,13 @@ export function dispatch_normal_event(event) {
|
|||||||
allow_edit_history: noop,
|
allow_edit_history: noop,
|
||||||
allow_message_editing: noop,
|
allow_message_editing: noop,
|
||||||
avatar_changes_disabled: settings_account.update_avatar_change_display,
|
avatar_changes_disabled: settings_account.update_avatar_change_display,
|
||||||
bot_creation_policy: settings_bots.update_bot_permissions_ui,
|
|
||||||
can_add_custom_emoji_group: noop,
|
can_add_custom_emoji_group: noop,
|
||||||
can_add_subscribers_group: noop,
|
can_add_subscribers_group: noop,
|
||||||
|
can_create_bots_group: noop,
|
||||||
can_create_groups: noop,
|
can_create_groups: noop,
|
||||||
can_create_private_channel_group: noop,
|
can_create_private_channel_group: noop,
|
||||||
can_create_public_channel_group: noop,
|
can_create_public_channel_group: noop,
|
||||||
|
can_create_write_only_bots_group: noop,
|
||||||
can_delete_any_message_group: noop,
|
can_delete_any_message_group: noop,
|
||||||
can_delete_own_message_group: noop,
|
can_delete_own_message_group: noop,
|
||||||
can_manage_all_groups: noop,
|
can_manage_all_groups: noop,
|
||||||
@@ -319,6 +320,13 @@ export function dispatch_normal_event(event) {
|
|||||||
settings_emoji.update_custom_emoji_ui();
|
settings_emoji.update_custom_emoji_ui();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
key === "can_create_bots_group" ||
|
||||||
|
key === "can_create_write_only_bots_group"
|
||||||
|
) {
|
||||||
|
settings_bots.update_bot_permissions_ui();
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
key === "can_create_public_channel_group" ||
|
key === "can_create_public_channel_group" ||
|
||||||
key === "can_create_private_channel_group" ||
|
key === "can_create_private_channel_group" ||
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export function update_lock_icon_in_sidebar(): void {
|
|||||||
|
|
||||||
$(".org-settings-list .locked").show();
|
$(".org-settings-list .locked").show();
|
||||||
|
|
||||||
if (settings_bots.can_create_new_bots()) {
|
if (settings_bots.can_create_incoming_webhooks()) {
|
||||||
$(".org-settings-list li[data-section='bot-list-admin'] .locked").hide();
|
$(".org-settings-list li[data-section='bot-list-admin'] .locked").hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ export function build_page(): void {
|
|||||||
zuliprc: "zuliprc",
|
zuliprc: "zuliprc",
|
||||||
botserverrc: "botserverrc",
|
botserverrc: "botserverrc",
|
||||||
timezones: timezones.timezones,
|
timezones: timezones.timezones,
|
||||||
can_create_new_bots: settings_bots.can_create_new_bots(),
|
can_create_new_bots: settings_bots.can_create_incoming_webhooks(),
|
||||||
settings_label,
|
settings_label,
|
||||||
demote_inactive_streams_values: settings_config.demote_inactive_streams_values,
|
demote_inactive_streams_values: settings_config.demote_inactive_streams_values,
|
||||||
web_mark_read_on_scroll_policy_values:
|
web_mark_read_on_scroll_policy_values:
|
||||||
@@ -146,8 +146,8 @@ export function build_page(): void {
|
|||||||
settings_config.automatically_follow_or_unmute_topics_policy_values,
|
settings_config.automatically_follow_or_unmute_topics_policy_values,
|
||||||
});
|
});
|
||||||
|
|
||||||
settings_bots.update_bot_settings_tip($("#personal-bot-settings-tip"), false);
|
|
||||||
$(".settings-box").html(rendered_settings_tab);
|
$(".settings-box").html(rendered_settings_tab);
|
||||||
|
settings_bots.update_bot_settings_tip($("#personal-bot-settings-tip"));
|
||||||
common.adjust_mac_kbd_tags("#user_enter_sends_label kbd");
|
common.adjust_mac_kbd_tags("#user_enter_sends_label kbd");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ export function initialize(): void {
|
|||||||
is_guest: current_user.is_guest,
|
is_guest: current_user.is_guest,
|
||||||
show_uploaded_files_section: realm.max_file_upload_size_mib > 0,
|
show_uploaded_files_section: realm.max_file_upload_size_mib > 0,
|
||||||
show_emoji_settings_lock: !settings_data.user_can_add_custom_emoji(),
|
show_emoji_settings_lock: !settings_data.user_can_add_custom_emoji(),
|
||||||
can_create_new_bots: settings_bots.can_create_new_bots(),
|
can_create_new_bots: settings_bots.can_create_incoming_webhooks(),
|
||||||
can_edit_user_panel:
|
can_edit_user_panel:
|
||||||
current_user.is_admin ||
|
current_user.is_admin ||
|
||||||
settings_data.user_can_create_multiuse_invite() ||
|
settings_data.user_can_create_multiuse_invite() ||
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import * as list_widget from "./list_widget.ts";
|
|||||||
import {page_params} from "./page_params.ts";
|
import {page_params} from "./page_params.ts";
|
||||||
import * as people from "./people.ts";
|
import * as people from "./people.ts";
|
||||||
import * as settings_data from "./settings_data.ts";
|
import * as settings_data from "./settings_data.ts";
|
||||||
import {current_user, realm} from "./state_data.ts";
|
import {realm} from "./state_data.ts";
|
||||||
import type {HTMLSelectOneElement} from "./types.ts";
|
import type {HTMLSelectOneElement} from "./types.ts";
|
||||||
import * as ui_report from "./ui_report.ts";
|
import * as ui_report from "./ui_report.ts";
|
||||||
import type {UploadWidget} from "./upload_widget.ts";
|
import type {UploadWidget} from "./upload_widget.ts";
|
||||||
@@ -154,59 +154,43 @@ export function generate_botserverrc_content(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bot_creation_policy_values = {
|
|
||||||
admins_only: {
|
|
||||||
code: 3,
|
|
||||||
description: $t({defaultMessage: "Admins"}),
|
|
||||||
},
|
|
||||||
everyone: {
|
|
||||||
code: 1,
|
|
||||||
description: $t({defaultMessage: "Admins, moderators and members"}),
|
|
||||||
},
|
|
||||||
restricted: {
|
|
||||||
code: 2,
|
|
||||||
description: $t({
|
|
||||||
defaultMessage: "Admins, moderators and members, but only admins can add generic bots",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export function can_create_new_bots(): boolean {
|
export function can_create_new_bots(): boolean {
|
||||||
if (current_user.is_admin) {
|
return settings_data.user_has_permission_for_group_setting(
|
||||||
return true;
|
realm.realm_can_create_bots_group,
|
||||||
|
"can_create_bots_group",
|
||||||
|
"realm",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current_user.is_guest) {
|
export function can_create_incoming_webhooks(): boolean {
|
||||||
return false;
|
// User who have the permission to create any bot can also
|
||||||
|
// create incoming webhooks.
|
||||||
|
return (
|
||||||
|
can_create_new_bots() ||
|
||||||
|
settings_data.user_has_permission_for_group_setting(
|
||||||
|
realm.realm_can_create_write_only_bots_group,
|
||||||
|
"can_create_write_only_bots_group",
|
||||||
|
"realm",
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return realm.realm_bot_creation_policy !== bot_creation_policy_values.admins_only.code;
|
export function update_bot_settings_tip($tip_container: JQuery): void {
|
||||||
}
|
if (can_create_new_bots()) {
|
||||||
|
|
||||||
export function update_bot_settings_tip($tip_container: JQuery, for_org_settings: boolean): void {
|
|
||||||
if (
|
|
||||||
!current_user.is_admin &&
|
|
||||||
realm.realm_bot_creation_policy === bot_creation_policy_values.everyone.code
|
|
||||||
) {
|
|
||||||
$tip_container.hide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current_user.is_admin && !for_org_settings) {
|
|
||||||
$tip_container.hide();
|
$tip_container.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rendered_tip = render_bot_settings_tip({
|
const rendered_tip = render_bot_settings_tip({
|
||||||
realm_bot_creation_policy: realm.realm_bot_creation_policy,
|
can_create_any_bots: can_create_new_bots(),
|
||||||
permission_type: bot_creation_policy_values,
|
can_create_incoming_webhooks: can_create_incoming_webhooks(),
|
||||||
});
|
});
|
||||||
$tip_container.show();
|
$tip_container.show();
|
||||||
$tip_container.html(rendered_tip);
|
$tip_container.html(rendered_tip);
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_add_bot_button(): void {
|
function update_add_bot_button(): void {
|
||||||
if (can_create_new_bots()) {
|
if (can_create_incoming_webhooks()) {
|
||||||
$("#bot-settings .add-a-new-bot").show();
|
$("#bot-settings .add-a-new-bot").show();
|
||||||
$("#admin-bot-list .add-new-bots").show();
|
$("#admin-bot-list .add-new-bots").show();
|
||||||
$("#admin-bot-list .manage-your-bots").hide();
|
$("#admin-bot-list .manage-your-bots").hide();
|
||||||
@@ -223,10 +207,9 @@ function update_add_bot_button(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function update_bot_permissions_ui(): void {
|
export function update_bot_permissions_ui(): void {
|
||||||
update_bot_settings_tip($("#admin-bot-settings-tip"), true);
|
update_bot_settings_tip($("#admin-bot-settings-tip"));
|
||||||
update_bot_settings_tip($("#personal-bot-settings-tip"), false);
|
update_bot_settings_tip($("#personal-bot-settings-tip"));
|
||||||
update_add_bot_button();
|
update_add_bot_button();
|
||||||
$("#id_realm_bot_creation_policy").val(realm.realm_bot_creation_policy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function add_a_new_bot(): void {
|
export function add_a_new_bot(): void {
|
||||||
|
|||||||
@@ -803,9 +803,11 @@ export function check_realm_settings_property_changed(elem: HTMLElement): boolea
|
|||||||
break;
|
break;
|
||||||
case "realm_can_add_custom_emoji_group":
|
case "realm_can_add_custom_emoji_group":
|
||||||
case "realm_can_add_subscribers_group":
|
case "realm_can_add_subscribers_group":
|
||||||
|
case "realm_can_create_bots_group":
|
||||||
case "realm_can_create_groups":
|
case "realm_can_create_groups":
|
||||||
case "realm_can_create_public_channel_group":
|
case "realm_can_create_public_channel_group":
|
||||||
case "realm_can_create_private_channel_group":
|
case "realm_can_create_private_channel_group":
|
||||||
|
case "realm_can_create_write_only_bots_group":
|
||||||
case "realm_can_delete_any_message_group":
|
case "realm_can_delete_any_message_group":
|
||||||
case "realm_can_delete_own_message_group":
|
case "realm_can_delete_own_message_group":
|
||||||
case "realm_can_invite_users_group":
|
case "realm_can_invite_users_group":
|
||||||
@@ -1050,10 +1052,12 @@ export function populate_data_for_realm_settings_request(
|
|||||||
"can_access_all_users_group",
|
"can_access_all_users_group",
|
||||||
"can_add_custom_emoji_group",
|
"can_add_custom_emoji_group",
|
||||||
"can_add_subscribers_group",
|
"can_add_subscribers_group",
|
||||||
|
"can_create_bots_group",
|
||||||
"can_create_groups",
|
"can_create_groups",
|
||||||
"can_create_private_channel_group",
|
"can_create_private_channel_group",
|
||||||
"can_create_public_channel_group",
|
"can_create_public_channel_group",
|
||||||
"can_create_web_public_channel_group",
|
"can_create_web_public_channel_group",
|
||||||
|
"can_create_write_only_bots_group",
|
||||||
"can_manage_all_groups",
|
"can_manage_all_groups",
|
||||||
"can_delete_any_message_group",
|
"can_delete_any_message_group",
|
||||||
"can_delete_own_message_group",
|
"can_delete_own_message_group",
|
||||||
@@ -1513,9 +1517,11 @@ export const group_setting_widget_map = new Map<string, GroupSettingPillContaine
|
|||||||
["can_send_message_group", null],
|
["can_send_message_group", null],
|
||||||
["realm_can_add_custom_emoji_group", null],
|
["realm_can_add_custom_emoji_group", null],
|
||||||
["realm_can_add_subscribers_group", null],
|
["realm_can_add_subscribers_group", null],
|
||||||
|
["realm_can_create_bots_group", null],
|
||||||
["realm_can_create_groups", null],
|
["realm_can_create_groups", null],
|
||||||
["realm_can_create_public_channel_group", null],
|
["realm_can_create_public_channel_group", null],
|
||||||
["realm_can_create_private_channel_group", null],
|
["realm_can_create_private_channel_group", null],
|
||||||
|
["realm_can_create_write_only_bots_group", null],
|
||||||
["realm_can_delete_any_message_group", null],
|
["realm_can_delete_any_message_group", null],
|
||||||
["realm_can_delete_own_message_group", null],
|
["realm_can_delete_own_message_group", null],
|
||||||
["realm_can_invite_users_group", null],
|
["realm_can_invite_users_group", null],
|
||||||
|
|||||||
@@ -522,9 +522,11 @@ export function discard_realm_property_element_changes(elem: HTMLElement): void
|
|||||||
break;
|
break;
|
||||||
case "realm_can_add_custom_emoji_group":
|
case "realm_can_add_custom_emoji_group":
|
||||||
case "realm_can_add_subscribers_group":
|
case "realm_can_add_subscribers_group":
|
||||||
|
case "realm_can_create_bots_group":
|
||||||
case "realm_can_create_groups":
|
case "realm_can_create_groups":
|
||||||
case "realm_can_create_public_channel_group":
|
case "realm_can_create_public_channel_group":
|
||||||
case "realm_can_create_private_channel_group":
|
case "realm_can_create_private_channel_group":
|
||||||
|
case "realm_can_create_write_only_bots_group":
|
||||||
case "realm_can_delete_any_message_group":
|
case "realm_can_delete_any_message_group":
|
||||||
case "realm_can_delete_own_message_group":
|
case "realm_can_delete_own_message_group":
|
||||||
case "realm_can_invite_users_group":
|
case "realm_can_invite_users_group":
|
||||||
|
|||||||
@@ -291,15 +291,16 @@ export const realm_schema = z.object({
|
|||||||
big_blue_button: z.optional(z.object({name: z.string(), id: z.number()})),
|
big_blue_button: z.optional(z.object({name: z.string(), id: z.number()})),
|
||||||
}),
|
}),
|
||||||
realm_avatar_changes_disabled: z.boolean(),
|
realm_avatar_changes_disabled: z.boolean(),
|
||||||
realm_bot_creation_policy: z.number(),
|
|
||||||
realm_bot_domain: z.string(),
|
realm_bot_domain: z.string(),
|
||||||
realm_can_access_all_users_group: z.number(),
|
realm_can_access_all_users_group: z.number(),
|
||||||
realm_can_add_custom_emoji_group: group_setting_value_schema,
|
realm_can_add_custom_emoji_group: group_setting_value_schema,
|
||||||
realm_can_add_subscribers_group: group_setting_value_schema,
|
realm_can_add_subscribers_group: group_setting_value_schema,
|
||||||
|
realm_can_create_bots_group: group_setting_value_schema,
|
||||||
realm_can_create_groups: group_setting_value_schema,
|
realm_can_create_groups: group_setting_value_schema,
|
||||||
realm_can_create_public_channel_group: group_setting_value_schema,
|
realm_can_create_public_channel_group: group_setting_value_schema,
|
||||||
realm_can_create_private_channel_group: group_setting_value_schema,
|
realm_can_create_private_channel_group: group_setting_value_schema,
|
||||||
realm_can_create_web_public_channel_group: z.number(),
|
realm_can_create_web_public_channel_group: z.number(),
|
||||||
|
realm_can_create_write_only_bots_group: group_setting_value_schema,
|
||||||
realm_can_delete_any_message_group: group_setting_value_schema,
|
realm_can_delete_any_message_group: group_setting_value_schema,
|
||||||
realm_can_delete_own_message_group: group_setting_value_schema,
|
realm_can_delete_own_message_group: group_setting_value_schema,
|
||||||
realm_can_invite_users_group: group_setting_value_schema,
|
realm_can_invite_users_group: group_setting_value_schema,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{#if (eq realm_bot_creation_policy permission_type.admins_only.code)}}
|
{{#unless can_create_any_bots}}
|
||||||
<div class='tip'>{{t "This organization is configured so that only administrators can add bots." }}</div>
|
{{#if can_create_incoming_webhooks}}
|
||||||
{{else if (eq realm_bot_creation_policy permission_type.restricted.code) }}
|
<div class='tip'>{{t "You can create bots that can only send messages." }}</div>
|
||||||
<div class='tip'>{{t "This organization is configured so that only administrators can add generic bots." }}</div>
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class='tip'>{{t "This organization is configured so that anyone can add bots." }}</div>
|
<div class='tip'>{{t "You do not have permission to create bots." }}</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{/unless}}
|
||||||
|
|||||||
@@ -335,12 +335,13 @@
|
|||||||
{{> settings_save_discard_widget section_name="other-permissions" }}
|
{{> settings_save_discard_widget section_name="other-permissions" }}
|
||||||
</div>
|
</div>
|
||||||
<div class="m-10 inline-block organization-permissions-parent">
|
<div class="m-10 inline-block organization-permissions-parent">
|
||||||
<div class="input-group">
|
{{> group_setting_value_pill_input
|
||||||
<label for="id_realm_bot_creation_policy" class="settings-field-label">{{t "Who can add bots" }}</label>
|
setting_name="realm_can_create_write_only_bots_group"
|
||||||
<select name="realm_bot_creation_policy" class="setting-widget prop-element settings_select bootstrap-focus-style" id="id_realm_bot_creation_policy" data-setting-widget-type="number">
|
label=(t 'Who can create bots that send messages into Zulip')}}
|
||||||
{{> dropdown_options_widget option_values=bot_creation_policy_values}}
|
|
||||||
</select>
|
{{> group_setting_value_pill_input
|
||||||
</div>
|
setting_name="realm_can_create_bots_group"
|
||||||
|
label=(t 'Who can create any bot')}}
|
||||||
|
|
||||||
{{> group_setting_value_pill_input
|
{{> group_setting_value_pill_input
|
||||||
setting_name="realm_can_add_custom_emoji_group"
|
setting_name="realm_can_add_custom_emoji_group"
|
||||||
|
|||||||
@@ -510,28 +510,7 @@ run_test("realm settings", ({override}) => {
|
|||||||
assert.equal(realm[parameter_name], true);
|
assert.equal(realm[parameter_name], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_realm_integer(event, parameter_name) {
|
let event = event_fixtures.realm__update__invite_required;
|
||||||
override(realm, parameter_name, 1);
|
|
||||||
event = {...event};
|
|
||||||
event.value = 2;
|
|
||||||
dispatch(event);
|
|
||||||
assert.equal(realm[parameter_name], 2);
|
|
||||||
|
|
||||||
event = {...event};
|
|
||||||
event.value = 3;
|
|
||||||
dispatch(event);
|
|
||||||
assert.equal(realm[parameter_name], 3);
|
|
||||||
|
|
||||||
event = {...event};
|
|
||||||
event.value = 1;
|
|
||||||
dispatch(event);
|
|
||||||
assert.equal(realm[parameter_name], 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let event = event_fixtures.realm__update__bot_creation_policy;
|
|
||||||
test_realm_integer(event, "realm_bot_creation_policy");
|
|
||||||
|
|
||||||
event = event_fixtures.realm__update__invite_required;
|
|
||||||
test_realm_boolean(event, "realm_invite_required");
|
test_realm_boolean(event, "realm_invite_required");
|
||||||
|
|
||||||
event = event_fixtures.realm__update__want_advertise_in_communities_directory;
|
event = event_fixtures.realm__update__want_advertise_in_communities_directory;
|
||||||
@@ -597,6 +576,7 @@ run_test("realm settings", ({override}) => {
|
|||||||
override(realm, "realm_authentication_methods", {Google: {enabled: false, available: true}});
|
override(realm, "realm_authentication_methods", {Google: {enabled: false, available: true}});
|
||||||
override(realm, "realm_can_add_custom_emoji_group", 1);
|
override(realm, "realm_can_add_custom_emoji_group", 1);
|
||||||
override(realm, "realm_can_add_subscribers_group", 1);
|
override(realm, "realm_can_add_subscribers_group", 1);
|
||||||
|
override(realm, "realm_can_create_bots_group", 1);
|
||||||
override(realm, "realm_can_create_public_channel_group", 1);
|
override(realm, "realm_can_create_public_channel_group", 1);
|
||||||
override(realm, "realm_can_invite_users_group", 1);
|
override(realm, "realm_can_invite_users_group", 1);
|
||||||
override(realm, "realm_can_move_messages_between_topics_group", 1);
|
override(realm, "realm_can_move_messages_between_topics_group", 1);
|
||||||
@@ -620,6 +600,7 @@ run_test("realm settings", ({override}) => {
|
|||||||
});
|
});
|
||||||
assert_same(realm.realm_can_add_custom_emoji_group, 3);
|
assert_same(realm.realm_can_add_custom_emoji_group, 3);
|
||||||
assert_same(realm.realm_can_add_subscribers_group, 3);
|
assert_same(realm.realm_can_add_subscribers_group, 3);
|
||||||
|
assert_same(realm.realm_can_create_bots_group, 3);
|
||||||
assert_same(realm.realm_can_create_public_channel_group, 3);
|
assert_same(realm.realm_can_create_public_channel_group, 3);
|
||||||
assert_same(realm.realm_can_invite_users_group, 3);
|
assert_same(realm.realm_can_invite_users_group, 3);
|
||||||
assert_same(realm.realm_can_move_messages_between_topics_group, 3);
|
assert_same(realm.realm_can_move_messages_between_topics_group, 3);
|
||||||
|
|||||||
@@ -266,13 +266,6 @@ exports.fixtures = {
|
|||||||
realm_id: 2,
|
realm_id: 2,
|
||||||
},
|
},
|
||||||
|
|
||||||
realm__update__bot_creation_policy: {
|
|
||||||
type: "realm",
|
|
||||||
op: "update",
|
|
||||||
property: "bot_creation_policy",
|
|
||||||
value: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
realm__update__default_code_block_language: {
|
realm__update__default_code_block_language: {
|
||||||
type: "realm",
|
type: "realm",
|
||||||
op: "update",
|
op: "update",
|
||||||
@@ -363,6 +356,7 @@ exports.fixtures = {
|
|||||||
},
|
},
|
||||||
can_add_custom_emoji_group: 3,
|
can_add_custom_emoji_group: 3,
|
||||||
can_add_subscribers_group: 3,
|
can_add_subscribers_group: 3,
|
||||||
|
can_create_bots_group: 3,
|
||||||
can_create_public_channel_group: 3,
|
can_create_public_channel_group: 3,
|
||||||
can_invite_users_group: 3,
|
can_invite_users_group: 3,
|
||||||
can_move_messages_between_topics_group: 3,
|
can_move_messages_between_topics_group: 3,
|
||||||
|
|||||||
@@ -95,15 +95,3 @@ test("generate_botserverrc_content", () => {
|
|||||||
|
|
||||||
assert.equal(content, expected);
|
assert.equal(content, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can_create_new_bots", ({override}) => {
|
|
||||||
override(current_user, "is_admin", true);
|
|
||||||
assert.ok(settings_bots.can_create_new_bots());
|
|
||||||
|
|
||||||
override(current_user, "is_admin", false);
|
|
||||||
override(realm, "realm_bot_creation_policy", 1);
|
|
||||||
assert.ok(settings_bots.can_create_new_bots());
|
|
||||||
|
|
||||||
override(realm, "realm_bot_creation_policy", 3);
|
|
||||||
assert.ok(!settings_bots.can_create_new_bots());
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ mock_esm("../src/loading", {
|
|||||||
mock_esm("../src/scroll_util", {scroll_element_into_container: noop});
|
mock_esm("../src/scroll_util", {scroll_element_into_container: noop});
|
||||||
set_global("document", "document-stub");
|
set_global("document", "document-stub");
|
||||||
|
|
||||||
const settings_bots = zrequire("settings_bots");
|
|
||||||
const settings_account = zrequire("settings_account");
|
const settings_account = zrequire("settings_account");
|
||||||
const settings_components = zrequire("settings_components");
|
const settings_components = zrequire("settings_components");
|
||||||
const settings_org = zrequire("settings_org");
|
const settings_org = zrequire("settings_org");
|
||||||
@@ -100,7 +99,6 @@ function createSaveButtons(subsection) {
|
|||||||
|
|
||||||
function test_submit_settings_form(override, submit_form) {
|
function test_submit_settings_form(override, submit_form) {
|
||||||
Object.assign(realm, {
|
Object.assign(realm, {
|
||||||
realm_bot_creation_policy: settings_bots.bot_creation_policy_values.restricted.code,
|
|
||||||
realm_waiting_period_threshold: 1,
|
realm_waiting_period_threshold: 1,
|
||||||
realm_default_language: '"es"',
|
realm_default_language: '"es"',
|
||||||
});
|
});
|
||||||
@@ -129,22 +127,7 @@ function test_submit_settings_form(override, submit_form) {
|
|||||||
|
|
||||||
$("#id_realm_waiting_period_threshold").val(10);
|
$("#id_realm_waiting_period_threshold").val(10);
|
||||||
|
|
||||||
const $bot_creation_policy_elem = $("#id_realm_bot_creation_policy");
|
|
||||||
$bot_creation_policy_elem.val("1");
|
|
||||||
$bot_creation_policy_elem.attr("id", "id_realm_bot_creation_policy");
|
|
||||||
$bot_creation_policy_elem.data = () => "number";
|
|
||||||
|
|
||||||
let $subsection_elem = $(`#org-${CSS.escape(subsection)}`);
|
let $subsection_elem = $(`#org-${CSS.escape(subsection)}`);
|
||||||
$subsection_elem.set_find_results(".prop-element", [$bot_creation_policy_elem]);
|
|
||||||
|
|
||||||
patched = false;
|
|
||||||
submit_form.call({to_$: () => $(".save-discard-widget-button.save-button")}, ev);
|
|
||||||
assert.ok(patched);
|
|
||||||
|
|
||||||
let expected_value = {
|
|
||||||
bot_creation_policy: 1,
|
|
||||||
};
|
|
||||||
assert.deepEqual(data, expected_value);
|
|
||||||
|
|
||||||
subsection = "user-defaults";
|
subsection = "user-defaults";
|
||||||
stubs = createSaveButtons(subsection);
|
stubs = createSaveButtons(subsection);
|
||||||
@@ -155,7 +138,6 @@ function test_submit_settings_form(override, submit_form) {
|
|||||||
const $realm_default_language_elem = $("#id_realm_default_language");
|
const $realm_default_language_elem = $("#id_realm_default_language");
|
||||||
$realm_default_language_elem.val("en");
|
$realm_default_language_elem.val("en");
|
||||||
$realm_default_language_elem.attr("id", "id_realm_default_language");
|
$realm_default_language_elem.attr("id", "id_realm_default_language");
|
||||||
$realm_default_language_elem.data = () => "string";
|
|
||||||
|
|
||||||
$subsection_elem = $(`#org-${CSS.escape(subsection)}`);
|
$subsection_elem = $(`#org-${CSS.escape(subsection)}`);
|
||||||
$subsection_elem.set_find_results(".prop-element", [$realm_default_language_elem]);
|
$subsection_elem.set_find_results(".prop-element", [$realm_default_language_elem]);
|
||||||
@@ -163,7 +145,7 @@ function test_submit_settings_form(override, submit_form) {
|
|||||||
submit_form.call({to_$: () => $(".save-discard-widget-button.save-button")}, ev);
|
submit_form.call({to_$: () => $(".save-discard-widget-button.save-button")}, ev);
|
||||||
assert.ok(patched);
|
assert.ok(patched);
|
||||||
|
|
||||||
expected_value = {
|
const expected_value = {
|
||||||
default_language: "en",
|
default_language: "en",
|
||||||
};
|
};
|
||||||
assert.deepEqual(data, expected_value);
|
assert.deepEqual(data, expected_value);
|
||||||
@@ -566,12 +548,7 @@ test("set_up", ({override, override_rewire}) => {
|
|||||||
// elements involved in disabling the can_create_groups input.
|
// elements involved in disabling the can_create_groups input.
|
||||||
override(realm, "zulip_plan_is_not_limited", true);
|
override(realm, "zulip_plan_is_not_limited", true);
|
||||||
|
|
||||||
override_rewire(settings_components, "get_input_element_value", (elem) => {
|
override_rewire(settings_components, "get_input_element_value", (elem) => $(elem).val());
|
||||||
if ($(elem).data() === "number") {
|
|
||||||
return Number.parseInt($(elem).val(), 10);
|
|
||||||
}
|
|
||||||
return $(elem).val();
|
|
||||||
});
|
|
||||||
|
|
||||||
// TEST set_up() here, but this mostly just allows us to
|
// TEST set_up() here, but this mostly just allows us to
|
||||||
// get access to the click handlers.
|
// get access to the click handlers.
|
||||||
|
|||||||
@@ -512,10 +512,12 @@ class GroupSettingUpdateData(GroupSettingUpdateDataCore):
|
|||||||
can_access_all_users_group: int | AnonymousSettingGroupDict | None = None
|
can_access_all_users_group: int | AnonymousSettingGroupDict | None = None
|
||||||
can_add_custom_emoji_group: int | AnonymousSettingGroupDict | None = None
|
can_add_custom_emoji_group: int | AnonymousSettingGroupDict | None = None
|
||||||
can_add_subscribers_group: int | AnonymousSettingGroupDict | None = None
|
can_add_subscribers_group: int | AnonymousSettingGroupDict | None = None
|
||||||
|
can_create_bots_group: int | AnonymousSettingGroupDict | None = None
|
||||||
can_create_groups: int | AnonymousSettingGroupDict | None = None
|
can_create_groups: int | AnonymousSettingGroupDict | None = None
|
||||||
can_create_public_channel_group: int | AnonymousSettingGroupDict | None = None
|
can_create_public_channel_group: int | AnonymousSettingGroupDict | None = None
|
||||||
can_create_private_channel_group: int | AnonymousSettingGroupDict | None = None
|
can_create_private_channel_group: int | AnonymousSettingGroupDict | None = None
|
||||||
can_create_web_public_channel_group: int | AnonymousSettingGroupDict | None = None
|
can_create_web_public_channel_group: int | AnonymousSettingGroupDict | None = None
|
||||||
|
can_create_write_only_bots_group: int | AnonymousSettingGroupDict | None = None
|
||||||
can_delete_any_message_group: int | AnonymousSettingGroupDict | None = None
|
can_delete_any_message_group: int | AnonymousSettingGroupDict | None = None
|
||||||
can_delete_own_message_group: int | AnonymousSettingGroupDict | None = None
|
can_delete_own_message_group: int | AnonymousSettingGroupDict | None = None
|
||||||
can_invite_users_group: int | AnonymousSettingGroupDict | None = None
|
can_invite_users_group: int | AnonymousSettingGroupDict | None = None
|
||||||
|
|||||||
@@ -19,11 +19,7 @@ from zulip_bots.custom_exceptions import ConfigValidationError
|
|||||||
from zerver.lib.avatar import avatar_url, get_avatar_field, get_avatar_for_inaccessible_user
|
from zerver.lib.avatar import avatar_url, get_avatar_field, get_avatar_for_inaccessible_user
|
||||||
from zerver.lib.cache import cache_with_key, get_cross_realm_dicts_key
|
from zerver.lib.cache import cache_with_key, get_cross_realm_dicts_key
|
||||||
from zerver.lib.create_user import get_dummy_email_address_for_display_regex
|
from zerver.lib.create_user import get_dummy_email_address_for_display_regex
|
||||||
from zerver.lib.exceptions import (
|
from zerver.lib.exceptions import JsonableError, OrganizationOwnerRequiredError
|
||||||
JsonableError,
|
|
||||||
OrganizationAdministratorRequiredError,
|
|
||||||
OrganizationOwnerRequiredError,
|
|
||||||
)
|
|
||||||
from zerver.lib.string_validation import check_string_is_printable
|
from zerver.lib.string_validation import check_string_is_printable
|
||||||
from zerver.lib.timestamp import timestamp_to_datetime
|
from zerver.lib.timestamp import timestamp_to_datetime
|
||||||
from zerver.lib.timezone import canonicalize_timezone
|
from zerver.lib.timezone import canonicalize_timezone
|
||||||
@@ -41,7 +37,7 @@ from zerver.models import (
|
|||||||
UserProfile,
|
UserProfile,
|
||||||
)
|
)
|
||||||
from zerver.models.groups import SystemGroups
|
from zerver.models.groups import SystemGroups
|
||||||
from zerver.models.realms import BotCreationPolicyEnum, get_fake_email_domain, require_unique_names
|
from zerver.models.realms import get_fake_email_domain, require_unique_names
|
||||||
from zerver.models.users import (
|
from zerver.models.users import (
|
||||||
active_non_guest_user_ids,
|
active_non_guest_user_ids,
|
||||||
active_user_ids,
|
active_user_ids,
|
||||||
@@ -188,20 +184,22 @@ def add_service(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_bot_creation_policy(user_profile: UserProfile, bot_type: int) -> None:
|
def check_can_create_bot(user_profile: UserProfile, bot_type: int) -> None:
|
||||||
# Realm administrators can always add bot
|
if user_has_permission_for_group_setting(
|
||||||
if user_profile.is_realm_admin:
|
user_profile.realm.can_create_bots_group,
|
||||||
|
user_profile,
|
||||||
|
Realm.REALM_PERMISSION_GROUP_SETTINGS["can_create_bots_group"],
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
if user_profile.realm.bot_creation_policy == BotCreationPolicyEnum.EVERYONE:
|
if bot_type == UserProfile.INCOMING_WEBHOOK_BOT and user_has_permission_for_group_setting(
|
||||||
return
|
user_profile.realm.can_create_write_only_bots_group,
|
||||||
if user_profile.realm.bot_creation_policy == BotCreationPolicyEnum.ADMINS_ONLY:
|
user_profile,
|
||||||
raise OrganizationAdministratorRequiredError
|
Realm.REALM_PERMISSION_GROUP_SETTINGS["can_create_write_only_bots_group"],
|
||||||
if (
|
|
||||||
user_profile.realm.bot_creation_policy == BotCreationPolicyEnum.LIMIT_GENERIC_BOTS
|
|
||||||
and bot_type == UserProfile.DEFAULT_BOT
|
|
||||||
):
|
):
|
||||||
raise OrganizationAdministratorRequiredError
|
return
|
||||||
|
|
||||||
|
raise JsonableError(_("Insufficient permission"))
|
||||||
|
|
||||||
|
|
||||||
def check_valid_bot_type(user_profile: UserProfile, bot_type: int) -> None:
|
def check_valid_bot_type(user_profile: UserProfile, bot_type: int) -> None:
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 5.0.10 on 2025-01-23 15:14
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0655_alter_stream_can_add_subscribers_group"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="realm",
|
||||||
|
name="can_create_bots_group",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.RESTRICT,
|
||||||
|
related_name="+",
|
||||||
|
to="zerver.usergroup",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="realm",
|
||||||
|
name="can_create_write_only_bots_group",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.RESTRICT,
|
||||||
|
related_name="+",
|
||||||
|
to="zerver.usergroup",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
from django.db import migrations
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
from django.db.migrations.state import StateApps
|
||||||
|
from django.db.models import OuterRef
|
||||||
|
|
||||||
|
|
||||||
|
def set_default_value_for_can_create_bots_group(
|
||||||
|
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
|
||||||
|
) -> None:
|
||||||
|
Realm = apps.get_model("zerver", "Realm")
|
||||||
|
NamedUserGroup = apps.get_model("zerver", "NamedUserGroup")
|
||||||
|
|
||||||
|
bot_creation_policy_to_group_name = {
|
||||||
|
1: "role:members",
|
||||||
|
2: "role:administrators",
|
||||||
|
3: "role:administrators",
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, group_name in bot_creation_policy_to_group_name.items():
|
||||||
|
Realm.objects.filter(can_create_bots_group=None, bot_creation_policy=id).update(
|
||||||
|
can_create_bots_group=NamedUserGroup.objects.filter(
|
||||||
|
name=group_name, realm=OuterRef("id"), is_system_group=True
|
||||||
|
).values("pk")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_default_value_for_can_create_write_only_bots_group(
|
||||||
|
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
|
||||||
|
) -> None:
|
||||||
|
Realm = apps.get_model("zerver", "Realm")
|
||||||
|
NamedUserGroup = apps.get_model("zerver", "NamedUserGroup")
|
||||||
|
|
||||||
|
bot_creation_policy_to_group_name = {
|
||||||
|
1: "role:members",
|
||||||
|
2: "role:members",
|
||||||
|
3: "role:administrators",
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, group_name in bot_creation_policy_to_group_name.items():
|
||||||
|
Realm.objects.filter(can_create_write_only_bots_group=None, bot_creation_policy=id).update(
|
||||||
|
can_create_write_only_bots_group=NamedUserGroup.objects.filter(
|
||||||
|
name=group_name, realm=OuterRef("id"), is_system_group=True
|
||||||
|
).values("pk")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
atomic = False
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0656_realm_can_create_bots_group_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
set_default_value_for_can_create_bots_group,
|
||||||
|
elidable=True,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
),
|
||||||
|
migrations.RunPython(
|
||||||
|
set_default_value_for_can_create_write_only_bots_group,
|
||||||
|
elidable=True,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 5.0.10 on 2025-01-23 15:23
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0657_set_default_value_for_can_create_bots_group_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="realm",
|
||||||
|
name="can_create_bots_group",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.RESTRICT,
|
||||||
|
related_name="+",
|
||||||
|
to="zerver.usergroup",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="realm",
|
||||||
|
name="can_create_write_only_bots_group",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.RESTRICT,
|
||||||
|
related_name="+",
|
||||||
|
to="zerver.usergroup",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -340,6 +340,16 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
|||||||
"UserGroup", on_delete=models.RESTRICT, related_name="+"
|
"UserGroup", on_delete=models.RESTRICT, related_name="+"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# UserGroup which is allowed to create bots.
|
||||||
|
can_create_bots_group = models.ForeignKey(
|
||||||
|
"UserGroup", on_delete=models.RESTRICT, related_name="+"
|
||||||
|
)
|
||||||
|
|
||||||
|
# UserGroup which is allowed to create incoming webhooks.
|
||||||
|
can_create_write_only_bots_group = models.ForeignKey(
|
||||||
|
"UserGroup", on_delete=models.RESTRICT, related_name="+"
|
||||||
|
)
|
||||||
|
|
||||||
# Global policy for who is allowed to use wildcard mentions in
|
# Global policy for who is allowed to use wildcard mentions in
|
||||||
# streams with a large number of subscribers. Anyone can use
|
# streams with a large number of subscribers. Anyone can use
|
||||||
# wildcard mentions in small streams regardless of this setting.
|
# wildcard mentions in small streams regardless of this setting.
|
||||||
@@ -697,6 +707,13 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
|||||||
allow_everyone_group=False,
|
allow_everyone_group=False,
|
||||||
default_group_name=SystemGroups.MEMBERS,
|
default_group_name=SystemGroups.MEMBERS,
|
||||||
),
|
),
|
||||||
|
can_create_bots_group=GroupPermissionSetting(
|
||||||
|
require_system_group=False,
|
||||||
|
allow_internet_group=False,
|
||||||
|
allow_nobody_group=True,
|
||||||
|
allow_everyone_group=False,
|
||||||
|
default_group_name=SystemGroups.MEMBERS,
|
||||||
|
),
|
||||||
can_create_groups=GroupPermissionSetting(
|
can_create_groups=GroupPermissionSetting(
|
||||||
require_system_group=False,
|
require_system_group=False,
|
||||||
allow_internet_group=False,
|
allow_internet_group=False,
|
||||||
@@ -731,6 +748,13 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
|||||||
SystemGroups.NOBODY,
|
SystemGroups.NOBODY,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
can_create_write_only_bots_group=GroupPermissionSetting(
|
||||||
|
require_system_group=False,
|
||||||
|
allow_internet_group=False,
|
||||||
|
allow_nobody_group=True,
|
||||||
|
allow_everyone_group=False,
|
||||||
|
default_group_name=SystemGroups.MEMBERS,
|
||||||
|
),
|
||||||
can_delete_any_message_group=GroupPermissionSetting(
|
can_delete_any_message_group=GroupPermissionSetting(
|
||||||
require_system_group=False,
|
require_system_group=False,
|
||||||
allow_internet_group=False,
|
allow_internet_group=False,
|
||||||
@@ -1180,6 +1204,8 @@ def get_realm_with_settings(realm_id: int) -> Realm:
|
|||||||
"can_add_custom_emoji_group__named_user_group",
|
"can_add_custom_emoji_group__named_user_group",
|
||||||
"can_add_subscribers_group",
|
"can_add_subscribers_group",
|
||||||
"can_add_subscribers_group__named_user_group",
|
"can_add_subscribers_group__named_user_group",
|
||||||
|
"can_create_bots_group",
|
||||||
|
"can_create_bots_group__named_user_group",
|
||||||
"can_create_groups",
|
"can_create_groups",
|
||||||
"can_create_groups__named_user_group",
|
"can_create_groups__named_user_group",
|
||||||
"can_create_public_channel_group",
|
"can_create_public_channel_group",
|
||||||
@@ -1188,6 +1214,8 @@ def get_realm_with_settings(realm_id: int) -> Realm:
|
|||||||
"can_create_private_channel_group__named_user_group",
|
"can_create_private_channel_group__named_user_group",
|
||||||
"can_create_web_public_channel_group",
|
"can_create_web_public_channel_group",
|
||||||
"can_create_web_public_channel_group__named_user_group",
|
"can_create_web_public_channel_group__named_user_group",
|
||||||
|
"can_create_write_only_bots_group",
|
||||||
|
"can_create_write_only_bots_group__named_user_group",
|
||||||
"can_delete_any_message_group",
|
"can_delete_any_message_group",
|
||||||
"can_delete_any_message_group__named_user_group",
|
"can_delete_any_message_group__named_user_group",
|
||||||
"can_delete_own_message_group",
|
"can_delete_own_message_group",
|
||||||
|
|||||||
@@ -792,20 +792,19 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def allowed_bot_types(self) -> list[int]:
|
def allowed_bot_types(self) -> list[int]:
|
||||||
from zerver.models.realms import BotCreationPolicyEnum
|
|
||||||
|
|
||||||
allowed_bot_types = []
|
allowed_bot_types = []
|
||||||
if (
|
if self.has_permission("can_create_bots_group"):
|
||||||
self.is_realm_admin
|
allowed_bot_types.extend(
|
||||||
or self.realm.bot_creation_policy != BotCreationPolicyEnum.LIMIT_GENERIC_BOTS
|
[
|
||||||
):
|
UserProfile.DEFAULT_BOT,
|
||||||
allowed_bot_types.append(UserProfile.DEFAULT_BOT)
|
|
||||||
allowed_bot_types += [
|
|
||||||
UserProfile.INCOMING_WEBHOOK_BOT,
|
UserProfile.INCOMING_WEBHOOK_BOT,
|
||||||
UserProfile.OUTGOING_WEBHOOK_BOT,
|
UserProfile.OUTGOING_WEBHOOK_BOT,
|
||||||
]
|
]
|
||||||
|
)
|
||||||
if settings.EMBEDDED_BOTS_ENABLED:
|
if settings.EMBEDDED_BOTS_ENABLED:
|
||||||
allowed_bot_types.append(UserProfile.EMBEDDED_BOT)
|
allowed_bot_types.append(UserProfile.EMBEDDED_BOT)
|
||||||
|
elif self.has_permission("can_create_write_only_bots_group"):
|
||||||
|
allowed_bot_types.append(UserProfile.INCOMING_WEBHOOK_BOT)
|
||||||
return allowed_bot_types
|
return allowed_bot_types
|
||||||
|
|
||||||
def email_address_is_realm_public(self) -> bool:
|
def email_address_is_realm_public(self) -> bool:
|
||||||
|
|||||||
@@ -4480,6 +4480,29 @@ paths:
|
|||||||
**Changes**: New in Zulip 10.0 (feature level 299). Previously
|
**Changes**: New in Zulip 10.0 (feature level 299). Previously
|
||||||
`user_group_edit_policy` field used to control the permission
|
`user_group_edit_policy` field used to control the permission
|
||||||
to create user groups.
|
to create user groups.
|
||||||
|
can_create_bots_group:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
|
- description: |
|
||||||
|
A [group-setting value](/api/group-setting-values) defining
|
||||||
|
the set of users who have permission to create all types of bot users
|
||||||
|
in the organization. See also `can_create_write_only_bots_group`.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 344). Previously, this
|
||||||
|
permission was controlled by the enum `bot_creation_policy`. Values
|
||||||
|
were 1=Members, 2=Generic bots limited to administrators, 3=Administrators.
|
||||||
|
can_create_write_only_bots_group:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
|
- description: |
|
||||||
|
A [group-setting value](/api/group-setting-values) defining
|
||||||
|
the set of users who have permission to create bot users that
|
||||||
|
can only send messages in the organization, i.e. incoming webhooks,
|
||||||
|
in addition to the users who are present in `can_create_bots_group`.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 344). Previously, this
|
||||||
|
permission was controlled by the enum `bot_creation_policy`. Values
|
||||||
|
were 1=Members, 2=Generic bots limited to administrators, 3=Administrators.
|
||||||
can_create_public_channel_group:
|
can_create_public_channel_group:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: "#/components/schemas/GroupSettingValue"
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
@@ -16730,6 +16753,29 @@ paths:
|
|||||||
**Changes**: New in Zulip 10.0 (feature level 299). Previously
|
**Changes**: New in Zulip 10.0 (feature level 299). Previously
|
||||||
`realm_user_group_edit_policy` field used to control the
|
`realm_user_group_edit_policy` field used to control the
|
||||||
permission to create user groups.
|
permission to create user groups.
|
||||||
|
realm_can_create_bots_group:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
|
- description: |
|
||||||
|
A [group-setting value](/api/group-setting-values) defining
|
||||||
|
the set of users who have permission to create all types of bot users
|
||||||
|
in the organization. See also `can_create_write_only_bots_group`.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 344). Previously, this
|
||||||
|
permission was controlled by the enum `bot_creation_policy`. Values
|
||||||
|
were 1=Members, 2=Generic bots limited to administrators, 3=Administrators.
|
||||||
|
realm_can_create_write_only_bots_group:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
|
- description: |
|
||||||
|
A [group-setting value](/api/group-setting-values) defining
|
||||||
|
the set of users who have permission to create bot users that
|
||||||
|
can only send messages in the organization, i.e. incoming webhooks,
|
||||||
|
in addition to the users who are present in `can_create_bots_group`.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 344). Previously, this
|
||||||
|
permission was controlled by the enum `bot_creation_policy`. Values
|
||||||
|
were 1=Members, 2=Generic bots limited to administrators, 3=Administrators.
|
||||||
realm_can_manage_all_groups:
|
realm_can_manage_all_groups:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: "#/components/schemas/GroupSettingValue"
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import filecmp
|
import filecmp
|
||||||
import os
|
import os
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
@@ -9,8 +9,12 @@ from django.test import override_settings
|
|||||||
from zulip_bots.custom_exceptions import ConfigValidationError
|
from zulip_bots.custom_exceptions import ConfigValidationError
|
||||||
|
|
||||||
from zerver.actions.bots import do_change_bot_owner, do_change_default_sending_stream
|
from zerver.actions.bots import do_change_bot_owner, do_change_default_sending_stream
|
||||||
from zerver.actions.realm_settings import do_set_realm_user_default_setting
|
from zerver.actions.realm_settings import (
|
||||||
|
do_change_realm_permission_group_setting,
|
||||||
|
do_set_realm_user_default_setting,
|
||||||
|
)
|
||||||
from zerver.actions.streams import do_change_stream_permission
|
from zerver.actions.streams import do_change_stream_permission
|
||||||
|
from zerver.actions.user_groups import check_add_user_group
|
||||||
from zerver.actions.users import do_change_can_create_users, do_change_user_role, do_deactivate_user
|
from zerver.actions.users import do_change_can_create_users, do_change_user_role, do_deactivate_user
|
||||||
from zerver.lib.bot_config import ConfigError, get_bot_config
|
from zerver.lib.bot_config import ConfigError, get_bot_config
|
||||||
from zerver.lib.bot_lib import get_bot_handler
|
from zerver.lib.bot_lib import get_bot_handler
|
||||||
@@ -21,7 +25,8 @@ from zerver.lib.utils import assert_is_not_none
|
|||||||
from zerver.lib.webhooks.common import WebhookConfigOption
|
from zerver.lib.webhooks.common import WebhookConfigOption
|
||||||
from zerver.models import RealmUserDefault, Service, Subscription, UserProfile
|
from zerver.models import RealmUserDefault, Service, Subscription, UserProfile
|
||||||
from zerver.models.bots import get_bot_services
|
from zerver.models.bots import get_bot_services
|
||||||
from zerver.models.realms import BotCreationPolicyEnum, get_realm
|
from zerver.models.groups import NamedUserGroup, SystemGroups
|
||||||
|
from zerver.models.realms import get_realm
|
||||||
from zerver.models.streams import get_stream
|
from zerver.models.streams import get_stream
|
||||||
from zerver.models.users import bot_owner_user_ids, get_user, is_cross_realm_bot_email
|
from zerver.models.users import bot_owner_user_ids, get_user, is_cross_realm_bot_email
|
||||||
|
|
||||||
@@ -808,101 +813,241 @@ 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_no_generic_bots_allowed_for_non_admins(self) -> None:
|
def check_user_can_create_bot(
|
||||||
|
self, user: str, bot_name: str, bot_type: int, error_msg: Optional["str"] = None
|
||||||
|
) -> None:
|
||||||
bot_info = {
|
bot_info = {
|
||||||
"full_name": "The Bot of Hamlet",
|
"full_name": bot_name,
|
||||||
"short_name": "hambot",
|
"short_name": bot_name,
|
||||||
"bot_type": 1,
|
"bot_type": bot_type,
|
||||||
}
|
}
|
||||||
bot_email = "hambot-bot@zulip.testserver"
|
bot_email = f"{bot_name}-bot@zulip.testserver"
|
||||||
bot_realm = get_realm("zulip")
|
bot_realm = get_realm("zulip")
|
||||||
bot_realm.bot_creation_policy = BotCreationPolicyEnum.LIMIT_GENERIC_BOTS
|
self.login(user)
|
||||||
bot_realm.save(update_fields=["bot_creation_policy"])
|
|
||||||
|
|
||||||
# A regular user cannot create a generic bot
|
|
||||||
self.login("hamlet")
|
|
||||||
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)
|
if error_msg is not None:
|
||||||
self.assert_json_error(result, "Must be an organization administrator")
|
self.assert_json_error(result, error_msg)
|
||||||
|
else:
|
||||||
# But can create an incoming webhook
|
self.assert_json_success(result)
|
||||||
self.assert_num_bots_equal(0)
|
|
||||||
self.create_bot(bot_type=UserProfile.INCOMING_WEBHOOK_BOT)
|
|
||||||
self.assert_num_bots_equal(1)
|
|
||||||
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, bot_type)
|
||||||
|
self.assertEqual(profile.bot_owner, self.example_user(user))
|
||||||
|
|
||||||
def test_no_generic_bot_reactivation_allowed_for_non_admins(self) -> None:
|
def test_can_create_write_only_bots_group(self) -> None:
|
||||||
self.login("hamlet")
|
"""
|
||||||
self.create_bot(bot_type=UserProfile.DEFAULT_BOT)
|
The `can_create_write_only_bots_group` realm setting works properly.
|
||||||
|
"""
|
||||||
|
realm = get_realm("zulip")
|
||||||
|
hamlet = self.example_user("hamlet")
|
||||||
|
cordelia = self.example_user("cordelia")
|
||||||
|
|
||||||
bot_realm = get_realm("zulip")
|
administrators_system_group = NamedUserGroup.objects.get(
|
||||||
bot_realm.bot_creation_policy = BotCreationPolicyEnum.LIMIT_GENERIC_BOTS
|
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
|
||||||
bot_realm.save(update_fields=["bot_creation_policy"])
|
)
|
||||||
|
moderators_system_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.MODERATORS, realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
|
members_system_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.MEMBERS, realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
|
|
||||||
bot_email = "hambot-bot@zulip.testserver"
|
do_change_realm_permission_group_setting(
|
||||||
bot_user = get_user(bot_email, bot_realm)
|
realm,
|
||||||
do_deactivate_user(bot_user, acting_user=None)
|
"can_create_bots_group",
|
||||||
|
administrators_system_group,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
|
||||||
# A regular user cannot reactivate a generic bot
|
do_change_realm_permission_group_setting(
|
||||||
self.assert_num_bots_equal(0)
|
realm,
|
||||||
result = self.client_post(f"/json/users/{bot_user.id}/reactivate")
|
"can_create_write_only_bots_group",
|
||||||
self.assert_json_error(result, "Must be an organization administrator")
|
members_system_group,
|
||||||
self.assert_num_bots_equal(0)
|
acting_user=None,
|
||||||
|
)
|
||||||
|
|
||||||
def test_no_generic_bots_allowed_for_admins(self) -> None:
|
self.check_user_can_create_bot("hamlet", "testbot-1", UserProfile.INCOMING_WEBHOOK_BOT)
|
||||||
bot_email = "hambot-bot@zulip.testserver"
|
self.check_user_can_create_bot(
|
||||||
bot_realm = get_realm("zulip")
|
"polonius", "testbot-2", UserProfile.INCOMING_WEBHOOK_BOT, "Not allowed for guest users"
|
||||||
bot_realm.bot_creation_policy = BotCreationPolicyEnum.LIMIT_GENERIC_BOTS
|
)
|
||||||
bot_realm.save(update_fields=["bot_creation_policy"])
|
|
||||||
|
|
||||||
# An administrator can create any type of bot
|
do_change_realm_permission_group_setting(
|
||||||
self.login("iago")
|
realm,
|
||||||
self.assert_num_bots_equal(0)
|
"can_create_write_only_bots_group",
|
||||||
self.create_bot(bot_type=UserProfile.DEFAULT_BOT)
|
moderators_system_group,
|
||||||
self.assert_num_bots_equal(1)
|
acting_user=None,
|
||||||
profile = get_user(bot_email, bot_realm)
|
)
|
||||||
self.assertEqual(profile.bot_type, UserProfile.DEFAULT_BOT)
|
self.check_user_can_create_bot("shiva", "testbot-3", UserProfile.INCOMING_WEBHOOK_BOT)
|
||||||
|
self.check_user_can_create_bot("iago", "testbot-4", UserProfile.INCOMING_WEBHOOK_BOT)
|
||||||
|
self.check_user_can_create_bot(
|
||||||
|
"hamlet", "testbot-5", UserProfile.INCOMING_WEBHOOK_BOT, "Insufficient permission"
|
||||||
|
)
|
||||||
|
|
||||||
def test_no_bots_allowed_for_non_admins(self) -> None:
|
do_change_realm_permission_group_setting(
|
||||||
bot_info = {
|
realm,
|
||||||
"full_name": "The Bot of Hamlet",
|
"can_create_write_only_bots_group",
|
||||||
"short_name": "hambot",
|
administrators_system_group,
|
||||||
"bot_type": 1,
|
acting_user=None,
|
||||||
}
|
)
|
||||||
bot_realm = get_realm("zulip")
|
self.check_user_can_create_bot("iago", "testbot-5", UserProfile.INCOMING_WEBHOOK_BOT)
|
||||||
bot_realm.bot_creation_policy = BotCreationPolicyEnum.ADMINS_ONLY
|
self.check_user_can_create_bot(
|
||||||
bot_realm.save(update_fields=["bot_creation_policy"])
|
"shiva", "testbot-6", UserProfile.INCOMING_WEBHOOK_BOT, "Insufficient permission"
|
||||||
|
)
|
||||||
|
|
||||||
# A regular user cannot create a generic bot
|
# Test for checking setting for non-system user group.
|
||||||
self.login("hamlet")
|
user_group = check_add_user_group(
|
||||||
self.assert_num_bots_equal(0)
|
realm, "new_group", [hamlet, cordelia], acting_user=hamlet
|
||||||
result = self.client_post("/json/bots", bot_info)
|
)
|
||||||
self.assert_num_bots_equal(0)
|
do_change_realm_permission_group_setting(
|
||||||
self.assert_json_error(result, "Must be an organization administrator")
|
realm, "can_create_write_only_bots_group", user_group, acting_user=None
|
||||||
|
)
|
||||||
|
|
||||||
# Also, a regular user cannot create a incoming bot
|
# Hamlet and Cordelia are in the allowed user group, so can create bots.
|
||||||
bot_info["bot_type"] = 2
|
self.check_user_can_create_bot("hamlet", "testbot-6", UserProfile.INCOMING_WEBHOOK_BOT)
|
||||||
self.login("hamlet")
|
self.check_user_can_create_bot("cordelia", "testbot-7", UserProfile.INCOMING_WEBHOOK_BOT)
|
||||||
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:
|
# Shiva is not in the allowed user group, so cannot create bots.
|
||||||
bot_email = "hambot-bot@zulip.testserver"
|
self.check_user_can_create_bot(
|
||||||
bot_realm = get_realm("zulip")
|
"shiva", "testbot-8", UserProfile.INCOMING_WEBHOOK_BOT, "Insufficient permission"
|
||||||
bot_realm.bot_creation_policy = BotCreationPolicyEnum.ADMINS_ONLY
|
)
|
||||||
bot_realm.save(update_fields=["bot_creation_policy"])
|
# Iago is not in `can_create_write_only_bots_group` but is in `can_create_bots_group`,
|
||||||
|
# so can create any bot.
|
||||||
|
self.check_user_can_create_bot("iago", "testbot-8", UserProfile.INCOMING_WEBHOOK_BOT)
|
||||||
|
|
||||||
# An administrator can create any type of bot
|
# Test for checking the setting for anonymous user group.
|
||||||
self.login("iago")
|
anonymous_user_group = self.create_or_update_anonymous_group_for_setting(
|
||||||
self.assert_num_bots_equal(0)
|
[hamlet],
|
||||||
self.create_bot(bot_type=UserProfile.DEFAULT_BOT)
|
[administrators_system_group],
|
||||||
self.assert_num_bots_equal(1)
|
)
|
||||||
profile = get_user(bot_email, bot_realm)
|
do_change_realm_permission_group_setting(
|
||||||
self.assertEqual(profile.bot_type, UserProfile.DEFAULT_BOT)
|
realm,
|
||||||
|
"can_create_write_only_bots_group",
|
||||||
|
anonymous_user_group,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Hamlet is the direct member of the anonymous user group, so can create bots.
|
||||||
|
self.check_user_can_create_bot("hamlet", "testbot-9", UserProfile.INCOMING_WEBHOOK_BOT)
|
||||||
|
# Iago is in the `administrators_system_group` subgroup, so can create bots.
|
||||||
|
self.check_user_can_create_bot("iago", "testbot-10", UserProfile.INCOMING_WEBHOOK_BOT)
|
||||||
|
# Shiva is not in the anonymous user group, so cannot create bots.
|
||||||
|
self.check_user_can_create_bot(
|
||||||
|
"shiva", "testbot-11", UserProfile.INCOMING_WEBHOOK_BOT, "Insufficient permission"
|
||||||
|
)
|
||||||
|
|
||||||
|
do_change_realm_permission_group_setting(
|
||||||
|
realm,
|
||||||
|
"can_create_write_only_bots_group",
|
||||||
|
moderators_system_group,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Iago is in `can_create_bots_group`, so can create any bot.
|
||||||
|
self.check_user_can_create_bot("iago", "testbot-11", UserProfile.DEFAULT_BOT)
|
||||||
|
self.check_user_can_create_bot("iago", "testbot-12", UserProfile.INCOMING_WEBHOOK_BOT)
|
||||||
|
# Shiva is only in `can_create_write_only_bots_group`, so can only create
|
||||||
|
# "INCOMING_WEBHOOK_BOT".
|
||||||
|
self.check_user_can_create_bot(
|
||||||
|
"shiva", "testbot-13", UserProfile.DEFAULT_BOT, "Insufficient permission"
|
||||||
|
)
|
||||||
|
self.check_user_can_create_bot("shiva", "testbot-13", UserProfile.INCOMING_WEBHOOK_BOT)
|
||||||
|
# Hamlet is in neither of the group, so cannot create any bot.
|
||||||
|
self.check_user_can_create_bot(
|
||||||
|
"hamlet", "testbot-14", UserProfile.DEFAULT_BOT, "Insufficient permission"
|
||||||
|
)
|
||||||
|
self.check_user_can_create_bot(
|
||||||
|
"hamlet", "testbot-14", UserProfile.INCOMING_WEBHOOK_BOT, "Insufficient permission"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_can_create_bots_group(self) -> None:
|
||||||
|
"""
|
||||||
|
The `can_create_bots_group` realm setting works properly.
|
||||||
|
"""
|
||||||
|
realm = get_realm("zulip")
|
||||||
|
hamlet = self.example_user("hamlet")
|
||||||
|
cordelia = self.example_user("cordelia")
|
||||||
|
|
||||||
|
administrators_system_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
|
moderators_system_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.MODERATORS, realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
|
members_system_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.MEMBERS, realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
|
|
||||||
|
do_change_realm_permission_group_setting(
|
||||||
|
realm,
|
||||||
|
"can_create_bots_group",
|
||||||
|
members_system_group,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.check_user_can_create_bot("hamlet", "testbot-1", UserProfile.DEFAULT_BOT)
|
||||||
|
self.check_user_can_create_bot(
|
||||||
|
"polonius", "testbot-2", UserProfile.DEFAULT_BOT, "Not allowed for guest users"
|
||||||
|
)
|
||||||
|
|
||||||
|
do_change_realm_permission_group_setting(
|
||||||
|
realm,
|
||||||
|
"can_create_bots_group",
|
||||||
|
moderators_system_group,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
self.check_user_can_create_bot("shiva", "testbot-3", UserProfile.DEFAULT_BOT)
|
||||||
|
self.check_user_can_create_bot("iago", "testbot-4", UserProfile.DEFAULT_BOT)
|
||||||
|
self.check_user_can_create_bot(
|
||||||
|
"hamlet", "testbot-5", UserProfile.DEFAULT_BOT, "Insufficient permission"
|
||||||
|
)
|
||||||
|
|
||||||
|
do_change_realm_permission_group_setting(
|
||||||
|
realm,
|
||||||
|
"can_create_bots_group",
|
||||||
|
administrators_system_group,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
self.check_user_can_create_bot("iago", "testbot-5", UserProfile.DEFAULT_BOT)
|
||||||
|
self.check_user_can_create_bot(
|
||||||
|
"shiva", "testbot-6", UserProfile.DEFAULT_BOT, "Insufficient permission"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test for checking setting for non-system user group.
|
||||||
|
user_group = check_add_user_group(
|
||||||
|
realm, "new_group", [hamlet, cordelia], acting_user=hamlet
|
||||||
|
)
|
||||||
|
do_change_realm_permission_group_setting(
|
||||||
|
realm, "can_create_bots_group", user_group, acting_user=None
|
||||||
|
)
|
||||||
|
|
||||||
|
# Hamlet and Cordelia are in the allowed user group, so can create bots.
|
||||||
|
self.check_user_can_create_bot("hamlet", "testbot-6", UserProfile.DEFAULT_BOT)
|
||||||
|
self.check_user_can_create_bot("cordelia", "testbot-7", UserProfile.DEFAULT_BOT)
|
||||||
|
|
||||||
|
# Iago is not in the allowed user group, so cannot create bots.
|
||||||
|
self.check_user_can_create_bot(
|
||||||
|
"iago", "testbot-8", UserProfile.DEFAULT_BOT, "Insufficient permission"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test for checking the setting for anonymous user group.
|
||||||
|
anonymous_user_group = self.create_or_update_anonymous_group_for_setting(
|
||||||
|
[hamlet],
|
||||||
|
[administrators_system_group],
|
||||||
|
)
|
||||||
|
do_change_realm_permission_group_setting(
|
||||||
|
realm,
|
||||||
|
"can_create_bots_group",
|
||||||
|
anonymous_user_group,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Hamlet is the direct member of the anonymous user group, so can create bots.
|
||||||
|
self.check_user_can_create_bot("hamlet", "testbot-8", UserProfile.DEFAULT_BOT)
|
||||||
|
# Iago is in the `administrators_system_group` subgroup, so can create bots.
|
||||||
|
self.check_user_can_create_bot("iago", "testbot-9", UserProfile.DEFAULT_BOT)
|
||||||
|
# Shiva is not in the anonymous user group, so cannot create bots.
|
||||||
|
self.check_user_can_create_bot(
|
||||||
|
"shiva", "testbot-10", UserProfile.DEFAULT_BOT, "Insufficient permission"
|
||||||
|
)
|
||||||
|
|
||||||
def test_reactivating_bot_with_deactivated_owner(self) -> None:
|
def test_reactivating_bot_with_deactivated_owner(self) -> None:
|
||||||
self.login("hamlet")
|
self.login("hamlet")
|
||||||
|
|||||||
@@ -131,10 +131,12 @@ class HomeTest(ZulipTestCase):
|
|||||||
"realm_can_access_all_users_group",
|
"realm_can_access_all_users_group",
|
||||||
"realm_can_add_custom_emoji_group",
|
"realm_can_add_custom_emoji_group",
|
||||||
"realm_can_add_subscribers_group",
|
"realm_can_add_subscribers_group",
|
||||||
|
"realm_can_create_bots_group",
|
||||||
"realm_can_create_groups",
|
"realm_can_create_groups",
|
||||||
"realm_can_create_private_channel_group",
|
"realm_can_create_private_channel_group",
|
||||||
"realm_can_create_public_channel_group",
|
"realm_can_create_public_channel_group",
|
||||||
"realm_can_create_web_public_channel_group",
|
"realm_can_create_web_public_channel_group",
|
||||||
|
"realm_can_create_write_only_bots_group",
|
||||||
"realm_can_delete_any_message_group",
|
"realm_can_delete_any_message_group",
|
||||||
"realm_can_delete_own_message_group",
|
"realm_can_delete_own_message_group",
|
||||||
"realm_can_invite_users_group",
|
"realm_can_invite_users_group",
|
||||||
@@ -275,7 +277,7 @@ class HomeTest(ZulipTestCase):
|
|||||||
|
|
||||||
# Verify succeeds once logged-in
|
# Verify succeeds once logged-in
|
||||||
with (
|
with (
|
||||||
self.assert_database_query_count(54),
|
self.assert_database_query_count(59),
|
||||||
patch("zerver.lib.cache.cache_set") as cache_mock,
|
patch("zerver.lib.cache.cache_set") as cache_mock,
|
||||||
):
|
):
|
||||||
result = self._get_home_page(stream="Denmark")
|
result = self._get_home_page(stream="Denmark")
|
||||||
@@ -580,7 +582,7 @@ class HomeTest(ZulipTestCase):
|
|||||||
# Verify number of queries for Realm admin isn't much higher than for normal users.
|
# Verify number of queries for Realm admin isn't much higher than for normal users.
|
||||||
self.login("iago")
|
self.login("iago")
|
||||||
with (
|
with (
|
||||||
self.assert_database_query_count(54),
|
self.assert_database_query_count(59),
|
||||||
patch("zerver.lib.cache.cache_set") as cache_mock,
|
patch("zerver.lib.cache.cache_set") as cache_mock,
|
||||||
):
|
):
|
||||||
result = self._get_home_page()
|
result = self._get_home_page()
|
||||||
@@ -612,7 +614,7 @@ class HomeTest(ZulipTestCase):
|
|||||||
self._get_home_page()
|
self._get_home_page()
|
||||||
|
|
||||||
# Then for the second page load, measure the number of queries.
|
# Then for the second page load, measure the number of queries.
|
||||||
with self.assert_database_query_count(49):
|
with self.assert_database_query_count(54):
|
||||||
result = self._get_home_page()
|
result = self._get_home_page()
|
||||||
|
|
||||||
# Do a sanity check that our new streams were in the payload.
|
# Do a sanity check that our new streams were in the payload.
|
||||||
|
|||||||
@@ -132,10 +132,12 @@ def update_realm(
|
|||||||
digest_emails_enabled: Json[bool] | None = None,
|
digest_emails_enabled: Json[bool] | None = None,
|
||||||
message_content_allowed_in_email_notifications: Json[bool] | None = None,
|
message_content_allowed_in_email_notifications: Json[bool] | None = None,
|
||||||
bot_creation_policy: Json[BotCreationPolicyEnum] | None = None,
|
bot_creation_policy: Json[BotCreationPolicyEnum] | None = None,
|
||||||
|
can_create_bots_group: Json[GroupSettingChangeRequest] | None = None,
|
||||||
can_create_groups: Json[GroupSettingChangeRequest] | None = None,
|
can_create_groups: Json[GroupSettingChangeRequest] | None = None,
|
||||||
can_create_public_channel_group: Json[GroupSettingChangeRequest] | None = None,
|
can_create_public_channel_group: Json[GroupSettingChangeRequest] | None = None,
|
||||||
can_create_private_channel_group: Json[GroupSettingChangeRequest] | None = None,
|
can_create_private_channel_group: Json[GroupSettingChangeRequest] | None = None,
|
||||||
can_create_web_public_channel_group: Json[GroupSettingChangeRequest] | None = None,
|
can_create_web_public_channel_group: Json[GroupSettingChangeRequest] | None = None,
|
||||||
|
can_create_write_only_bots_group: Json[GroupSettingChangeRequest] | None = None,
|
||||||
can_invite_users_group: Json[GroupSettingChangeRequest] | None = None,
|
can_invite_users_group: Json[GroupSettingChangeRequest] | None = None,
|
||||||
can_manage_all_groups: Json[GroupSettingChangeRequest] | None = None,
|
can_manage_all_groups: Json[GroupSettingChangeRequest] | None = None,
|
||||||
can_move_messages_between_channels_group: Json[GroupSettingChangeRequest] | None = None,
|
can_move_messages_between_channels_group: Json[GroupSettingChangeRequest] | None = None,
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ from zerver.lib.users import (
|
|||||||
access_user_by_email,
|
access_user_by_email,
|
||||||
access_user_by_id,
|
access_user_by_id,
|
||||||
add_service,
|
add_service,
|
||||||
check_bot_creation_policy,
|
|
||||||
check_bot_name_available,
|
check_bot_name_available,
|
||||||
check_can_access_user,
|
check_can_access_user,
|
||||||
|
check_can_create_bot,
|
||||||
check_full_name,
|
check_full_name,
|
||||||
check_short_name,
|
check_short_name,
|
||||||
check_valid_bot_config,
|
check_valid_bot_config,
|
||||||
@@ -189,7 +189,7 @@ def reactivate_user_backend(
|
|||||||
)
|
)
|
||||||
if target.is_bot:
|
if target.is_bot:
|
||||||
assert target.bot_type is not None
|
assert target.bot_type is not None
|
||||||
check_bot_creation_policy(user_profile, target.bot_type)
|
check_can_create_bot(user_profile, target.bot_type)
|
||||||
check_bot_name_available(user_profile.realm_id, target.full_name, is_activation=True)
|
check_bot_name_available(user_profile.realm_id, target.full_name, is_activation=True)
|
||||||
do_reactivate_user(target, acting_user=user_profile)
|
do_reactivate_user(target, acting_user=user_profile)
|
||||||
return json_success(request)
|
return json_success(request)
|
||||||
@@ -614,7 +614,7 @@ def add_bot_backend(
|
|||||||
is_activation=False,
|
is_activation=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
check_bot_creation_policy(user_profile, bot_type)
|
check_can_create_bot(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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user