mirror of https://github.com/zulip/zulip.git
topic_mentions: Fix restriction rule for @-topic mentions.
Now, the topic wildcard mention follows the following rules: * If the topic has less than 15 participants , anyone can use @ topic mentions. * For more than 15, the org setting 'wildcard_mention_policy' determines who can use @ topic mentions. Earlier, topic wildcard mentions followed the same restriction as stream wildcard mentions, which was incorrect. Fixes part of #27700.
This commit is contained in:
parent
31a731469d
commit
49388d5d3d
|
@ -20,6 +20,16 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 8.0
|
## Changes in Zulip 8.0
|
||||||
|
|
||||||
|
**Feature level 229**
|
||||||
|
|
||||||
|
* [`PATCH /messages/{message_id}`](/api/update-message), [`POST
|
||||||
|
/messages`](/api/send-message): Topic wildcard mentions involving
|
||||||
|
large numbers of participants are now restricted by
|
||||||
|
`wildcard_mention_policy`. The server now uses the
|
||||||
|
`STREAM_WILDCARD_MENTION_NOT_ALLOWED` and
|
||||||
|
`TOPIC_WILDCARD_MENTION_NOT_ALLOWED` error codes when a message is
|
||||||
|
rejected because of `wildcard_mention_policy`.
|
||||||
|
|
||||||
**Feature level 228**
|
**Feature level 228**
|
||||||
|
|
||||||
* [`GET /events`](/api/get-events): `realm_user` events with `op: "update"`
|
* [`GET /events`](/api/get-events): `realm_user` events with `op: "update"`
|
||||||
|
|
|
@ -33,7 +33,7 @@ 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 = 228
|
API_FEATURE_LEVEL = 229
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -5,6 +5,7 @@ import $ from "jquery";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
import render_success_message_scheduled_banner from "../templates/compose_banner/success_message_scheduled_banner.hbs";
|
import render_success_message_scheduled_banner from "../templates/compose_banner/success_message_scheduled_banner.hbs";
|
||||||
|
import render_wildcard_mention_not_allowed_error from "../templates/compose_banner/wildcard_mention_not_allowed_error.hbs";
|
||||||
|
|
||||||
import * as channel from "./channel";
|
import * as channel from "./channel";
|
||||||
import * as compose_banner from "./compose_banner";
|
import * as compose_banner from "./compose_banner";
|
||||||
|
@ -180,16 +181,26 @@ export function send_message(request = create_message_object()) {
|
||||||
send_message_success(request, data);
|
send_message_success(request, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function error(response) {
|
function error(response, server_error_code) {
|
||||||
// If we're not local echo'ing messages, or if this message was not
|
// Error callback for failed message send attempts.
|
||||||
// locally echoed, show error in compose box
|
|
||||||
if (!locally_echoed) {
|
if (!locally_echoed) {
|
||||||
compose_banner.show_error_message(
|
if (server_error_code === "TOPIC_WILDCARD_MENTION_NOT_ALLOWED") {
|
||||||
response,
|
// The topic wildcard mention permission code path has
|
||||||
compose_banner.CLASSNAMES.generic_compose_error,
|
// a special error.
|
||||||
$("#compose_banners"),
|
const new_row = render_wildcard_mention_not_allowed_error({
|
||||||
$("textarea#compose-textarea"),
|
banner_type: compose_banner.ERROR,
|
||||||
);
|
classname: compose_banner.CLASSNAMES.wildcards_not_allowed,
|
||||||
|
});
|
||||||
|
compose_banner.append_compose_banner_to_banner_list(new_row, $("#compose_banners"));
|
||||||
|
} else {
|
||||||
|
compose_banner.show_error_message(
|
||||||
|
response,
|
||||||
|
compose_banner.CLASSNAMES.generic_compose_error,
|
||||||
|
$("#compose_banners"),
|
||||||
|
$("textarea#compose-textarea"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// For messages that were not locally echoed, we're
|
// For messages that were not locally echoed, we're
|
||||||
// responsible for hiding the compose spinner to restore
|
// responsible for hiding the compose spinner to restore
|
||||||
// the compose box so one can send a next message.
|
// the compose box so one can send a next message.
|
||||||
|
|
|
@ -96,7 +96,7 @@ function resend_message(message, $row, {on_send_message_success, send_message})
|
||||||
failed_message_success(message_id);
|
failed_message_success(message_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function on_error(response) {
|
function on_error(response, _server_error_code) {
|
||||||
message_send_error(message.id, response);
|
message_send_error(message.id, response);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
hide_retry_spinner($row);
|
hide_retry_spinner($row);
|
||||||
|
|
|
@ -85,13 +85,22 @@ function contains_problematic_linkifier({content, get_linkifier_map}) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function contains_topic_wildcard_mention(content) {
|
||||||
|
// If the content has topic wildcard mention (@**topic**) then don't
|
||||||
|
// render it locally. We have only server-side restriction check for
|
||||||
|
// @topic mention. This helps to show the error message (no permission)
|
||||||
|
// via the compose banner and not to local-echo then fail due to restriction.
|
||||||
|
return content.includes("@**topic**");
|
||||||
|
}
|
||||||
|
|
||||||
function content_contains_backend_only_syntax({content, get_linkifier_map}) {
|
function content_contains_backend_only_syntax({content, get_linkifier_map}) {
|
||||||
// Try to guess whether or not a message contains syntax that only the
|
// Try to guess whether or not a message contains syntax that only the
|
||||||
// backend Markdown processor can correctly handle.
|
// backend Markdown processor can correctly handle.
|
||||||
// If it doesn't, we can immediately render it client-side for local echo.
|
// If it doesn't, we can immediately render it client-side for local echo.
|
||||||
return (
|
return (
|
||||||
contains_preview_link(content) ||
|
contains_preview_link(content) ||
|
||||||
contains_problematic_linkifier({content, get_linkifier_map})
|
contains_problematic_linkifier({content, get_linkifier_map}) ||
|
||||||
|
contains_topic_wildcard_mention(content)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import ClipboardJS from "clipboard";
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
|
|
||||||
import * as resolved_topic from "../shared/src/resolved_topic";
|
import * as resolved_topic from "../shared/src/resolved_topic";
|
||||||
|
import render_wildcard_mention_not_allowed_error from "../templates/compose_banner/wildcard_mention_not_allowed_error.hbs";
|
||||||
import render_delete_message_modal from "../templates/confirm_dialog/confirm_delete_message.hbs";
|
import render_delete_message_modal from "../templates/confirm_dialog/confirm_delete_message.hbs";
|
||||||
import render_confirm_moving_messages_modal from "../templates/confirm_dialog/confirm_moving_messages.hbs";
|
import render_confirm_moving_messages_modal from "../templates/confirm_dialog/confirm_moving_messages.hbs";
|
||||||
import render_message_edit_form from "../templates/message_edit_form.hbs";
|
import render_message_edit_form from "../templates/message_edit_form.hbs";
|
||||||
|
@ -1070,13 +1071,23 @@ export function save_message_row_edit($row) {
|
||||||
|
|
||||||
hide_message_edit_spinner($row);
|
hide_message_edit_spinner($row);
|
||||||
if (xhr.readyState !== 0) {
|
if (xhr.readyState !== 0) {
|
||||||
|
const $container = compose_banner.get_compose_banner_container(
|
||||||
|
$row.find("textarea"),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (xhr.responseJSON?.code === "TOPIC_WILDCARD_MENTION_NOT_ALLOWED") {
|
||||||
|
const new_row = render_wildcard_mention_not_allowed_error({
|
||||||
|
banner_type: compose_banner.ERROR,
|
||||||
|
classname: compose_banner.CLASSNAMES.wildcards_not_allowed,
|
||||||
|
});
|
||||||
|
compose_banner.append_compose_banner_to_banner_list(new_row, $container);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const message = channel.xhr_error_message(
|
const message = channel.xhr_error_message(
|
||||||
$t({defaultMessage: "Error editing message"}),
|
$t({defaultMessage: "Error editing message"}),
|
||||||
xhr,
|
xhr,
|
||||||
);
|
);
|
||||||
const $container = compose_banner.get_compose_banner_container(
|
|
||||||
$row.find("textarea"),
|
|
||||||
);
|
|
||||||
compose_banner.show_error_message(
|
compose_banner.show_error_message(
|
||||||
message,
|
message,
|
||||||
compose_banner.CLASSNAMES.generic_compose_error,
|
compose_banner.CLASSNAMES.generic_compose_error,
|
||||||
|
|
|
@ -71,7 +71,7 @@ export function send_message(request, on_success, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = channel.xhr_error_message("Error sending message", xhr);
|
const response = channel.xhr_error_message("Error sending message", xhr);
|
||||||
error(response);
|
error(response, xhr.responseJSON?.code);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -97,7 +97,7 @@ export function reply_message(opts) {
|
||||||
// already handles things like reporting times to the server.)
|
// already handles things like reporting times to the server.)
|
||||||
}
|
}
|
||||||
|
|
||||||
function error() {
|
function error(_response, _server_error_code) {
|
||||||
// TODO: In our current use case, which is widgets, to meaningfully
|
// TODO: In our current use case, which is widgets, to meaningfully
|
||||||
// handle errors, we would want the widget to provide some
|
// handle errors, we would want the widget to provide some
|
||||||
// kind of callback to us so it can do some appropriate UI.
|
// kind of callback to us so it can do some appropriate UI.
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
{{#> compose_banner }}
|
{{#> compose_banner }}
|
||||||
<p class="banner_message">
|
<p class="banner_message">
|
||||||
{{#tr}}You do not have permission to use <b>@{stream_wildcard_mention}</b> mentions in this stream.{{/tr}}
|
{{#if stream_wildcard_mention}}
|
||||||
|
{{#tr}}You do not have permission to use <b>@{stream_wildcard_mention}</b> mentions in this stream.{{/tr}}
|
||||||
|
{{else}}
|
||||||
|
{{#tr}}You do not have permission to use <b>@topic</b> mentions in this topic.{{/tr}}
|
||||||
|
{{/if}}
|
||||||
</p>
|
</p>
|
||||||
{{/compose_banner}}
|
{{/compose_banner}}
|
||||||
|
|
|
@ -101,7 +101,7 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="realm_wildcard_mention_policy" class="dropdown-title">{{t "Who can use @all/@everyone mentions in large streams" }}
|
<label for="realm_wildcard_mention_policy" class="dropdown-title">{{t "Who can notify a large number of users with a wildcard mention" }}
|
||||||
{{> ../help_link_widget link="/help/restrict-wildcard-mentions" }}
|
{{> ../help_link_widget link="/help/restrict-wildcard-mentions" }}
|
||||||
</label>
|
</label>
|
||||||
<select name="realm_wildcard_mention_policy" id="id_realm_wildcard_mention_policy" class="prop-element settings_select bootstrap-focus-style" data-setting-widget-type="number">
|
<select name="realm_wildcard_mention_policy" id="id_realm_wildcard_mention_policy" class="prop-element settings_select bootstrap-focus-style" data-setting-widget-type="number">
|
||||||
|
|
|
@ -100,6 +100,34 @@ run_test("transmit_message_ajax_reload_pending", () => {
|
||||||
assert.ok(reload_initiated);
|
assert.ok(reload_initiated);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
run_test("topic wildcard mention not allowed", ({override}) => {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
const success = () => {
|
||||||
|
throw new Error("unexpected success");
|
||||||
|
};
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
const error = (_response, server_error_code) => {
|
||||||
|
assert.equal(server_error_code, "TOPIC_WILDCARD_MENTION_NOT_ALLOWED");
|
||||||
|
};
|
||||||
|
|
||||||
|
override(reload_state, "is_pending", () => false);
|
||||||
|
|
||||||
|
const request = {foo: "bar"};
|
||||||
|
override(channel, "post", (opts) => {
|
||||||
|
assert.equal(opts.url, "/json/messages");
|
||||||
|
assert.equal(opts.data.foo, "bar");
|
||||||
|
const xhr = {
|
||||||
|
responseJSON: {
|
||||||
|
code: "TOPIC_WILDCARD_MENTION_NOT_ALLOWED",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
opts.error(xhr, "bad request");
|
||||||
|
});
|
||||||
|
|
||||||
|
transmit.send_message(request, success, error);
|
||||||
|
});
|
||||||
|
|
||||||
run_test("reply_message_stream", ({override}) => {
|
run_test("reply_message_stream", ({override}) => {
|
||||||
const social_stream_id = 555;
|
const social_stream_id = 555;
|
||||||
stream_data.add_sub({
|
stream_data.add_sub({
|
||||||
|
|
|
@ -22,7 +22,12 @@ from zerver.actions.message_send import (
|
||||||
)
|
)
|
||||||
from zerver.actions.uploads import check_attachment_reference_change
|
from zerver.actions.uploads import check_attachment_reference_change
|
||||||
from zerver.actions.user_topics import bulk_do_set_user_topic_visibility_policy
|
from zerver.actions.user_topics import bulk_do_set_user_topic_visibility_policy
|
||||||
from zerver.lib.exceptions import JsonableError, MessageMoveError
|
from zerver.lib.exceptions import (
|
||||||
|
JsonableError,
|
||||||
|
MessageMoveError,
|
||||||
|
StreamWildcardMentionNotAllowedError,
|
||||||
|
TopicWildcardMentionNotAllowedError,
|
||||||
|
)
|
||||||
from zerver.lib.markdown import MessageRenderingResult, topic_links
|
from zerver.lib.markdown import MessageRenderingResult, topic_links
|
||||||
from zerver.lib.markdown import version as markdown_version
|
from zerver.lib.markdown import version as markdown_version
|
||||||
from zerver.lib.mention import MentionBackend, MentionData, silent_mention_syntax_for_user
|
from zerver.lib.mention import MentionBackend, MentionData, silent_mention_syntax_for_user
|
||||||
|
@ -31,9 +36,10 @@ from zerver.lib.message import (
|
||||||
bulk_access_messages,
|
bulk_access_messages,
|
||||||
check_user_group_mention_allowed,
|
check_user_group_mention_allowed,
|
||||||
normalize_body,
|
normalize_body,
|
||||||
|
stream_wildcard_mention_allowed,
|
||||||
|
topic_wildcard_mention_allowed,
|
||||||
truncate_topic,
|
truncate_topic,
|
||||||
update_to_dict_cache,
|
update_to_dict_cache,
|
||||||
wildcard_mention_allowed,
|
|
||||||
)
|
)
|
||||||
from zerver.lib.queue import queue_json_publish
|
from zerver.lib.queue import queue_json_publish
|
||||||
from zerver.lib.stream_subscription import get_active_subscriptions_for_stream_id
|
from zerver.lib.stream_subscription import get_active_subscriptions_for_stream_id
|
||||||
|
@ -51,6 +57,7 @@ from zerver.lib.topic import (
|
||||||
TOPIC_LINKS,
|
TOPIC_LINKS,
|
||||||
TOPIC_NAME,
|
TOPIC_NAME,
|
||||||
messages_for_topic,
|
messages_for_topic,
|
||||||
|
participants_for_topic,
|
||||||
save_message_for_edit_use_case,
|
save_message_for_edit_use_case,
|
||||||
update_edit_history,
|
update_edit_history,
|
||||||
update_messages_for_topic_edit,
|
update_messages_for_topic_edit,
|
||||||
|
@ -1274,12 +1281,19 @@ def check_update_message(
|
||||||
)
|
)
|
||||||
links_for_embed |= rendering_result.links_for_preview
|
links_for_embed |= rendering_result.links_for_preview
|
||||||
|
|
||||||
if message.is_stream_message() and rendering_result.has_wildcard_mention():
|
if message.is_stream_message() and rendering_result.mentions_stream_wildcard:
|
||||||
stream = access_stream_by_id(user_profile, message.recipient.type_id)[0]
|
stream = access_stream_by_id(user_profile, message.recipient.type_id)[0]
|
||||||
if not wildcard_mention_allowed(message.sender, stream, message.realm):
|
if not stream_wildcard_mention_allowed(message.sender, stream, message.realm):
|
||||||
raise JsonableError(
|
raise StreamWildcardMentionNotAllowedError
|
||||||
_("You do not have permission to use wildcard mentions in this stream.")
|
|
||||||
)
|
if message.is_stream_message() and rendering_result.mentions_topic_wildcard:
|
||||||
|
topic_participant_count = len(
|
||||||
|
participants_for_topic(message.realm.id, message.recipient.id, message.topic_name())
|
||||||
|
)
|
||||||
|
if not topic_wildcard_mention_allowed(
|
||||||
|
message.sender, topic_participant_count, message.realm
|
||||||
|
):
|
||||||
|
raise TopicWildcardMentionNotAllowedError
|
||||||
|
|
||||||
if rendering_result.mentions_user_group_ids:
|
if rendering_result.mentions_user_group_ids:
|
||||||
mentioned_group_ids = list(rendering_result.mentions_user_group_ids)
|
mentioned_group_ids = list(rendering_result.mentions_user_group_ids)
|
||||||
|
|
|
@ -39,7 +39,9 @@ from zerver.lib.exceptions import (
|
||||||
JsonableError,
|
JsonableError,
|
||||||
MarkdownRenderingError,
|
MarkdownRenderingError,
|
||||||
StreamDoesNotExistError,
|
StreamDoesNotExistError,
|
||||||
|
StreamWildcardMentionNotAllowedError,
|
||||||
StreamWithIDDoesNotExistError,
|
StreamWithIDDoesNotExistError,
|
||||||
|
TopicWildcardMentionNotAllowedError,
|
||||||
ZephyrMessageAlreadySentError,
|
ZephyrMessageAlreadySentError,
|
||||||
)
|
)
|
||||||
from zerver.lib.markdown import MessageRenderingResult
|
from zerver.lib.markdown import MessageRenderingResult
|
||||||
|
@ -52,9 +54,10 @@ from zerver.lib.message import (
|
||||||
normalize_body,
|
normalize_body,
|
||||||
render_markdown,
|
render_markdown,
|
||||||
set_visibility_policy_possible,
|
set_visibility_policy_possible,
|
||||||
|
stream_wildcard_mention_allowed,
|
||||||
|
topic_wildcard_mention_allowed,
|
||||||
truncate_topic,
|
truncate_topic,
|
||||||
visibility_policy_for_send_message,
|
visibility_policy_for_send_message,
|
||||||
wildcard_mention_allowed,
|
|
||||||
)
|
)
|
||||||
from zerver.lib.muted_users import get_muting_users
|
from zerver.lib.muted_users import get_muting_users
|
||||||
from zerver.lib.notification_data import (
|
from zerver.lib.notification_data import (
|
||||||
|
@ -1710,12 +1713,18 @@ def check_message(
|
||||||
|
|
||||||
if (
|
if (
|
||||||
stream is not None
|
stream is not None
|
||||||
and message_send_dict.rendering_result.has_wildcard_mention()
|
and message_send_dict.rendering_result.mentions_stream_wildcard
|
||||||
and not wildcard_mention_allowed(sender, stream, realm)
|
and not stream_wildcard_mention_allowed(sender, stream, realm)
|
||||||
):
|
):
|
||||||
raise JsonableError(
|
raise StreamWildcardMentionNotAllowedError
|
||||||
_("You do not have permission to use wildcard mentions in this stream.")
|
|
||||||
)
|
topic_participant_count = len(message_send_dict.topic_participant_user_ids)
|
||||||
|
if (
|
||||||
|
stream is not None
|
||||||
|
and message_send_dict.rendering_result.mentions_topic_wildcard
|
||||||
|
and not topic_wildcard_mention_allowed(sender, topic_participant_count, realm)
|
||||||
|
):
|
||||||
|
raise TopicWildcardMentionNotAllowedError
|
||||||
|
|
||||||
if message_send_dict.rendering_result.mentions_user_group_ids:
|
if message_send_dict.rendering_result.mentions_user_group_ids:
|
||||||
mentioned_group_ids = list(message_send_dict.rendering_result.mentions_user_group_ids)
|
mentioned_group_ids = list(message_send_dict.rendering_result.mentions_user_group_ids)
|
||||||
|
|
|
@ -47,6 +47,8 @@ class ErrorCode(Enum):
|
||||||
REACTION_DOES_NOT_EXIST = auto()
|
REACTION_DOES_NOT_EXIST = auto()
|
||||||
SERVER_NOT_READY = auto()
|
SERVER_NOT_READY = auto()
|
||||||
MISSING_REMOTE_REALM = auto()
|
MISSING_REMOTE_REALM = auto()
|
||||||
|
TOPIC_WILDCARD_MENTION_NOT_ALLOWED = auto()
|
||||||
|
STREAM_WILDCARD_MENTION_NOT_ALLOWED = auto()
|
||||||
|
|
||||||
|
|
||||||
class JsonableError(Exception):
|
class JsonableError(Exception):
|
||||||
|
@ -584,3 +586,27 @@ class MissingRemoteRealmError(JsonableError): # nocoverage
|
||||||
@override
|
@override
|
||||||
def msg_format() -> str:
|
def msg_format() -> str:
|
||||||
return _("Organization not registered")
|
return _("Organization not registered")
|
||||||
|
|
||||||
|
|
||||||
|
class StreamWildcardMentionNotAllowedError(JsonableError):
|
||||||
|
code: ErrorCode = ErrorCode.STREAM_WILDCARD_MENTION_NOT_ALLOWED
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@override
|
||||||
|
def msg_format() -> str:
|
||||||
|
return _("You do not have permission to use stream wildcard mentions in this stream.")
|
||||||
|
|
||||||
|
|
||||||
|
class TopicWildcardMentionNotAllowedError(JsonableError):
|
||||||
|
code: ErrorCode = ErrorCode.TOPIC_WILDCARD_MENTION_NOT_ALLOWED
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@override
|
||||||
|
def msg_format() -> str:
|
||||||
|
return _("You do not have permission to use topic wildcard mentions in this topic.")
|
||||||
|
|
|
@ -134,9 +134,6 @@ class MessageRenderingResult:
|
||||||
user_ids_with_alert_words: Set[int]
|
user_ids_with_alert_words: Set[int]
|
||||||
potential_attachment_path_ids: List[str]
|
potential_attachment_path_ids: List[str]
|
||||||
|
|
||||||
def has_wildcard_mention(self) -> bool:
|
|
||||||
return self.mentions_stream_wildcard or self.mentions_topic_wildcard
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DbData:
|
class DbData:
|
||||||
|
|
|
@ -1664,14 +1664,13 @@ def get_recent_private_conversations(user_profile: UserProfile) -> Dict[int, Dic
|
||||||
return recipient_map
|
return recipient_map
|
||||||
|
|
||||||
|
|
||||||
def wildcard_mention_allowed(sender: UserProfile, stream: Stream, realm: Realm) -> bool:
|
def wildcard_mention_policy_authorizes_user(sender: UserProfile, realm: Realm) -> bool:
|
||||||
# If there are fewer than Realm.WILDCARD_MENTION_THRESHOLD, we
|
"""Helper function for 'topic_wildcard_mention_allowed' and
|
||||||
# allow sending. In the future, we may want to make this behavior
|
'stream_wildcard_mention_allowed' to check if the sender is allowed to use
|
||||||
# a default, and also just allow explicitly setting whether this
|
wildcard mentions based on the 'wildcard_mention_policy' setting of that realm.
|
||||||
# applies to a stream as an override.
|
This check is used only if the participants count in the topic or the subscribers
|
||||||
if num_subscribers_for_stream_id(stream.id) <= Realm.WILDCARD_MENTION_THRESHOLD:
|
count in the stream is greater than 'Realm.WILDCARD_MENTION_THRESHOLD'.
|
||||||
return True
|
"""
|
||||||
|
|
||||||
if realm.wildcard_mention_policy == Realm.WILDCARD_MENTION_POLICY_NOBODY:
|
if realm.wildcard_mention_policy == Realm.WILDCARD_MENTION_POLICY_NOBODY:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -1693,6 +1692,24 @@ def wildcard_mention_allowed(sender: UserProfile, stream: Stream, realm: Realm)
|
||||||
raise AssertionError("Invalid wildcard mention policy")
|
raise AssertionError("Invalid wildcard mention policy")
|
||||||
|
|
||||||
|
|
||||||
|
def topic_wildcard_mention_allowed(
|
||||||
|
sender: UserProfile, topic_participant_count: int, realm: Realm
|
||||||
|
) -> bool:
|
||||||
|
if topic_participant_count <= Realm.WILDCARD_MENTION_THRESHOLD:
|
||||||
|
return True
|
||||||
|
return wildcard_mention_policy_authorizes_user(sender, realm)
|
||||||
|
|
||||||
|
|
||||||
|
def stream_wildcard_mention_allowed(sender: UserProfile, stream: Stream, realm: Realm) -> bool:
|
||||||
|
# If there are fewer than Realm.WILDCARD_MENTION_THRESHOLD, we
|
||||||
|
# allow sending. In the future, we may want to make this behavior
|
||||||
|
# a default, and also just allow explicitly setting whether this
|
||||||
|
# applies to a stream as an override.
|
||||||
|
if num_subscribers_for_stream_id(stream.id) <= Realm.WILDCARD_MENTION_THRESHOLD:
|
||||||
|
return True
|
||||||
|
return wildcard_mention_policy_authorizes_user(sender, realm)
|
||||||
|
|
||||||
|
|
||||||
def check_user_group_mention_allowed(sender: UserProfile, user_group_ids: List[int]) -> None:
|
def check_user_group_mention_allowed(sender: UserProfile, user_group_ids: List[int]) -> None:
|
||||||
user_groups = UserGroup.objects.filter(id__in=user_group_ids).select_related(
|
user_groups = UserGroup.objects.filter(id__in=user_group_ids).select_related(
|
||||||
"can_mention_group"
|
"can_mention_group"
|
||||||
|
|
|
@ -6404,6 +6404,38 @@ paths:
|
||||||
description: |
|
description: |
|
||||||
A typical failed JSON response for when a direct message is sent to a user
|
A typical failed JSON response for when a direct message is sent to a user
|
||||||
that does not exist:
|
that does not exist:
|
||||||
|
- allOf:
|
||||||
|
- $ref: "#/components/schemas/CodedError"
|
||||||
|
- example:
|
||||||
|
{
|
||||||
|
"result": "error",
|
||||||
|
"msg": "You do not have permission to use stream wildcard mentions in this stream.",
|
||||||
|
"code": "STREAM_WILDCARD_MENTION_NOT_ALLOWED",
|
||||||
|
}
|
||||||
|
description: |
|
||||||
|
An example JSON error response for when the message was rejected because
|
||||||
|
of the organization's `wildcard_mention_policy` and large number of
|
||||||
|
subscribers to the stream.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 8.0 (feature level 229). Previously, this
|
||||||
|
error returned the `"BAD_REQUEST"` code.
|
||||||
|
- allOf:
|
||||||
|
- $ref: "#/components/schemas/CodedError"
|
||||||
|
- example:
|
||||||
|
{
|
||||||
|
"result": "error",
|
||||||
|
"msg": "You do not have permission to use topic wildcard mentions in this topic.",
|
||||||
|
"code": "TOPIC_WILDCARD_MENTION_NOT_ALLOWED",
|
||||||
|
}
|
||||||
|
description: |
|
||||||
|
An example JSON error response for when the message was rejected because
|
||||||
|
the message contains a topic wildcard mention, but the user doesn't have
|
||||||
|
permission to use such a mention in this topic due to the
|
||||||
|
`wildcard_mention_policy` (and large number of participants in this
|
||||||
|
specific topic).
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 8.0 (feature level 229). Previously,
|
||||||
|
`wildcard_mention_policy` was not enforced for topic mentions.
|
||||||
/messages/{message_id}/history:
|
/messages/{message_id}/history:
|
||||||
get:
|
get:
|
||||||
operationId: get-message-history
|
operationId: get-message-history
|
||||||
|
@ -7668,6 +7700,38 @@ paths:
|
||||||
dialog.
|
dialog.
|
||||||
|
|
||||||
**Changes**: New in Zulip 7.0 (feature level 172).
|
**Changes**: New in Zulip 7.0 (feature level 172).
|
||||||
|
- allOf:
|
||||||
|
- $ref: "#/components/schemas/CodedError"
|
||||||
|
- example:
|
||||||
|
{
|
||||||
|
"result": "error",
|
||||||
|
"msg": "You do not have permission to use stream wildcard mentions in this stream.",
|
||||||
|
"code": "STREAM_WILDCARD_MENTION_NOT_ALLOWED",
|
||||||
|
}
|
||||||
|
description: |
|
||||||
|
An example JSON error response for when the message was rejected because
|
||||||
|
of the organization's `wildcard_mention_policy` and large number of
|
||||||
|
subscribers to the stream.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 8.0 (feature level 229). Previously, this
|
||||||
|
error returned the `"BAD_REQUEST"` code.
|
||||||
|
- allOf:
|
||||||
|
- $ref: "#/components/schemas/CodedError"
|
||||||
|
- example:
|
||||||
|
{
|
||||||
|
"result": "error",
|
||||||
|
"msg": "You do not have permission to use topic wildcard mentions in this topic.",
|
||||||
|
"code": "TOPIC_WILDCARD_MENTION_NOT_ALLOWED",
|
||||||
|
}
|
||||||
|
description: |
|
||||||
|
An example JSON error response for when the message was rejected because
|
||||||
|
the message contains a topic wildcard mention, but the user doesn't have
|
||||||
|
permission to use such a mention in this topic due to the
|
||||||
|
`wildcard_mention_policy` (and large number of participants in this
|
||||||
|
specific topic).
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 8.0 (feature level 229). Previously,
|
||||||
|
`wildcard_mention_policy` was not enforced for topic mentions.
|
||||||
delete:
|
delete:
|
||||||
operationId: delete-message
|
operationId: delete-message
|
||||||
summary: Delete a message
|
summary: Delete a message
|
||||||
|
|
|
@ -2284,18 +2284,11 @@ class EditMessageTest(EditMessageTestCase):
|
||||||
acting_user=None,
|
acting_user=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch("zerver.lib.message.num_subscribers_for_stream_id", return_value=17):
|
# Less than 'Realm.WILDCARD_MENTION_THRESHOLD' participants
|
||||||
result = self.client_patch(
|
participants_user_ids = set(range(1, 10))
|
||||||
"/json/messages/" + str(message_id),
|
with mock.patch(
|
||||||
{
|
"zerver.actions.message_edit.participants_for_topic", return_value=participants_user_ids
|
||||||
"content": "Hello @**topic**",
|
):
|
||||||
},
|
|
||||||
)
|
|
||||||
self.assert_json_error(
|
|
||||||
result, "You do not have permission to use wildcard mentions in this stream."
|
|
||||||
)
|
|
||||||
|
|
||||||
with mock.patch("zerver.lib.message.num_subscribers_for_stream_id", return_value=14):
|
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
"/json/messages/" + str(message_id),
|
"/json/messages/" + str(message_id),
|
||||||
{
|
{
|
||||||
|
@ -2304,9 +2297,27 @@ class EditMessageTest(EditMessageTestCase):
|
||||||
)
|
)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
|
||||||
|
# More than 'Realm.WILDCARD_MENTION_THRESHOLD' participants.
|
||||||
|
participants_user_ids = set(range(1, 20))
|
||||||
|
with mock.patch(
|
||||||
|
"zerver.actions.message_edit.participants_for_topic", return_value=participants_user_ids
|
||||||
|
):
|
||||||
|
result = self.client_patch(
|
||||||
|
"/json/messages/" + str(message_id),
|
||||||
|
{
|
||||||
|
"content": "Hello @**topic**",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assert_json_error(
|
||||||
|
result, "You do not have permission to use topic wildcard mentions in this topic."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Shiva is moderator
|
||||||
self.login("shiva")
|
self.login("shiva")
|
||||||
message_id = self.send_stream_message(shiva, stream_name, "Hi everyone")
|
message_id = self.send_stream_message(shiva, stream_name, "Hi everyone")
|
||||||
with mock.patch("zerver.lib.message.num_subscribers_for_stream_id", return_value=17):
|
with mock.patch(
|
||||||
|
"zerver.actions.message_edit.participants_for_topic", return_value=participants_user_ids
|
||||||
|
):
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
"/json/messages/" + str(message_id),
|
"/json/messages/" + str(message_id),
|
||||||
{
|
{
|
||||||
|
@ -2389,7 +2400,7 @@ class EditMessageTest(EditMessageTestCase):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assert_json_error(
|
self.assert_json_error(
|
||||||
result, "You do not have permission to use wildcard mentions in this stream."
|
result, "You do not have permission to use stream wildcard mentions in this stream."
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch("zerver.lib.message.num_subscribers_for_stream_id", return_value=14):
|
with mock.patch("zerver.lib.message.num_subscribers_for_stream_id", return_value=14):
|
||||||
|
|
|
@ -1783,11 +1783,14 @@ class StreamMessagesTest(ZulipTestCase):
|
||||||
self.assertTrue(user_message.flags.mentioned)
|
self.assertTrue(user_message.flags.mentioned)
|
||||||
|
|
||||||
def send_and_verify_topic_wildcard_mention_message(
|
def send_and_verify_topic_wildcard_mention_message(
|
||||||
self, sender_name: str, test_fails: bool = False, sub_count: int = 16
|
self, sender_name: str, test_fails: bool = False, topic_participant_count: int = 20
|
||||||
) -> None:
|
) -> None:
|
||||||
sender = self.example_user(sender_name)
|
sender = self.example_user(sender_name)
|
||||||
content = "@**topic** test topic wildcard mention"
|
content = "@**topic** test topic wildcard mention"
|
||||||
with mock.patch("zerver.lib.message.num_subscribers_for_stream_id", return_value=sub_count):
|
participants_user_ids = set(range(topic_participant_count))
|
||||||
|
with mock.patch(
|
||||||
|
"zerver.actions.message_send.participants_for_topic", return_value=participants_user_ids
|
||||||
|
):
|
||||||
if not test_fails:
|
if not test_fails:
|
||||||
msg_id = self.send_stream_message(sender, "test_stream", content)
|
msg_id = self.send_stream_message(sender, "test_stream", content)
|
||||||
result = self.api_get(sender, "/api/v1/messages/" + str(msg_id))
|
result = self.api_get(sender, "/api/v1/messages/" + str(msg_id))
|
||||||
|
@ -1796,7 +1799,7 @@ class StreamMessagesTest(ZulipTestCase):
|
||||||
else:
|
else:
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaisesRegex(
|
||||||
JsonableError,
|
JsonableError,
|
||||||
"You do not have permission to use wildcard mentions in this stream.",
|
"You do not have permission to use topic wildcard mentions in this topic.",
|
||||||
):
|
):
|
||||||
self.send_stream_message(sender, "test_stream", content)
|
self.send_stream_message(sender, "test_stream", content)
|
||||||
|
|
||||||
|
@ -1828,8 +1831,8 @@ class StreamMessagesTest(ZulipTestCase):
|
||||||
acting_user=None,
|
acting_user=None,
|
||||||
)
|
)
|
||||||
self.send_and_verify_topic_wildcard_mention_message("polonius", test_fails=True)
|
self.send_and_verify_topic_wildcard_mention_message("polonius", test_fails=True)
|
||||||
# There is no restriction on small streams.
|
# There is no restriction on topics with less than 'Realm.WILDCARD_MENTION_THRESHOLD' participants.
|
||||||
self.send_and_verify_topic_wildcard_mention_message("polonius", sub_count=10)
|
self.send_and_verify_topic_wildcard_mention_message("polonius", topic_participant_count=10)
|
||||||
self.send_and_verify_topic_wildcard_mention_message("cordelia")
|
self.send_and_verify_topic_wildcard_mention_message("cordelia")
|
||||||
|
|
||||||
do_set_realm_property(
|
do_set_realm_property(
|
||||||
|
@ -1846,7 +1849,7 @@ class StreamMessagesTest(ZulipTestCase):
|
||||||
cordelia.date_joined = timezone_now()
|
cordelia.date_joined = timezone_now()
|
||||||
cordelia.save()
|
cordelia.save()
|
||||||
self.send_and_verify_topic_wildcard_mention_message("cordelia", test_fails=True)
|
self.send_and_verify_topic_wildcard_mention_message("cordelia", test_fails=True)
|
||||||
self.send_and_verify_topic_wildcard_mention_message("cordelia", sub_count=10)
|
self.send_and_verify_topic_wildcard_mention_message("cordelia", topic_participant_count=10)
|
||||||
# Administrators and moderators can use wildcard mentions even if they are new.
|
# Administrators and moderators can use wildcard mentions even if they are new.
|
||||||
self.send_and_verify_topic_wildcard_mention_message("iago")
|
self.send_and_verify_topic_wildcard_mention_message("iago")
|
||||||
self.send_and_verify_topic_wildcard_mention_message("shiva")
|
self.send_and_verify_topic_wildcard_mention_message("shiva")
|
||||||
|
@ -1862,7 +1865,7 @@ class StreamMessagesTest(ZulipTestCase):
|
||||||
acting_user=None,
|
acting_user=None,
|
||||||
)
|
)
|
||||||
self.send_and_verify_topic_wildcard_mention_message("cordelia", test_fails=True)
|
self.send_and_verify_topic_wildcard_mention_message("cordelia", test_fails=True)
|
||||||
self.send_and_verify_topic_wildcard_mention_message("cordelia", sub_count=10)
|
self.send_and_verify_topic_wildcard_mention_message("cordelia", topic_participant_count=10)
|
||||||
self.send_and_verify_topic_wildcard_mention_message("shiva")
|
self.send_and_verify_topic_wildcard_mention_message("shiva")
|
||||||
|
|
||||||
cordelia.date_joined = timezone_now()
|
cordelia.date_joined = timezone_now()
|
||||||
|
@ -1871,15 +1874,15 @@ class StreamMessagesTest(ZulipTestCase):
|
||||||
realm, "wildcard_mention_policy", Realm.WILDCARD_MENTION_POLICY_ADMINS, acting_user=None
|
realm, "wildcard_mention_policy", Realm.WILDCARD_MENTION_POLICY_ADMINS, acting_user=None
|
||||||
)
|
)
|
||||||
self.send_and_verify_topic_wildcard_mention_message("shiva", test_fails=True)
|
self.send_and_verify_topic_wildcard_mention_message("shiva", test_fails=True)
|
||||||
# There is no restriction on small streams.
|
# There is no restriction on topics with less than 'Realm.WILDCARD_MENTION_THRESHOLD' participants.
|
||||||
self.send_and_verify_topic_wildcard_mention_message("shiva", sub_count=10)
|
self.send_and_verify_topic_wildcard_mention_message("shiva", topic_participant_count=10)
|
||||||
self.send_and_verify_topic_wildcard_mention_message("iago")
|
self.send_and_verify_topic_wildcard_mention_message("iago")
|
||||||
|
|
||||||
do_set_realm_property(
|
do_set_realm_property(
|
||||||
realm, "wildcard_mention_policy", Realm.WILDCARD_MENTION_POLICY_NOBODY, acting_user=None
|
realm, "wildcard_mention_policy", Realm.WILDCARD_MENTION_POLICY_NOBODY, acting_user=None
|
||||||
)
|
)
|
||||||
self.send_and_verify_topic_wildcard_mention_message("iago", test_fails=True)
|
self.send_and_verify_topic_wildcard_mention_message("iago", test_fails=True)
|
||||||
self.send_and_verify_topic_wildcard_mention_message("iago", sub_count=10)
|
self.send_and_verify_topic_wildcard_mention_message("iago", topic_participant_count=10)
|
||||||
|
|
||||||
def send_and_verify_stream_wildcard_mention_message(
|
def send_and_verify_stream_wildcard_mention_message(
|
||||||
self, sender_name: str, test_fails: bool = False, sub_count: int = 16
|
self, sender_name: str, test_fails: bool = False, sub_count: int = 16
|
||||||
|
@ -1895,7 +1898,7 @@ class StreamMessagesTest(ZulipTestCase):
|
||||||
else:
|
else:
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaisesRegex(
|
||||||
JsonableError,
|
JsonableError,
|
||||||
"You do not have permission to use wildcard mentions in this stream.",
|
"You do not have permission to use stream wildcard mentions in this stream.",
|
||||||
):
|
):
|
||||||
self.send_stream_message(sender, "test_stream", content)
|
self.send_stream_message(sender, "test_stream", content)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue