mention: Allow silent mentioning system user groups.

Previously, we do not allow mentioning system user groups
at all. Now we want to use silent mention syntax for system
groups in the message sent when updating the posting permission
for a stream, so it is important to allowing silent mentioning
system groups at least. And there is no problem in allowing
silent mentions of system groups for all users.

We do not allow mentioning system groups as can_mention_group
for them is set to "Nobody" group.
This commit is contained in:
Sahil Batra
2024-12-20 14:33:43 +05:30
committed by Tim Abbott
parent aa8f47774f
commit fa099f7ce7
6 changed files with 56 additions and 24 deletions

View File

@@ -212,7 +212,7 @@ export const update_elements = ($content: JQuery): void => {
if (user_group_id && $(this).find(".highlight").length === 0) { if (user_group_id && $(this).find(".highlight").length === 0) {
// Edit the mention to show the current name for the // Edit the mention to show the current name for the
// user group, if its not in search. // user group, if its not in search.
set_name_in_mention_element(this, user_group.name); set_name_in_mention_element(this, user_groups.get_display_group_name(user_group.name));
} }
}); });

View File

@@ -51,6 +51,7 @@ from zerver.lib.mention import (
FullNameInfo, FullNameInfo,
MentionBackend, MentionBackend,
MentionData, MentionData,
get_user_group_mention_display_name,
) )
from zerver.lib.outgoing_http import OutgoingSession from zerver.lib.outgoing_http import OutgoingSession
from zerver.lib.subdomains import is_static_or_current_realm_url from zerver.lib.subdomains import is_static_or_current_realm_url
@@ -1989,7 +1990,7 @@ class UserGroupMentionPattern(CompiledInlineProcessor):
if not silent: if not silent:
self.zmd.zulip_rendering_result.mentions_user_group_ids.add(user_group.id) self.zmd.zulip_rendering_result.mentions_user_group_ids.add(user_group.id)
name = user_group.name name = get_user_group_mention_display_name(user_group)
user_group_id = str(user_group.id) user_group_id = str(user_group.id)
else: else:
# Don't highlight @-mentions that don't refer to a valid user # Don't highlight @-mentions that don't refer to a valid user

View File

