mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 14:03:30 +00:00 
			
		
		
		
	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:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						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.markdown import get_markdown_requests, get_markdown_time
 | 
			
		||||
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.subdomains import get_subdomain
 | 
			
		||||
from zerver.lib.types import ViewFuncT
 | 
			
		||||
@@ -306,14 +306,21 @@ class RequestContext(MiddlewareMixin):
 | 
			
		||||
            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,
 | 
			
		||||
    # that has priority. Otherwise, extract the client from the
 | 
			
		||||
    # User-Agent.
 | 
			
		||||
    if "client" in request.GET:  # nocoverage
 | 
			
		||||
        return request.GET["client"], None
 | 
			
		||||
    if "client" in request.POST:
 | 
			
		||||
        return request.POST["client"], None
 | 
			
		||||
    # USER_AGENT.
 | 
			
		||||
    if req_client is not None:
 | 
			
		||||
        return req_client, None
 | 
			
		||||
    if "HTTP_USER_AGENT" in request.META:
 | 
			
		||||
        user_agent: Optional[Dict[str, str]] = parse_user_agent(request.META["HTTP_USER_AGENT"])
 | 
			
		||||
    else:
 | 
			
		||||
@@ -353,7 +360,13 @@ class LogRequests(MiddlewareMixin):
 | 
			
		||||
            # Avoid re-initializing request_notes.log_data if it's already there.
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        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 = {}
 | 
			
		||||
        record_request_start_data(request_notes.log_data)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -85,7 +85,7 @@ from zerver.lib.validator import (
 | 
			
		||||
    to_non_negative_int,
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
        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:
 | 
			
		||||
        @has_request_variables
 | 
			
		||||
        def double(
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user