diff --git a/zerver/lib/events.py b/zerver/lib/events.py index 5658ba5dec..01b3ddcfe5 100644 --- a/zerver/lib/events.py +++ b/zerver/lib/events.py @@ -1316,7 +1316,7 @@ def apply_event( def do_events_register( - user_profile: UserProfile, + user_profile: Optional[UserProfile], realm: Realm, user_client: Client, apply_markdown: bool = True, @@ -1356,6 +1356,33 @@ def do_events_register( else: event_types_set = None + if user_profile is None: + # TODO: Unify this with the below code path once if/when we + # support requesting an event queue for spectators. + # + # Doing so likely has a prerequisite of making this function's + # caller enforce client_gravatar=False, + # include_subscribers=False and include_streams=False. + ret = fetch_initial_state_data( + user_profile, + realm=realm, + event_types=event_types_set, + queue_id=None, + # Force client_gravatar=False for security reasons. + client_gravatar=False, + user_avatar_url_field_optional=user_avatar_url_field_optional, + user_settings_object=user_settings_object, + # slim_presence is a noop, because presence is not included. + slim_presence=True, + # Force include_subscribers=False for security reasons. + include_subscribers=False, + # Force include_streams=False for security reasons. + include_streams=False, + ) + + post_process_state(user_profile, ret, notification_settings_null=False) + return ret + # Fill up the UserMessage rows if a soft-deactivated user has returned reactivate_user_if_soft_deactivated(user_profile) diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 9ab52df11f..3a2ab0c18e 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -9207,8 +9207,12 @@ paths: msg: {} queue_id: type: string + nullable: true description: | The ID of the queue that has been allocated for your client. + + Will be None only for unauthenticated access in realms that have + enabled the [public access option](/help/public-access-option). last_event_id: type: integer description: | diff --git a/zerver/tests/test_event_system.py b/zerver/tests/test_event_system.py index fc0bc5e00d..ebf8c412c9 100644 --- a/zerver/tests/test_event_system.py +++ b/zerver/tests/test_event_system.py @@ -143,6 +143,23 @@ class EventsEndpointTest(ZulipTestCase): self.assertEqual(result_dict["realm_emoji"], []) self.assertEqual(result_dict["queue_id"], "15:13") + def test_events_register_spectators(self) -> None: + # Verify that POST /register works for spectators, but not for + # normal users. + with self.settings(WEB_PUBLIC_STREAMS_ENABLED=False): + result = self.client_post("/json/register", dict()) + self.assert_json_error( + result, + "Not logged in: API authentication or user session required", + status_code=401, + ) + + result = self.client_post("/json/register", dict()) + self.assert_json_success(result) + result_dict = result.json() + self.assertEqual(result_dict["queue_id"], None) + self.assertEqual(result_dict["realm_uri"], "http://zulip.testserver") + def test_events_register_endpoint_all_public_streams_access(self) -> None: guest_user = self.example_user("polonius") normal_user = self.example_user("hamlet") diff --git a/zerver/views/events_register.py b/zerver/views/events_register.py index a7b4ea3290..a9155af31d 100644 --- a/zerver/views/events_register.py +++ b/zerver/views/events_register.py @@ -1,10 +1,12 @@ -from typing import Dict, Optional, Sequence +from typing import Dict, Optional, Sequence, Union +from django.contrib.auth.models import AnonymousUser from django.http import HttpRequest, HttpResponse from django.utils.translation import gettext as _ +from zerver.context_processors import get_valid_realm_from_request from zerver.lib.events import do_events_register -from zerver.lib.exceptions import JsonableError +from zerver.lib.exceptions import JsonableError, MissingAuthenticationError from zerver.lib.request import REQ, RequestNotes, has_request_variables from zerver.lib.response import json_success from zerver.lib.validator import check_bool, check_dict, check_int, check_list, check_string @@ -35,7 +37,7 @@ NarrowT = Sequence[Sequence[str]] @has_request_variables def events_register_backend( request: HttpRequest, - user_profile: UserProfile, + maybe_user_profile: Union[UserProfile, AnonymousUser], apply_markdown: bool = REQ(default=False, json_validator=check_bool), client_gravatar: bool = REQ(default=True, json_validator=check_bool), slim_presence: bool = REQ(default=False, json_validator=check_bool), @@ -71,11 +73,24 @@ def events_register_backend( ), queue_lifespan_secs: int = REQ(json_validator=check_int, default=0, documentation_pending=True), ) -> HttpResponse: - if all_public_streams and not user_profile.can_access_public_streams(): - raise JsonableError(_("User not authorized for this query")) + if maybe_user_profile.is_authenticated: + user_profile = maybe_user_profile + assert isinstance(user_profile, UserProfile) + realm = user_profile.realm - all_public_streams = _default_all_public_streams(user_profile, all_public_streams) - narrow = _default_narrow(user_profile, narrow) + if all_public_streams and not user_profile.can_access_public_streams(): + raise JsonableError(_("User not authorized for this query")) + + all_public_streams = _default_all_public_streams(user_profile, all_public_streams) + narrow = _default_narrow(user_profile, narrow) + else: + user_profile = None + realm = get_valid_realm_from_request(request) + + if not realm.allow_web_public_streams_access(): + raise MissingAuthenticationError() + + all_public_streams = False if client_capabilities is None: client_capabilities = {} @@ -85,7 +100,7 @@ def events_register_backend( ret = do_events_register( user_profile, - user_profile.realm, + realm, client, apply_markdown, client_gravatar, diff --git a/zproject/urls.py b/zproject/urls.py index a790f1a963..3677801921 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -469,7 +469,7 @@ v1_api_and_json_patterns = [ rest_path("users/me/subscriptions/muted_topics", PATCH=update_muted_topic), rest_path("users/me/muted_users/", POST=mute_user, DELETE=unmute_user), # used to register for an event queue in tornado - rest_path("register", POST=events_register_backend), + rest_path("register", POST=(events_register_backend, {"allow_anonymous_user_web"})), # events -> zerver.tornado.views rest_path("events", GET=get_events, DELETE=cleanup_event_queue), # report -> zerver.views.report