@@ -5,10 +5,12 @@ from re import Match
from django.conf import settings from django.conf import settings
from django.db.models import Q from django.db.models import Q
from django_stubs_ext import StrPromise
from zerver.lib.user_groups import get_recursive_group_members from zerver.lib.user_groups import get_recursive_group_members
from zerver.lib.users import get_inaccessible_user_ids from zerver.lib.users import get_inaccessible_user_ids
from zerver.models import NamedUserGroup, UserProfile from zerver.models import NamedUserGroup, UserProfile
from zerver.models.groups import SystemGroups
from zerver.models.streams import get_linkable_streams from zerver.models.streams import get_linkable_streams
BEFORE_MENTION_ALLOWED_REGEX = r"(?<![^\s\'\"\(\{\[\/<])" BEFORE_MENTION_ALLOWED_REGEX = r"(?<![^\s\'\"\(\{\[\/<])"
@@ -271,7 +273,7 @@ class MentionData:
user_group_names = possible_user_group_mentions(content) user_group_names = possible_user_group_mentions(content)
if user_group_names: if user_group_names:
for group in NamedUserGroup.objects.filter( for group in NamedUserGroup.objects.filter(
realm_id=realm_id, name__in=user_group_names, is_system_group=False realm_id=realm_id, name__in=user_group_names
): ):
self.user_group_name_info[group.name.lower()] = group self.user_group_name_info[group.name.lower()] = group
self.user_group_members[group.id] = [ self.user_group_members[group.id] = [
@@ -308,3 +310,10 @@ class MentionData:
def silent_mention_syntax_for_user(user_profile: UserProfile) -> str: def silent_mention_syntax_for_user(user_profile: UserProfile) -> str:
return f"@_**{user_profile.full_name}|{user_profile.id}**" return f"@_**{user_profile.full_name}|{user_profile.id}**"
def get_user_group_mention_display_name(user_group: NamedUserGroup) -> StrPromise | str:
if user_group.is_system_group:
return SystemGroups.GROUP_DISPLAY_NAME_MAP[user_group.name]
return user_group.name

View File

@@ -1,6 +1,7 @@
from django.db import models from django.db import models
from django.db.models import CASCADE from django.db.models import CASCADE
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from django.utils.translation import gettext_lazy
from django_cte import CTEManager from django_cte import CTEManager
from zerver.lib.types import GroupPermissionSetting from zerver.lib.types import GroupPermissionSetting
@@ -17,6 +18,17 @@ class SystemGroups:
EVERYONE = "role:everyone" EVERYONE = "role:everyone"
NOBODY = "role:nobody" NOBODY = "role:nobody"
GROUP_DISPLAY_NAME_MAP = {
NOBODY: gettext_lazy("Nobody"),
OWNERS: gettext_lazy("Owners"),
ADMINISTRATORS: gettext_lazy("Administrators"),
MODERATORS: gettext_lazy("Moderators"),
FULL_MEMBERS: gettext_lazy("Full members"),
MEMBERS: gettext_lazy("Members"),
EVERYONE: gettext_lazy("Everyone"),
EVERYONE_ON_INTERNET: gettext_lazy("Everyone on the internet"),
}
class UserGroup(models.Model): # type: ignore[django-manager-missing] # django-stubs cannot resolve the custom CTEManager yet https://github.com/typeddjango/django-stubs/issues/1023 class UserGroup(models.Model): # type: ignore[django-manager-missing] # django-stubs cannot resolve the custom CTEManager yet https://github.com/typeddjango/django-stubs/issues/1023
objects: CTEManager = CTEManager() objects: CTEManager = CTEManager()

View File

@@ -2871,6 +2871,7 @@ class MarkdownMentionTest(ZulipTestCase):
update_message_and_check_flag("edited", False) update_message_and_check_flag("edited", False)
update_message_and_check_flag("@*support*", True) update_message_and_check_flag("@*support*", True)
update_message_and_check_flag("@_*support*", False) update_message_and_check_flag("@_*support*", False)
update_message_and_check_flag("@_*role:administrators*", False)
def test_user_group_mention_invalid(self) -> None: def test_user_group_mention_invalid(self) -> None:
sender_user_profile = self.example_user("othello") sender_user_profile = self.example_user("othello")
@@ -2907,6 +2908,17 @@ class MarkdownMentionTest(ZulipTestCase):
self.assertEqual(rendering_result.mentions_user_group_ids, set()) self.assertEqual(rendering_result.mentions_user_group_ids, set())
admins_group = NamedUserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, realm=sender_user_profile.realm, is_system_group=True
)
content = "Please contact @_*role:administrators*"
rendering_result = render_message_markdown(msg, content)
self.assertEqual(
rendering_result.rendered_content,
"<p>Please contact "
f'<span class="user-group-mention silent" data-user-group-id="{admins_group.id}">Administrators</span></p>',
)
def test_deactivated_user_group_mention(self) -> None: def test_deactivated_user_group_mention(self) -> None:
sender_user_profile = self.example_user("othello") sender_user_profile = self.example_user("othello")
msg = Message( msg = Message(
@@ -2950,27 +2962,6 @@ class MarkdownMentionTest(ZulipTestCase):
assert_silent_mention("```quote\n@*backend*\n```") assert_silent_mention("```quote\n@*backend*\n```")
assert_silent_mention("```quote\n@_*backend*\n```") assert_silent_mention("```quote\n@_*backend*\n```")
def test_system_user_group_mention(self) -> None:
desdemona = self.example_user("desdemona")
iago = self.example_user("iago")
hamlet = self.example_user("hamlet")
moderators_group = NamedUserGroup.objects.get(
realm=iago.realm, name=SystemGroups.MODERATORS, is_system_group=True
)
content = "@*role:moderators* @**King Hamlet** test message"
# Owner cannot mention a system user group.
msg = Message(sender=desdemona, sending_client=get_client("test"), realm=desdemona.realm)
rendering_result = render_message_markdown(msg, content)
self.assertEqual(rendering_result.mentions_user_ids, {hamlet.id})
self.assertNotIn(moderators_group.id, rendering_result.mentions_user_group_ids)
# Admin belonging to user group also cannot mention a system user group.
msg = Message(sender=iago, sending_client=get_client("test"), realm=iago.realm)
rendering_result = render_message_markdown(msg, content)
self.assertEqual(rendering_result.mentions_user_ids, {hamlet.id})
self.assertNotIn(moderators_group.id, rendering_result.mentions_user_group_ids)
class MarkdownStreamMentionTests(ZulipTestCase): class MarkdownStreamMentionTests(ZulipTestCase):
def test_stream_single(self) -> None: def test_stream_single(self) -> None:

View File

@@ -2322,6 +2322,25 @@ class StreamMessagesTest(ZulipTestCase):
result = self.api_get(cordelia, "/api/v1/messages/" + str(msg_id)) result = self.api_get(cordelia, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result) self.assert_json_success(result)
# Test mentioning system groups where can_mention_group is
# set to "Nobody" group.
self.assertEqual(
moderators_system_group.can_mention_group.named_user_group.name, SystemGroups.NOBODY
)
content = "Test mentioning user group @*role:moderators*"
with self.assertRaisesRegex(
JsonableError,
f"You are not allowed to mention user group '{moderators_system_group.name}'.",
):
self.send_stream_message(iago, "test_stream", content)
# silent mentioning system groups is allowed.
content = "Test mentioning user group @_*role:moderators*"
msg_id = self.send_stream_message(iago, "test_stream", content)
result = self.api_get(cordelia, "/api/v1/messages/" + str(msg_id))
self.assert_json_success(result)
def test_stream_message_mirroring(self) -> None: def test_stream_message_mirroring(self) -> None:
user = self.mit_user("starnine") user = self.mit_user("starnine")
self.subscribe(user, "Verona") self.subscribe(user, "Verona")