mirror of https://github.com/zulip/zulip.git
settings: Add two realm settings to restrict direct messages.
Fixes #24467.
This commit is contained in:
parent
318d3e3cca
commit
6098c2cebe
|
@ -20,6 +20,18 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 9.0
|
## Changes in Zulip 9.0
|
||||||
|
|
||||||
|
**Feature level 270**
|
||||||
|
|
||||||
|
* `PATCH /realm`, [`POST /register`](/api/register-queue),
|
||||||
|
[`GET /events`](/api/get-events): Added two new realm settings,
|
||||||
|
`direct_message_initiator_group`, which is a
|
||||||
|
[group-setting value](/api/group-setting-values) describing the
|
||||||
|
set of users with permission to initiate direct message thread, and
|
||||||
|
`direct_message_permission_group`, which is a
|
||||||
|
[group-setting value](/api/group-setting-values) describing the
|
||||||
|
set of users of which at least one member must be included as sender
|
||||||
|
or recipient in all personal and group direct messages.
|
||||||
|
|
||||||
**Feature level 269**
|
**Feature level 269**
|
||||||
|
|
||||||
* [`POST /register`](/api/register-queue), [`PATCH
|
* [`POST /register`](/api/register-queue), [`PATCH
|
||||||
|
|
|
@ -33,7 +33,9 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# 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 = 269
|
|
||||||
|
API_FEATURE_LEVEL = 270 # Last bumped for direct_message_permission_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
|
||||||
|
|
|
@ -142,6 +142,8 @@ export function build_page() {
|
||||||
language_list,
|
language_list,
|
||||||
realm_default_language_name: get_language_name(realm.realm_default_language),
|
realm_default_language_name: get_language_name(realm.realm_default_language),
|
||||||
realm_default_language_code: realm.realm_default_language,
|
realm_default_language_code: realm.realm_default_language,
|
||||||
|
realm_direct_message_initiator_group_id: realm.realm_direct_message_initiator_group,
|
||||||
|
realm_direct_message_permission_group_id: realm.realm_direct_message_permission_group,
|
||||||
realm_waiting_period_threshold: realm.realm_waiting_period_threshold,
|
realm_waiting_period_threshold: realm.realm_waiting_period_threshold,
|
||||||
realm_new_stream_announcements_stream_id: realm.realm_new_stream_announcements_stream_id,
|
realm_new_stream_announcements_stream_id: realm.realm_new_stream_announcements_stream_id,
|
||||||
realm_signup_announcements_stream_id: realm.realm_signup_announcements_stream_id,
|
realm_signup_announcements_stream_id: realm.realm_signup_announcements_stream_id,
|
||||||
|
@ -276,6 +278,10 @@ export function build_page() {
|
||||||
|
|
||||||
tippy.default($("#realm_can_access_all_users_group_widget_container")[0], opts);
|
tippy.default($("#realm_can_access_all_users_group_widget_container")[0], opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settings_org.check_disable_direct_message_initiator_group_dropdown(
|
||||||
|
realm.realm_direct_message_permission_group,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function launch(section, user_settings_tab) {
|
export function launch(section, user_settings_tab) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import * as compose_validate from "./compose_validate";
|
||||||
import * as drafts from "./drafts";
|
import * as drafts from "./drafts";
|
||||||
import * as message_lists from "./message_lists";
|
import * as message_lists from "./message_lists";
|
||||||
import type {Message} from "./message_store";
|
import type {Message} from "./message_store";
|
||||||
|
import * as message_util from "./message_util";
|
||||||
import * as message_viewport from "./message_viewport";
|
import * as message_viewport from "./message_viewport";
|
||||||
import * as narrow_state from "./narrow_state";
|
import * as narrow_state from "./narrow_state";
|
||||||
import {page_params} from "./page_params";
|
import {page_params} from "./page_params";
|
||||||
|
@ -22,9 +23,7 @@ import * as people from "./people";
|
||||||
import * as popovers from "./popovers";
|
import * as popovers from "./popovers";
|
||||||
import * as reload_state from "./reload_state";
|
import * as reload_state from "./reload_state";
|
||||||
import * as resize from "./resize";
|
import * as resize from "./resize";
|
||||||
import * as settings_config from "./settings_config";
|
|
||||||
import * as spectators from "./spectators";
|
import * as spectators from "./spectators";
|
||||||
import {realm} from "./state_data";
|
|
||||||
import * as stream_data from "./stream_data";
|
import * as stream_data from "./stream_data";
|
||||||
|
|
||||||
// Opts sent to `compose_actions.start`.
|
// Opts sent to `compose_actions.start`.
|
||||||
|
@ -530,26 +529,21 @@ export function on_narrow(opts: NarrowActivateOpts): void {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Do not open compose box if organization has disabled sending
|
// Do not open compose box if sender is not allowed to send direct message.
|
||||||
// direct messages and recipient is not a bot.
|
const recipient_ids_string = people.emails_strings_to_user_ids_string(
|
||||||
|
opts.private_message_recipient,
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
realm.realm_private_message_policy ===
|
recipient_ids_string &&
|
||||||
settings_config.private_message_policy_values.disabled.code &&
|
!message_util.user_can_send_direct_message(recipient_ids_string)
|
||||||
opts.private_message_recipient
|
|
||||||
) {
|
) {
|
||||||
const emails = opts.private_message_recipient.split(",");
|
// If we are navigating between direct message conversation,
|
||||||
if (
|
// we want the compose box to close for non-bot users.
|
||||||
emails.length !== 1 ||
|
if (compose_state.composing()) {
|
||||||
emails[0] === undefined ||
|
cancel();
|
||||||
!people.get_by_email(emails[0])!.is_bot
|
|
||||||
) {
|
|
||||||
// If we are navigating between direct message conversations,
|
|
||||||
// we want the compose box to close for non-bot users.
|
|
||||||
if (compose_state.composing()) {
|
|
||||||
cancel();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the compose box, passing the option to skip attempting
|
// Open the compose box, passing the option to skip attempting
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
|
|
||||||
|
import render_cannot_send_direct_message_error from "../templates/compose_banner/cannot_send_direct_message_error.hbs";
|
||||||
import render_compose_banner from "../templates/compose_banner/compose_banner.hbs";
|
import render_compose_banner from "../templates/compose_banner/compose_banner.hbs";
|
||||||
import render_stream_does_not_exist_error from "../templates/compose_banner/stream_does_not_exist_error.hbs";
|
import render_stream_does_not_exist_error from "../templates/compose_banner/stream_does_not_exist_error.hbs";
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ export const CLASSNAMES = {
|
||||||
stream_does_not_exist: "stream_does_not_exist",
|
stream_does_not_exist: "stream_does_not_exist",
|
||||||
missing_stream: "missing_stream",
|
missing_stream: "missing_stream",
|
||||||
no_post_permissions: "no_post_permissions",
|
no_post_permissions: "no_post_permissions",
|
||||||
private_messages_disabled: "private_messages_disabled",
|
cannot_send_direct_message: "cannot_send_direct_message",
|
||||||
missing_private_message_recipient: "missing_private_message_recipient",
|
missing_private_message_recipient: "missing_private_message_recipient",
|
||||||
invalid_recipient: "invalid_recipient",
|
invalid_recipient: "invalid_recipient",
|
||||||
invalid_recipients: "invalid_recipients",
|
invalid_recipients: "invalid_recipients",
|
||||||
|
@ -183,6 +184,21 @@ export function show_error_message(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function cannot_send_direct_message_error(error_message: string): void {
|
||||||
|
// Remove any existing banners with this warning.
|
||||||
|
$(`#compose_banners .${CSS.escape(CLASSNAMES.cannot_send_direct_message)}`).remove();
|
||||||
|
|
||||||
|
const new_row_html = render_cannot_send_direct_message_error({
|
||||||
|
banner_type: ERROR,
|
||||||
|
error_message,
|
||||||
|
classname: CLASSNAMES.cannot_send_direct_message,
|
||||||
|
});
|
||||||
|
append_compose_banner_to_banner_list($(new_row_html), $("#compose_banners"));
|
||||||
|
hide_compose_spinner();
|
||||||
|
|
||||||
|
$("#private_message_recipient").trigger("focus").trigger("select");
|
||||||
|
}
|
||||||
|
|
||||||
export function show_stream_does_not_exist_error(stream_name: string): void {
|
export function show_stream_does_not_exist_error(stream_name: string): void {
|
||||||
// Remove any existing banners with this warning.
|
// Remove any existing banners with this warning.
|
||||||
$(`#compose_banners .${CSS.escape(CLASSNAMES.stream_does_not_exist)}`).remove();
|
$(`#compose_banners .${CSS.escape(CLASSNAMES.stream_does_not_exist)}`).remove();
|
||||||
|
|
|
@ -4,6 +4,7 @@ import * as compose_actions from "./compose_actions";
|
||||||
import {$t} from "./i18n";
|
import {$t} from "./i18n";
|
||||||
import * as message_lists from "./message_lists";
|
import * as message_lists from "./message_lists";
|
||||||
import * as message_store from "./message_store";
|
import * as message_store from "./message_store";
|
||||||
|
import * as message_util from "./message_util";
|
||||||
import * as narrow_state from "./narrow_state";
|
import * as narrow_state from "./narrow_state";
|
||||||
import * as people from "./people";
|
import * as people from "./people";
|
||||||
import * as stream_data from "./stream_data";
|
import * as stream_data from "./stream_data";
|
||||||
|
@ -132,7 +133,7 @@ export function update_buttons_for_private(): void {
|
||||||
|
|
||||||
let disable_reply;
|
let disable_reply;
|
||||||
|
|
||||||
if (!pm_ids_string || people.user_can_direct_message(pm_ids_string)) {
|
if (!pm_ids_string || message_util.user_can_send_direct_message(pm_ids_string)) {
|
||||||
disable_reply = false;
|
disable_reply = false;
|
||||||
} else {
|
} else {
|
||||||
// disable the [Message X] button when in a private narrow
|
// disable the [Message X] button when in a private narrow
|
||||||
|
|
|
@ -19,12 +19,11 @@ import * as dropdown_widget from "./dropdown_widget";
|
||||||
import type {Option} from "./dropdown_widget";
|
import type {Option} from "./dropdown_widget";
|
||||||
import {$t} from "./i18n";
|
import {$t} from "./i18n";
|
||||||
import * as narrow_state from "./narrow_state";
|
import * as narrow_state from "./narrow_state";
|
||||||
import * as people from "./people";
|
|
||||||
import * as settings_config from "./settings_config";
|
|
||||||
import {realm} from "./state_data";
|
import {realm} from "./state_data";
|
||||||
import * as stream_data from "./stream_data";
|
import * as stream_data from "./stream_data";
|
||||||
import * as sub_store from "./sub_store";
|
import * as sub_store from "./sub_store";
|
||||||
import * as ui_util from "./ui_util";
|
import * as ui_util from "./ui_util";
|
||||||
|
import * as user_groups from "./user_groups";
|
||||||
import * as util from "./util";
|
import * as util from "./util";
|
||||||
|
|
||||||
type MessageType = "stream" | "private";
|
type MessageType = "stream" | "private";
|
||||||
|
@ -116,12 +115,7 @@ export function update_on_recipient_change(): void {
|
||||||
export function get_posting_policy_error_message(): string {
|
export function get_posting_policy_error_message(): string {
|
||||||
if (compose_state.selected_recipient_id === "direct") {
|
if (compose_state.selected_recipient_id === "direct") {
|
||||||
const recipients = compose_pm_pill.get_user_ids_string();
|
const recipients = compose_pm_pill.get_user_ids_string();
|
||||||
if (!people.user_can_direct_message(recipients)) {
|
return compose_validate.check_dm_permissions_and_get_error_string(recipients);
|
||||||
return $t({
|
|
||||||
defaultMessage: "You are not allowed to send direct messages in this organization.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNumber(compose_state.selected_recipient_id)) {
|
if (!isNumber(compose_state.selected_recipient_id)) {
|
||||||
|
@ -146,11 +140,13 @@ export function check_posting_policy_for_compose_box(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
let banner_classname = compose_banner.CLASSNAMES.no_post_permissions;
|
let banner_classname = compose_banner.CLASSNAMES.no_post_permissions;
|
||||||
if (compose_state.selected_recipient_id === "direct") {
|
|
||||||
banner_classname = compose_banner.CLASSNAMES.private_messages_disabled;
|
|
||||||
}
|
|
||||||
compose_validate.set_recipient_disallowed(true);
|
compose_validate.set_recipient_disallowed(true);
|
||||||
compose_banner.show_error_message(banner_text, banner_classname, $("#compose_banners"));
|
if (compose_state.selected_recipient_id === "direct") {
|
||||||
|
banner_classname = compose_banner.CLASSNAMES.cannot_send_direct_message;
|
||||||
|
compose_banner.cannot_send_direct_message_error(banner_text);
|
||||||
|
} else {
|
||||||
|
compose_banner.show_error_message(banner_text, banner_classname, $("#compose_banners"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function switch_message_type(message_type: MessageType): void {
|
function switch_message_type(message_type: MessageType): void {
|
||||||
|
@ -263,10 +259,8 @@ function get_options_for_recipient_widget(): Option[] {
|
||||||
name: $t({defaultMessage: "Direct message"}),
|
name: $t({defaultMessage: "Direct message"}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
const {name} = user_groups.get_user_group_from_id(realm.realm_direct_message_permission_group);
|
||||||
realm.realm_private_message_policy ===
|
if (name !== "role:nobody") {
|
||||||
settings_config.private_message_policy_values.by_anyone.code
|
|
||||||
) {
|
|
||||||
options.unshift(direct_messages_option);
|
options.unshift(direct_messages_option);
|
||||||
} else {
|
} else {
|
||||||
options.push(direct_messages_option);
|
options.push(direct_messages_option);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import * as compose_recipient from "./compose_recipient";
|
||||||
import * as compose_state from "./compose_state";
|
import * as compose_state from "./compose_state";
|
||||||
import * as compose_validate from "./compose_validate";
|
import * as compose_validate from "./compose_validate";
|
||||||
import {$t} from "./i18n";
|
import {$t} from "./i18n";
|
||||||
|
import {pick_empty_narrow_banner} from "./narrow_banner";
|
||||||
import * as narrow_state from "./narrow_state";
|
import * as narrow_state from "./narrow_state";
|
||||||
import * as popover_menus from "./popover_menus";
|
import * as popover_menus from "./popover_menus";
|
||||||
import {EXTRA_LONG_HOVER_DELAY, INSTANT_HOVER_DELAY, LONG_HOVER_DELAY} from "./tippyjs";
|
import {EXTRA_LONG_HOVER_DELAY, INSTANT_HOVER_DELAY, LONG_HOVER_DELAY} from "./tippyjs";
|
||||||
|
@ -47,11 +48,7 @@ export function initialize(): void {
|
||||||
const button_type = $elem.attr("data-reply-button-type");
|
const button_type = $elem.attr("data-reply-button-type");
|
||||||
switch (button_type) {
|
switch (button_type) {
|
||||||
case "direct_disabled": {
|
case "direct_disabled": {
|
||||||
instance.setContent(
|
instance.setContent(pick_empty_narrow_banner().title);
|
||||||
parse_html(
|
|
||||||
$("#compose_reply_direct_disabled_button_tooltip_template").html(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case "selected_message": {
|
case "selected_message": {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import * as compose_state from "./compose_state";
|
||||||
import * as compose_ui from "./compose_ui";
|
import * as compose_ui from "./compose_ui";
|
||||||
import {$t} from "./i18n";
|
import {$t} from "./i18n";
|
||||||
import * as message_store from "./message_store";
|
import * as message_store from "./message_store";
|
||||||
|
import * as message_util from "./message_util";
|
||||||
import * as narrow_state from "./narrow_state";
|
import * as narrow_state from "./narrow_state";
|
||||||
import * as peer_data from "./peer_data";
|
import * as peer_data from "./peer_data";
|
||||||
import * as people from "./people";
|
import * as people from "./people";
|
||||||
|
@ -26,6 +27,7 @@ import * as stream_data from "./stream_data";
|
||||||
import * as sub_store from "./sub_store";
|
import * as sub_store from "./sub_store";
|
||||||
import type {StreamSubscription} from "./sub_store";
|
import type {StreamSubscription} from "./sub_store";
|
||||||
import type {UserOrMention} from "./typeahead_helper";
|
import type {UserOrMention} from "./typeahead_helper";
|
||||||
|
import * as user_groups from "./user_groups";
|
||||||
import * as util from "./util";
|
import * as util from "./util";
|
||||||
|
|
||||||
let user_acknowledged_stream_wildcard = false;
|
let user_acknowledged_stream_wildcard = false;
|
||||||
|
@ -110,6 +112,32 @@ export function needs_subscribe_warning(user_id: number, stream_id: number): boo
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function check_dm_permissions_and_get_error_string(user_ids_string: string): string {
|
||||||
|
if (!people.user_can_direct_message(user_ids_string)) {
|
||||||
|
const {name} = user_groups.get_user_group_from_id(
|
||||||
|
realm.realm_direct_message_permission_group,
|
||||||
|
);
|
||||||
|
if (name === "role:nobody") {
|
||||||
|
return $t({
|
||||||
|
defaultMessage: "Direct messages are disabled in this organization.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return $t({
|
||||||
|
defaultMessage: "This conversation does not include any users who can authorize it.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
message_util.get_direct_message_permission_hints(user_ids_string)
|
||||||
|
.is_known_empty_conversation &&
|
||||||
|
!people.user_can_initiate_direct_message_thread(user_ids_string)
|
||||||
|
) {
|
||||||
|
return $t({
|
||||||
|
defaultMessage: "You are not allowed to start direct message conversations.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
function get_stream_id_for_textarea($textarea: JQuery<HTMLTextAreaElement>): number | undefined {
|
function get_stream_id_for_textarea($textarea: JQuery<HTMLTextAreaElement>): number | undefined {
|
||||||
// Returns the stream ID, if any, associated with the textarea:
|
// Returns the stream ID, if any, associated with the textarea:
|
||||||
// The recipient of a message being edited, or the target
|
// The recipient of a message being edited, or the target
|
||||||
|
@ -619,20 +647,9 @@ function validate_stream_message(scheduling_message: boolean): boolean {
|
||||||
// for now)
|
// for now)
|
||||||
function validate_private_message(): boolean {
|
function validate_private_message(): boolean {
|
||||||
const user_ids = compose_pm_pill.get_user_ids();
|
const user_ids = compose_pm_pill.get_user_ids();
|
||||||
|
const user_ids_string = util.sorted_ids(user_ids).join(",");
|
||||||
const $banner_container = $("#compose_banners");
|
const $banner_container = $("#compose_banners");
|
||||||
|
|
||||||
const user_ids_string = user_ids.join(",");
|
|
||||||
|
|
||||||
if (!people.user_can_direct_message(user_ids_string)) {
|
|
||||||
compose_banner.show_error_message(
|
|
||||||
$t({defaultMessage: "Direct messages are disabled in this organization."}),
|
|
||||||
compose_banner.CLASSNAMES.private_messages_disabled,
|
|
||||||
$banner_container,
|
|
||||||
$("#private_message_recipient"),
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (compose_state.private_message_recipient().length === 0) {
|
if (compose_state.private_message_recipient().length === 0) {
|
||||||
compose_banner.show_error_message(
|
compose_banner.show_error_message(
|
||||||
$t({defaultMessage: "Please specify at least one valid recipient."}),
|
$t({defaultMessage: "Please specify at least one valid recipient."}),
|
||||||
|
@ -646,6 +663,12 @@ function validate_private_message(): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const direct_message_error_string = check_dm_permissions_and_get_error_string(user_ids_string);
|
||||||
|
if (direct_message_error_string) {
|
||||||
|
compose_banner.cannot_send_direct_message_error(direct_message_error_string);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const invalid_recipients = get_invalid_recipient_emails();
|
const invalid_recipients = get_invalid_recipient_emails();
|
||||||
|
|
||||||
let context = {};
|
let context = {};
|
||||||
|
|
|
@ -10,6 +10,7 @@ import * as markdown from "./markdown";
|
||||||
import * as message_lists from "./message_lists";
|
import * as message_lists from "./message_lists";
|
||||||
import * as message_live_update from "./message_live_update";
|
import * as message_live_update from "./message_live_update";
|
||||||
import * as message_store from "./message_store";
|
import * as message_store from "./message_store";
|
||||||
|
import * as message_util from "./message_util";
|
||||||
import * as people from "./people";
|
import * as people from "./people";
|
||||||
import * as pm_list from "./pm_list";
|
import * as pm_list from "./pm_list";
|
||||||
import * as recent_view_data from "./recent_view_data";
|
import * as recent_view_data from "./recent_view_data";
|
||||||
|
@ -224,6 +225,14 @@ export function try_deliver_locally(message_request, insert_new_messages) {
|
||||||
// view; this is useful to ensure it will be visible in other
|
// view; this is useful to ensure it will be visible in other
|
||||||
// views that we might navigate to before we get a response from
|
// views that we might navigate to before we get a response from
|
||||||
// the server.
|
// the server.
|
||||||
|
if (
|
||||||
|
message_request.to_user_ids &&
|
||||||
|
!people.user_can_initiate_direct_message_thread(message_request.to_user_ids) &&
|
||||||
|
!message_util.get_direct_message_permission_hints(message_request.to_user_ids)
|
||||||
|
.is_local_echo_safe
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
if (markdown.contains_backend_only_syntax(message_request.content)) {
|
if (markdown.contains_backend_only_syntax(message_request.content)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ import $ from "jquery";
|
||||||
import {all_messages_data} from "./all_messages_data";
|
import {all_messages_data} from "./all_messages_data";
|
||||||
import * as blueslip from "./blueslip";
|
import * as blueslip from "./blueslip";
|
||||||
import * as channel from "./channel";
|
import * as channel from "./channel";
|
||||||
|
import * as compose_closed_ui from "./compose_closed_ui";
|
||||||
|
import * as compose_recipient from "./compose_recipient";
|
||||||
import * as direct_message_group_data from "./direct_message_group_data";
|
import * as direct_message_group_data from "./direct_message_group_data";
|
||||||
import * as message_feed_loading from "./message_feed_loading";
|
import * as message_feed_loading from "./message_feed_loading";
|
||||||
import * as message_feed_top_notices from "./message_feed_top_notices";
|
import * as message_feed_top_notices from "./message_feed_top_notices";
|
||||||
|
@ -106,6 +108,8 @@ function process_result(data, opts) {
|
||||||
// Even after loading more messages, we have
|
// Even after loading more messages, we have
|
||||||
// no messages to display in this narrow.
|
// no messages to display in this narrow.
|
||||||
narrow_banner.show_empty_narrow_message();
|
narrow_banner.show_empty_narrow_message();
|
||||||
|
compose_closed_ui.update_buttons_for_private();
|
||||||
|
compose_recipient.check_posting_policy_for_compose_box();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.num_before > 0 && !has_found_oldest) {
|
if (opts.num_before > 0 && !has_found_oldest) {
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
|
import assert from "minimalistic-assert";
|
||||||
|
|
||||||
import {all_messages_data} from "./all_messages_data";
|
import {all_messages_data} from "./all_messages_data";
|
||||||
import type {MessageListData} from "./message_list_data";
|
import type {MessageListData} from "./message_list_data";
|
||||||
import type {MessageList, RenderInfo} from "./message_lists";
|
import {type MessageList, type RenderInfo} from "./message_lists";
|
||||||
|
import * as message_lists from "./message_lists";
|
||||||
import * as message_store from "./message_store";
|
import * as message_store from "./message_store";
|
||||||
import type {Message} from "./message_store";
|
import type {Message} from "./message_store";
|
||||||
|
import * as people from "./people";
|
||||||
|
import * as pm_conversations from "./pm_conversations";
|
||||||
import * as unread from "./unread";
|
import * as unread from "./unread";
|
||||||
import * as unread_ui from "./unread_ui";
|
import * as unread_ui from "./unread_ui";
|
||||||
|
|
||||||
|
type DirectMessagePermissionHints = {
|
||||||
|
is_known_empty_conversation: boolean;
|
||||||
|
is_local_echo_safe: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export function do_unread_count_updates(messages: Message[], expect_no_new_unreads = false): void {
|
export function do_unread_count_updates(messages: Message[], expect_no_new_unreads = false): void {
|
||||||
const any_new_unreads = unread.process_loaded_messages(messages, expect_no_new_unreads);
|
const any_new_unreads = unread.process_loaded_messages(messages, expect_no_new_unreads);
|
||||||
|
|
||||||
|
@ -112,3 +122,45 @@ export function get_topics_for_message_ids(message_ids: number[]): Map<string, [
|
||||||
}
|
}
|
||||||
return topics;
|
return topics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function get_direct_message_permission_hints(
|
||||||
|
recipient_ids_string: string,
|
||||||
|
): DirectMessagePermissionHints {
|
||||||
|
// Check if there are any previous messages in the DM conversation.
|
||||||
|
const have_conversation_in_cache =
|
||||||
|
pm_conversations.recent.has_conversation(recipient_ids_string);
|
||||||
|
if (have_conversation_in_cache) {
|
||||||
|
return {is_known_empty_conversation: false, is_local_echo_safe: true};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not, we need to check if the current filter matches the DM view we
|
||||||
|
// are composing to.
|
||||||
|
const dm_conversation = message_lists.current?.data?.filter.operands("dm")[0];
|
||||||
|
if (dm_conversation) {
|
||||||
|
const current_user_ids_string = people.emails_strings_to_user_ids_string(dm_conversation);
|
||||||
|
assert(current_user_ids_string !== undefined);
|
||||||
|
// If it matches and the messages for the current filter are fetched,
|
||||||
|
// then there are certainly no messages in the conversation.
|
||||||
|
if (
|
||||||
|
people.pm_lookup_key(recipient_ids_string) ===
|
||||||
|
people.pm_lookup_key(current_user_ids_string) &&
|
||||||
|
message_lists.current?.data?.fetch_status.has_found_newest()
|
||||||
|
) {
|
||||||
|
return {is_known_empty_conversation: true, is_local_echo_safe: true};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it does not match, then there can be messages in the DM conversation
|
||||||
|
// which are not fetched locally and hence we disable local echo for clean
|
||||||
|
// error handling in case there are no messages in the conversation and
|
||||||
|
// user is not allowed to initiate DM conversations.
|
||||||
|
return {is_known_empty_conversation: false, is_local_echo_safe: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function user_can_send_direct_message(user_ids_string: string): boolean {
|
||||||
|
return (
|
||||||
|
(!get_direct_message_permission_hints(user_ids_string).is_known_empty_conversation ||
|
||||||
|
people.user_can_initiate_direct_message_thread(user_ids_string)) &&
|
||||||
|
people.user_can_direct_message(user_ids_string)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -2,16 +2,17 @@ import $ from "jquery";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import assert from "minimalistic-assert";
|
import assert from "minimalistic-assert";
|
||||||
|
|
||||||
|
import * as compose_validate from "./compose_validate";
|
||||||
import {$t, $t_html} from "./i18n";
|
import {$t, $t_html} from "./i18n";
|
||||||
import type {NarrowBannerData, SearchData} from "./narrow_error";
|
import type {NarrowBannerData, SearchData} from "./narrow_error";
|
||||||
import {narrow_error} from "./narrow_error";
|
import {narrow_error} from "./narrow_error";
|
||||||
import * as narrow_state from "./narrow_state";
|
import * as narrow_state from "./narrow_state";
|
||||||
import {page_params} from "./page_params";
|
import {page_params} from "./page_params";
|
||||||
import * as people from "./people";
|
import * as people from "./people";
|
||||||
import * as settings_config from "./settings_config";
|
|
||||||
import * as spectators from "./spectators";
|
import * as spectators from "./spectators";
|
||||||
import {realm} from "./state_data";
|
import {realm} from "./state_data";
|
||||||
import * as stream_data from "./stream_data";
|
import * as stream_data from "./stream_data";
|
||||||
|
import * as util from "./util";
|
||||||
|
|
||||||
const SPECTATOR_STREAM_NARROW_BANNER = {
|
const SPECTATOR_STREAM_NARROW_BANNER = {
|
||||||
title: "",
|
title: "",
|
||||||
|
@ -104,7 +105,7 @@ function retrieve_search_query_data(): SearchData {
|
||||||
return search_string_result;
|
return search_string_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pick_empty_narrow_banner(): NarrowBannerData {
|
export function pick_empty_narrow_banner(): NarrowBannerData {
|
||||||
const default_banner = {
|
const default_banner = {
|
||||||
title: $t({defaultMessage: "There are no messages here."}),
|
title: $t({defaultMessage: "There are no messages here."}),
|
||||||
// Spectators cannot start a conversation.
|
// Spectators cannot start a conversation.
|
||||||
|
@ -230,17 +231,6 @@ function pick_empty_narrow_banner(): NarrowBannerData {
|
||||||
return MENTIONS_VIEW_EMPTY_BANNER;
|
return MENTIONS_VIEW_EMPTY_BANNER;
|
||||||
case "dm":
|
case "dm":
|
||||||
// You have no direct messages.
|
// You have no direct messages.
|
||||||
if (
|
|
||||||
realm.realm_private_message_policy ===
|
|
||||||
settings_config.private_message_policy_values.disabled.code
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
title: $t({
|
|
||||||
defaultMessage:
|
|
||||||
"You are not allowed to send direct messages in this organization.",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
title: $t({defaultMessage: "You have no direct messages yet!"}),
|
title: $t({defaultMessage: "You have no direct messages yet!"}),
|
||||||
html: $t_html(
|
html: $t_html(
|
||||||
|
@ -324,16 +314,21 @@ function pick_empty_narrow_banner(): NarrowBannerData {
|
||||||
}
|
}
|
||||||
const user_ids = people.emails_strings_to_user_ids_array(first_operand);
|
const user_ids = people.emails_strings_to_user_ids_array(first_operand);
|
||||||
assert(user_ids?.[0] !== undefined);
|
assert(user_ids?.[0] !== undefined);
|
||||||
if (
|
const user_ids_string = util.sorted_ids(user_ids).join(",");
|
||||||
realm.realm_private_message_policy ===
|
const direct_message_error_string =
|
||||||
settings_config.private_message_policy_values.disabled.code &&
|
compose_validate.check_dm_permissions_and_get_error_string(user_ids_string);
|
||||||
(user_ids.length !== 1 || !people.get_by_user_id(user_ids[0]).is_bot)
|
if (direct_message_error_string) {
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
title: $t({
|
title: direct_message_error_string,
|
||||||
defaultMessage:
|
html: $t_html(
|
||||||
"You are not allowed to send direct messages in this organization.",
|
{
|
||||||
}),
|
defaultMessage: "<z-link>Learn more.</z-link>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"z-link": (content_html) =>
|
||||||
|
`<a target="_blank" rel="noopener noreferrer" href="/help/restrict-direct-messages">${content_html.join("")}</a>`,
|
||||||
|
},
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!first_operand.includes(",")) {
|
if (!first_operand.includes(",")) {
|
||||||
|
@ -409,16 +404,21 @@ function pick_empty_narrow_banner(): NarrowBannerData {
|
||||||
title: $t({defaultMessage: "This user does not exist!"}),
|
title: $t({defaultMessage: "This user does not exist!"}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (
|
const person_id_string = person_in_dms.user_id.toString();
|
||||||
realm.realm_private_message_policy ===
|
const direct_message_error_string =
|
||||||
settings_config.private_message_policy_values.disabled.code &&
|
compose_validate.check_dm_permissions_and_get_error_string(person_id_string);
|
||||||
!person_in_dms.is_bot
|
if (direct_message_error_string) {
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
title: $t({
|
title: direct_message_error_string,
|
||||||
defaultMessage:
|
html: $t_html(
|
||||||
"You are not allowed to send direct messages in this organization.",
|
{
|
||||||
}),
|
defaultMessage: "<z-link>Learn more.</z-link>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"z-link": (content_html) =>
|
||||||
|
`<a target="_blank" rel="noopener noreferrer" href="/help/restrict-direct-messages">${content_html.join("")}</a>`,
|
||||||
|
},
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (people.is_current_user(first_operand)) {
|
if (people.is_current_user(first_operand)) {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import type {
|
||||||
} from "./state_data";
|
} from "./state_data";
|
||||||
import {current_user, realm} from "./state_data";
|
import {current_user, realm} from "./state_data";
|
||||||
import * as timerender from "./timerender";
|
import * as timerender from "./timerender";
|
||||||
|
import {is_user_in_group} from "./user_groups";
|
||||||
import {user_settings} from "./user_settings";
|
import {user_settings} from "./user_settings";
|
||||||
import * as util from "./util";
|
import * as util from "./util";
|
||||||
|
|
||||||
|
@ -760,28 +761,38 @@ export function should_add_guest_user_indicator(user_id: number): boolean {
|
||||||
return user.is_guest;
|
return user.is_guest;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function user_can_direct_message(recipient_ids_string: string): boolean {
|
export function user_can_initiate_direct_message_thread(recipient_ids_string: string): boolean {
|
||||||
// Common function for checking if a user can send a direct
|
const direct_message_initiator_group_id = realm.realm_direct_message_initiator_group;
|
||||||
// message to the target user (or group of users) represented by a
|
|
||||||
// user ids string.
|
|
||||||
|
|
||||||
// Regardless of policy, we allow sending direct messages to bots and to self.
|
|
||||||
const recipient_ids = user_ids_string_to_ids_array(recipient_ids_string);
|
const recipient_ids = user_ids_string_to_ids_array(recipient_ids_string);
|
||||||
if (
|
if (is_user_in_group(direct_message_initiator_group_id, my_user_id)) {
|
||||||
recipient_ids.length === 1 &&
|
return true;
|
||||||
recipient_ids[0] !== undefined &&
|
}
|
||||||
(is_valid_bot_user(recipient_ids[0]) || is_my_user_id(recipient_ids[0]))
|
for (const recipient of recipient_ids) {
|
||||||
) {
|
if (!is_valid_bot_user(recipient) && recipient !== my_user_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function user_can_direct_message(recipient_ids_string: string): boolean {
|
||||||
|
const direct_message_permission_group_id = realm.realm_direct_message_permission_group;
|
||||||
|
const recipient_ids = user_ids_string_to_ids_array(recipient_ids_string);
|
||||||
|
if (is_user_in_group(direct_message_permission_group_id, my_user_id)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
let other_human_recipients_exist = false;
|
||||||
realm.realm_private_message_policy ===
|
for (const recipient_id of recipient_ids) {
|
||||||
settings_config.private_message_policy_values.disabled.code
|
if (is_valid_bot_user(recipient_id) || recipient_id === my_user_id) {
|
||||||
) {
|
continue;
|
||||||
return false;
|
}
|
||||||
|
if (is_user_in_group(direct_message_permission_group_id, recipient_id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
other_human_recipients_exist = true;
|
||||||
}
|
}
|
||||||
return true;
|
return !other_human_recipients_exist;
|
||||||
}
|
}
|
||||||
|
|
||||||
function gravatar_url_for_email(email: string): string {
|
function gravatar_url_for_email(email: string): string {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import * as browser_history from "./browser_history";
|
||||||
import {buddy_list} from "./buddy_list";
|
import {buddy_list} from "./buddy_list";
|
||||||
import * as compose_call from "./compose_call";
|
import * as compose_call from "./compose_call";
|
||||||
import * as compose_call_ui from "./compose_call_ui";
|
import * as compose_call_ui from "./compose_call_ui";
|
||||||
|
import * as compose_closed_ui from "./compose_closed_ui";
|
||||||
import * as compose_pm_pill from "./compose_pm_pill";
|
import * as compose_pm_pill from "./compose_pm_pill";
|
||||||
import * as compose_recipient from "./compose_recipient";
|
import * as compose_recipient from "./compose_recipient";
|
||||||
import * as compose_state from "./compose_state";
|
import * as compose_state from "./compose_state";
|
||||||
|
@ -212,6 +213,8 @@ export function dispatch_normal_event(event) {
|
||||||
description: noop,
|
description: noop,
|
||||||
digest_emails_enabled: noop,
|
digest_emails_enabled: noop,
|
||||||
digest_weekday: noop,
|
digest_weekday: noop,
|
||||||
|
direct_message_initiator_group: noop,
|
||||||
|
direct_message_permission_group: noop,
|
||||||
email_changes_disabled: settings_account.update_email_change_display,
|
email_changes_disabled: settings_account.update_email_change_display,
|
||||||
disallow_disposable_email_addresses: noop,
|
disallow_disposable_email_addresses: noop,
|
||||||
inline_image_preview: noop,
|
inline_image_preview: noop,
|
||||||
|
@ -229,7 +232,6 @@ export function dispatch_normal_event(event) {
|
||||||
name_changes_disabled: settings_account.update_name_change_display,
|
name_changes_disabled: settings_account.update_name_change_display,
|
||||||
new_stream_announcements_stream_id: stream_ui_updates.update_announce_stream_option,
|
new_stream_announcements_stream_id: stream_ui_updates.update_announce_stream_option,
|
||||||
org_type: noop,
|
org_type: noop,
|
||||||
private_message_policy: compose_recipient.check_posting_policy_for_compose_box,
|
|
||||||
push_notifications_enabled: noop,
|
push_notifications_enabled: noop,
|
||||||
require_unique_names: noop,
|
require_unique_names: noop,
|
||||||
send_welcome_emails: noop,
|
send_welcome_emails: noop,
|
||||||
|
@ -296,6 +298,17 @@ export function dispatch_normal_event(event) {
|
||||||
stream_settings_ui.update_stream_privacy_choices(key);
|
stream_settings_ui.update_stream_privacy_choices(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
key === "direct_message_initiator_group" ||
|
||||||
|
key === "direct_message_permission_group"
|
||||||
|
) {
|
||||||
|
settings_org.check_disable_direct_message_initiator_group_dropdown(
|
||||||
|
realm.realm_direct_message_permission_group,
|
||||||
|
);
|
||||||
|
compose_closed_ui.update_buttons_for_private();
|
||||||
|
compose_recipient.check_posting_policy_for_compose_box();
|
||||||
|
}
|
||||||
|
|
||||||
if (key === "edit_topic_policy") {
|
if (key === "edit_topic_policy") {
|
||||||
message_live_update.rerender_messages_view();
|
message_live_update.rerender_messages_view();
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,7 +207,6 @@ type simple_dropdown_realm_settings = Pick<
|
||||||
| "realm_create_web_public_stream_policy"
|
| "realm_create_web_public_stream_policy"
|
||||||
| "realm_invite_to_stream_policy"
|
| "realm_invite_to_stream_policy"
|
||||||
| "realm_user_group_edit_policy"
|
| "realm_user_group_edit_policy"
|
||||||
| "realm_private_message_policy"
|
|
||||||
| "realm_add_custom_emoji_policy"
|
| "realm_add_custom_emoji_policy"
|
||||||
| "realm_invite_to_realm_policy"
|
| "realm_invite_to_realm_policy"
|
||||||
| "realm_wildcard_mention_policy"
|
| "realm_wildcard_mention_policy"
|
||||||
|
@ -474,6 +473,8 @@ const dropdown_widget_map = new Map<string, DropdownWidget | null>([
|
||||||
["can_mention_group", null],
|
["can_mention_group", 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_direct_message_initiator_group", null],
|
||||||
|
["realm_direct_message_permission_group", null],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function get_widget_for_dropdown_list_settings(
|
export function get_widget_for_dropdown_list_settings(
|
||||||
|
@ -781,6 +782,8 @@ export function check_realm_settings_property_changed(elem: HTMLElement): boolea
|
||||||
case "realm_can_access_all_users_group":
|
case "realm_can_access_all_users_group":
|
||||||
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_direct_message_initiator_group":
|
||||||
|
case "realm_direct_message_permission_group":
|
||||||
proposed_val = get_dropdown_list_widget_setting_value($elem);
|
proposed_val = get_dropdown_list_widget_setting_value($elem);
|
||||||
break;
|
break;
|
||||||
case "realm_message_content_edit_limit_seconds":
|
case "realm_message_content_edit_limit_seconds":
|
||||||
|
@ -974,6 +977,8 @@ export function populate_data_for_realm_settings_request(
|
||||||
const realm_group_settings_using_new_api_format = new Set([
|
const realm_group_settings_using_new_api_format = new Set([
|
||||||
"can_create_private_channel_group",
|
"can_create_private_channel_group",
|
||||||
"can_create_public_channel_group",
|
"can_create_public_channel_group",
|
||||||
|
"direct_message_initiator_group",
|
||||||
|
"direct_message_permission_group",
|
||||||
]);
|
]);
|
||||||
if (realm_group_settings_using_new_api_format.has(property_name)) {
|
if (realm_group_settings_using_new_api_format.has(property_name)) {
|
||||||
const old_value = get_realm_settings_property_value(
|
const old_value = get_realm_settings_property_value(
|
||||||
|
|
|
@ -256,19 +256,6 @@ export const email_invite_to_realm_policy_values = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const private_message_policy_values = {
|
|
||||||
by_anyone: {
|
|
||||||
order: 1,
|
|
||||||
code: 1,
|
|
||||||
description: $t({defaultMessage: "Admins, moderators, members and guests"}),
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
order: 2,
|
|
||||||
code: 2,
|
|
||||||
description: $t({defaultMessage: "Direct messages disabled"}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const wildcard_mention_policy_values = {
|
export const wildcard_mention_policy_values = {
|
||||||
by_everyone: {
|
by_everyone: {
|
||||||
order: 1,
|
order: 1,
|
||||||
|
|
|
@ -95,9 +95,6 @@ export function get_organization_settings_options() {
|
||||||
options.common_policy_values = settings_components.get_sorted_options_list(
|
options.common_policy_values = settings_components.get_sorted_options_list(
|
||||||
settings_config.common_policy_values,
|
settings_config.common_policy_values,
|
||||||
);
|
);
|
||||||
options.private_message_policy_values = settings_components.get_sorted_options_list(
|
|
||||||
settings_config.private_message_policy_values,
|
|
||||||
);
|
|
||||||
options.wildcard_mention_policy_values = settings_components.get_sorted_options_list(
|
options.wildcard_mention_policy_values = settings_components.get_sorted_options_list(
|
||||||
settings_config.wildcard_mention_policy_values,
|
settings_config.wildcard_mention_policy_values,
|
||||||
);
|
);
|
||||||
|
@ -129,7 +126,6 @@ const simple_dropdown_properties = [
|
||||||
"realm_create_web_public_stream_policy",
|
"realm_create_web_public_stream_policy",
|
||||||
"realm_invite_to_stream_policy",
|
"realm_invite_to_stream_policy",
|
||||||
"realm_user_group_edit_policy",
|
"realm_user_group_edit_policy",
|
||||||
"realm_private_message_policy",
|
|
||||||
"realm_add_custom_emoji_policy",
|
"realm_add_custom_emoji_policy",
|
||||||
"realm_invite_to_realm_policy",
|
"realm_invite_to_realm_policy",
|
||||||
"realm_wildcard_mention_policy",
|
"realm_wildcard_mention_policy",
|
||||||
|
@ -373,6 +369,14 @@ function set_create_web_public_stream_dropdown_visibility() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function check_disable_direct_message_initiator_group_dropdown(current_value) {
|
||||||
|
if (current_value === user_groups.get_user_group_from_name("role:nobody").id) {
|
||||||
|
$("#realm_direct_message_initiator_group_widget").prop("disabled", true);
|
||||||
|
} else {
|
||||||
|
$("#realm_direct_message_initiator_group_widget").prop("disabled", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function populate_realm_domains_label(realm_domains) {
|
export function populate_realm_domains_label(realm_domains) {
|
||||||
if (!meta.loaded) {
|
if (!meta.loaded) {
|
||||||
return;
|
return;
|
||||||
|
@ -472,6 +476,11 @@ function update_dependent_subsettings(property_name) {
|
||||||
case "realm_enable_spectator_access":
|
case "realm_enable_spectator_access":
|
||||||
set_create_web_public_stream_dropdown_visibility();
|
set_create_web_public_stream_dropdown_visibility();
|
||||||
break;
|
break;
|
||||||
|
case "realm_direct_message_permission_group":
|
||||||
|
check_disable_direct_message_initiator_group_dropdown(
|
||||||
|
realm.realm_direct_message_permission_group,
|
||||||
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,6 +500,8 @@ export function discard_realm_property_element_changes(elem) {
|
||||||
case "realm_zulip_update_announcements_stream_id":
|
case "realm_zulip_update_announcements_stream_id":
|
||||||
case "realm_default_code_block_language":
|
case "realm_default_code_block_language":
|
||||||
case "realm_create_multiuse_invite_group":
|
case "realm_create_multiuse_invite_group":
|
||||||
|
case "realm_direct_message_initiator_group":
|
||||||
|
case "realm_direct_message_permission_group":
|
||||||
case "realm_can_access_all_users_group":
|
case "realm_can_access_all_users_group":
|
||||||
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":
|
||||||
|
@ -744,7 +755,12 @@ export function set_up() {
|
||||||
maybe_disable_widgets();
|
maybe_disable_widgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_up_dropdown_widget(setting_name, setting_options, setting_type) {
|
function set_up_dropdown_widget(
|
||||||
|
setting_name,
|
||||||
|
setting_options,
|
||||||
|
setting_type,
|
||||||
|
custom_dropdown_widget_callback,
|
||||||
|
) {
|
||||||
const $save_discard_widget_container = $(`#id_${CSS.escape(setting_name)}`).closest(
|
const $save_discard_widget_container = $(`#id_${CSS.escape(setting_name)}`).closest(
|
||||||
".settings-subsection-parent",
|
".settings-subsection-parent",
|
||||||
);
|
);
|
||||||
|
@ -774,6 +790,9 @@ function set_up_dropdown_widget(setting_name, setting_options, setting_type) {
|
||||||
settings_components.save_discard_realm_settings_widget_status_handler(
|
settings_components.save_discard_realm_settings_widget_status_handler(
|
||||||
$save_discard_widget_container,
|
$save_discard_widget_container,
|
||||||
);
|
);
|
||||||
|
if (custom_dropdown_widget_callback !== undefined) {
|
||||||
|
custom_dropdown_widget_callback(this.current_value);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
tippy_props: {
|
tippy_props: {
|
||||||
placement: "bottom-start",
|
placement: "bottom-start",
|
||||||
|
@ -799,7 +818,17 @@ export function set_up_dropdown_widget_for_realm_group_settings() {
|
||||||
for (const setting_name of realm_group_permission_settings) {
|
for (const setting_name of realm_group_permission_settings) {
|
||||||
const get_setting_options = () =>
|
const get_setting_options = () =>
|
||||||
user_groups.get_realm_user_groups_for_dropdown_list_widget(setting_name, "realm");
|
user_groups.get_realm_user_groups_for_dropdown_list_widget(setting_name, "realm");
|
||||||
set_up_dropdown_widget("realm_" + setting_name, get_setting_options, "group");
|
let dropdown_list_item_click_callback;
|
||||||
|
if (setting_name === "direct_message_permission_group") {
|
||||||
|
dropdown_list_item_click_callback =
|
||||||
|
check_disable_direct_message_initiator_group_dropdown;
|
||||||
|
}
|
||||||
|
set_up_dropdown_widget(
|
||||||
|
"realm_" + setting_name,
|
||||||
|
get_setting_options,
|
||||||
|
"group",
|
||||||
|
dropdown_list_item_click_callback,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -278,6 +278,8 @@ const realm_schema = z.object({
|
||||||
realm_description: z.string(),
|
realm_description: z.string(),
|
||||||
realm_digest_emails_enabled: NOT_TYPED_YET,
|
realm_digest_emails_enabled: NOT_TYPED_YET,
|
||||||
realm_digest_weekday: NOT_TYPED_YET,
|
realm_digest_weekday: NOT_TYPED_YET,
|
||||||
|
realm_direct_message_initiator_group: z.number(),
|
||||||
|
realm_direct_message_permission_group: z.number(),
|
||||||
realm_disallow_disposable_email_addresses: z.boolean(),
|
realm_disallow_disposable_email_addresses: z.boolean(),
|
||||||
realm_domains: z.array(
|
realm_domains: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
|
|
|
@ -568,7 +568,10 @@ export function initialize_everything(state_data) {
|
||||||
user_status.initialize(state_data.user_status);
|
user_status.initialize(state_data.user_status);
|
||||||
compose_recipient.initialize();
|
compose_recipient.initialize();
|
||||||
compose_pm_pill.initialize({
|
compose_pm_pill.initialize({
|
||||||
on_pill_create_or_remove: compose_recipient.update_placeholder_text,
|
on_pill_create_or_remove() {
|
||||||
|
compose_recipient.update_placeholder_text();
|
||||||
|
compose_recipient.check_posting_policy_for_compose_box();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
compose_closed_ui.initialize();
|
compose_closed_ui.initialize();
|
||||||
compose_reply.initialize();
|
compose_reply.initialize();
|
||||||
|
|
|
@ -24,6 +24,7 @@ import * as dialog_widget from "./dialog_widget";
|
||||||
import * as hash_util from "./hash_util";
|
import * as hash_util from "./hash_util";
|
||||||
import {$t, $t_html} from "./i18n";
|
import {$t, $t_html} from "./i18n";
|
||||||
import * as message_lists from "./message_lists";
|
import * as message_lists from "./message_lists";
|
||||||
|
import {user_can_send_direct_message} from "./message_util";
|
||||||
import * as message_view from "./message_view";
|
import * as message_view from "./message_view";
|
||||||
import * as muted_users from "./muted_users";
|
import * as muted_users from "./muted_users";
|
||||||
import * as overlays from "./overlays";
|
import * as overlays from "./overlays";
|
||||||
|
@ -32,7 +33,6 @@ import * as people from "./people";
|
||||||
import * as popover_menus from "./popover_menus";
|
import * as popover_menus from "./popover_menus";
|
||||||
import {hide_all} from "./popovers";
|
import {hide_all} from "./popovers";
|
||||||
import * as rows from "./rows";
|
import * as rows from "./rows";
|
||||||
import * as settings_config from "./settings_config";
|
|
||||||
import * as sidebar_ui from "./sidebar_ui";
|
import * as sidebar_ui from "./sidebar_ui";
|
||||||
import {current_user, realm} from "./state_data";
|
import {current_user, realm} from "./state_data";
|
||||||
import * as timerender from "./timerender";
|
import * as timerender from "./timerender";
|
||||||
|
@ -263,13 +263,13 @@ function get_user_card_popover_data(
|
||||||
.map((f) => user_profile.get_custom_profile_field_data(user, f, field_types))
|
.map((f) => user_profile.get_custom_profile_field_data(user, f, field_types))
|
||||||
.filter((f) => f.display_in_profile_summary && f.value !== undefined && f.value !== null);
|
.filter((f) => f.display_in_profile_summary && f.value !== undefined && f.value !== null);
|
||||||
|
|
||||||
|
const user_id_string = user.user_id.toString();
|
||||||
|
const can_send_private_message =
|
||||||
|
user_can_send_direct_message(user_id_string) && is_active && !is_me;
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
invisible_mode,
|
invisible_mode,
|
||||||
can_send_private_message:
|
can_send_private_message,
|
||||||
is_active &&
|
|
||||||
!is_me &&
|
|
||||||
realm.realm_private_message_policy !==
|
|
||||||
settings_config.private_message_policy_values.disabled.code,
|
|
||||||
display_profile_fields,
|
display_profile_fields,
|
||||||
has_message_context,
|
has_message_context,
|
||||||
is_active,
|
is_active,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type {z} from "zod";
|
||||||
import * as blueslip from "./blueslip";
|
import * as blueslip from "./blueslip";
|
||||||
import {FoldDict} from "./fold_dict";
|
import {FoldDict} from "./fold_dict";
|
||||||
import * as group_permission_settings from "./group_permission_settings";
|
import * as group_permission_settings from "./group_permission_settings";
|
||||||
|
import {$t} from "./i18n";
|
||||||
import * as settings_config from "./settings_config";
|
import * as settings_config from "./settings_config";
|
||||||
import type {StateData, user_group_schema} from "./state_data";
|
import type {StateData, user_group_schema} from "./state_data";
|
||||||
import {current_user} from "./state_data";
|
import {current_user} from "./state_data";
|
||||||
|
@ -225,6 +226,14 @@ export function is_user_in_group(user_group_id: number, user_id: number): boolea
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function get_display_name_for_system_group_option(setting_name: string, name: string): string {
|
||||||
|
// We use a special label for the "Nobody" system group for clarity.
|
||||||
|
if (setting_name === "direct_message_permission_group" && name === "Nobody") {
|
||||||
|
return $t({defaultMessage: "Direct messages disabled"});
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
export function get_realm_user_groups_for_dropdown_list_widget(
|
export function get_realm_user_groups_for_dropdown_list_widget(
|
||||||
setting_name: string,
|
setting_name: string,
|
||||||
setting_type: "realm" | "stream" | "group",
|
setting_type: "realm" | "stream" | "group",
|
||||||
|
@ -277,7 +286,7 @@ export function get_realm_user_groups_for_dropdown_list_widget(
|
||||||
throw new Error(`Unknown group name: ${group.name}`);
|
throw new Error(`Unknown group name: ${group.name}`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
name: group.display_name,
|
name: get_display_name_for_system_group_option(setting_name, group.display_name),
|
||||||
unique_id: user_group.id,
|
unique_id: user_group.id,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{{#> compose_banner }}
|
||||||
|
<p class="banner_message">
|
||||||
|
{{error_message}}
|
||||||
|
{{#tr}}
|
||||||
|
<z-link>Learn more.</z-link>
|
||||||
|
{{#*inline "z-link"}}<a target="_blank" rel="noopener noreferrer" href="/help/restrict-direct-messages">{{> @partial-block}}</a>{{/inline}}
|
||||||
|
{{/tr}}
|
||||||
|
</p>
|
||||||
|
{{/compose_banner}}
|
|
@ -110,6 +110,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="org-direct-message-permissions" class="settings-subsection-parent">
|
||||||
|
<div class="subsection-header">
|
||||||
|
<h3>{{t "Direct message permissions" }}
|
||||||
|
{{> ../help_link_widget link="/help/restrict-direct-messages" }}
|
||||||
|
</h3>
|
||||||
|
{{> settings_save_discard_widget section_name="direct-message-permissions" }}
|
||||||
|
</div>
|
||||||
|
{{> ../dropdown_widget_with_label
|
||||||
|
widget_name="realm_direct_message_permission_group"
|
||||||
|
label=(t 'Who can authorize a direct message conversation')
|
||||||
|
value_type="number" }}
|
||||||
|
|
||||||
|
{{> ../dropdown_widget_with_label
|
||||||
|
widget_name="realm_direct_message_initiator_group"
|
||||||
|
label=(t 'Who can start a direct message conversation')
|
||||||
|
value_type="number" }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="org-msg-editing" class="settings-subsection-parent">
|
<div id="org-msg-editing" class="settings-subsection-parent">
|
||||||
<div class="subsection-header">
|
<div class="subsection-header">
|
||||||
<h3>{{t "Message editing" }}
|
<h3>{{t "Message editing" }}
|
||||||
|
@ -336,15 +354,6 @@
|
||||||
{{> dropdown_options_widget option_values=common_policy_values}}
|
{{> dropdown_options_widget option_values=common_policy_values}}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="realm_private_message_policy" class="settings-field-label">{{t "Who can use direct messages" }} ({{t "beta" }})
|
|
||||||
{{> ../help_link_widget link="/help/restrict-direct-messages" }}
|
|
||||||
</label>
|
|
||||||
<select name="realm_private_message_policy" class="setting-widget prop-element settings_select bootstrap-focus-style" id="id_realm_private_message_policy" data-setting-widget-type="number">
|
|
||||||
{{> dropdown_options_widget option_values=private_message_policy_values}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -18,9 +18,6 @@
|
||||||
{{t 'Reply to selected conversation' }}
|
{{t 'Reply to selected conversation' }}
|
||||||
{{tooltip_hotkey_hints "R"}}
|
{{tooltip_hotkey_hints "R"}}
|
||||||
</template>
|
</template>
|
||||||
<template id="compose_reply_direct_disabled_button_tooltip_template">
|
|
||||||
{{t 'You are not allowed to send direct messages in this organization.' }}
|
|
||||||
</template>
|
|
||||||
<template id="left_bar_compose_mobile_button_tooltip_template">
|
<template id="left_bar_compose_mobile_button_tooltip_template">
|
||||||
{{t 'New message' }}
|
{{t 'New message' }}
|
||||||
{{tooltip_hotkey_hints "C"}}
|
{{tooltip_hotkey_hints "C"}}
|
||||||
|
|
|
@ -11,7 +11,7 @@ const {run_test, noop} = require("./lib/test");
|
||||||
const $ = require("./lib/zjquery");
|
const $ = require("./lib/zjquery");
|
||||||
const {current_user, page_params, realm, user_settings} = require("./lib/zpage_params");
|
const {current_user, page_params, realm, user_settings} = require("./lib/zpage_params");
|
||||||
|
|
||||||
const settings_config = zrequire("settings_config");
|
const user_groups = zrequire("user_groups");
|
||||||
|
|
||||||
set_global("document", {
|
set_global("document", {
|
||||||
querySelector() {},
|
querySelector() {},
|
||||||
|
@ -115,6 +115,23 @@ const social = {
|
||||||
};
|
};
|
||||||
stream_data.add_sub(social);
|
stream_data.add_sub(social);
|
||||||
|
|
||||||
|
const nobody = {
|
||||||
|
name: "role:nobody",
|
||||||
|
id: 1,
|
||||||
|
members: new Set([]),
|
||||||
|
is_system_group: true,
|
||||||
|
direct_subgroup_ids: new Set([]),
|
||||||
|
};
|
||||||
|
const everyone = {
|
||||||
|
name: "role:everyone",
|
||||||
|
id: 2,
|
||||||
|
members: new Set([30]),
|
||||||
|
is_system_group: true,
|
||||||
|
direct_subgroup_ids: new Set([]),
|
||||||
|
};
|
||||||
|
|
||||||
|
user_groups.initialize({realm_user_groups: [nobody, everyone]});
|
||||||
|
|
||||||
function test_ui(label, f) {
|
function test_ui(label, f) {
|
||||||
// TODO: initialize data more aggressively.
|
// TODO: initialize data more aggressively.
|
||||||
run_test(label, f);
|
run_test(label, f);
|
||||||
|
@ -462,6 +479,8 @@ test_ui("finish", ({override, override_rewire}) => {
|
||||||
compose_state.set_message_type("private");
|
compose_state.set_message_type("private");
|
||||||
override(compose_pm_pill, "get_emails", () => "bob@example.com");
|
override(compose_pm_pill, "get_emails", () => "bob@example.com");
|
||||||
override(compose_pm_pill, "get_user_ids", () => []);
|
override(compose_pm_pill, "get_user_ids", () => []);
|
||||||
|
override(realm, "realm_direct_message_permission_group", nobody.id);
|
||||||
|
override(realm, "realm_direct_message_initiator_group", everyone.id);
|
||||||
|
|
||||||
let compose_finished_event_checked = false;
|
let compose_finished_event_checked = false;
|
||||||
$(document).on("compose_finished.zulip", () => {
|
$(document).on("compose_finished.zulip", () => {
|
||||||
|
@ -817,11 +836,8 @@ test_ui("create_message_object", ({override, override_rewire}) => {
|
||||||
|
|
||||||
test_ui("DM policy disabled", ({override, override_rewire}) => {
|
test_ui("DM policy disabled", ({override, override_rewire}) => {
|
||||||
// Disable dms in the organisation
|
// Disable dms in the organisation
|
||||||
override(
|
override(realm, "realm_direct_message_permission_group", nobody.id);
|
||||||
realm,
|
override(realm, "realm_direct_message_initiator_group", everyone.id);
|
||||||
"realm_private_message_policy",
|
|
||||||
settings_config.private_message_policy_values.disabled.code,
|
|
||||||
);
|
|
||||||
let reply_disabled = false;
|
let reply_disabled = false;
|
||||||
override_rewire(compose_closed_ui, "update_reply_button_state", (disabled = false) => {
|
override_rewire(compose_closed_ui, "update_reply_button_state", (disabled = false) => {
|
||||||
reply_disabled = disabled;
|
reply_disabled = disabled;
|
||||||
|
@ -839,6 +855,8 @@ test_ui("DM policy disabled", ({override, override_rewire}) => {
|
||||||
test_ui("narrow_button_titles", ({override}) => {
|
test_ui("narrow_button_titles", ({override}) => {
|
||||||
override(narrow_state, "pm_ids_string", () => "31");
|
override(narrow_state, "pm_ids_string", () => "31");
|
||||||
override(narrow_state, "is_message_feed_visible", () => true);
|
override(narrow_state, "is_message_feed_visible", () => true);
|
||||||
|
override(realm, "realm_direct_message_permission_group", everyone.id);
|
||||||
|
override(realm, "realm_direct_message_initiator_group", everyone.id);
|
||||||
compose_closed_ui.update_buttons_for_private();
|
compose_closed_ui.update_buttons_for_private();
|
||||||
assert.equal(
|
assert.equal(
|
||||||
$("#new_conversation_button").text(),
|
$("#new_conversation_button").text(),
|
||||||
|
|
|
@ -5,10 +5,27 @@ const {strict: assert} = require("assert");
|
||||||
const {mock_banners} = require("./lib/compose_banner");
|
const {mock_banners} = require("./lib/compose_banner");
|
||||||
const {mock_esm, set_global, zrequire} = require("./lib/namespace");
|
const {mock_esm, set_global, zrequire} = require("./lib/namespace");
|
||||||
const {run_test, noop} = require("./lib/test");
|
const {run_test, noop} = require("./lib/test");
|
||||||
|
const blueslip = require("./lib/zblueslip");
|
||||||
const $ = require("./lib/zjquery");
|
const $ = require("./lib/zjquery");
|
||||||
const {realm} = require("./lib/zpage_params");
|
const {realm} = require("./lib/zpage_params");
|
||||||
|
|
||||||
const settings_config = zrequire("settings_config");
|
const user_groups = zrequire("user_groups");
|
||||||
|
|
||||||
|
const nobody = {
|
||||||
|
name: "role:nobody",
|
||||||
|
id: 1,
|
||||||
|
members: new Set([]),
|
||||||
|
is_system_group: true,
|
||||||
|
direct_subgroup_ids: new Set([]),
|
||||||
|
};
|
||||||
|
const everyone = {
|
||||||
|
name: "role:everyone",
|
||||||
|
id: 2,
|
||||||
|
members: new Set([30]),
|
||||||
|
is_system_group: true,
|
||||||
|
direct_subgroup_ids: new Set([]),
|
||||||
|
};
|
||||||
|
user_groups.initialize({realm_user_groups: [nobody, everyone]});
|
||||||
|
|
||||||
set_global("document", {
|
set_global("document", {
|
||||||
to_$: () => $("document-stub"),
|
to_$: () => $("document-stub"),
|
||||||
|
@ -365,6 +382,8 @@ test("reply_with_mention", ({override, override_rewire, mock_template}) => {
|
||||||
test("quote_and_reply", ({disallow, override, override_rewire}) => {
|
test("quote_and_reply", ({disallow, override, override_rewire}) => {
|
||||||
override_rewire(compose_recipient, "on_compose_select_recipient_update", noop);
|
override_rewire(compose_recipient, "on_compose_select_recipient_update", noop);
|
||||||
override_rewire(compose_reply, "selection_within_message_id", () => undefined);
|
override_rewire(compose_reply, "selection_within_message_id", () => undefined);
|
||||||
|
override(realm, "realm_direct_message_permission_group", nobody.id);
|
||||||
|
override(realm, "realm_direct_message_initiator_group", everyone.id);
|
||||||
|
|
||||||
mock_banners();
|
mock_banners();
|
||||||
compose_state.set_message_type("stream");
|
compose_state.set_message_type("stream");
|
||||||
|
@ -508,6 +527,7 @@ test("on_narrow", ({override, override_rewire}) => {
|
||||||
};
|
};
|
||||||
people.add_active_user(bot);
|
people.add_active_user(bot);
|
||||||
|
|
||||||
|
user_groups.initialize({realm_user_groups: [nobody, everyone]});
|
||||||
let cancel_called = false;
|
let cancel_called = false;
|
||||||
override_rewire(compose_actions, "cancel", () => {
|
override_rewire(compose_actions, "cancel", () => {
|
||||||
cancel_called = true;
|
cancel_called = true;
|
||||||
|
@ -544,8 +564,8 @@ test("on_narrow", ({override, override_rewire}) => {
|
||||||
start_called = true;
|
start_called = true;
|
||||||
});
|
});
|
||||||
narrowed_by_pm_reply = true;
|
narrowed_by_pm_reply = true;
|
||||||
realm.realm_private_message_policy =
|
realm.realm_direct_message_permission_group = nobody.id;
|
||||||
settings_config.private_message_policy_values.disabled.code;
|
realm.realm_direct_message_initiator_group = everyone.id;
|
||||||
compose_actions.on_narrow({
|
compose_actions.on_narrow({
|
||||||
force_close: false,
|
force_close: false,
|
||||||
trigger: "not-search",
|
trigger: "not-search",
|
||||||
|
@ -560,8 +580,8 @@ test("on_narrow", ({override, override_rewire}) => {
|
||||||
});
|
});
|
||||||
assert.ok(start_called);
|
assert.ok(start_called);
|
||||||
|
|
||||||
realm.realm_private_message_policy =
|
realm.realm_direct_message_permission_group = everyone.id;
|
||||||
settings_config.private_message_policy_values.by_anyone.code;
|
blueslip.expect("warn", "Unknown emails");
|
||||||
compose_actions.on_narrow({
|
compose_actions.on_narrow({
|
||||||
force_close: false,
|
force_close: false,
|
||||||
trigger: "not-search",
|
trigger: "not-search",
|
||||||
|
|
|
@ -21,6 +21,7 @@ const settings_config = zrequire("settings_config");
|
||||||
const settings_data = mock_esm("../src/settings_data");
|
const settings_data = mock_esm("../src/settings_data");
|
||||||
const stream_data = zrequire("stream_data");
|
const stream_data = zrequire("stream_data");
|
||||||
const compose_recipient = zrequire("/compose_recipient");
|
const compose_recipient = zrequire("/compose_recipient");
|
||||||
|
const user_groups = zrequire("user_groups");
|
||||||
|
|
||||||
const me = {
|
const me = {
|
||||||
email: "me@example.com",
|
email: "me@example.com",
|
||||||
|
@ -39,6 +40,7 @@ const bob = {
|
||||||
email: "bob@example.com",
|
email: "bob@example.com",
|
||||||
user_id: 32,
|
user_id: 32,
|
||||||
full_name: "Bob",
|
full_name: "Bob",
|
||||||
|
is_admin: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const social_sub = {
|
const social_sub = {
|
||||||
|
@ -64,6 +66,29 @@ const welcome_bot = {
|
||||||
|
|
||||||
people.add_cross_realm_user(welcome_bot);
|
people.add_cross_realm_user(welcome_bot);
|
||||||
|
|
||||||
|
const nobody = {
|
||||||
|
name: "role:nobody",
|
||||||
|
id: 1,
|
||||||
|
members: new Set([]),
|
||||||
|
is_system_group: true,
|
||||||
|
direct_subgroup_ids: new Set([]),
|
||||||
|
};
|
||||||
|
const everyone = {
|
||||||
|
name: "role:everyone",
|
||||||
|
id: 2,
|
||||||
|
members: new Set([30]),
|
||||||
|
is_system_group: true,
|
||||||
|
direct_subgroup_ids: new Set([]),
|
||||||
|
};
|
||||||
|
const admin = {
|
||||||
|
name: "role:administrators",
|
||||||
|
id: 3,
|
||||||
|
members: new Set([32]),
|
||||||
|
is_system_group: true,
|
||||||
|
direct_subgroup_ids: new Set([]),
|
||||||
|
};
|
||||||
|
|
||||||
|
user_groups.initialize({realm_user_groups: [nobody, everyone, admin]});
|
||||||
function test_ui(label, f) {
|
function test_ui(label, f) {
|
||||||
// The sloppy_$ flag lets us reuse setup from prior tests.
|
// The sloppy_$ flag lets us reuse setup from prior tests.
|
||||||
run_test(label, (helpers) => {
|
run_test(label, (helpers) => {
|
||||||
|
@ -148,6 +173,8 @@ test_ui("validate", ({mock_template}) => {
|
||||||
add_content_to_compose_box();
|
add_content_to_compose_box();
|
||||||
compose_state.private_message_recipient("");
|
compose_state.private_message_recipient("");
|
||||||
let pm_recipient_error_rendered = false;
|
let pm_recipient_error_rendered = false;
|
||||||
|
realm.realm_direct_message_permission_group = everyone.id;
|
||||||
|
realm.realm_direct_message_initiator_group = everyone.id;
|
||||||
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
|
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
|
||||||
assert.equal(data.classname, compose_banner.CLASSNAMES.missing_private_message_recipient);
|
assert.equal(data.classname, compose_banner.CLASSNAMES.missing_private_message_recipient);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
@ -167,6 +194,16 @@ test_ui("validate", ({mock_template}) => {
|
||||||
assert.ok(compose_validate.validate());
|
assert.ok(compose_validate.validate());
|
||||||
assert.ok(!pm_recipient_error_rendered);
|
assert.ok(!pm_recipient_error_rendered);
|
||||||
|
|
||||||
|
realm.realm_direct_message_initiator_group = admin.id;
|
||||||
|
assert.ok(compose_validate.validate());
|
||||||
|
assert.ok(!pm_recipient_error_rendered);
|
||||||
|
|
||||||
|
realm.realm_direct_message_permission_group = admin.id;
|
||||||
|
assert.ok(compose_validate.validate());
|
||||||
|
assert.ok(!pm_recipient_error_rendered);
|
||||||
|
|
||||||
|
realm.realm_direct_message_initiator_group = everyone.id;
|
||||||
|
realm.realm_direct_message_permission_group = everyone.id;
|
||||||
people.deactivate(bob);
|
people.deactivate(bob);
|
||||||
let deactivated_user_error_rendered = false;
|
let deactivated_user_error_rendered = false;
|
||||||
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
|
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
|
||||||
|
|
|
@ -28,6 +28,7 @@ const alert_words_ui = mock_esm("../src/alert_words_ui");
|
||||||
const attachments_ui = mock_esm("../src/attachments_ui");
|
const attachments_ui = mock_esm("../src/attachments_ui");
|
||||||
const audible_notifications = mock_esm("../src/audible_notifications");
|
const audible_notifications = mock_esm("../src/audible_notifications");
|
||||||
const bot_data = mock_esm("../src/bot_data");
|
const bot_data = mock_esm("../src/bot_data");
|
||||||
|
const compose_banner = mock_esm("../src/compose_banner");
|
||||||
const compose_pm_pill = mock_esm("../src/compose_pm_pill");
|
const compose_pm_pill = mock_esm("../src/compose_pm_pill");
|
||||||
const theme = mock_esm("../src/theme");
|
const theme = mock_esm("../src/theme");
|
||||||
const emoji_picker = mock_esm("../src/emoji_picker");
|
const emoji_picker = mock_esm("../src/emoji_picker");
|
||||||
|
@ -441,6 +442,7 @@ run_test("realm settings", ({override}) => {
|
||||||
current_user.is_admin = true;
|
current_user.is_admin = true;
|
||||||
realm.realm_date_created = new Date("2023-01-01Z");
|
realm.realm_date_created = new Date("2023-01-01Z");
|
||||||
|
|
||||||
|
override(settings_org, "check_disable_direct_message_initiator_group_dropdown", noop);
|
||||||
override(settings_org, "sync_realm_settings", noop);
|
override(settings_org, "sync_realm_settings", noop);
|
||||||
override(settings_bots, "update_bot_permissions_ui", noop);
|
override(settings_bots, "update_bot_permissions_ui", noop);
|
||||||
override(settings_invites, "update_invite_user_panel", noop);
|
override(settings_invites, "update_invite_user_panel", noop);
|
||||||
|
@ -449,6 +451,7 @@ run_test("realm settings", ({override}) => {
|
||||||
override(narrow_title, "redraw_title", noop);
|
override(narrow_title, "redraw_title", noop);
|
||||||
override(navbar_alerts, "check_profile_incomplete", noop);
|
override(navbar_alerts, "check_profile_incomplete", noop);
|
||||||
override(navbar_alerts, "show_profile_incomplete", noop);
|
override(navbar_alerts, "show_profile_incomplete", noop);
|
||||||
|
override(compose_banner, "clear_errors", noop);
|
||||||
|
|
||||||
function test_electron_dispatch(event, fake_send_event) {
|
function test_electron_dispatch(event, fake_send_event) {
|
||||||
with_overrides(({override}) => {
|
with_overrides(({override}) => {
|
||||||
|
@ -573,6 +576,7 @@ run_test("realm settings", ({override}) => {
|
||||||
realm.realm_edit_topic_policy = 3;
|
realm.realm_edit_topic_policy = 3;
|
||||||
realm.realm_authentication_methods = {Google: {enabled: false, available: true}};
|
realm.realm_authentication_methods = {Google: {enabled: false, available: true}};
|
||||||
realm.realm_can_create_public_channel_group = 1;
|
realm.realm_can_create_public_channel_group = 1;
|
||||||
|
realm.realm_direct_message_permission_group = 1;
|
||||||
override(settings_org, "populate_auth_methods", noop);
|
override(settings_org, "populate_auth_methods", noop);
|
||||||
dispatch(event);
|
dispatch(event);
|
||||||
assert_same(realm.realm_create_multiuse_invite_group, 3);
|
assert_same(realm.realm_create_multiuse_invite_group, 3);
|
||||||
|
@ -583,6 +587,7 @@ run_test("realm settings", ({override}) => {
|
||||||
Google: {enabled: true, available: true},
|
Google: {enabled: true, available: true},
|
||||||
});
|
});
|
||||||
assert_same(realm.realm_can_create_public_channel_group, 3);
|
assert_same(realm.realm_can_create_public_channel_group, 3);
|
||||||
|
assert_same(realm.realm_direct_message_permission_group, 3);
|
||||||
assert_same(update_stream_privacy_choices_called, true);
|
assert_same(update_stream_privacy_choices_called, true);
|
||||||
|
|
||||||
event = event_fixtures.realm__update_dict__icon;
|
event = event_fixtures.realm__update_dict__icon;
|
||||||
|
|
|
@ -374,6 +374,7 @@ exports.fixtures = {
|
||||||
Google: {enabled: true, available: true},
|
Google: {enabled: true, available: true},
|
||||||
},
|
},
|
||||||
can_create_public_channel_group: 3,
|
can_create_public_channel_group: 3,
|
||||||
|
direct_message_permission_group: 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,13 @@ const stream_data = zrequire("stream_data");
|
||||||
const {Filter} = zrequire("../src/filter");
|
const {Filter} = zrequire("../src/filter");
|
||||||
const message_view = zrequire("message_view");
|
const message_view = zrequire("message_view");
|
||||||
const narrow_title = zrequire("narrow_title");
|
const narrow_title = zrequire("narrow_title");
|
||||||
const settings_config = zrequire("settings_config");
|
|
||||||
const recent_view_util = zrequire("recent_view_util");
|
const recent_view_util = zrequire("recent_view_util");
|
||||||
const inbox_util = zrequire("inbox_util");
|
const inbox_util = zrequire("inbox_util");
|
||||||
const message_lists = zrequire("message_lists");
|
const message_lists = zrequire("message_lists");
|
||||||
|
const user_groups = zrequire("user_groups");
|
||||||
|
|
||||||
mock_esm("../src/compose_banner", {
|
mock_esm("../src/compose_banner", {
|
||||||
|
clear_errors() {},
|
||||||
clear_search_view_banner() {},
|
clear_search_view_banner() {},
|
||||||
});
|
});
|
||||||
const compose_pm_pill = mock_esm("../src/compose_pm_pill");
|
const compose_pm_pill = mock_esm("../src/compose_pm_pill");
|
||||||
|
@ -49,6 +50,9 @@ function set_filter(terms) {
|
||||||
message_lists.set_current({
|
message_lists.set_current({
|
||||||
data: {
|
data: {
|
||||||
filter: new Filter(terms),
|
filter: new Filter(terms),
|
||||||
|
fetch_status: {
|
||||||
|
has_found_newest: () => true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -78,6 +82,23 @@ const bot = {
|
||||||
is_bot: true,
|
is_bot: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const nobody = {
|
||||||
|
name: "role:nobody",
|
||||||
|
id: 1,
|
||||||
|
members: new Set([]),
|
||||||
|
is_system_group: true,
|
||||||
|
direct_subgroup_ids: new Set([]),
|
||||||
|
};
|
||||||
|
const everyone = {
|
||||||
|
name: "role:everyone",
|
||||||
|
id: 2,
|
||||||
|
members: new Set([5]),
|
||||||
|
is_system_group: true,
|
||||||
|
direct_subgroup_ids: new Set([]),
|
||||||
|
};
|
||||||
|
|
||||||
|
user_groups.initialize({realm_user_groups: [nobody, everyone]});
|
||||||
|
|
||||||
run_test("empty_narrow_html", ({mock_template}) => {
|
run_test("empty_narrow_html", ({mock_template}) => {
|
||||||
mock_template("empty_feed_notice.hbs", true, (_data, html) => html);
|
mock_template("empty_feed_notice.hbs", true, (_data, html) => html);
|
||||||
|
|
||||||
|
@ -298,21 +319,8 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// organization has disabled sending direct messages
|
realm.realm_direct_message_permission_group = everyone.id;
|
||||||
realm.realm_private_message_policy =
|
realm.realm_direct_message_initiator_group = everyone.id;
|
||||||
settings_config.private_message_policy_values.disabled.code;
|
|
||||||
set_filter([["is", "dm"]]);
|
|
||||||
narrow_banner.show_empty_narrow_message();
|
|
||||||
assert.equal(
|
|
||||||
$(".empty_feed_notice_main").html(),
|
|
||||||
empty_narrow_html(
|
|
||||||
"translated: You are not allowed to send direct messages in this organization.",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// sending direct messages enabled
|
|
||||||
realm.realm_private_message_policy =
|
|
||||||
settings_config.private_message_policy_values.by_anyone.code;
|
|
||||||
set_filter([["is", "dm"]]);
|
set_filter([["is", "dm"]]);
|
||||||
narrow_banner.show_empty_narrow_message();
|
narrow_banner.show_empty_narrow_message();
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
@ -345,8 +353,7 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// organization has disabled sending direct messages
|
// organization has disabled sending direct messages
|
||||||
realm.realm_private_message_policy =
|
realm.realm_direct_message_permission_group = nobody.id;
|
||||||
settings_config.private_message_policy_values.disabled.code;
|
|
||||||
|
|
||||||
// prioritize information about invalid user(s) in narrow/search
|
// prioritize information about invalid user(s) in narrow/search
|
||||||
set_filter([["dm", ["Yo"]]]);
|
set_filter([["dm", ["Yo"]]]);
|
||||||
|
@ -369,7 +376,8 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
$(".empty_feed_notice_main").html(),
|
$(".empty_feed_notice_main").html(),
|
||||||
empty_narrow_html(
|
empty_narrow_html(
|
||||||
"translated: You are not allowed to send direct messages in this organization.",
|
"translated: Direct messages are disabled in this organization.",
|
||||||
|
'translated HTML: <a target="_blank" rel="noopener noreferrer" href="/help/restrict-direct-messages">Learn more.</a>',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -393,13 +401,13 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
$(".empty_feed_notice_main").html(),
|
$(".empty_feed_notice_main").html(),
|
||||||
empty_narrow_html(
|
empty_narrow_html(
|
||||||
"translated: You are not allowed to send direct messages in this organization.",
|
"translated: Direct messages are disabled in this organization.",
|
||||||
|
'translated HTML: <a target="_blank" rel="noopener noreferrer" href="/help/restrict-direct-messages">Learn more.</a>',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// sending direct messages enabled
|
// sending direct messages enabled
|
||||||
realm.realm_private_message_policy =
|
realm.realm_direct_message_permission_group = everyone.id;
|
||||||
settings_config.private_message_policy_values.by_anyone.code;
|
|
||||||
set_filter([["dm", "alice@example.com"]]);
|
set_filter([["dm", "alice@example.com"]]);
|
||||||
narrow_banner.show_empty_narrow_message();
|
narrow_banner.show_empty_narrow_message();
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
@ -433,8 +441,7 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// organization has disabled sending direct messages
|
// organization has disabled sending direct messages
|
||||||
realm.realm_private_message_policy =
|
realm.realm_direct_message_permission_group = nobody.id;
|
||||||
settings_config.private_message_policy_values.disabled.code;
|
|
||||||
|
|
||||||
// prioritize information about invalid user in narrow/search
|
// prioritize information about invalid user in narrow/search
|
||||||
set_filter([["dm-including", ["Yo"]]]);
|
set_filter([["dm-including", ["Yo"]]]);
|
||||||
|
@ -449,7 +456,8 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
$(".empty_feed_notice_main").html(),
|
$(".empty_feed_notice_main").html(),
|
||||||
empty_narrow_html(
|
empty_narrow_html(
|
||||||
"translated: You are not allowed to send direct messages in this organization.",
|
"translated: Direct messages are disabled in this organization.",
|
||||||
|
'translated HTML: <a target="_blank" rel="noopener noreferrer" href="/help/restrict-direct-messages">Learn more.</a>',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -463,8 +471,8 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// sending direct messages enabled
|
// sending direct messages enabled
|
||||||
realm.realm_private_message_policy =
|
realm.realm_direct_message_permission_group = everyone.id;
|
||||||
settings_config.private_message_policy_values.by_anyone.code;
|
realm.realm_direct_message_permission_group = everyone.id;
|
||||||
set_filter([["dm-including", "alice@example.com"]]);
|
set_filter([["dm-including", "alice@example.com"]]);
|
||||||
narrow_banner.show_empty_narrow_message();
|
narrow_banner.show_empty_narrow_message();
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
|
|
@ -19,6 +19,7 @@ const settings_data = mock_esm("../src/settings_data", {
|
||||||
|
|
||||||
const muted_users = zrequire("muted_users");
|
const muted_users = zrequire("muted_users");
|
||||||
const people = zrequire("people");
|
const people = zrequire("people");
|
||||||
|
const user_groups = zrequire("user_groups");
|
||||||
|
|
||||||
const welcome_bot = {
|
const welcome_bot = {
|
||||||
email: "welcome-bot@example.com",
|
email: "welcome-bot@example.com",
|
||||||
|
@ -59,6 +60,23 @@ function initialize() {
|
||||||
people._add_user(unknown_user);
|
people._add_user(unknown_user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nobody = {
|
||||||
|
name: "role:nobody",
|
||||||
|
id: 1,
|
||||||
|
members: new Set([]),
|
||||||
|
is_system_group: true,
|
||||||
|
direct_subgroup_ids: new Set([]),
|
||||||
|
};
|
||||||
|
const everyone = {
|
||||||
|
name: "role:everyone",
|
||||||
|
id: 2,
|
||||||
|
members: new Set([30]),
|
||||||
|
is_system_group: true,
|
||||||
|
direct_subgroup_ids: new Set([]),
|
||||||
|
};
|
||||||
|
|
||||||
|
user_groups.initialize({realm_user_groups: [nobody, everyone]});
|
||||||
|
|
||||||
function test_people(label, f) {
|
function test_people(label, f) {
|
||||||
run_test(label, (helpers) => {
|
run_test(label, (helpers) => {
|
||||||
initialize();
|
initialize();
|
||||||
|
@ -1485,6 +1503,17 @@ test_people("get_user_by_id_assert_valid", ({override}) => {
|
||||||
assert.equal(user.email, charles.email);
|
assert.equal(user.email, charles.email);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test_people("user_can_initiate_direct_message_thread", () => {
|
||||||
|
people.add_active_user(welcome_bot);
|
||||||
|
realm.realm_direct_message_initiator_group = nobody.id;
|
||||||
|
assert.ok(!people.user_can_initiate_direct_message_thread("32"));
|
||||||
|
// Can send if only bots and self are present.
|
||||||
|
assert.ok(people.user_can_initiate_direct_message_thread("4,30"));
|
||||||
|
|
||||||
|
realm.realm_direct_message_initiator_group = everyone.id;
|
||||||
|
assert.ok(people.user_can_initiate_direct_message_thread("32"));
|
||||||
|
});
|
||||||
|
|
||||||
// reset to native Date()
|
// reset to native Date()
|
||||||
run_test("reset MockDate", () => {
|
run_test("reset MockDate", () => {
|
||||||
MockDate.reset();
|
MockDate.reset();
|
||||||
|
|
|
@ -22,7 +22,7 @@ import orjson
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import IntegrityError, transaction
|
from django.db import IntegrityError, transaction
|
||||||
from django.db.models import F
|
from django.db.models import F, Q
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.timezone import now as timezone_now
|
from django.utils.timezone import now as timezone_now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
@ -39,6 +39,8 @@ from zerver.lib.alert_words import get_alert_word_automaton
|
||||||
from zerver.lib.cache import cache_with_key, user_profile_delivery_email_cache_key
|
from zerver.lib.cache import cache_with_key, user_profile_delivery_email_cache_key
|
||||||
from zerver.lib.create_user import create_user
|
from zerver.lib.create_user import create_user
|
||||||
from zerver.lib.exceptions import (
|
from zerver.lib.exceptions import (
|
||||||
|
DirectMessageInitiationError,
|
||||||
|
DirectMessagePermissionError,
|
||||||
JsonableError,
|
JsonableError,
|
||||||
MarkdownRenderingError,
|
MarkdownRenderingError,
|
||||||
StreamDoesNotExistError,
|
StreamDoesNotExistError,
|
||||||
|
@ -80,6 +82,7 @@ from zerver.lib.string_validation import check_stream_name
|
||||||
from zerver.lib.timestamp import timestamp_to_datetime
|
from zerver.lib.timestamp import timestamp_to_datetime
|
||||||
from zerver.lib.topic import participants_for_topic
|
from zerver.lib.topic import participants_for_topic
|
||||||
from zerver.lib.url_preview.types import UrlEmbedData
|
from zerver.lib.url_preview.types import UrlEmbedData
|
||||||
|
from zerver.lib.user_groups import is_any_user_in_group, is_user_in_group
|
||||||
from zerver.lib.user_message import UserMessageLite, bulk_insert_ums
|
from zerver.lib.user_message import UserMessageLite, bulk_insert_ums
|
||||||
from zerver.lib.users import (
|
from zerver.lib.users import (
|
||||||
check_can_access_user,
|
check_can_access_user,
|
||||||
|
@ -104,7 +107,6 @@ from zerver.models import (
|
||||||
)
|
)
|
||||||
from zerver.models.clients import get_client
|
from zerver.models.clients import get_client
|
||||||
from zerver.models.groups import SystemGroups
|
from zerver.models.groups import SystemGroups
|
||||||
from zerver.models.realms import PrivateMessagePolicyEnum
|
|
||||||
from zerver.models.recipients import get_direct_message_group_user_ids
|
from zerver.models.recipients import get_direct_message_group_user_ids
|
||||||
from zerver.models.scheduled_jobs import NotificationTriggers
|
from zerver.models.scheduled_jobs import NotificationTriggers
|
||||||
from zerver.models.streams import get_stream, get_stream_by_id_in_realm
|
from zerver.models.streams import get_stream, get_stream_by_id_in_realm
|
||||||
|
@ -1525,19 +1527,61 @@ def validate_stream_id_with_pm_notification(
|
||||||
return stream
|
return stream
|
||||||
|
|
||||||
|
|
||||||
def check_private_message_policy(
|
def check_can_send_direct_message(
|
||||||
realm: Realm, sender: UserProfile, user_profiles: Sequence[UserProfile]
|
realm: Realm, sender: UserProfile, recipient_users: Sequence[UserProfile], recipient: Recipient
|
||||||
) -> None:
|
) -> None:
|
||||||
if realm.private_message_policy == PrivateMessagePolicyEnum.DISABLED:
|
if sender.is_bot:
|
||||||
if sender.is_bot or (
|
return
|
||||||
len(user_profiles) == 1 and (user_profiles[0].is_bot or user_profiles[0] == sender)
|
|
||||||
):
|
if all(user_profile.is_bot or user_profile.id == sender.id for user_profile in recipient_users):
|
||||||
# We allow direct messages only between users and bots or to oneself,
|
return
|
||||||
# to avoid breaking the tutorial as well as automated
|
|
||||||
# notifications from system bots to users.
|
direct_message_permission_group = realm.direct_message_permission_group
|
||||||
|
|
||||||
|
if (
|
||||||
|
not hasattr(direct_message_permission_group, "named_user_group")
|
||||||
|
or direct_message_permission_group.named_user_group.name != SystemGroups.EVERYONE
|
||||||
|
):
|
||||||
|
user_ids = [recipient_user.id for recipient_user in recipient_users] + [sender.id]
|
||||||
|
if not is_any_user_in_group(direct_message_permission_group, user_ids):
|
||||||
|
is_nobody_group = (
|
||||||
|
direct_message_permission_group.named_user_group.name == SystemGroups.NOBODY
|
||||||
|
)
|
||||||
|
raise DirectMessagePermissionError(is_nobody_group)
|
||||||
|
|
||||||
|
direct_message_initiator_group = realm.direct_message_initiator_group
|
||||||
|
if (
|
||||||
|
not hasattr(direct_message_initiator_group, "named_user_group")
|
||||||
|
or direct_message_initiator_group.named_user_group.name != SystemGroups.EVERYONE
|
||||||
|
):
|
||||||
|
if is_user_in_group(direct_message_initiator_group, sender):
|
||||||
return
|
return
|
||||||
|
|
||||||
raise JsonableError(_("Direct messages are disabled in this organization."))
|
# TODO: This check is inefficient; we should in the future be able to cache
|
||||||
|
# on the Huddle object whether the conversation already exists, likely in the
|
||||||
|
# form of a `first_message_id` field, and be able to save doing this check in the
|
||||||
|
# common case that this is not the first message in a conversation.
|
||||||
|
if recipient.type == Recipient.PERSONAL:
|
||||||
|
recipient_user_profile = recipient_users[0]
|
||||||
|
previous_messages_exist = (
|
||||||
|
Message.objects.filter(
|
||||||
|
realm=realm,
|
||||||
|
recipient__type=Recipient.PERSONAL,
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
Q(sender=sender, recipient=recipient)
|
||||||
|
| Q(sender=recipient_user_profile, recipient_id=sender.recipient_id)
|
||||||
|
)
|
||||||
|
.exists()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
assert recipient.type == Recipient.DIRECT_MESSAGE_GROUP
|
||||||
|
previous_messages_exist = Message.objects.filter(
|
||||||
|
realm=realm,
|
||||||
|
recipient=recipient,
|
||||||
|
).exists()
|
||||||
|
if not previous_messages_exist:
|
||||||
|
raise DirectMessageInitiationError
|
||||||
|
|
||||||
|
|
||||||
def check_sender_can_access_recipients(
|
def check_sender_can_access_recipients(
|
||||||
|
@ -1692,8 +1736,6 @@ def check_message(
|
||||||
|
|
||||||
check_sender_can_access_recipients(realm, sender, user_profiles)
|
check_sender_can_access_recipients(realm, sender, user_profiles)
|
||||||
|
|
||||||
check_private_message_policy(realm, sender, user_profiles)
|
|
||||||
|
|
||||||
recipients_for_user_creation_events = get_recipients_for_user_creation_events(
|
recipients_for_user_creation_events = get_recipients_for_user_creation_events(
|
||||||
realm, sender, user_profiles
|
realm, sender, user_profiles
|
||||||
)
|
)
|
||||||
|
@ -1709,6 +1751,8 @@ def check_message(
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
assert isinstance(e.messages[0], str)
|
assert isinstance(e.messages[0], str)
|
||||||
raise JsonableError(e.messages[0])
|
raise JsonableError(e.messages[0])
|
||||||
|
|
||||||
|
check_can_send_direct_message(realm, sender, user_profiles, recipient)
|
||||||
else:
|
else:
|
||||||
# This is defensive code--Addressee already validates
|
# This is defensive code--Addressee already validates
|
||||||
# the message type.
|
# the message type.
|
||||||
|
|
|
@ -113,8 +113,14 @@ def get_usable_missed_message_address(address: str) -> MissedMessageEmailAddress
|
||||||
mm_address = MissedMessageEmailAddress.objects.select_related(
|
mm_address = MissedMessageEmailAddress.objects.select_related(
|
||||||
"user_profile",
|
"user_profile",
|
||||||
"user_profile__realm",
|
"user_profile__realm",
|
||||||
|
# Fetch group settings that are needed to determine whether a user
|
||||||
|
# can send a direct message to a given recipient.
|
||||||
"user_profile__realm__can_access_all_users_group",
|
"user_profile__realm__can_access_all_users_group",
|
||||||
"user_profile__realm__can_access_all_users_group__named_user_group",
|
"user_profile__realm__can_access_all_users_group__named_user_group",
|
||||||
|
"user_profile__realm__direct_message_initiator_group",
|
||||||
|
"user_profile__realm__direct_message_initiator_group__named_user_group",
|
||||||
|
"user_profile__realm__direct_message_permission_group",
|
||||||
|
"user_profile__realm__direct_message_permission_group__named_user_group",
|
||||||
"message",
|
"message",
|
||||||
"message__sender",
|
"message__sender",
|
||||||
"message__recipient",
|
"message__recipient",
|
||||||
|
|
|
@ -1049,6 +1049,8 @@ group_setting_update_data_type = DictType(
|
||||||
("can_access_all_users_group", int),
|
("can_access_all_users_group", int),
|
||||||
("can_create_public_channel_group", group_setting_type),
|
("can_create_public_channel_group", group_setting_type),
|
||||||
("can_create_private_channel_group", group_setting_type),
|
("can_create_private_channel_group", group_setting_type),
|
||||||
|
("direct_message_initiator_group", group_setting_type),
|
||||||
|
("direct_message_permission_group", group_setting_type),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -519,6 +519,25 @@ class InvitationError(JsonableError):
|
||||||
self.daily_limit_reached: bool = daily_limit_reached
|
self.daily_limit_reached: bool = daily_limit_reached
|
||||||
|
|
||||||
|
|
||||||
|
class DirectMessageInitiationError(JsonableError):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@override
|
||||||
|
def msg_format() -> str:
|
||||||
|
return _("You do not have permission to initiate direct message conversations.")
|
||||||
|
|
||||||
|
|
||||||
|
class DirectMessagePermissionError(JsonableError):
|
||||||
|
def __init__(self, is_nobody_group: bool) -> None:
|
||||||
|
if is_nobody_group:
|
||||||
|
msg = _("Direct messages are disabled in this organization.")
|
||||||
|
else:
|
||||||
|
msg = _("You do not have permission to send direct messages to this recipient.")
|
||||||
|
super().__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
class AccessDeniedError(JsonableError):
|
class AccessDeniedError(JsonableError):
|
||||||
http_status_code = 403
|
http_status_code = 403
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Generated by Django 4.2.8 on 2024-01-01 10:02
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0548_realmuserdefault_web_channel_default_view_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="realm",
|
||||||
|
name="direct_message_initiator_group",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.RESTRICT,
|
||||||
|
related_name="+",
|
||||||
|
to="zerver.usergroup",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="realm",
|
||||||
|
name="direct_message_permission_group",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.RESTRICT,
|
||||||
|
related_name="+",
|
||||||
|
to="zerver.usergroup",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Generated by Django 4.2.8 on 2024-01-01 10:03
|
||||||
|
|
||||||
|
|
||||||
|
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_direct_message_initiator_group_and_more(
|
||||||
|
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
|
||||||
|
) -> None:
|
||||||
|
Realm = apps.get_model("zerver", "Realm")
|
||||||
|
NamedUserGroup = apps.get_model("zerver", "NamedUserGroup")
|
||||||
|
|
||||||
|
EVERYONE_GROUP_NAME = "role:everyone"
|
||||||
|
NOBODY_GROUP_NAME = "role:nobody"
|
||||||
|
PRIVATE_MESSAGE_POLICY_DISABLED = 2
|
||||||
|
|
||||||
|
Realm.objects.filter(
|
||||||
|
direct_message_initiator_group=None,
|
||||||
|
).update(
|
||||||
|
direct_message_initiator_group=NamedUserGroup.objects.filter(
|
||||||
|
name=EVERYONE_GROUP_NAME, realm=OuterRef("id"), is_system_group=True
|
||||||
|
).values("pk")
|
||||||
|
)
|
||||||
|
Realm.objects.filter(
|
||||||
|
direct_message_permission_group=None, private_message_policy=PRIVATE_MESSAGE_POLICY_DISABLED
|
||||||
|
).update(
|
||||||
|
direct_message_permission_group=NamedUserGroup.objects.filter(
|
||||||
|
name=NOBODY_GROUP_NAME, realm=OuterRef("id"), is_system_group=True
|
||||||
|
).values("pk")
|
||||||
|
)
|
||||||
|
Realm.objects.filter(
|
||||||
|
direct_message_permission_group=None,
|
||||||
|
).exclude(private_message_policy=PRIVATE_MESSAGE_POLICY_DISABLED).update(
|
||||||
|
direct_message_permission_group=NamedUserGroup.objects.filter(
|
||||||
|
name=EVERYONE_GROUP_NAME, realm=OuterRef("id"), is_system_group=True
|
||||||
|
).values("pk")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
atomic = False
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0549_realm_direct_message_initiator_group_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
set_default_value_for_direct_message_initiator_group_and_more,
|
||||||
|
elidable=True,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Generated by Django 4.2.8 on 2024-01-01 11:28
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0550_set_default_value_for_realm_direct_message_initiator_group_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="realm",
|
||||||
|
name="direct_message_initiator_group",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.RESTRICT,
|
||||||
|
related_name="+",
|
||||||
|
to="zerver.usergroup",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="realm",
|
||||||
|
name="direct_message_permission_group",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.RESTRICT,
|
||||||
|
related_name="+",
|
||||||
|
to="zerver.usergroup",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -332,6 +332,18 @@ 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 of which at least one member must be included as sender
|
||||||
|
# or recipient in all personal and group direct messages.
|
||||||
|
direct_message_initiator_group = models.ForeignKey(
|
||||||
|
"UserGroup", on_delete=models.RESTRICT, related_name="+"
|
||||||
|
)
|
||||||
|
|
||||||
|
# UserGroup whose members must be included as sender or recipient in all
|
||||||
|
# direct messages.
|
||||||
|
direct_message_permission_group = models.ForeignKey(
|
||||||
|
"UserGroup", on_delete=models.RESTRICT, related_name="+"
|
||||||
|
)
|
||||||
|
|
||||||
# on_delete field here is set to RESTRICT because we don't want to allow
|
# on_delete field here is set to RESTRICT because we don't want to allow
|
||||||
# deleting a user group in case it is referenced by this setting.
|
# deleting a user group in case it is referenced by this setting.
|
||||||
# We are not using PROTECT since we want to allow deletion of user groups
|
# We are not using PROTECT since we want to allow deletion of user groups
|
||||||
|
@ -724,11 +736,31 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
||||||
default_group_name=SystemGroups.MEMBERS,
|
default_group_name=SystemGroups.MEMBERS,
|
||||||
id_field_name="can_create_private_channel_group_id",
|
id_field_name="can_create_private_channel_group_id",
|
||||||
),
|
),
|
||||||
|
direct_message_initiator_group=GroupPermissionSetting(
|
||||||
|
require_system_group=False,
|
||||||
|
allow_internet_group=False,
|
||||||
|
allow_owners_group=True,
|
||||||
|
allow_nobody_group=True,
|
||||||
|
allow_everyone_group=True,
|
||||||
|
default_group_name=SystemGroups.EVERYONE,
|
||||||
|
id_field_name="direct_message_initiator_group_id",
|
||||||
|
),
|
||||||
|
direct_message_permission_group=GroupPermissionSetting(
|
||||||
|
require_system_group=False,
|
||||||
|
allow_internet_group=False,
|
||||||
|
allow_owners_group=True,
|
||||||
|
allow_nobody_group=True,
|
||||||
|
allow_everyone_group=True,
|
||||||
|
default_group_name=SystemGroups.EVERYONE,
|
||||||
|
id_field_name="direct_message_permission_group_id",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT = [
|
REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT = [
|
||||||
"can_create_private_channel_group",
|
"can_create_private_channel_group",
|
||||||
"can_create_public_channel_group",
|
"can_create_public_channel_group",
|
||||||
|
"direct_message_initiator_group",
|
||||||
|
"direct_message_permission_group",
|
||||||
]
|
]
|
||||||
|
|
||||||
DIGEST_WEEKDAY_VALUES = [0, 1, 2, 3, 4, 5, 6]
|
DIGEST_WEEKDAY_VALUES = [0, 1, 2, 3, 4, 5, 6]
|
||||||
|
@ -1092,6 +1124,10 @@ def get_realm_with_settings(realm_id: int) -> Realm:
|
||||||
"can_create_public_channel_group__named_user_group",
|
"can_create_public_channel_group__named_user_group",
|
||||||
"can_create_private_channel_group",
|
"can_create_private_channel_group",
|
||||||
"can_create_private_channel_group__named_user_group",
|
"can_create_private_channel_group__named_user_group",
|
||||||
|
"direct_message_initiator_group",
|
||||||
|
"direct_message_initiator_group__named_user_group",
|
||||||
|
"direct_message_permission_group",
|
||||||
|
"direct_message_permission_group__named_user_group",
|
||||||
).get(id=realm_id)
|
).get(id=realm_id)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -780,6 +780,8 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings):
|
||||||
"create_multiuse_invite_group",
|
"create_multiuse_invite_group",
|
||||||
"create_web_public_stream_policy",
|
"create_web_public_stream_policy",
|
||||||
"delete_own_message_policy",
|
"delete_own_message_policy",
|
||||||
|
"direct_message_initiator_group",
|
||||||
|
"direct_message_permission_group",
|
||||||
"edit_topic_policy",
|
"edit_topic_policy",
|
||||||
"invite_to_stream_policy",
|
"invite_to_stream_policy",
|
||||||
"invite_to_realm_policy",
|
"invite_to_realm_policy",
|
||||||
|
@ -913,6 +915,10 @@ def get_user_profile_by_id(user_profile_id: int) -> UserProfile:
|
||||||
"realm",
|
"realm",
|
||||||
"realm__can_access_all_users_group",
|
"realm__can_access_all_users_group",
|
||||||
"realm__can_access_all_users_group__named_user_group",
|
"realm__can_access_all_users_group__named_user_group",
|
||||||
|
"realm__direct_message_initiator_group",
|
||||||
|
"realm__direct_message_initiator_group__named_user_group",
|
||||||
|
"realm__direct_message_permission_group",
|
||||||
|
"realm__direct_message_permission_group__named_user_group",
|
||||||
"bot_owner",
|
"bot_owner",
|
||||||
).get(id=user_profile_id)
|
).get(id=user_profile_id)
|
||||||
|
|
||||||
|
@ -963,6 +969,10 @@ def get_user_by_delivery_email(email: str, realm: "Realm") -> UserProfile:
|
||||||
"realm",
|
"realm",
|
||||||
"realm__can_access_all_users_group",
|
"realm__can_access_all_users_group",
|
||||||
"realm__can_access_all_users_group__named_user_group",
|
"realm__can_access_all_users_group__named_user_group",
|
||||||
|
"realm__direct_message_initiator_group",
|
||||||
|
"realm__direct_message_initiator_group__named_user_group",
|
||||||
|
"realm__direct_message_permission_group",
|
||||||
|
"realm__direct_message_permission_group__named_user_group",
|
||||||
"bot_owner",
|
"bot_owner",
|
||||||
).get(delivery_email__iexact=email.strip(), realm=realm)
|
).get(delivery_email__iexact=email.strip(), realm=realm)
|
||||||
|
|
||||||
|
@ -1003,6 +1013,10 @@ def get_user(email: str, realm: "Realm") -> UserProfile:
|
||||||
"realm",
|
"realm",
|
||||||
"realm__can_access_all_users_group",
|
"realm__can_access_all_users_group",
|
||||||
"realm__can_access_all_users_group__named_user_group",
|
"realm__can_access_all_users_group__named_user_group",
|
||||||
|
"realm__direct_message_initiator_group",
|
||||||
|
"realm__direct_message_initiator_group__named_user_group",
|
||||||
|
"realm__direct_message_permission_group",
|
||||||
|
"realm__direct_message_permission_group__named_user_group",
|
||||||
"bot_owner",
|
"bot_owner",
|
||||||
).get(email__iexact=email.strip(), realm=realm)
|
).get(email__iexact=email.strip(), realm=realm)
|
||||||
|
|
||||||
|
|
|
@ -4306,6 +4306,37 @@ paths:
|
||||||
description: |
|
description: |
|
||||||
The day of the week when the organization will send
|
The day of the week when the organization will send
|
||||||
its weekly digest email to inactive users.
|
its weekly digest email to inactive users.
|
||||||
|
direct_message_initiator_group:
|
||||||
|
allOf:
|
||||||
|
- description: |
|
||||||
|
A [group-setting value](/api/group-setting-values) defining the set of
|
||||||
|
users who have permission to start a new direct message conversation
|
||||||
|
involving other non-bot users. Users who are outside this group and attempt
|
||||||
|
to send the first direct message to a given collection of recipient users
|
||||||
|
will receive an error, unless all other recipients are bots or the sender.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 9.0 (feature level 270).
|
||||||
|
|
||||||
|
Previously, access to send direct messages was controlled by the
|
||||||
|
`private_message_policy` realm setting, which supported values of
|
||||||
|
1 (enabled) and 2 (disabled).
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
|
direct_message_permission_group:
|
||||||
|
allOf:
|
||||||
|
- description: |
|
||||||
|
A [group-setting value](/api/group-setting-values) defining the set of
|
||||||
|
users who have permission to fully use direct messages. Users outside
|
||||||
|
this group can only send direct messages to conversations where all the
|
||||||
|
recipients are in this group, are bots, or are the sender, ensuring that
|
||||||
|
every direct message conversation will be visible to at least one user in
|
||||||
|
this group.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 9.0 (feature level 270).
|
||||||
|
|
||||||
|
Previously, access to send direct messages was controlled by the
|
||||||
|
`private_message_policy` realm setting, which supported values of
|
||||||
|
1 (enabled) and 2 (disabled).
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
disallow_disposable_email_addresses:
|
disallow_disposable_email_addresses:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: |
|
description: |
|
||||||
|
@ -15764,6 +15795,37 @@ paths:
|
||||||
- 2 = Nobody
|
- 2 = Nobody
|
||||||
|
|
||||||
**Changes**: New in Zulip 3.0 (feature level 1).
|
**Changes**: New in Zulip 3.0 (feature level 1).
|
||||||
|
realm_direct_message_initiator_group:
|
||||||
|
allOf:
|
||||||
|
- description: |
|
||||||
|
A [group-setting value](/api/group-setting-values) defining the set of
|
||||||
|
users who have permission to start a new direct message conversation
|
||||||
|
involving other non-bot users. Users who are outside this group and attempt
|
||||||
|
to send the first direct message to a given collection of recipient users
|
||||||
|
will receive an error, unless all other recipients are bots or the sender.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 9.0 (feature level 270).
|
||||||
|
|
||||||
|
Previously, access to send direct messages was controlled by the
|
||||||
|
`private_message_policy` realm setting, which supported values of
|
||||||
|
1 (enabled) and 2 (disabled).
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
|
realm_direct_message_permission_group:
|
||||||
|
allOf:
|
||||||
|
- description: |
|
||||||
|
A [group-setting value](/api/group-setting-values) defining the set of
|
||||||
|
users who have permission to fully use direct messages. Users outside
|
||||||
|
this group can only send direct messages to conversations where all the
|
||||||
|
recipients are in this group, are bots, or are the sender, ensuring that
|
||||||
|
every direct message conversation will be visible to at least one user in
|
||||||
|
this group.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 9.0 (feature level 270).
|
||||||
|
|
||||||
|
Previously, access to send direct messages was controlled by the
|
||||||
|
`private_message_policy` realm setting, which supported values of
|
||||||
|
1 (enabled) and 2 (disabled).
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
realm_user_group_edit_policy:
|
realm_user_group_edit_policy:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
|
|
|
@ -142,6 +142,8 @@ class HomeTest(ZulipTestCase):
|
||||||
"realm_description",
|
"realm_description",
|
||||||
"realm_digest_emails_enabled",
|
"realm_digest_emails_enabled",
|
||||||
"realm_digest_weekday",
|
"realm_digest_weekday",
|
||||||
|
"realm_direct_message_initiator_group",
|
||||||
|
"realm_direct_message_permission_group",
|
||||||
"realm_disallow_disposable_email_addresses",
|
"realm_disallow_disposable_email_addresses",
|
||||||
"realm_domains",
|
"realm_domains",
|
||||||
"realm_edit_topic_policy",
|
"realm_edit_topic_policy",
|
||||||
|
|
|
@ -26,13 +26,20 @@ from zerver.actions.message_send import (
|
||||||
internal_send_stream_message_by_name,
|
internal_send_stream_message_by_name,
|
||||||
send_rate_limited_pm_notification_to_bot_owner,
|
send_rate_limited_pm_notification_to_bot_owner,
|
||||||
)
|
)
|
||||||
from zerver.actions.realm_settings import do_set_realm_property
|
from zerver.actions.realm_settings import (
|
||||||
|
do_change_realm_permission_group_setting,
|
||||||
|
do_set_realm_property,
|
||||||
|
)
|
||||||
from zerver.actions.streams import do_change_stream_post_policy
|
from zerver.actions.streams import do_change_stream_post_policy
|
||||||
from zerver.actions.user_groups import add_subgroups_to_user_group, check_add_user_group
|
from zerver.actions.user_groups import add_subgroups_to_user_group, check_add_user_group
|
||||||
from zerver.actions.user_settings import do_change_user_setting
|
from zerver.actions.user_settings import do_change_user_setting
|
||||||
from zerver.actions.users import do_change_can_forge_sender, do_deactivate_user
|
from zerver.actions.users import do_change_can_forge_sender, do_deactivate_user
|
||||||
from zerver.lib.addressee import Addressee
|
from zerver.lib.addressee import Addressee
|
||||||
from zerver.lib.exceptions import JsonableError
|
from zerver.lib.exceptions import (
|
||||||
|
DirectMessageInitiationError,
|
||||||
|
DirectMessagePermissionError,
|
||||||
|
JsonableError,
|
||||||
|
)
|
||||||
from zerver.lib.message import get_raw_unread_data, get_recent_private_conversations
|
from zerver.lib.message import get_raw_unread_data, get_recent_private_conversations
|
||||||
from zerver.lib.message_cache import MessageDict
|
from zerver.lib.message_cache import MessageDict
|
||||||
from zerver.lib.per_request_cache import flush_per_request_caches
|
from zerver.lib.per_request_cache import flush_per_request_caches
|
||||||
|
@ -60,7 +67,7 @@ from zerver.models import (
|
||||||
)
|
)
|
||||||
from zerver.models.constants import MAX_TOPIC_NAME_LENGTH
|
from zerver.models.constants import MAX_TOPIC_NAME_LENGTH
|
||||||
from zerver.models.groups import SystemGroups
|
from zerver.models.groups import SystemGroups
|
||||||
from zerver.models.realms import PrivateMessagePolicyEnum, WildcardMentionPolicyEnum, get_realm
|
from zerver.models.realms import WildcardMentionPolicyEnum, get_realm
|
||||||
from zerver.models.recipients import get_or_create_direct_message_group
|
from zerver.models.recipients import get_or_create_direct_message_group
|
||||||
from zerver.models.streams import get_stream
|
from zerver.models.streams import get_stream
|
||||||
from zerver.models.users import get_system_bot, get_user
|
from zerver.models.users import get_system_bot, get_user
|
||||||
|
@ -2412,27 +2419,169 @@ class PersonalMessageSendTest(ZulipTestCase):
|
||||||
receiver=self.example_user("othello"),
|
receiver=self.example_user("othello"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_private_message_policy(self) -> None:
|
def test_direct_message_initiator_group_setting(self) -> None:
|
||||||
"""
|
"""
|
||||||
Tests that PrivateMessagePolicyEnum.DISABLED works correctly.
|
Tests that direct_message_initiator_group_setting works correctly.
|
||||||
"""
|
"""
|
||||||
user_profile = self.example_user("hamlet")
|
user_profile = self.example_user("hamlet")
|
||||||
|
polonius = self.example_user("polonius")
|
||||||
|
admin = self.example_user("iago")
|
||||||
|
cordelia = self.example_user("cordelia")
|
||||||
|
realm = user_profile.realm
|
||||||
|
direct_message_group_1 = [user_profile, admin, polonius]
|
||||||
|
direct_message_group_2 = [user_profile, admin, polonius, cordelia]
|
||||||
|
administrators_system_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
self.login_user(user_profile)
|
self.login_user(user_profile)
|
||||||
do_set_realm_property(
|
self.send_personal_message(user_profile, polonius)
|
||||||
user_profile.realm,
|
do_change_realm_permission_group_setting(
|
||||||
"private_message_policy",
|
realm,
|
||||||
PrivateMessagePolicyEnum.DISABLED,
|
"direct_message_initiator_group",
|
||||||
|
administrators_system_group,
|
||||||
acting_user=None,
|
acting_user=None,
|
||||||
)
|
)
|
||||||
with self.assertRaises(JsonableError):
|
|
||||||
self.send_personal_message(user_profile, self.example_user("cordelia"))
|
# We can send to Polonius because we'd previously messaged him.
|
||||||
|
self.send_personal_message(user_profile, polonius)
|
||||||
|
# Tests if we can send messages to self irrespective of the value of the setting.
|
||||||
|
self.send_personal_message(user_profile, user_profile)
|
||||||
|
|
||||||
|
# We cannot send to users with whom we does not have any direct message conversation.
|
||||||
|
with self.assertRaises(DirectMessageInitiationError) as direct_message_initiation_error:
|
||||||
|
self.send_personal_message(user_profile, cordelia)
|
||||||
|
self.assertEqual(
|
||||||
|
str(direct_message_initiation_error.exception),
|
||||||
|
"You do not have permission to initiate direct message conversations.",
|
||||||
|
)
|
||||||
|
with self.assertRaises(DirectMessageInitiationError):
|
||||||
|
self.send_personal_message(user_profile, admin)
|
||||||
|
|
||||||
|
# Have the administrator send a message, and verify that allows the user to reply.
|
||||||
|
self.send_personal_message(admin, user_profile)
|
||||||
|
with self.assert_database_query_count(16):
|
||||||
|
self.send_personal_message(user_profile, admin)
|
||||||
|
|
||||||
|
# Tests that user cannot initiate direct message thread in groups.
|
||||||
|
with self.assertRaises(DirectMessageInitiationError):
|
||||||
|
self.send_group_direct_message(user_profile, direct_message_group_1)
|
||||||
|
|
||||||
|
# Have the administrator send a message to the direct message group, and verify
|
||||||
|
# that allows the user to reply.
|
||||||
|
self.send_group_direct_message(admin, direct_message_group_1)
|
||||||
|
with self.assert_database_query_count(20):
|
||||||
|
self.send_group_direct_message(user_profile, direct_message_group_1)
|
||||||
|
|
||||||
|
# We cannot sent to `direct_message_group_2` as no message has been sent to this group yet.
|
||||||
|
with self.assertRaises(DirectMessageInitiationError):
|
||||||
|
self.send_group_direct_message(user_profile, direct_message_group_2)
|
||||||
|
|
||||||
bot_profile = self.create_test_bot("testbot", user_profile)
|
bot_profile = self.create_test_bot("testbot", user_profile)
|
||||||
notification_bot = get_system_bot("notification-bot@zulip.com", user_profile.realm_id)
|
notification_bot = get_system_bot("notification-bot@zulip.com", user_profile.realm_id)
|
||||||
|
# Tests if messages to and from bots are allowed irrespective of the value of the setting.
|
||||||
self.send_personal_message(user_profile, notification_bot)
|
self.send_personal_message(user_profile, notification_bot)
|
||||||
self.send_personal_message(user_profile, bot_profile)
|
self.send_personal_message(user_profile, bot_profile)
|
||||||
self.send_personal_message(bot_profile, user_profile)
|
self.send_personal_message(bot_profile, user_profile)
|
||||||
|
|
||||||
|
# Tests if the permission works when the setting is set to a combination of
|
||||||
|
# groups and users.
|
||||||
|
user_group = self.create_or_update_anonymous_group_for_setting(
|
||||||
|
[user_profile],
|
||||||
|
[administrators_system_group],
|
||||||
|
)
|
||||||
|
do_change_realm_permission_group_setting(
|
||||||
|
realm,
|
||||||
|
"direct_message_initiator_group",
|
||||||
|
user_group,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
self.send_personal_message(user_profile, cordelia)
|
||||||
|
|
||||||
|
def test_direct_message_permission_group_setting(self) -> None:
|
||||||
|
"""
|
||||||
|
Tests that direct_message_permission_group_setting works correctly.
|
||||||
|
"""
|
||||||
|
user_profile = self.example_user("hamlet")
|
||||||
|
cordelia = self.example_user("cordelia")
|
||||||
|
polonius = self.example_user("polonius")
|
||||||
|
admin = self.example_user("iago")
|
||||||
|
realm = user_profile.realm
|
||||||
|
direct_message_group = [user_profile, cordelia, admin]
|
||||||
|
direct_message_group_without_admin = [user_profile, cordelia, polonius]
|
||||||
|
administrators_system_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
|
nobody_system_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.NOBODY, realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
|
self.login_user(user_profile)
|
||||||
|
do_change_realm_permission_group_setting(
|
||||||
|
realm,
|
||||||
|
"direct_message_permission_group",
|
||||||
|
administrators_system_group,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
# Tests if the user is allowed to send to administrators.
|
||||||
|
with self.assert_database_query_count(16):
|
||||||
|
self.send_personal_message(user_profile, admin)
|
||||||
|
self.send_personal_message(admin, user_profile)
|
||||||
|
# Tests if we can send messages to self irrespective of the value of the setting.
|
||||||
|
self.send_personal_message(user_profile, user_profile)
|
||||||
|
|
||||||
|
# We cannot send direct messages unless one of the recipient is in the
|
||||||
|
# `direct_message_permission_group` (in this case, the
|
||||||
|
# `administrators_system_group`).
|
||||||
|
with self.assertRaises(DirectMessagePermissionError) as direct_message_permission_error:
|
||||||
|
self.send_personal_message(user_profile, cordelia)
|
||||||
|
self.assertEqual(
|
||||||
|
str(direct_message_permission_error.exception),
|
||||||
|
"You do not have permission to send direct messages to this recipient.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# We can send to this direct message group as it has administrator as one of the
|
||||||
|
# recipient.
|
||||||
|
with self.assert_database_query_count(24):
|
||||||
|
self.send_group_direct_message(user_profile, direct_message_group)
|
||||||
|
self.send_group_direct_message(admin, direct_message_group)
|
||||||
|
|
||||||
|
# But this one does not have an administrator. So, it should throw an error.
|
||||||
|
with self.assertRaises(DirectMessagePermissionError):
|
||||||
|
self.send_group_direct_message(user_profile, direct_message_group_without_admin)
|
||||||
|
|
||||||
|
bot_profile = self.create_test_bot("testbot", user_profile)
|
||||||
|
notification_bot = get_system_bot("notification-bot@zulip.com", user_profile.realm_id)
|
||||||
|
# Tests if messages to and from bots are allowed irrespective of the value of the setting.
|
||||||
|
self.send_personal_message(user_profile, notification_bot)
|
||||||
|
self.send_personal_message(user_profile, bot_profile)
|
||||||
|
self.send_personal_message(bot_profile, user_profile)
|
||||||
|
|
||||||
|
# Tests if the permission works when the setting is set to a combination of
|
||||||
|
# groups and users.
|
||||||
|
user_group = self.create_or_update_anonymous_group_for_setting(
|
||||||
|
[user_profile],
|
||||||
|
[administrators_system_group],
|
||||||
|
)
|
||||||
|
do_change_realm_permission_group_setting(
|
||||||
|
realm,
|
||||||
|
"direct_message_permission_group",
|
||||||
|
user_group,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
self.send_personal_message(user_profile, cordelia)
|
||||||
|
|
||||||
|
do_change_realm_permission_group_setting(
|
||||||
|
realm,
|
||||||
|
"direct_message_permission_group",
|
||||||
|
nobody_system_group,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
with self.assertRaises(DirectMessagePermissionError) as direct_message_permission_error:
|
||||||
|
self.send_personal_message(user_profile, cordelia)
|
||||||
|
self.assertEqual(
|
||||||
|
str(direct_message_permission_error.exception),
|
||||||
|
"Direct messages are disabled in this organization.",
|
||||||
|
)
|
||||||
|
|
||||||
def test_non_ascii_personal(self) -> None:
|
def test_non_ascii_personal(self) -> None:
|
||||||
"""
|
"""
|
||||||
Sending a direct message containing non-ASCII characters succeeds.
|
Sending a direct message containing non-ASCII characters succeeds.
|
||||||
|
@ -2678,7 +2827,15 @@ class InternalPrepTest(ZulipTestCase):
|
||||||
Test that a user can send a direct message to themselves and to a bot in a DM disabled organization
|
Test that a user can send a direct message to themselves and to a bot in a DM disabled organization
|
||||||
"""
|
"""
|
||||||
sender = self.example_user("hamlet")
|
sender = self.example_user("hamlet")
|
||||||
sender.realm.private_message_policy = PrivateMessagePolicyEnum.DISABLED
|
nobody_system_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.NOBODY, realm=sender.realm, is_system_group=True
|
||||||
|
)
|
||||||
|
do_change_realm_permission_group_setting(
|
||||||
|
sender.realm,
|
||||||
|
"direct_message_permission_group",
|
||||||
|
nobody_system_group,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
sender.realm.save()
|
sender.realm.save()
|
||||||
|
|
||||||
# Create a non-bot user
|
# Create a non-bot user
|
||||||
|
|
|
@ -149,6 +149,8 @@ def update_realm(
|
||||||
bot_creation_policy: Optional[Json[BotCreationPolicyEnum]] = None,
|
bot_creation_policy: Optional[Json[BotCreationPolicyEnum]] = None,
|
||||||
can_create_public_channel_group: Optional[Json[GroupSettingChangeRequest]] = None,
|
can_create_public_channel_group: Optional[Json[GroupSettingChangeRequest]] = None,
|
||||||
can_create_private_channel_group: Optional[Json[GroupSettingChangeRequest]] = None,
|
can_create_private_channel_group: Optional[Json[GroupSettingChangeRequest]] = None,
|
||||||
|
direct_message_initiator_group: Optional[Json[GroupSettingChangeRequest]] = None,
|
||||||
|
direct_message_permission_group: Optional[Json[GroupSettingChangeRequest]] = None,
|
||||||
create_web_public_stream_policy: Optional[Json[CreateWebPublicStreamPolicyEnum]] = None,
|
create_web_public_stream_policy: Optional[Json[CreateWebPublicStreamPolicyEnum]] = None,
|
||||||
invite_to_stream_policy: Optional[Json[CommonPolicyEnum]] = None,
|
invite_to_stream_policy: Optional[Json[CommonPolicyEnum]] = None,
|
||||||
move_messages_between_streams_policy: Optional[
|
move_messages_between_streams_policy: Optional[
|
||||||
|
|
Loading…
Reference in New Issue