mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	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:
		@@ -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));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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")
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user