mirror of https://github.com/zulip/zulip.git
middleware: Add `client` REQ parameter to `parse_client`.
If an API request specified a `client` parameter, we were already prioritizing that value over parsing the UserAgent. In order to have these parameters logged in the `RequestNotes` as processed parameters instead of ignored parameters, we add the `has_request_variables` decorator to `parse_client` and then process the potential `client` parameter through the REQ framework. Co-authored by: Tim Abbott <tabbott@zulip.com>
This commit is contained in:
parent
e6e4b7b3ef
commit
8154b4a9af
|
@ -40,7 +40,7 @@ from zerver.lib.exceptions import ErrorCode, JsonableError, MissingAuthenticatio
|
||||||
from zerver.lib.html_to_text import get_content_description
|
from zerver.lib.html_to_text import get_content_description
|
||||||
from zerver.lib.markdown import get_markdown_requests, get_markdown_time
|
from zerver.lib.markdown import get_markdown_requests, get_markdown_time
|
||||||
from zerver.lib.rate_limiter import RateLimitResult
|
from zerver.lib.rate_limiter import RateLimitResult
|
||||||
from zerver.lib.request import RequestNotes, set_request, unset_request
|
from zerver.lib.request import REQ, RequestNotes, has_request_variables, set_request, unset_request
|
||||||
from zerver.lib.response import json_response, json_response_from_error, json_unauthorized
|
from zerver.lib.response import json_response, json_response_from_error, json_unauthorized
|
||||||
from zerver.lib.subdomains import get_subdomain
|
from zerver.lib.subdomains import get_subdomain
|
||||||
from zerver.lib.types import ViewFuncT
|
from zerver.lib.types import ViewFuncT
|
||||||
|
@ -306,14 +306,21 @@ class RequestContext(MiddlewareMixin):
|
||||||
unset_request()
|
unset_request()
|
||||||
|
|
||||||
|
|
||||||
def parse_client(request: HttpRequest) -> Tuple[str, Optional[str]]:
|
# We take advantage of `has_request_variables` being called multiple times
|
||||||
|
# when processing a request in order to process any `client` parameter that
|
||||||
|
# may have been sent in the request content.
|
||||||
|
@has_request_variables
|
||||||
|
def parse_client(
|
||||||
|
request: HttpRequest,
|
||||||
|
# As `client` is a common element to all API endpoints, we choose
|
||||||
|
# not to document on every endpoint's individual parameters.
|
||||||
|
req_client: Optional[str] = REQ("client", default=None, intentionally_undocumented=True),
|
||||||
|
) -> Tuple[str, Optional[str]]:
|
||||||
# If the API request specified a client in the request content,
|
# If the API request specified a client in the request content,
|
||||||
# that has priority. Otherwise, extract the client from the
|
# that has priority. Otherwise, extract the client from the
|
||||||
# User-Agent.
|
# USER_AGENT.
|
||||||
if "client" in request.GET: # nocoverage
|
if req_client is not None:
|
||||||
return request.GET["client"], None
|
return req_client, None
|
||||||
if "client" in request.POST:
|
|
||||||
return request.POST["client"], None
|
|
||||||
if "HTTP_USER_AGENT" in request.META:
|
if "HTTP_USER_AGENT" in request.META:
|
||||||
user_agent: Optional[Dict[str, str]] = parse_user_agent(request.META["HTTP_USER_AGENT"])
|
user_agent: Optional[Dict[str, str]] = parse_user_agent(request.META["HTTP_USER_AGENT"])
|
||||||
else:
|
else:
|
||||||
|
@ -353,7 +360,13 @@ class LogRequests(MiddlewareMixin):
|
||||||
# Avoid re-initializing request_notes.log_data if it's already there.
|
# Avoid re-initializing request_notes.log_data if it's already there.
|
||||||
return
|
return
|
||||||
|
|
||||||
request_notes.client_name, request_notes.client_version = parse_client(request)
|
try:
|
||||||
|
request_notes.client_name, request_notes.client_version = parse_client(request)
|
||||||
|
except JsonableError as e:
|
||||||
|
logging.exception(e)
|
||||||
|
request_notes.client_name = "Unparsable"
|
||||||
|
request_notes.client_version = None
|
||||||
|
|
||||||
request_notes.log_data = {}
|
request_notes.log_data = {}
|
||||||
record_request_start_data(request_notes.log_data)
|
record_request_start_data(request_notes.log_data)
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ from zerver.lib.validator import (
|
||||||
to_non_negative_int,
|
to_non_negative_int,
|
||||||
to_wild_value,
|
to_wild_value,
|
||||||
)
|
)
|
||||||
from zerver.middleware import parse_client
|
from zerver.middleware import LogRequests, parse_client
|
||||||
from zerver.models import Realm, UserProfile, get_realm, get_user
|
from zerver.models import Realm, UserProfile, get_realm, get_user
|
||||||
|
|
||||||
if settings.ZILENCER_ENABLED:
|
if settings.ZILENCER_ENABLED:
|
||||||
|
@ -129,6 +129,28 @@ class DecoratorTestCase(ZulipTestCase):
|
||||||
] = "Mozilla/5.0 (Linux; Android 8.0.0; SM-G930F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36"
|
] = "Mozilla/5.0 (Linux; Android 8.0.0; SM-G930F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36"
|
||||||
self.assertEqual(parse_client(req), ("Mozilla", None))
|
self.assertEqual(parse_client(req), ("Mozilla", None))
|
||||||
|
|
||||||
|
post_req_with_client = HostRequestMock()
|
||||||
|
post_req_with_client.POST["client"] = "test_client_1"
|
||||||
|
post_req_with_client.META["HTTP_USER_AGENT"] = "ZulipMobile/26.22.145 (iOS 13.3.1)"
|
||||||
|
self.assertEqual(parse_client(post_req_with_client), ("test_client_1", None))
|
||||||
|
|
||||||
|
get_req_with_client = HostRequestMock()
|
||||||
|
get_req_with_client.GET["client"] = "test_client_2"
|
||||||
|
get_req_with_client.META["HTTP_USER_AGENT"] = "ZulipMobile/26.22.145 (iOS 13.3.1)"
|
||||||
|
self.assertEqual(parse_client(get_req_with_client), ("test_client_2", None))
|
||||||
|
|
||||||
|
def test_unparsable_user_agent(self) -> None:
|
||||||
|
request = HttpRequest()
|
||||||
|
request.POST["param"] = "test"
|
||||||
|
request.META["HTTP_USER_AGENT"] = "mocked should fail"
|
||||||
|
with mock.patch(
|
||||||
|
"zerver.middleware.parse_client", side_effect=JsonableError("message")
|
||||||
|
) as m, self.assertLogs(level="ERROR"):
|
||||||
|
LogRequests.process_request(self, request)
|
||||||
|
request_notes = RequestNotes.get_notes(request)
|
||||||
|
self.assertEqual(request_notes.client_name, "Unparsable")
|
||||||
|
m.assert_called_once()
|
||||||
|
|
||||||
def test_REQ_aliases(self) -> None:
|
def test_REQ_aliases(self) -> None:
|
||||||
@has_request_variables
|
@has_request_variables
|
||||||
def double(
|
def double(
|
||||||
|
|
Loading…
Reference in New Issue