user_topics: Validate 'topic' parameter length <= max_topic_length.

Earlier, 'topic' parameter length for
'/users/me/subscriptions/muted_topics' and '/user_topics' endpoints
were not validated before DB operations which resulted in exception:
'DataError: value too long for type character varying(60)'.

This commit adds validation for the topic name length to be
capped at 'max_topic_length' characters.

The doc is updated to suggest clients that the topic name should
have a maximum length of 'max_topic_length'.

Fixes #27796.

(cherry picked from commit c4330be2b1)
This commit is contained in:
Prakhar Pratyush 2024-01-03 16:21:13 +05:30 committed by Alex Vandiver
parent 8332486848
commit 3f875be21b
3 changed files with 47 additions and 3 deletions

View File

@ -9498,6 +9498,10 @@ paths:
description: |
The topic to (un)mute. Note that the request will succeed regardless of
whether any messages have been sent to the specified topic.
Clients should use the `max_topic_length` returned by the
[`POST /register`](/api/register-queue) endpoint to determine
the maximum topic length.
schema:
type: string
example: dinner
@ -9594,6 +9598,10 @@ paths:
The topic for which the personal preferences needs to be updated.
Note that the request will succeed regardless of whether
any messages have been sent to the specified topic.
Clients should use the `max_topic_length` returned by the
[`POST /register`](/api/register-queue) endpoint to determine
the maximum topic length.
schema:
type: string
example: dinner

View File

@ -13,6 +13,7 @@ from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import get_subscription
from zerver.lib.user_topics import get_topic_mutes, topic_has_visibility_policy
from zerver.models import UserProfile, UserTopic
from zerver.models.constants import MAX_TOPIC_NAME_LENGTH
from zerver.models.streams import get_stream
@ -199,6 +200,12 @@ class MutedTopicsTestsDeprecated(ZulipTestCase):
result = self.api_patch(user, url, data)
self.assert_json_error(result, "Please choose one: 'stream' or 'stream_id'.")
data = {"stream_id": stream.id, "topic": "a" * (MAX_TOPIC_NAME_LENGTH + 1), "op": "add"}
result = self.api_patch(user, url, data)
self.assert_json_error(
result, f"topic is too long (limit: {MAX_TOPIC_NAME_LENGTH} characters)"
)
def test_muted_topic_remove_invalid(self) -> None:
user = self.example_user("hamlet")
realm = user.realm
@ -233,6 +240,12 @@ class MutedTopicsTestsDeprecated(ZulipTestCase):
result = self.api_patch(user, url, data)
self.assert_json_error(result, "Please choose one: 'stream' or 'stream_id'.")
data = {"stream_id": stream.id, "topic": "a" * (MAX_TOPIC_NAME_LENGTH + 1), "op": "remove"}
result = self.api_patch(user, url, data)
self.assert_json_error(
result, f"topic is too long (limit: {MAX_TOPIC_NAME_LENGTH} characters)"
)
class MutedTopicsTests(ZulipTestCase):
def test_get_deactivated_muted_topic(self) -> None:
@ -436,6 +449,17 @@ class MutedTopicsTests(ZulipTestCase):
result = self.api_post(user, url, data)
self.assert_json_error(result, "Invalid stream ID")
stream = get_stream("Verona", user.realm)
data = {
"stream_id": stream.id,
"topic": "a" * (MAX_TOPIC_NAME_LENGTH + 1),
"visibility_policy": UserTopic.VisibilityPolicy.MUTED,
}
result = self.api_post(user, url, data)
self.assert_json_error(
result, f"topic is too long (limit: {MAX_TOPIC_NAME_LENGTH} characters)"
)
def test_muted_topic_remove_invalid(self) -> None:
user = self.example_user("hamlet")
self.login_user(user)
@ -450,6 +474,17 @@ class MutedTopicsTests(ZulipTestCase):
result = self.api_post(user, url, data)
self.assert_json_error(result, "Invalid stream ID")
stream = get_stream("Verona", user.realm)
data = {
"stream_id": stream.id,
"topic": "a" * (MAX_TOPIC_NAME_LENGTH + 1),
"visibility_policy": UserTopic.VisibilityPolicy.INHERIT,
}
result = self.api_post(user, url, data)
self.assert_json_error(
result, f"topic is too long (limit: {MAX_TOPIC_NAME_LENGTH} characters)"
)
class UnmutedTopicsTests(ZulipTestCase):
def test_user_ids_unmuting_topic(self) -> None:

View File

@ -15,8 +15,9 @@ from zerver.lib.streams import (
access_stream_to_remove_visibility_policy_by_name,
check_for_exactly_one_stream_arg,
)
from zerver.lib.validator import check_int, check_int_in, check_string_in
from zerver.lib.validator import check_capped_string, check_int, check_int_in, check_string_in
from zerver.models import UserProfile, UserTopic
from zerver.models.constants import MAX_TOPIC_NAME_LENGTH
def mute_topic(
@ -66,7 +67,7 @@ def update_muted_topic(
user_profile: UserProfile,
stream_id: Optional[int] = REQ(json_validator=check_int, default=None),
stream: Optional[str] = REQ(default=None),
topic: str = REQ(),
topic: str = REQ(str_validator=check_capped_string(MAX_TOPIC_NAME_LENGTH)),
op: str = REQ(str_validator=check_string_in(["add", "remove"])),
) -> HttpResponse:
check_for_exactly_one_stream_arg(stream_id=stream_id, stream=stream)
@ -94,7 +95,7 @@ def update_user_topic(
request: HttpRequest,
user_profile: UserProfile,
stream_id: int = REQ(json_validator=check_int),
topic: str = REQ(),
topic: str = REQ(str_validator=check_capped_string(MAX_TOPIC_NAME_LENGTH)),
visibility_policy: int = REQ(json_validator=check_int_in(UserTopic.VisibilityPolicy.values)),
) -> HttpResponse:
if visibility_policy == UserTopic.VisibilityPolicy.INHERIT: