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:
Prakhar Pratyush 2023-11-21 15:09:13 +05:30 committed by Tim Abbott
parent 31a731469d
commit 49388d5d3d
18 changed files with 282 additions and 68 deletions

View File

@ -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"`

View File

@ -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

View File

@ -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.

View File

@ -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);

View File

@ -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)
); );
} }

View File

@ -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,

View File

@ -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.

View File

@ -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}}

View File

@ -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">

View File

@ -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({

View File

@ -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)

View File

@ -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)

View File

@ -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.")

View File

@ -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:

View File

@ -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"

View File

@ -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

View File

@ -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):

View File

@ -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)