events: Add support for spectator access to /register.

This is necessary for the mobile/terminal clients to build spectator
support down the line. We'll also be using it for the web application,
in an upcoming commit.
This commit is contained in:
Tim Abbott 2022-05-03 14:48:52 -07:00
parent 53518e8a24
commit 2e86ea6540
5 changed files with 73 additions and 10 deletions

View File

@ -1316,7 +1316,7 @@ def apply_event(
def do_events_register( def do_events_register(
user_profile: UserProfile, user_profile: Optional[UserProfile],
realm: Realm, realm: Realm,
user_client: Client, user_client: Client,
apply_markdown: bool = True, apply_markdown: bool = True,
@ -1356,6 +1356,33 @@ def do_events_register(
else: else:
event_types_set = None 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 # Fill up the UserMessage rows if a soft-deactivated user has returned
reactivate_user_if_soft_deactivated(user_profile) reactivate_user_if_soft_deactivated(user_profile)

View File

@ -9207,8 +9207,12 @@ paths:
msg: {} msg: {}
queue_id: queue_id:
type: string type: string
nullable: true
description: | description: |
The ID of the queue that has been allocated for your client. 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: last_event_id:
type: integer type: integer
description: | description: |

View File

@ -143,6 +143,23 @@ class EventsEndpointTest(ZulipTestCase):
self.assertEqual(result_dict["realm_emoji"], []) self.assertEqual(result_dict["realm_emoji"], [])
self.assertEqual(result_dict["queue_id"], "15:13") 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: def test_events_register_endpoint_all_public_streams_access(self) -> None:
guest_user = self.example_user("polonius") guest_user = self.example_user("polonius")
normal_user = self.example_user("hamlet") normal_user = self.example_user("hamlet")

View File

@ -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.http import HttpRequest, HttpResponse
from django.utils.translation import gettext as _ 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.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.request import REQ, RequestNotes, has_request_variables
from zerver.lib.response import json_success from zerver.lib.response import json_success
from zerver.lib.validator import check_bool, check_dict, check_int, check_list, check_string 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 @has_request_variables
def events_register_backend( def events_register_backend(
request: HttpRequest, request: HttpRequest,
user_profile: UserProfile, maybe_user_profile: Union[UserProfile, AnonymousUser],
apply_markdown: bool = REQ(default=False, json_validator=check_bool), apply_markdown: bool = REQ(default=False, json_validator=check_bool),
client_gravatar: bool = REQ(default=True, json_validator=check_bool), client_gravatar: bool = REQ(default=True, json_validator=check_bool),
slim_presence: bool = REQ(default=False, 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), queue_lifespan_secs: int = REQ(json_validator=check_int, default=0, documentation_pending=True),
) -> HttpResponse: ) -> HttpResponse:
if all_public_streams and not user_profile.can_access_public_streams(): if maybe_user_profile.is_authenticated:
raise JsonableError(_("User not authorized for this query")) 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) if all_public_streams and not user_profile.can_access_public_streams():
narrow = _default_narrow(user_profile, narrow) 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: if client_capabilities is None:
client_capabilities = {} client_capabilities = {}
@ -85,7 +100,7 @@ def events_register_backend(
ret = do_events_register( ret = do_events_register(
user_profile, user_profile,
user_profile.realm, realm,
client, client,
apply_markdown, apply_markdown,
client_gravatar, client_gravatar,

View File

@ -469,7 +469,7 @@ v1_api_and_json_patterns = [
rest_path("users/me/subscriptions/muted_topics", PATCH=update_muted_topic), rest_path("users/me/subscriptions/muted_topics", PATCH=update_muted_topic),
rest_path("users/me/muted_users/<int:muted_user_id>", POST=mute_user, DELETE=unmute_user), rest_path("users/me/muted_users/<int:muted_user_id>", POST=mute_user, DELETE=unmute_user),
# used to register for an event queue in tornado # 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 # events -> zerver.tornado.views
rest_path("events", GET=get_events, DELETE=cleanup_event_queue), rest_path("events", GET=get_events, DELETE=cleanup_event_queue),
# report -> zerver.views.report # report -> zerver.views.report