mirror of https://github.com/zulip/zulip.git
message_edit: Allow spectators to access raw message content.
We allow spectators to fetch the raw / original content of a message which is used by the spectator to "View source" of the message.
This commit is contained in:
parent
e556481ba0
commit
ef84224eed
|
@ -24,7 +24,7 @@ from zerver.lib.cache import (
|
|||
to_dict_cache_key_id,
|
||||
)
|
||||
from zerver.lib.display_recipient import bulk_fetch_display_recipients
|
||||
from zerver.lib.exceptions import JsonableError
|
||||
from zerver.lib.exceptions import JsonableError, MissingAuthenticationError
|
||||
from zerver.lib.markdown import MessageRenderingResult, markdown_convert, topic_links
|
||||
from zerver.lib.markdown import version as markdown_version
|
||||
from zerver.lib.mention import MentionData
|
||||
|
@ -33,6 +33,7 @@ from zerver.lib.stream_subscription import (
|
|||
get_subscribed_stream_recipient_ids_for_user,
|
||||
num_subscribers_for_stream_id,
|
||||
)
|
||||
from zerver.lib.streams import get_web_public_streams_queryset
|
||||
from zerver.lib.timestamp import datetime_to_timestamp
|
||||
from zerver.lib.topic import DB_TOPIC_NAME, MESSAGE__TOPIC, TOPIC_LINKS, TOPIC_NAME
|
||||
from zerver.lib.topic_mutes import build_topic_mute_checker, topic_is_muted
|
||||
|
@ -709,6 +710,48 @@ def access_message(
|
|||
raise JsonableError(_("Invalid message(s)"))
|
||||
|
||||
|
||||
def access_web_public_message(
|
||||
realm: Realm,
|
||||
message_id: int,
|
||||
) -> Message:
|
||||
"""Access control method for unauthenticated requests interacting
|
||||
with a message in web public streams.
|
||||
"""
|
||||
|
||||
# We throw a MissingAuthenticationError for all errors in this
|
||||
# code path, to avoid potentially leaking information on whether a
|
||||
# message with the provided ID exists on the server if the client
|
||||
# shouldn't have access to it.
|
||||
if not realm.web_public_streams_enabled():
|
||||
raise MissingAuthenticationError()
|
||||
|
||||
try:
|
||||
message = Message.objects.select_related().get(id=message_id)
|
||||
except Message.DoesNotExist:
|
||||
raise MissingAuthenticationError()
|
||||
|
||||
if not message.is_stream_message():
|
||||
raise MissingAuthenticationError()
|
||||
|
||||
queryset = get_web_public_streams_queryset(realm)
|
||||
try:
|
||||
stream = queryset.get(id=message.recipient.type_id)
|
||||
except Stream.DoesNotExist:
|
||||
raise MissingAuthenticationError()
|
||||
|
||||
# These should all have been enforced by the code in
|
||||
# get_web_public_streams_queryset
|
||||
assert stream.is_web_public
|
||||
assert not stream.deactivated
|
||||
assert not stream.invite_only
|
||||
assert stream.history_public_to_subscribers
|
||||
|
||||
# Now that we've confirmed this message was sent to the target
|
||||
# web-public stream, we can return it as having been successfully
|
||||
# accessed.
|
||||
return message
|
||||
|
||||
|
||||
def has_message_access(
|
||||
user_profile: UserProfile,
|
||||
message: Message,
|
||||
|
|
|
@ -9,8 +9,10 @@ from django.http import HttpResponse
|
|||
from django.utils.timezone import now as timezone_now
|
||||
|
||||
from zerver.lib.actions import (
|
||||
do_change_plan_type,
|
||||
do_change_stream_post_policy,
|
||||
do_change_user_role,
|
||||
do_deactivate_stream,
|
||||
do_delete_messages,
|
||||
do_set_realm_property,
|
||||
do_update_message,
|
||||
|
@ -311,6 +313,78 @@ class EditMessageTest(EditMessageTestCase):
|
|||
result = self.client_get("/json/messages/" + str(msg_id))
|
||||
self.assert_json_error(result, "Invalid message(s)")
|
||||
|
||||
def test_fetch_raw_message_spectator(self) -> None:
|
||||
user_profile = self.example_user("iago")
|
||||
self.login("iago")
|
||||
web_public_stream = self.make_stream("web-public-stream", is_web_public=True)
|
||||
self.subscribe(user_profile, web_public_stream.name)
|
||||
|
||||
web_public_stream_msg_id = self.send_stream_message(
|
||||
user_profile, web_public_stream.name, content="web-public message"
|
||||
)
|
||||
|
||||
non_web_public_stream = self.make_stream("non-web-public-stream")
|
||||
non_web_public_stream_msg_id = self.send_stream_message(
|
||||
user_profile, non_web_public_stream.name, content="non web-public message"
|
||||
)
|
||||
|
||||
# Generate a private message to use in verification.
|
||||
private_message_id = self.send_personal_message(user_profile, user_profile)
|
||||
|
||||
invalid_message_id = private_message_id + 1000
|
||||
|
||||
self.logout()
|
||||
|
||||
# Confirm WEB_PUBLIC_STREAMS_ENABLED is enforced.
|
||||
with self.settings(WEB_PUBLIC_STREAMS_ENABLED=False):
|
||||
result = self.client_get("/json/messages/" + str(web_public_stream_msg_id))
|
||||
self.assert_json_error(
|
||||
result, "Not logged in: API authentication or user session required", 401
|
||||
)
|
||||
|
||||
# Verify success with web-public stream and default SELF_HOSTED plan type.
|
||||
result = self.client_get("/json/messages/" + str(web_public_stream_msg_id))
|
||||
self.assert_json_success(result)
|
||||
self.assertEqual(result.json()["raw_content"], "web-public message")
|
||||
|
||||
# Verify LIMITED plan type does not allow web-public access.
|
||||
do_change_plan_type(user_profile.realm, Realm.LIMITED, acting_user=None)
|
||||
result = self.client_get("/json/messages/" + str(web_public_stream_msg_id))
|
||||
self.assert_json_error(
|
||||
result, "Not logged in: API authentication or user session required", 401
|
||||
)
|
||||
|
||||
# Verify works with STANDARD_FREE plan type too.
|
||||
do_change_plan_type(user_profile.realm, Realm.STANDARD_FREE, acting_user=None)
|
||||
result = self.client_get("/json/messages/" + str(web_public_stream_msg_id))
|
||||
self.assert_json_success(result)
|
||||
self.assertEqual(result.json()["raw_content"], "web-public message")
|
||||
|
||||
# Verify private messages are rejected.
|
||||
result = self.client_get("/json/messages/" + str(private_message_id))
|
||||
self.assert_json_error(
|
||||
result, "Not logged in: API authentication or user session required", 401
|
||||
)
|
||||
|
||||
# Verify an actual public stream is required.
|
||||
result = self.client_get("/json/messages/" + str(non_web_public_stream_msg_id))
|
||||
self.assert_json_error(
|
||||
result, "Not logged in: API authentication or user session required", 401
|
||||
)
|
||||
|
||||
# Verify invalid message IDs are rejected with the same error message.
|
||||
result = self.client_get("/json/messages/" + str(invalid_message_id))
|
||||
self.assert_json_error(
|
||||
result, "Not logged in: API authentication or user session required", 401
|
||||
)
|
||||
|
||||
# Verify deactivated streams are rejected. This may change in the future.
|
||||
do_deactivate_stream(web_public_stream, acting_user=None)
|
||||
result = self.client_get("/json/messages/" + str(web_public_stream_msg_id))
|
||||
self.assert_json_error(
|
||||
result, "Not logged in: API authentication or user session required", 401
|
||||
)
|
||||
|
||||
def test_fetch_raw_message_stream_wrong_realm(self) -> None:
|
||||
user_profile = self.example_user("hamlet")
|
||||
self.login_user(user_profile)
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
import orjson
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.utils.timezone import now as timezone_now
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from zerver.context_processors import get_valid_realm_from_request
|
||||
from zerver.lib.actions import check_update_message, do_delete_messages
|
||||
from zerver.lib.exceptions import JsonableError
|
||||
from zerver.lib.html_diff import highlight_html_differences
|
||||
from zerver.lib.message import access_message
|
||||
from zerver.lib.message import access_message, access_web_public_message
|
||||
from zerver.lib.request import REQ, RequestNotes, has_request_variables
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.timestamp import datetime_to_timestamp
|
||||
|
@ -168,8 +170,14 @@ def delete_message_backend(
|
|||
@has_request_variables
|
||||
def json_fetch_raw_message(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
maybe_user_profile: Union[UserProfile, AnonymousUser],
|
||||
message_id: int = REQ(converter=to_non_negative_int, path_only=True),
|
||||
) -> HttpResponse:
|
||||
(message, user_message) = access_message(user_profile, message_id)
|
||||
|
||||
if not maybe_user_profile.is_authenticated:
|
||||
realm = get_valid_realm_from_request(request)
|
||||
message = access_web_public_message(realm, message_id)
|
||||
else:
|
||||
(message, user_message) = access_message(maybe_user_profile, message_id)
|
||||
|
||||
return json_success({"raw_content": message.content})
|
||||
|
|
|
@ -324,7 +324,7 @@ v1_api_and_json_patterns = [
|
|||
),
|
||||
rest_path(
|
||||
"messages/<int:message_id>",
|
||||
GET=json_fetch_raw_message,
|
||||
GET=(json_fetch_raw_message, {"allow_anonymous_user_web"}),
|
||||
PATCH=update_message_backend,
|
||||
DELETE=delete_message_backend,
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue