mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	CVE-2025-25195: Only send "active" change events to channel subscribers.
This fixes a bug where private stream event to update stream's active status was sent to all active users instead of just its subscribers.
This commit is contained in:
		@@ -1550,7 +1550,7 @@ def notify_stream_is_recently_active_update(stream: Stream, value: bool) -> None
 | 
			
		||||
        name=stream.name,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    send_event_on_commit(stream.realm, event, active_user_ids(stream.realm_id))
 | 
			
		||||
    send_event_on_commit(stream.realm, event, can_access_stream_metadata_user_ids(stream))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@transaction.atomic(durable=True)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,21 @@
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
from unittest import mock
 | 
			
		||||
 | 
			
		||||
import orjson
 | 
			
		||||
from django.utils.timezone import now as timezone_now
 | 
			
		||||
 | 
			
		||||
from zerver.actions.message_delete import do_delete_messages
 | 
			
		||||
from zerver.actions.realm_settings import (
 | 
			
		||||
    do_change_realm_permission_group_setting,
 | 
			
		||||
    do_set_realm_property,
 | 
			
		||||
)
 | 
			
		||||
from zerver.actions.streams import do_change_stream_group_based_setting
 | 
			
		||||
from zerver.actions.streams import do_change_stream_group_based_setting, do_change_stream_permission
 | 
			
		||||
from zerver.actions.user_groups import check_add_user_group
 | 
			
		||||
from zerver.lib.message import has_message_access
 | 
			
		||||
from zerver.lib.streams import check_update_all_streams_active_status
 | 
			
		||||
from zerver.lib.streams import (
 | 
			
		||||
    can_access_stream_metadata_user_ids,
 | 
			
		||||
    update_stream_active_status_for_realm,
 | 
			
		||||
)
 | 
			
		||||
from zerver.lib.test_classes import ZulipTestCase, get_topic_messages
 | 
			
		||||
from zerver.lib.test_helpers import queries_captured
 | 
			
		||||
from zerver.lib.url_encoding import near_stream_message_url
 | 
			
		||||
@@ -18,6 +23,7 @@ from zerver.models import Message, NamedUserGroup, Stream, UserMessage, UserProf
 | 
			
		||||
from zerver.models.groups import SystemGroups
 | 
			
		||||
from zerver.models.realms import get_realm
 | 
			
		||||
from zerver.models.streams import get_stream
 | 
			
		||||
from zerver.tornado.django_api import send_event_on_commit
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MessageMoveStreamTest(ZulipTestCase):
 | 
			
		||||
@@ -1932,7 +1938,84 @@ class MessageMoveStreamTest(ZulipTestCase):
 | 
			
		||||
        # Delete all messages in new stream and mark it as inactive.
 | 
			
		||||
        Message.objects.filter(recipient__type_id=new_stream.id, realm=user_profile.realm).delete()
 | 
			
		||||
 | 
			
		||||
        check_update_all_streams_active_status()
 | 
			
		||||
        with mock.patch("zerver.lib.streams.send_event_on_commit", wraps=send_event_on_commit) as m:
 | 
			
		||||
            update_stream_active_status_for_realm(
 | 
			
		||||
                user_profile.realm, timezone_now() - timedelta(days=10)
 | 
			
		||||
            )
 | 
			
		||||
            self.assertEqual(
 | 
			
		||||
                m.call_args.args,
 | 
			
		||||
                (
 | 
			
		||||
                    new_stream.realm,
 | 
			
		||||
                    dict(
 | 
			
		||||
                        type="stream",
 | 
			
		||||
                        op="update",
 | 
			
		||||
                        property="is_recently_active",
 | 
			
		||||
                        value=False,
 | 
			
		||||
                        stream_id=new_stream.id,
 | 
			
		||||
                        name=new_stream.name,
 | 
			
		||||
                    ),
 | 
			
		||||
                    can_access_stream_metadata_user_ids(new_stream),
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        new_stream.refresh_from_db()
 | 
			
		||||
        self.assertFalse(new_stream.is_recently_active)
 | 
			
		||||
 | 
			
		||||
        # Move the message to new stream should make active again.
 | 
			
		||||
        result = self.client_patch(
 | 
			
		||||
            f"/json/messages/{msg_id_later}",
 | 
			
		||||
            {
 | 
			
		||||
                "stream_id": new_stream.id,
 | 
			
		||||
                "propagate_mode": "change_later",
 | 
			
		||||
                "send_notification_to_new_thread": "false",
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        self.assert_json_success(result)
 | 
			
		||||
 | 
			
		||||
        new_stream.refresh_from_db()
 | 
			
		||||
        self.assertTrue(new_stream.is_recently_active)
 | 
			
		||||
 | 
			
		||||
    def test_move_message_update_private_stream_active_status(self) -> None:
 | 
			
		||||
        # Goal is to test that we only send the stream status update to subscribers.
 | 
			
		||||
        (user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
 | 
			
		||||
            "iago", "test move stream", "new stream", "test"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Mark stream as private
 | 
			
		||||
        do_change_stream_permission(
 | 
			
		||||
            new_stream,
 | 
			
		||||
            invite_only=True,
 | 
			
		||||
            history_public_to_subscribers=False,
 | 
			
		||||
            is_web_public=False,
 | 
			
		||||
            acting_user=user_profile,
 | 
			
		||||
        )
 | 
			
		||||
        # Delete all messages in new stream and mark it as inactive.
 | 
			
		||||
        Message.objects.filter(recipient__type_id=new_stream.id, realm=user_profile.realm).delete()
 | 
			
		||||
 | 
			
		||||
        with mock.patch("zerver.lib.streams.send_event_on_commit", wraps=send_event_on_commit) as m:
 | 
			
		||||
            update_stream_active_status_for_realm(
 | 
			
		||||
                user_profile.realm, timezone_now() - timedelta(days=10)
 | 
			
		||||
            )
 | 
			
		||||
            self.assertEqual(
 | 
			
		||||
                m.call_args.args,
 | 
			
		||||
                (
 | 
			
		||||
                    new_stream.realm,
 | 
			
		||||
                    dict(
 | 
			
		||||
                        type="stream",
 | 
			
		||||
                        op="update",
 | 
			
		||||
                        property="is_recently_active",
 | 
			
		||||
                        value=False,
 | 
			
		||||
                        stream_id=new_stream.id,
 | 
			
		||||
                        name=new_stream.name,
 | 
			
		||||
                    ),
 | 
			
		||||
                    # Only send the event to users with stream access.
 | 
			
		||||
                    {
 | 
			
		||||
                        9,  # Realm owner (Desdemona)
 | 
			
		||||
                        11,  # Subscriber (iago)
 | 
			
		||||
                    },
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        new_stream.refresh_from_db()
 | 
			
		||||
        self.assertFalse(new_stream.is_recently_active)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user