mirror of https://github.com/zulip/zulip.git
settings: Add `can_invite_users_group` realm setting.
Added `can_invite_users_group` realm setting to replace `invite_to_realm_policy`.
This commit is contained in:
parent
4e6d098cda
commit
4e89b4a88c
|
@ -20,6 +20,15 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 10.0
|
## Changes in Zulip 10.0
|
||||||
|
|
||||||
|
**Feature level 321**
|
||||||
|
|
||||||
|
* `PATCH /realm`, [`GET /events`](/api/get-events),
|
||||||
|
[`POST /register`](/api/register-queue):
|
||||||
|
Added `can_invite_users_group` realm setting which is a
|
||||||
|
[group-setting value](/api/group-setting-values) describing the set of users
|
||||||
|
with permission to send email invitations for inviting other users to the
|
||||||
|
organization.
|
||||||
|
|
||||||
**Feature level 320**
|
**Feature level 320**
|
||||||
|
|
||||||
* [`GET /users/me/subscriptions`](/api/get-subscriptions),
|
* [`GET /users/me/subscriptions`](/api/get-subscriptions),
|
||||||
|
|
|
@ -34,9 +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 = (
|
API_FEATURE_LEVEL = 321 # Last bumped for can_invite_users_group
|
||||||
320 # Last bumped for supporting anonymous groups for can_remove_subscribers_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
|
||||||
|
|
|
@ -233,7 +233,6 @@ export function dispatch_normal_event(event) {
|
||||||
disallow_disposable_email_addresses: noop,
|
disallow_disposable_email_addresses: noop,
|
||||||
inline_image_preview: noop,
|
inline_image_preview: noop,
|
||||||
inline_url_embed_preview: noop,
|
inline_url_embed_preview: noop,
|
||||||
invite_to_realm_policy: noop,
|
|
||||||
invite_required: noop,
|
invite_required: noop,
|
||||||
mandatory_topics: noop,
|
mandatory_topics: noop,
|
||||||
message_content_edit_limit_seconds: noop,
|
message_content_edit_limit_seconds: noop,
|
||||||
|
@ -273,12 +272,6 @@ export function dispatch_normal_event(event) {
|
||||||
electron_bridge?.send_event("realm_name", event.value);
|
electron_bridge?.send_event("realm_name", event.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.property === "invite_to_realm_policy") {
|
|
||||||
settings_invites.update_invite_user_panel();
|
|
||||||
sidebar_ui.update_invite_user_option();
|
|
||||||
gear_menu.rerender();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.property === "enable_spectator_access") {
|
if (event.property === "enable_spectator_access") {
|
||||||
stream_settings_ui.update_stream_privacy_choices(
|
stream_settings_ui.update_stream_privacy_choices(
|
||||||
"can_create_web_public_channel_group",
|
"can_create_web_public_channel_group",
|
||||||
|
@ -300,7 +293,10 @@ export function dispatch_normal_event(event) {
|
||||||
settings_org.sync_realm_settings(key);
|
settings_org.sync_realm_settings(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === "create_multiuse_invite_group") {
|
if (
|
||||||
|
key === "create_multiuse_invite_group" ||
|
||||||
|
key === "can_invite_users_group"
|
||||||
|
) {
|
||||||
settings_invites.update_invite_user_panel();
|
settings_invites.update_invite_user_panel();
|
||||||
sidebar_ui.update_invite_user_option();
|
sidebar_ui.update_invite_user_option();
|
||||||
gear_menu.rerender();
|
gear_menu.rerender();
|
||||||
|
|
|
@ -238,7 +238,6 @@ export function get_subsection_property_elements($subsection: JQuery): HTMLEleme
|
||||||
|
|
||||||
export const simple_dropdown_realm_settings_schema = realm_schema.pick({
|
export const simple_dropdown_realm_settings_schema = realm_schema.pick({
|
||||||
realm_invite_to_stream_policy: true,
|
realm_invite_to_stream_policy: true,
|
||||||
realm_invite_to_realm_policy: true,
|
|
||||||
realm_wildcard_mention_policy: true,
|
realm_wildcard_mention_policy: true,
|
||||||
realm_org_type: true,
|
realm_org_type: true,
|
||||||
});
|
});
|
||||||
|
@ -804,6 +803,7 @@ export function check_realm_settings_property_changed(elem: HTMLElement): boolea
|
||||||
case "realm_can_create_private_channel_group":
|
case "realm_can_create_private_channel_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_manage_all_groups":
|
case "realm_can_manage_all_groups":
|
||||||
case "realm_can_move_messages_between_channels_group":
|
case "realm_can_move_messages_between_channels_group":
|
||||||
case "realm_can_move_messages_between_topics_group":
|
case "realm_can_move_messages_between_topics_group":
|
||||||
|
@ -1055,6 +1055,7 @@ export function populate_data_for_realm_settings_request(
|
||||||
"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",
|
||||||
|
"can_invite_users_group",
|
||||||
"can_move_messages_between_channels_group",
|
"can_move_messages_between_channels_group",
|
||||||
"can_move_messages_between_topics_group",
|
"can_move_messages_between_topics_group",
|
||||||
"create_multiuse_invite_group",
|
"create_multiuse_invite_group",
|
||||||
|
@ -1502,6 +1503,7 @@ export const group_setting_widget_map = new Map<string, GroupSettingPillContaine
|
||||||
["realm_can_create_private_channel_group", null],
|
["realm_can_create_private_channel_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_manage_all_groups", null],
|
["realm_can_manage_all_groups", null],
|
||||||
["realm_can_move_messages_between_channels_group", null],
|
["realm_can_move_messages_between_channels_group", null],
|
||||||
["realm_can_move_messages_between_topics_group", null],
|
["realm_can_move_messages_between_topics_group", null],
|
||||||
|
@ -1618,6 +1620,7 @@ export const realm_group_setting_name_schema = z.enum([
|
||||||
"can_create_private_channel_group",
|
"can_create_private_channel_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_manage_all_groups",
|
"can_manage_all_groups",
|
||||||
"can_move_messages_between_channels_group",
|
"can_move_messages_between_channels_group",
|
||||||
"can_move_messages_between_topics_group",
|
"can_move_messages_between_topics_group",
|
||||||
|
|
|
@ -266,34 +266,6 @@ export const common_policy_values = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const email_invite_to_realm_policy_values = {
|
|
||||||
nobody: {
|
|
||||||
order: 1,
|
|
||||||
code: 6,
|
|
||||||
description: $t({defaultMessage: "Nobody"}),
|
|
||||||
},
|
|
||||||
by_admins_only: {
|
|
||||||
order: 2,
|
|
||||||
code: 2,
|
|
||||||
description: $t({defaultMessage: "Admins"}),
|
|
||||||
},
|
|
||||||
by_moderators_only: {
|
|
||||||
order: 3,
|
|
||||||
code: 4,
|
|
||||||
description: $t({defaultMessage: "Admins and moderators"}),
|
|
||||||
},
|
|
||||||
by_full_members: {
|
|
||||||
order: 4,
|
|
||||||
code: 3,
|
|
||||||
description: $t({defaultMessage: "Admins, moderators and full members"}),
|
|
||||||
},
|
|
||||||
by_members: {
|
|
||||||
order: 5,
|
|
||||||
code: 1,
|
|
||||||
description: $t({defaultMessage: "Admins, moderators and members"}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const wildcard_mention_policy_values = {
|
export const wildcard_mention_policy_values = {
|
||||||
by_everyone: {
|
by_everyone: {
|
||||||
order: 1,
|
order: 1,
|
||||||
|
|
|
@ -59,14 +59,6 @@ export function user_can_change_logo(): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
function user_has_permission(policy_value: number): boolean {
|
function user_has_permission(policy_value: number): boolean {
|
||||||
/* At present, nobody is not present in common_policy_values,
|
|
||||||
* but we include a check for it here, so that code using
|
|
||||||
* email_invite_to_realm_policy_values or other supersets can
|
|
||||||
* use this function. */
|
|
||||||
if (policy_value === settings_config.email_invite_to_realm_policy_values.nobody.code) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current_user.is_admin) {
|
if (current_user.is_admin) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -121,7 +113,11 @@ export function user_has_permission_for_group_setting(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function user_can_invite_users_by_email(): boolean {
|
export function user_can_invite_users_by_email(): boolean {
|
||||||
return user_has_permission(realm.realm_invite_to_realm_policy);
|
return user_has_permission_for_group_setting(
|
||||||
|
realm.realm_can_invite_users_group,
|
||||||
|
"can_invite_users_group",
|
||||||
|
"realm",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function user_can_create_multiuse_invite(): boolean {
|
export function user_can_create_multiuse_invite(): boolean {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import * as loading from "./loading.ts";
|
||||||
import * as people from "./people.ts";
|
import * as people from "./people.ts";
|
||||||
import * as settings_config from "./settings_config.ts";
|
import * as settings_config from "./settings_config.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 {current_user} from "./state_data.ts";
|
||||||
import * as timerender from "./timerender.ts";
|
import * as timerender from "./timerender.ts";
|
||||||
import * as ui_report from "./ui_report.ts";
|
import * as ui_report from "./ui_report.ts";
|
||||||
import * as util from "./util.ts";
|
import * as util from "./util.ts";
|
||||||
|
@ -319,55 +319,18 @@ export function on_load_success(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function update_invite_users_setting_tip(): void {
|
export function update_invite_users_setting_tip(): void {
|
||||||
if (settings_data.user_can_invite_users_by_email() && !current_user.is_admin) {
|
if (settings_data.user_can_invite_users_by_email()) {
|
||||||
$(".invite-user-settings-tip").hide();
|
$(".invite-user-settings-tip").hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const permission_type = settings_config.email_invite_to_realm_policy_values;
|
|
||||||
const current_permission = realm.realm_invite_to_realm_policy;
|
|
||||||
let tip_text;
|
|
||||||
switch (current_permission) {
|
|
||||||
case permission_type.by_admins_only.code: {
|
|
||||||
tip_text = $t({
|
|
||||||
defaultMessage:
|
|
||||||
"This organization is configured so that admins can invite users to this organization.",
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case permission_type.by_moderators_only.code: {
|
|
||||||
tip_text = $t({
|
|
||||||
defaultMessage:
|
|
||||||
"This organization is configured so that admins and moderators can invite users to this organization.",
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case permission_type.by_members.code: {
|
|
||||||
tip_text = $t({
|
|
||||||
defaultMessage:
|
|
||||||
"This organization is configured so that admins, moderators and members can invite users to this organization.",
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case permission_type.by_full_members.code: {
|
|
||||||
tip_text = $t({
|
|
||||||
defaultMessage:
|
|
||||||
"This organization is configured so that admins, moderators and full members can invite users to this organization.",
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
tip_text = $t({
|
|
||||||
defaultMessage:
|
|
||||||
"This organization is configured so that nobody can invite users to this organization.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$(".invite-user-settings-tip").show();
|
$(".invite-user-settings-tip").show();
|
||||||
$(".invite-user-settings-tip").text(tip_text);
|
$(".invite-user-settings-tip").text(
|
||||||
|
$t({
|
||||||
|
defaultMessage:
|
||||||
|
"You do not have permission to send invite emails in this organization.",
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function update_invite_user_panel(): void {
|
export function update_invite_user_panel(): void {
|
||||||
|
|
|
@ -164,7 +164,6 @@ export function enable_or_disable_group_permission_settings(): void {
|
||||||
type OrganizationSettingsOptions = {
|
type OrganizationSettingsOptions = {
|
||||||
common_policy_values: SettingOptionValueWithKey[];
|
common_policy_values: SettingOptionValueWithKey[];
|
||||||
wildcard_mention_policy_values: SettingOptionValueWithKey[];
|
wildcard_mention_policy_values: SettingOptionValueWithKey[];
|
||||||
invite_to_realm_policy_values: SettingOptionValueWithKey[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function get_organization_settings_options(): OrganizationSettingsOptions {
|
export function get_organization_settings_options(): OrganizationSettingsOptions {
|
||||||
|
@ -175,9 +174,6 @@ export function get_organization_settings_options(): OrganizationSettingsOptions
|
||||||
wildcard_mention_policy_values: settings_components.get_sorted_options_list(
|
wildcard_mention_policy_values: settings_components.get_sorted_options_list(
|
||||||
settings_config.wildcard_mention_policy_values,
|
settings_config.wildcard_mention_policy_values,
|
||||||
),
|
),
|
||||||
invite_to_realm_policy_values: settings_components.get_sorted_options_list(
|
|
||||||
settings_config.email_invite_to_realm_policy_values,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,6 +524,7 @@ export function discard_realm_property_element_changes(elem: HTMLElement): void
|
||||||
case "realm_can_create_private_channel_group":
|
case "realm_can_create_private_channel_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_manage_all_groups":
|
case "realm_can_manage_all_groups":
|
||||||
case "realm_can_move_messages_between_channels_group":
|
case "realm_can_move_messages_between_channels_group":
|
||||||
case "realm_can_move_messages_between_topics_group":
|
case "realm_can_move_messages_between_topics_group":
|
||||||
|
|
|
@ -288,6 +288,7 @@ export const realm_schema = z.object({
|
||||||
realm_can_create_web_public_channel_group: z.number(),
|
realm_can_create_web_public_channel_group: z.number(),
|
||||||
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_manage_all_groups: group_setting_value_schema,
|
realm_can_manage_all_groups: group_setting_value_schema,
|
||||||
realm_can_move_messages_between_channels_group: group_setting_value_schema,
|
realm_can_move_messages_between_channels_group: group_setting_value_schema,
|
||||||
realm_can_move_messages_between_topics_group: group_setting_value_schema,
|
realm_can_move_messages_between_topics_group: group_setting_value_schema,
|
||||||
|
@ -350,7 +351,6 @@ export const realm_schema = z.object({
|
||||||
realm_inline_image_preview: z.boolean(),
|
realm_inline_image_preview: z.boolean(),
|
||||||
realm_inline_url_embed_preview: z.boolean(),
|
realm_inline_url_embed_preview: z.boolean(),
|
||||||
realm_invite_required: z.boolean(),
|
realm_invite_required: z.boolean(),
|
||||||
realm_invite_to_realm_policy: z.number(),
|
|
||||||
realm_invite_to_stream_policy: z.number(),
|
realm_invite_to_stream_policy: z.number(),
|
||||||
realm_is_zephyr_mirror_realm: z.boolean(),
|
realm_is_zephyr_mirror_realm: z.boolean(),
|
||||||
realm_jitsi_server_url: z.nullable(z.string()),
|
realm_jitsi_server_url: z.nullable(z.string()),
|
||||||
|
|
|
@ -10,18 +10,10 @@
|
||||||
{{> settings_save_discard_widget section_name="join-settings" }}
|
{{> settings_save_discard_widget section_name="join-settings" }}
|
||||||
</div>
|
</div>
|
||||||
<div class="m-10 inline-block organization-permissions-parent">
|
<div class="m-10 inline-block organization-permissions-parent">
|
||||||
<div class="input-group">
|
|
||||||
{{> settings_checkbox
|
{{> group_setting_value_pill_input
|
||||||
setting_name="realm_invite_required"
|
setting_name="realm_can_invite_users_group"
|
||||||
prefix="id_"
|
label=(t 'Who can send email invitations to new users')}}
|
||||||
is_checked=realm_invite_required
|
|
||||||
label=admin_settings_label.realm_invite_required}}
|
|
||||||
<label for="id_realm_invite_to_realm_policy" class="settings-field-label">{{t "Who can send email invitations to new users" }}
|
|
||||||
</label>
|
|
||||||
<select name="realm_invite_to_realm_policy" id="id_realm_invite_to_realm_policy" class="prop-element settings_select bootstrap-focus-style" data-setting-widget-type="number">
|
|
||||||
{{> dropdown_options_widget option_values=invite_to_realm_policy_values}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{> group_setting_value_pill_input
|
{{> group_setting_value_pill_input
|
||||||
setting_name="realm_create_multiuse_invite_group"
|
setting_name="realm_create_multiuse_invite_group"
|
||||||
|
|
|
@ -539,9 +539,6 @@ run_test("realm settings", ({override}) => {
|
||||||
event = event_fixtures.realm__update__invite_required;
|
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__invite_to_realm_policy;
|
|
||||||
test_realm_integer(event, "realm_invite_to_realm_policy");
|
|
||||||
|
|
||||||
event = event_fixtures.realm__update__want_advertise_in_communities_directory;
|
event = event_fixtures.realm__update__want_advertise_in_communities_directory;
|
||||||
test_realm_boolean(event, "realm_want_advertise_in_communities_directory");
|
test_realm_boolean(event, "realm_want_advertise_in_communities_directory");
|
||||||
|
|
||||||
|
@ -605,6 +602,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_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_move_messages_between_topics_group", 1);
|
override(realm, "realm_can_move_messages_between_topics_group", 1);
|
||||||
override(realm, "realm_direct_message_permission_group", 1);
|
override(realm, "realm_direct_message_permission_group", 1);
|
||||||
override(realm, "realm_plan_type", 2);
|
override(realm, "realm_plan_type", 2);
|
||||||
|
@ -620,6 +618,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_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_move_messages_between_topics_group, 3);
|
assert_same(realm.realm_can_move_messages_between_topics_group, 3);
|
||||||
assert_same(realm.realm_direct_message_permission_group, 3);
|
assert_same(realm.realm_direct_message_permission_group, 3);
|
||||||
assert_same(realm.realm_plan_type, 3);
|
assert_same(realm.realm_plan_type, 3);
|
||||||
|
|
|
@ -302,13 +302,6 @@ exports.fixtures = {
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
realm__update__invite_to_realm_policy: {
|
|
||||||
type: "realm",
|
|
||||||
op: "update",
|
|
||||||
property: "invite_to_realm_policy",
|
|
||||||
value: 2,
|
|
||||||
},
|
|
||||||
|
|
||||||
realm__update__invite_to_stream_policy: {
|
realm__update__invite_to_stream_policy: {
|
||||||
type: "realm",
|
type: "realm",
|
||||||
op: "update",
|
op: "update",
|
||||||
|
@ -371,6 +364,7 @@ exports.fixtures = {
|
||||||
},
|
},
|
||||||
can_add_custom_emoji_group: 3,
|
can_add_custom_emoji_group: 3,
|
||||||
can_create_public_channel_group: 3,
|
can_create_public_channel_group: 3,
|
||||||
|
can_invite_users_group: 3,
|
||||||
can_move_messages_between_topics_group: 3,
|
can_move_messages_between_topics_group: 3,
|
||||||
direct_message_permission_group: 3,
|
direct_message_permission_group: 3,
|
||||||
plan_type: 3,
|
plan_type: 3,
|
||||||
|
|
|
@ -157,11 +157,6 @@ test_policy(
|
||||||
"realm_invite_to_stream_policy",
|
"realm_invite_to_stream_policy",
|
||||||
settings_data.user_can_subscribe_other_users,
|
settings_data.user_can_subscribe_other_users,
|
||||||
);
|
);
|
||||||
test_policy(
|
|
||||||
"user_can_invite_others_to_realm",
|
|
||||||
"realm_invite_to_realm_policy",
|
|
||||||
settings_data.user_can_invite_users_by_email,
|
|
||||||
);
|
|
||||||
|
|
||||||
test_realm_group_settings(
|
test_realm_group_settings(
|
||||||
"realm_can_add_custom_emoji_group",
|
"realm_can_add_custom_emoji_group",
|
||||||
|
@ -178,6 +173,11 @@ test_realm_group_settings(
|
||||||
settings_data.user_can_delete_own_message,
|
settings_data.user_can_delete_own_message,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test_realm_group_settings(
|
||||||
|
"realm_can_invite_users_group",
|
||||||
|
settings_data.user_can_invite_users_by_email,
|
||||||
|
);
|
||||||
|
|
||||||
test_realm_group_settings(
|
test_realm_group_settings(
|
||||||
"realm_can_move_messages_between_channels_group",
|
"realm_can_move_messages_between_channels_group",
|
||||||
settings_data.user_can_move_messages_between_streams,
|
settings_data.user_can_move_messages_between_streams,
|
||||||
|
@ -210,17 +210,6 @@ run_test("using_dark_theme", ({override}) => {
|
||||||
assert.equal(settings_data.using_dark_theme(), false);
|
assert.equal(settings_data.using_dark_theme(), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
run_test("user_can_invite_others_to_realm_nobody_case", ({override}) => {
|
|
||||||
override(current_user, "is_admin", true);
|
|
||||||
override(current_user, "is_guest", false);
|
|
||||||
override(
|
|
||||||
realm,
|
|
||||||
"realm_invite_to_realm_policy",
|
|
||||||
settings_config.email_invite_to_realm_policy_values.nobody.code,
|
|
||||||
);
|
|
||||||
assert.equal(settings_data.user_can_invite_users_by_email(), false);
|
|
||||||
});
|
|
||||||
|
|
||||||
run_test("user_email_not_configured", ({override}) => {
|
run_test("user_email_not_configured", ({override}) => {
|
||||||
const user_email_not_configured = settings_data.user_email_not_configured;
|
const user_email_not_configured = settings_data.user_email_not_configured;
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,6 @@ function test_submit_settings_form(override, submit_form) {
|
||||||
realm_waiting_period_threshold: 1,
|
realm_waiting_period_threshold: 1,
|
||||||
realm_default_language: '"es"',
|
realm_default_language: '"es"',
|
||||||
realm_invite_to_stream_policy: settings_config.common_policy_values.by_admins_only.code,
|
realm_invite_to_stream_policy: settings_config.common_policy_values.by_admins_only.code,
|
||||||
realm_invite_to_realm_policy: settings_config.common_policy_values.by_members.code,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
override(global, "setTimeout", (func) => func());
|
override(global, "setTimeout", (func) => func());
|
||||||
|
@ -142,15 +141,9 @@ function test_submit_settings_form(override, submit_form) {
|
||||||
$bot_creation_policy_elem.attr("id", "id_realm_bot_creation_policy");
|
$bot_creation_policy_elem.attr("id", "id_realm_bot_creation_policy");
|
||||||
$bot_creation_policy_elem.data = () => "number";
|
$bot_creation_policy_elem.data = () => "number";
|
||||||
|
|
||||||
const $invite_to_realm_policy_elem = $("#id_realm_invite_to_realm_policy");
|
|
||||||
$invite_to_realm_policy_elem.val("2");
|
|
||||||
$invite_to_realm_policy_elem.attr("id", "id_realm_invite_to_realm_policy");
|
|
||||||
$invite_to_realm_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", [
|
$subsection_elem.set_find_results(".prop-element", [
|
||||||
$bot_creation_policy_elem,
|
$bot_creation_policy_elem,
|
||||||
$invite_to_realm_policy_elem,
|
|
||||||
$invite_to_stream_policy_elem,
|
$invite_to_stream_policy_elem,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -160,7 +153,6 @@ function test_submit_settings_form(override, submit_form) {
|
||||||
|
|
||||||
let expected_value = {
|
let expected_value = {
|
||||||
bot_creation_policy: 1,
|
bot_creation_policy: 1,
|
||||||
invite_to_realm_policy: 2,
|
|
||||||
invite_to_stream_policy: 1,
|
invite_to_stream_policy: 1,
|
||||||
};
|
};
|
||||||
assert.deepEqual(data, expected_value);
|
assert.deepEqual(data, expected_value);
|
||||||
|
@ -317,7 +309,6 @@ function test_sync_realm_settings({override}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
test_common_policy("invite_to_stream_policy");
|
test_common_policy("invite_to_stream_policy");
|
||||||
test_common_policy("invite_to_realm_policy");
|
|
||||||
|
|
||||||
{
|
{
|
||||||
/* Test message content edit limit minutes sync */
|
/* Test message content edit limit minutes sync */
|
||||||
|
@ -373,9 +364,9 @@ function test_sync_realm_settings({override}) {
|
||||||
|
|
||||||
{
|
{
|
||||||
// Test hiding save-discard buttons on live-updating.
|
// Test hiding save-discard buttons on live-updating.
|
||||||
const $property_elem = $("#id_realm_invite_to_realm_policy");
|
const $property_elem = $("#id_realm_invite_to_stream_policy");
|
||||||
$property_elem.length = 1;
|
$property_elem.length = 1;
|
||||||
$property_elem.attr("id", "id_realm_invite_to_realm_policy");
|
$property_elem.attr("id", "id_realm_invite_to_stream_policy");
|
||||||
$property_elem.closest = () => $subsection_stub;
|
$property_elem.closest = () => $subsection_stub;
|
||||||
|
|
||||||
const save_button_stubs = createSaveButtons("subsection-stub");
|
const save_button_stubs = createSaveButtons("subsection-stub");
|
||||||
|
@ -386,13 +377,13 @@ function test_sync_realm_settings({override}) {
|
||||||
$property_elem.val(settings_config.common_policy_values.by_admins_only.code);
|
$property_elem.val(settings_config.common_policy_values.by_admins_only.code);
|
||||||
override(
|
override(
|
||||||
realm,
|
realm,
|
||||||
"realm_invite_to_realm_policy",
|
"realm_invite_to_stream_policy",
|
||||||
settings_config.common_policy_values.by_members.code,
|
settings_config.common_policy_values.by_members.code,
|
||||||
);
|
);
|
||||||
save_button_stubs.$save_button_controls.removeClass("hide");
|
save_button_stubs.$save_button_controls.removeClass("hide");
|
||||||
$subsection_stub.set_find_results(".prop-element", [$property_elem]);
|
$subsection_stub.set_find_results(".prop-element", [$property_elem]);
|
||||||
|
|
||||||
settings_org.sync_realm_settings("invite_to_realm_policy");
|
settings_org.sync_realm_settings("invite_to_stream_policy");
|
||||||
assert.equal(save_button_stubs.props.hidden, true);
|
assert.equal(save_button_stubs.props.hidden, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ from zerver.models import (
|
||||||
from zerver.models.groups import SystemGroups
|
from zerver.models.groups import SystemGroups
|
||||||
from zerver.models.presence import PresenceSequence
|
from zerver.models.presence import PresenceSequence
|
||||||
from zerver.models.realm_audit_logs import AuditLogEventType
|
from zerver.models.realm_audit_logs import AuditLogEventType
|
||||||
from zerver.models.realms import CommonPolicyEnum, InviteToRealmPolicyEnum
|
from zerver.models.realms import CommonPolicyEnum
|
||||||
from zproject.backends import all_default_backend_names
|
from zproject.backends import all_default_backend_names
|
||||||
|
|
||||||
|
|
||||||
|
@ -115,8 +115,6 @@ def set_realm_permissions_based_on_org_type(realm: Realm) -> None:
|
||||||
Realm.ORG_TYPES["education_nonprofit"]["id"],
|
Realm.ORG_TYPES["education_nonprofit"]["id"],
|
||||||
Realm.ORG_TYPES["education"]["id"],
|
Realm.ORG_TYPES["education"]["id"],
|
||||||
):
|
):
|
||||||
# Limit user creation to administrators.
|
|
||||||
realm.invite_to_realm_policy = InviteToRealmPolicyEnum.ADMINS_ONLY
|
|
||||||
# Don't allow members (students) to manage user groups or
|
# Don't allow members (students) to manage user groups or
|
||||||
# stream subscriptions.
|
# stream subscriptions.
|
||||||
realm.invite_to_stream_policy = CommonPolicyEnum.MODERATORS_ONLY
|
realm.invite_to_stream_policy = CommonPolicyEnum.MODERATORS_ONLY
|
||||||
|
@ -290,6 +288,10 @@ def do_create_realm(
|
||||||
Realm.ORG_TYPES["education_nonprofit"]["id"]: SystemGroups.MODERATORS,
|
Realm.ORG_TYPES["education_nonprofit"]["id"]: SystemGroups.MODERATORS,
|
||||||
Realm.ORG_TYPES["education"]["id"]: SystemGroups.MODERATORS,
|
Realm.ORG_TYPES["education"]["id"]: SystemGroups.MODERATORS,
|
||||||
},
|
},
|
||||||
|
"can_invite_users_group": {
|
||||||
|
Realm.ORG_TYPES["education_nonprofit"]["id"]: SystemGroups.ADMINISTRATORS,
|
||||||
|
Realm.ORG_TYPES["education"]["id"]: SystemGroups.ADMINISTRATORS,
|
||||||
|
},
|
||||||
"can_move_messages_between_channels_group": {
|
"can_move_messages_between_channels_group": {
|
||||||
Realm.ORG_TYPES["education_nonprofit"]["id"]: SystemGroups.MODERATORS,
|
Realm.ORG_TYPES["education_nonprofit"]["id"]: SystemGroups.MODERATORS,
|
||||||
Realm.ORG_TYPES["education"]["id"]: SystemGroups.MODERATORS,
|
Realm.ORG_TYPES["education"]["id"]: SystemGroups.MODERATORS,
|
||||||
|
|
|
@ -1077,6 +1077,7 @@ group_setting_update_data_type = DictType(
|
||||||
("can_create_web_public_channel_group", group_setting_type),
|
("can_create_web_public_channel_group", group_setting_type),
|
||||||
("can_delete_any_message_group", group_setting_type),
|
("can_delete_any_message_group", group_setting_type),
|
||||||
("can_delete_own_message_group", group_setting_type),
|
("can_delete_own_message_group", group_setting_type),
|
||||||
|
("can_invite_users_group", group_setting_type),
|
||||||
("can_manage_all_groups", group_setting_type),
|
("can_manage_all_groups", group_setting_type),
|
||||||
("can_move_messages_between_channels_group", group_setting_type),
|
("can_move_messages_between_channels_group", group_setting_type),
|
||||||
("can_move_messages_between_topics_group", group_setting_type),
|
("can_move_messages_between_topics_group", group_setting_type),
|
||||||
|
|
|
@ -589,7 +589,7 @@ def fetch_initial_state_data(
|
||||||
or state["can_create_web_public_streams"]
|
or state["can_create_web_public_streams"]
|
||||||
)
|
)
|
||||||
state["can_subscribe_other_users"] = settings_user.can_subscribe_other_users()
|
state["can_subscribe_other_users"] = settings_user.can_subscribe_other_users()
|
||||||
state["can_invite_others_to_realm"] = settings_user.can_invite_users_by_email()
|
state["can_invite_others_to_realm"] = settings_user.can_invite_users_by_email(realm)
|
||||||
state["is_admin"] = settings_user.is_realm_admin
|
state["is_admin"] = settings_user.is_realm_admin
|
||||||
state["is_owner"] = settings_user.is_realm_owner
|
state["is_owner"] = settings_user.is_realm_owner
|
||||||
state["is_moderator"] = settings_user.is_moderator
|
state["is_moderator"] = settings_user.is_moderator
|
||||||
|
@ -1302,26 +1302,11 @@ def apply_event(
|
||||||
else state["server_jitsi_server_url"]
|
else state["server_jitsi_server_url"]
|
||||||
)
|
)
|
||||||
|
|
||||||
policy_permission_dict = {
|
|
||||||
"invite_to_stream_policy": "can_subscribe_other_users",
|
|
||||||
"invite_to_realm_policy": "can_invite_others_to_realm",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Tricky interaction: Whether we can create streams and can subscribe other users
|
|
||||||
# can get changed here.
|
|
||||||
|
|
||||||
if field == "realm_waiting_period_threshold":
|
|
||||||
for policy, permission in policy_permission_dict.items():
|
|
||||||
if permission in state:
|
|
||||||
state[permission] = user_profile.has_permission(policy)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
event["property"] in policy_permission_dict
|
event["property"] == "invite_to_stream_policy"
|
||||||
and policy_permission_dict[event["property"]] in state
|
and "can_subscribe_other_users" in state
|
||||||
):
|
):
|
||||||
state[policy_permission_dict[event["property"]]] = user_profile.has_permission(
|
state["can_subscribe_other_users"] = user_profile.has_permission(event["property"])
|
||||||
event["property"]
|
|
||||||
)
|
|
||||||
elif event["op"] == "update_dict":
|
elif event["op"] == "update_dict":
|
||||||
for key, value in event["data"].items():
|
for key, value in event["data"].items():
|
||||||
if key == "max_file_upload_size_mib":
|
if key == "max_file_upload_size_mib":
|
||||||
|
@ -1377,6 +1362,11 @@ def apply_event(
|
||||||
or state["can_create_web_public_streams"]
|
or state["can_create_web_public_streams"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if key == "can_invite_users_group" and "can_invite_others_to_realm" in state:
|
||||||
|
state["can_invite_others_to_realm"] = user_profile.has_permission(
|
||||||
|
"can_invite_users_group"
|
||||||
|
)
|
||||||
|
|
||||||
if key == "plan_type":
|
if key == "plan_type":
|
||||||
# Then there are some extra fields that also need to be set.
|
# Then there are some extra fields that also need to be set.
|
||||||
state["zulip_plan_is_not_limited"] = value != Realm.PLAN_TYPE_LIMITED
|
state["zulip_plan_is_not_limited"] = value != Realm.PLAN_TYPE_LIMITED
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 5.0.9 on 2024-11-13 18:13
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0624_alter_realmexport_tarball_size_bytes"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="realm",
|
||||||
|
name="can_invite_users_group",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.RESTRICT,
|
||||||
|
related_name="+",
|
||||||
|
to="zerver.usergroup",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Generated by Django 4.2.1 on 2023-06-12 10:47
|
||||||
|
|
||||||
|
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_invite_users_group(
|
||||||
|
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
|
||||||
|
) -> None:
|
||||||
|
Realm = apps.get_model("zerver", "Realm")
|
||||||
|
NamedUserGroup = apps.get_model("zerver", "NamedUserGroup")
|
||||||
|
|
||||||
|
invite_to_realm_policy_to_group_name = {
|
||||||
|
1: "role:members",
|
||||||
|
2: "role:administrators",
|
||||||
|
3: "role:fullmembers",
|
||||||
|
4: "role:moderators",
|
||||||
|
6: "role:nobody",
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, group_name in invite_to_realm_policy_to_group_name.items():
|
||||||
|
Realm.objects.filter(can_invite_users_group=None, invite_to_realm_policy=id).update(
|
||||||
|
can_invite_users_group=NamedUserGroup.objects.filter(
|
||||||
|
name=group_name, realm=OuterRef("id"), is_system_group=True
|
||||||
|
).values("pk")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
atomic = False
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0625_realm_can_invite_users_group"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
set_default_value_for_can_invite_users_group,
|
||||||
|
elidable=True,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
)
|
||||||
|
]
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 5.0.9 on 2024-11-16 07:59
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0626_set_default_value_for_can_invite_users_group"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="realm",
|
||||||
|
name="can_invite_users_group",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.RESTRICT,
|
||||||
|
related_name="+",
|
||||||
|
to="zerver.usergroup",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -305,6 +305,11 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
||||||
default=InviteToRealmPolicyEnum.MEMBERS_ONLY
|
default=InviteToRealmPolicyEnum.MEMBERS_ONLY
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# UserGroup whose members are allowed to invite other users to organization.
|
||||||
|
can_invite_users_group = models.ForeignKey(
|
||||||
|
"UserGroup", on_delete=models.RESTRICT, related_name="+"
|
||||||
|
)
|
||||||
|
|
||||||
# UserGroup whose members are allowed to create invite link.
|
# UserGroup whose members are allowed to create invite link.
|
||||||
create_multiuse_invite_group = models.ForeignKey(
|
create_multiuse_invite_group = models.ForeignKey(
|
||||||
"UserGroup", on_delete=models.RESTRICT, related_name="+"
|
"UserGroup", on_delete=models.RESTRICT, related_name="+"
|
||||||
|
@ -759,6 +764,15 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
||||||
default_group_name=SystemGroups.EVERYONE,
|
default_group_name=SystemGroups.EVERYONE,
|
||||||
id_field_name="can_delete_own_message_group_id",
|
id_field_name="can_delete_own_message_group_id",
|
||||||
),
|
),
|
||||||
|
can_invite_users_group=GroupPermissionSetting(
|
||||||
|
require_system_group=False,
|
||||||
|
allow_internet_group=False,
|
||||||
|
allow_owners_group=False,
|
||||||
|
allow_nobody_group=True,
|
||||||
|
allow_everyone_group=False,
|
||||||
|
default_group_name=SystemGroups.MEMBERS,
|
||||||
|
id_field_name="can_invite_users_group_id",
|
||||||
|
),
|
||||||
can_manage_all_groups=GroupPermissionSetting(
|
can_manage_all_groups=GroupPermissionSetting(
|
||||||
require_system_group=False,
|
require_system_group=False,
|
||||||
allow_internet_group=False,
|
allow_internet_group=False,
|
||||||
|
@ -1198,6 +1212,8 @@ def get_realm_with_settings(realm_id: int) -> Realm:
|
||||||
"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",
|
||||||
"can_delete_own_message_group__named_user_group",
|
"can_delete_own_message_group__named_user_group",
|
||||||
|
"can_invite_users_group",
|
||||||
|
"can_invite_users_group__named_user_group",
|
||||||
"can_manage_all_groups",
|
"can_manage_all_groups",
|
||||||
"can_manage_all_groups__named_user_group",
|
"can_manage_all_groups__named_user_group",
|
||||||
"can_move_messages_between_channels_group",
|
"can_move_messages_between_channels_group",
|
||||||
|
|
|
@ -816,10 +816,10 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings):
|
||||||
from zerver.lib.user_groups import user_has_permission_for_group_setting
|
from zerver.lib.user_groups import user_has_permission_for_group_setting
|
||||||
from zerver.models import Realm
|
from zerver.models import Realm
|
||||||
|
|
||||||
if policy_name not in Realm.REALM_PERMISSION_GROUP_SETTINGS and policy_name not in [
|
if (
|
||||||
"invite_to_stream_policy",
|
policy_name not in Realm.REALM_PERMISSION_GROUP_SETTINGS
|
||||||
"invite_to_realm_policy",
|
and policy_name != "invite_to_stream_policy"
|
||||||
]:
|
):
|
||||||
raise AssertionError("Invalid policy")
|
raise AssertionError("Invalid policy")
|
||||||
|
|
||||||
if policy_name in Realm.REALM_PERMISSION_GROUP_SETTINGS:
|
if policy_name in Realm.REALM_PERMISSION_GROUP_SETTINGS:
|
||||||
|
@ -880,8 +880,8 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings):
|
||||||
def can_subscribe_other_users(self) -> bool:
|
def can_subscribe_other_users(self) -> bool:
|
||||||
return self.has_permission("invite_to_stream_policy")
|
return self.has_permission("invite_to_stream_policy")
|
||||||
|
|
||||||
def can_invite_users_by_email(self) -> bool:
|
def can_invite_users_by_email(self, realm: Optional["Realm"] = None) -> bool:
|
||||||
return self.has_permission("invite_to_realm_policy")
|
return self.has_permission("can_invite_users_group", realm)
|
||||||
|
|
||||||
def can_create_multiuse_invite_to_realm(self) -> bool:
|
def can_create_multiuse_invite_to_realm(self) -> bool:
|
||||||
return self.has_permission("create_multiuse_invite_group")
|
return self.has_permission("create_multiuse_invite_group")
|
||||||
|
|
|
@ -4531,6 +4531,21 @@ paths:
|
||||||
setting controlled this permission; `true` corresponded to `Everyone`, and
|
setting controlled this permission; `true` corresponded to `Everyone`, and
|
||||||
`false` to `Admins`.
|
`false` to `Admins`.
|
||||||
- $ref: "#/components/schemas/GroupSettingValue"
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
|
can_invite_users_group:
|
||||||
|
allOf:
|
||||||
|
- description: |
|
||||||
|
A [group-setting value](/api/group-setting-values) defining the set of
|
||||||
|
users who have permission to send email invitations for inviting other users
|
||||||
|
to the organization.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 321). Previously, this
|
||||||
|
permission was controlled by the enum `invite_to_realm_policy`. Values
|
||||||
|
were 1=Members, 2=Admins, 3=Full members, 4=Moderators, 6=Nobody.
|
||||||
|
|
||||||
|
Before Zulip 4.0 (feature level 50), the `invite_by_admins_only` boolean
|
||||||
|
setting controlled this permission; `true` corresponded to `Admins`, and
|
||||||
|
`false` to `Members`.
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
can_move_messages_between_channels_group:
|
can_move_messages_between_channels_group:
|
||||||
allOf:
|
allOf:
|
||||||
- description: |
|
- description: |
|
||||||
|
@ -16362,6 +16377,23 @@ paths:
|
||||||
setting controlled this permission; `true` corresponded to `Everyone`, and
|
setting controlled this permission; `true` corresponded to `Everyone`, and
|
||||||
`false` to `Admins`.
|
`false` to `Admins`.
|
||||||
- $ref: "#/components/schemas/GroupSettingValue"
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
|
realm_can_invite_users_group:
|
||||||
|
allOf:
|
||||||
|
- description: |
|
||||||
|
Present if `realm` is present in `fetch_event_types`.
|
||||||
|
|
||||||
|
A [group-setting value](/api/group-setting-values) defining the set of
|
||||||
|
users who have permission to send email invitations for inviting other users
|
||||||
|
to the organization.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 321). Previously, this
|
||||||
|
permission was controlled by the enum `invite_to_realm_policy`. Values
|
||||||
|
were 1=Members, 2=Admins, 3=Full members, 4=Moderators, 6=Nobody.
|
||||||
|
|
||||||
|
Before Zulip 4.0 (feature level 50), the `invite_by_admins_only` boolean
|
||||||
|
setting controlled this permission; `true` corresponded to `Admins`, and
|
||||||
|
`false` to `Members`.
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
realm_can_move_messages_between_channels_group:
|
realm_can_move_messages_between_channels_group:
|
||||||
allOf:
|
allOf:
|
||||||
- description: |
|
- description: |
|
||||||
|
|
|
@ -1192,7 +1192,7 @@ class FetchQueriesTest(ZulipTestCase):
|
||||||
realm = get_realm_with_settings(realm_id=user.realm_id)
|
realm = get_realm_with_settings(realm_id=user.realm_id)
|
||||||
|
|
||||||
with (
|
with (
|
||||||
self.assert_database_query_count(42),
|
self.assert_database_query_count(43),
|
||||||
mock.patch("zerver.lib.events.always_want") as want_mock,
|
mock.patch("zerver.lib.events.always_want") as want_mock,
|
||||||
):
|
):
|
||||||
fetch_initial_state_data(user, realm=realm)
|
fetch_initial_state_data(user, realm=realm)
|
||||||
|
@ -1217,7 +1217,7 @@ class FetchQueriesTest(ZulipTestCase):
|
||||||
realm_filters=0,
|
realm_filters=0,
|
||||||
realm_linkifiers=0,
|
realm_linkifiers=0,
|
||||||
realm_playgrounds=1,
|
realm_playgrounds=1,
|
||||||
realm_user=4,
|
realm_user=5,
|
||||||
realm_user_groups=3,
|
realm_user_groups=3,
|
||||||
realm_user_settings_defaults=1,
|
realm_user_settings_defaults=1,
|
||||||
recent_private_conversations=1,
|
recent_private_conversations=1,
|
||||||
|
|
|
@ -136,6 +136,7 @@ class HomeTest(ZulipTestCase):
|
||||||
"realm_can_create_web_public_channel_group",
|
"realm_can_create_web_public_channel_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_manage_all_groups",
|
"realm_can_manage_all_groups",
|
||||||
"realm_can_move_messages_between_channels_group",
|
"realm_can_move_messages_between_channels_group",
|
||||||
"realm_can_move_messages_between_topics_group",
|
"realm_can_move_messages_between_topics_group",
|
||||||
|
@ -271,7 +272,7 @@ class HomeTest(ZulipTestCase):
|
||||||
|
|
||||||
# Verify succeeds once logged-in
|
# Verify succeeds once logged-in
|
||||||
with (
|
with (
|
||||||
self.assert_database_query_count(52),
|
self.assert_database_query_count(53),
|
||||||
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")
|
||||||
|
@ -576,7 +577,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(52),
|
self.assert_database_query_count(53),
|
||||||
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()
|
||||||
|
@ -608,7 +609,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(47):
|
with self.assert_database_query_count(48):
|
||||||
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.
|
||||||
|
|
|
@ -43,6 +43,7 @@ from zerver.actions.realm_settings import (
|
||||||
do_change_realm_plan_type,
|
do_change_realm_plan_type,
|
||||||
do_set_realm_property,
|
do_set_realm_property,
|
||||||
)
|
)
|
||||||
|
from zerver.actions.user_groups import check_add_user_group
|
||||||
from zerver.actions.user_settings import do_change_full_name
|
from zerver.actions.user_settings import do_change_full_name
|
||||||
from zerver.actions.users import change_user_is_active
|
from zerver.actions.users import change_user_is_active
|
||||||
from zerver.context_processors import common_context
|
from zerver.context_processors import common_context
|
||||||
|
@ -68,7 +69,7 @@ from zerver.models import (
|
||||||
UserProfile,
|
UserProfile,
|
||||||
)
|
)
|
||||||
from zerver.models.groups import SystemGroups
|
from zerver.models.groups import SystemGroups
|
||||||
from zerver.models.realms import CommonPolicyEnum, InviteToRealmPolicyEnum, get_realm
|
from zerver.models.realms import CommonPolicyEnum, get_realm
|
||||||
from zerver.models.streams import get_stream
|
from zerver.models.streams import get_stream
|
||||||
from zerver.models.users import get_user_by_delivery_email
|
from zerver.models.users import get_user_by_delivery_email
|
||||||
from zerver.views.invite import INVITATION_LINK_VALIDITY_MINUTES, get_invitee_emails_set
|
from zerver.views.invite import INVITATION_LINK_VALIDITY_MINUTES, get_invitee_emails_set
|
||||||
|
@ -850,26 +851,33 @@ class InviteUserTest(InviteUserBase):
|
||||||
self.submit_reg_form_for_user(invitee, "password")
|
self.submit_reg_form_for_user(invitee, "password")
|
||||||
self.check_user_subscribed_only_to_streams("test1", [denmark, sandbox, verona, zulip])
|
self.check_user_subscribed_only_to_streams("test1", [denmark, sandbox, verona, zulip])
|
||||||
|
|
||||||
def test_can_invite_others_to_realm(self) -> None:
|
|
||||||
def validation_func(user_profile: UserProfile) -> bool:
|
|
||||||
return user_profile.can_invite_users_by_email()
|
|
||||||
|
|
||||||
realm = get_realm("zulip")
|
|
||||||
do_set_realm_property(
|
|
||||||
realm, "invite_to_realm_policy", InviteToRealmPolicyEnum.NOBODY, acting_user=None
|
|
||||||
)
|
|
||||||
desdemona = self.example_user("desdemona")
|
|
||||||
self.assertFalse(validation_func(desdemona))
|
|
||||||
|
|
||||||
self.check_has_permission_policies("invite_to_realm_policy", validation_func)
|
|
||||||
|
|
||||||
def test_invite_others_to_realm_setting(self) -> None:
|
def test_invite_others_to_realm_setting(self) -> None:
|
||||||
"""
|
"""
|
||||||
The invite_to_realm_policy realm setting works properly.
|
The `can_invite_users_group` realm setting works properly.
|
||||||
"""
|
"""
|
||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
do_set_realm_property(
|
|
||||||
realm, "invite_to_realm_policy", InviteToRealmPolicyEnum.NOBODY, acting_user=None
|
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
|
||||||
|
)
|
||||||
|
full_members_system_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.FULL_MEMBERS, realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
|
members_system_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.MEMBERS, realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
|
nobody_system_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.NOBODY, realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
|
|
||||||
|
do_change_realm_permission_group_setting(
|
||||||
|
realm,
|
||||||
|
"can_invite_users_group",
|
||||||
|
nobody_system_group,
|
||||||
|
acting_user=None,
|
||||||
)
|
)
|
||||||
self.login("desdemona")
|
self.login("desdemona")
|
||||||
email = "alice-test@zulip.com"
|
email = "alice-test@zulip.com"
|
||||||
|
@ -880,8 +888,11 @@ class InviteUserTest(InviteUserBase):
|
||||||
"Insufficient permission",
|
"Insufficient permission",
|
||||||
)
|
)
|
||||||
|
|
||||||
do_set_realm_property(
|
do_change_realm_permission_group_setting(
|
||||||
realm, "invite_to_realm_policy", InviteToRealmPolicyEnum.ADMINS_ONLY, acting_user=None
|
realm,
|
||||||
|
"can_invite_users_group",
|
||||||
|
administrators_system_group,
|
||||||
|
acting_user=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.login("shiva")
|
self.login("shiva")
|
||||||
|
@ -900,10 +911,10 @@ class InviteUserTest(InviteUserBase):
|
||||||
|
|
||||||
mail.outbox = []
|
mail.outbox = []
|
||||||
|
|
||||||
do_set_realm_property(
|
do_change_realm_permission_group_setting(
|
||||||
realm,
|
realm,
|
||||||
"invite_to_realm_policy",
|
"can_invite_users_group",
|
||||||
InviteToRealmPolicyEnum.MODERATORS_ONLY,
|
moderators_system_group,
|
||||||
acting_user=None,
|
acting_user=None,
|
||||||
)
|
)
|
||||||
self.login("hamlet")
|
self.login("hamlet")
|
||||||
|
@ -923,8 +934,11 @@ class InviteUserTest(InviteUserBase):
|
||||||
|
|
||||||
mail.outbox = []
|
mail.outbox = []
|
||||||
|
|
||||||
do_set_realm_property(
|
do_change_realm_permission_group_setting(
|
||||||
realm, "invite_to_realm_policy", InviteToRealmPolicyEnum.MEMBERS_ONLY, acting_user=None
|
realm,
|
||||||
|
"can_invite_users_group",
|
||||||
|
members_system_group,
|
||||||
|
acting_user=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.login("polonius")
|
self.login("polonius")
|
||||||
|
@ -941,16 +955,17 @@ class InviteUserTest(InviteUserBase):
|
||||||
|
|
||||||
mail.outbox = []
|
mail.outbox = []
|
||||||
|
|
||||||
do_set_realm_property(
|
do_change_realm_permission_group_setting(
|
||||||
realm,
|
realm,
|
||||||
"invite_to_realm_policy",
|
"can_invite_users_group",
|
||||||
InviteToRealmPolicyEnum.FULL_MEMBERS_ONLY,
|
full_members_system_group,
|
||||||
acting_user=None,
|
acting_user=None,
|
||||||
)
|
)
|
||||||
do_set_realm_property(realm, "waiting_period_threshold", 1000, acting_user=None)
|
|
||||||
|
|
||||||
hamlet = self.example_user("hamlet")
|
hamlet = self.example_user("hamlet")
|
||||||
hamlet.date_joined = timezone_now() - timedelta(days=realm.waiting_period_threshold - 1)
|
hamlet.date_joined = timezone_now() - timedelta(days=9)
|
||||||
|
|
||||||
|
do_set_realm_property(realm, "waiting_period_threshold", 10, acting_user=None)
|
||||||
|
|
||||||
email = "issac-test@zulip.com"
|
email = "issac-test@zulip.com"
|
||||||
email2 = "steven-test@zulip.com"
|
email2 = "steven-test@zulip.com"
|
||||||
|
@ -967,6 +982,60 @@ class InviteUserTest(InviteUserBase):
|
||||||
self.assertTrue(find_key_by_email(email2))
|
self.assertTrue(find_key_by_email(email2))
|
||||||
self.check_sent_emails([email, email2])
|
self.check_sent_emails([email, email2])
|
||||||
|
|
||||||
|
cordelia = self.example_user("cordelia")
|
||||||
|
|
||||||
|
# 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_invite_users_group", user_group, acting_user=None
|
||||||
|
)
|
||||||
|
|
||||||
|
# Hamlet and Cordelia are in the allowed user group, so can send email
|
||||||
|
# invitations.
|
||||||
|
self.login("hamlet")
|
||||||
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
||||||
|
self.login("cordelia")
|
||||||
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
||||||
|
|
||||||
|
# Iago is not in the allowed user group, so cannot send email
|
||||||
|
# invitations.
|
||||||
|
self.login("iago")
|
||||||
|
self.assert_json_error(
|
||||||
|
self.invite(invitee, ["Denmark"]),
|
||||||
|
"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_invite_users_group",
|
||||||
|
anonymous_user_group,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Hamlet is the direct member of the anonymous user group, so can send
|
||||||
|
# email invitations.
|
||||||
|
self.login("hamlet")
|
||||||
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
||||||
|
# Iago is in the `administrators_system_group` subgroup, so can send email
|
||||||
|
# invitations.
|
||||||
|
self.login("iago")
|
||||||
|
self.assert_json_success(self.invite(invitee, ["Denmark"]))
|
||||||
|
|
||||||
|
# Shiva is not in the anonymous user group, so cannot send email
|
||||||
|
# invitations.
|
||||||
|
self.login("shiva")
|
||||||
|
self.assert_json_error(
|
||||||
|
self.invite(invitee, ["Denmark"]),
|
||||||
|
"Insufficient permission",
|
||||||
|
)
|
||||||
|
|
||||||
def test_invite_user_signup_initial_history(self) -> None:
|
def test_invite_user_signup_initial_history(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test that a new user invited to a stream receives some initial
|
Test that a new user invited to a stream receives some initial
|
||||||
|
|
|
@ -113,13 +113,13 @@ class RealmTest(ZulipTestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(realm.can_create_public_channel_group_id, admins_group.id)
|
self.assertEqual(realm.can_create_public_channel_group_id, admins_group.id)
|
||||||
|
|
||||||
self.assertEqual(realm.invite_to_realm_policy, InviteToRealmPolicyEnum.ADMINS_ONLY)
|
|
||||||
self.assertEqual(realm.invite_to_stream_policy, CommonPolicyEnum.MODERATORS_ONLY)
|
self.assertEqual(realm.invite_to_stream_policy, CommonPolicyEnum.MODERATORS_ONLY)
|
||||||
realm = get_realm("test_education_non_profit")
|
realm = get_realm("test_education_non_profit")
|
||||||
moderators_group = NamedUserGroup.objects.get(
|
moderators_group = NamedUserGroup.objects.get(
|
||||||
name=SystemGroups.MODERATORS, realm=realm, is_system_group=True
|
name=SystemGroups.MODERATORS, realm=realm, is_system_group=True
|
||||||
)
|
)
|
||||||
self.assertEqual(realm.can_create_groups.id, moderators_group.id)
|
self.assertEqual(realm.can_create_groups.id, moderators_group.id)
|
||||||
|
self.assertEqual(realm.can_invite_users_group.id, admins_group.id)
|
||||||
self.assertEqual(realm.can_move_messages_between_channels_group.id, moderators_group.id)
|
self.assertEqual(realm.can_move_messages_between_channels_group.id, moderators_group.id)
|
||||||
|
|
||||||
def test_permission_for_education_for_profit_organization(self) -> None:
|
def test_permission_for_education_for_profit_organization(self) -> None:
|
||||||
|
@ -134,13 +134,13 @@ class RealmTest(ZulipTestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(realm.can_create_public_channel_group_id, admins_group.id)
|
self.assertEqual(realm.can_create_public_channel_group_id, admins_group.id)
|
||||||
|
|
||||||
self.assertEqual(realm.invite_to_realm_policy, InviteToRealmPolicyEnum.ADMINS_ONLY)
|
|
||||||
self.assertEqual(realm.invite_to_stream_policy, CommonPolicyEnum.MODERATORS_ONLY)
|
self.assertEqual(realm.invite_to_stream_policy, CommonPolicyEnum.MODERATORS_ONLY)
|
||||||
realm = get_realm("test_education_for_profit")
|
realm = get_realm("test_education_for_profit")
|
||||||
moderators_group = NamedUserGroup.objects.get(
|
moderators_group = NamedUserGroup.objects.get(
|
||||||
name=SystemGroups.MODERATORS, realm=realm, is_system_group=True
|
name=SystemGroups.MODERATORS, realm=realm, is_system_group=True
|
||||||
)
|
)
|
||||||
self.assertEqual(realm.can_create_groups.id, moderators_group.id)
|
self.assertEqual(realm.can_create_groups.id, moderators_group.id)
|
||||||
|
self.assertEqual(realm.can_invite_users_group.id, admins_group.id)
|
||||||
self.assertEqual(realm.can_move_messages_between_channels_group.id, moderators_group.id)
|
self.assertEqual(realm.can_move_messages_between_channels_group.id, moderators_group.id)
|
||||||
|
|
||||||
def test_realm_enable_spectator_access(self) -> None:
|
def test_realm_enable_spectator_access(self) -> None:
|
||||||
|
@ -2349,6 +2349,9 @@ class RealmAPITest(ZulipTestCase):
|
||||||
def test_can_create_groups_setting_requires_owner(self) -> None:
|
def test_can_create_groups_setting_requires_owner(self) -> None:
|
||||||
self.do_test_changing_groups_setting_by_owners_only("can_create_groups")
|
self.do_test_changing_groups_setting_by_owners_only("can_create_groups")
|
||||||
|
|
||||||
|
def test_can_invite_users_group_setting_requires_owner(self) -> None:
|
||||||
|
self.do_test_changing_groups_setting_by_owners_only("can_invite_users_group")
|
||||||
|
|
||||||
def test_can_manage_all_groups_setting_requires_owner(self) -> None:
|
def test_can_manage_all_groups_setting_requires_owner(self) -> None:
|
||||||
self.do_test_changing_groups_setting_by_owners_only("can_manage_all_groups")
|
self.do_test_changing_groups_setting_by_owners_only("can_manage_all_groups")
|
||||||
|
|
||||||
|
|
|
@ -139,6 +139,7 @@ def update_realm(
|
||||||
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_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,
|
||||||
can_move_messages_between_topics_group: Json[GroupSettingChangeRequest] | None = None,
|
can_move_messages_between_topics_group: Json[GroupSettingChangeRequest] | None = None,
|
||||||
|
@ -223,6 +224,7 @@ def update_realm(
|
||||||
or invite_required is not None
|
or invite_required is not None
|
||||||
or create_multiuse_invite_group is not None
|
or create_multiuse_invite_group is not None
|
||||||
or can_create_groups is not None
|
or can_create_groups is not None
|
||||||
|
or can_invite_users_group is not None
|
||||||
or can_manage_all_groups is not None
|
or can_manage_all_groups is not None
|
||||||
) and not user_profile.is_realm_owner:
|
) and not user_profile.is_realm_owner:
|
||||||
raise OrganizationOwnerRequiredError
|
raise OrganizationOwnerRequiredError
|
||||||
|
|
Loading…
Reference in New Issue