Remove SynapseRequest.get_user_agent (#9069)

SynapseRequest is in danger of becoming a bit of a dumping-ground for "useful stuff relating to Requests",
which isn't really its intention (its purpose is to override render, finished and connectionLost to set up the 
LoggingContext and write the right entries to the request log).

Putting utility functions inside SynapseRequest means that lots of our code ends up requiring a
SynapseRequest when there is nothing synapse-specific about the Request at all, and any old
twisted.web.iweb.IRequest will do. This increases code coupling and makes testing more difficult.

In short: move get_user_agent out to a utility function.
This commit is contained in:
Richard van der Hoff 2021-01-12 12:34:16 +00:00 committed by GitHub
parent b161528fcc
commit 2ec8ca5e60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 29 additions and 26 deletions

1
changelog.d/9069.misc Normal file
View File

@ -0,0 +1 @@
Remove `SynapseRequest.get_user_agent`.

View File

@ -33,6 +33,7 @@ from synapse.api.errors import (
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
from synapse.appservice import ApplicationService from synapse.appservice import ApplicationService
from synapse.events import EventBase from synapse.events import EventBase
from synapse.http import get_request_user_agent
from synapse.http.site import SynapseRequest from synapse.http.site import SynapseRequest
from synapse.logging import opentracing as opentracing from synapse.logging import opentracing as opentracing
from synapse.storage.databases.main.registration import TokenLookupResult from synapse.storage.databases.main.registration import TokenLookupResult
@ -187,7 +188,7 @@ class Auth:
""" """
try: try:
ip_addr = self.hs.get_ip_from_request(request) ip_addr = self.hs.get_ip_from_request(request)
user_agent = request.get_user_agent("") user_agent = get_request_user_agent(request)
access_token = self.get_access_token_from_request(request) access_token = self.get_access_token_from_request(request)

View File

@ -49,8 +49,10 @@ from synapse.api.errors import (
UserDeactivatedError, UserDeactivatedError,
) )
from synapse.api.ratelimiting import Ratelimiter from synapse.api.ratelimiting import Ratelimiter
from synapse.handlers._base import BaseHandler
from synapse.handlers.ui_auth import INTERACTIVE_AUTH_CHECKERS from synapse.handlers.ui_auth import INTERACTIVE_AUTH_CHECKERS
from synapse.handlers.ui_auth.checkers import UserInteractiveAuthChecker from synapse.handlers.ui_auth.checkers import UserInteractiveAuthChecker
from synapse.http import get_request_user_agent
from synapse.http.server import finish_request, respond_with_html from synapse.http.server import finish_request, respond_with_html
from synapse.http.site import SynapseRequest from synapse.http.site import SynapseRequest
from synapse.logging.context import defer_to_thread from synapse.logging.context import defer_to_thread
@ -62,8 +64,6 @@ from synapse.util.async_helpers import maybe_awaitable
from synapse.util.msisdn import phone_number_to_msisdn from synapse.util.msisdn import phone_number_to_msisdn
from synapse.util.threepids import canonicalise_email from synapse.util.threepids import canonicalise_email
from ._base import BaseHandler
if TYPE_CHECKING: if TYPE_CHECKING:
from synapse.app.homeserver import HomeServer from synapse.app.homeserver import HomeServer
@ -539,7 +539,7 @@ class AuthHandler(BaseHandler):
# authentication flow. # authentication flow.
await self.store.set_ui_auth_clientdict(sid, clientdict) await self.store.set_ui_auth_clientdict(sid, clientdict)
user_agent = request.get_user_agent("") user_agent = get_request_user_agent(request)
await self.store.add_user_agent_ip_to_ui_auth_session( await self.store.add_user_agent_ip_to_ui_auth_session(
session.session_id, user_agent, clientip session.session_id, user_agent, clientip

View File

@ -23,6 +23,7 @@ from typing_extensions import NoReturn, Protocol
from twisted.web.http import Request from twisted.web.http import Request
from synapse.api.errors import Codes, RedirectException, SynapseError from synapse.api.errors import Codes, RedirectException, SynapseError
from synapse.http import get_request_user_agent
from synapse.http.server import respond_with_html from synapse.http.server import respond_with_html
from synapse.http.site import SynapseRequest from synapse.http.site import SynapseRequest
from synapse.types import JsonDict, UserID, contains_invalid_mxid_characters from synapse.types import JsonDict, UserID, contains_invalid_mxid_characters
@ -362,7 +363,7 @@ class SsoHandler:
attributes, attributes,
auth_provider_id, auth_provider_id,
remote_user_id, remote_user_id,
request.get_user_agent(""), get_request_user_agent(request),
request.getClientIP(), request.getClientIP(),
) )
@ -628,7 +629,7 @@ class SsoHandler:
attributes, attributes,
session.auth_provider_id, session.auth_provider_id,
session.remote_user_id, session.remote_user_id,
request.get_user_agent(""), get_request_user_agent(request),
request.getClientIP(), request.getClientIP(),
) )

View File

@ -17,6 +17,7 @@ import re
from twisted.internet import task from twisted.internet import task
from twisted.web.client import FileBodyProducer from twisted.web.client import FileBodyProducer
from twisted.web.iweb import IRequest
from synapse.api.errors import SynapseError from synapse.api.errors import SynapseError
@ -50,3 +51,17 @@ class QuieterFileBodyProducer(FileBodyProducer):
FileBodyProducer.stopProducing(self) FileBodyProducer.stopProducing(self)
except task.TaskStopped: except task.TaskStopped:
pass pass
def get_request_user_agent(request: IRequest, default: str = "") -> str:
"""Return the last User-Agent header, or the given default.
"""
# There could be raw utf-8 bytes in the User-Agent header.
# N.B. if you don't do this, the logger explodes cryptically
# with maximum recursion trying to log errors about
# the charset problem.
# c.f. https://github.com/matrix-org/synapse/issues/3471
h = request.getHeader(b"User-Agent")
return h.decode("ascii", "replace") if h else default

View File

@ -20,7 +20,7 @@ from twisted.python.failure import Failure
from twisted.web.server import Request, Site from twisted.web.server import Request, Site
from synapse.config.server import ListenerConfig from synapse.config.server import ListenerConfig
from synapse.http import redact_uri from synapse.http import get_request_user_agent, redact_uri
from synapse.http.request_metrics import RequestMetrics, requests_counter from synapse.http.request_metrics import RequestMetrics, requests_counter
from synapse.logging.context import LoggingContext, PreserveLoggingContext from synapse.logging.context import LoggingContext, PreserveLoggingContext
from synapse.types import Requester from synapse.types import Requester
@ -113,15 +113,6 @@ class SynapseRequest(Request):
method = self.method.decode("ascii") method = self.method.decode("ascii")
return method return method
def get_user_agent(self, default: str) -> str:
"""Return the last User-Agent header, or the given default.
"""
user_agent = self.requestHeaders.getRawHeaders(b"User-Agent", [None])[-1]
if user_agent is None:
return default
return user_agent.decode("ascii", "replace")
def render(self, resrc): def render(self, resrc):
# this is called once a Resource has been found to serve the request; in our # this is called once a Resource has been found to serve the request; in our
# case the Resource in question will normally be a JsonResource. # case the Resource in question will normally be a JsonResource.
@ -292,12 +283,7 @@ class SynapseRequest(Request):
# and can see that we're doing something wrong. # and can see that we're doing something wrong.
authenticated_entity = repr(self.requester) # type: ignore[unreachable] authenticated_entity = repr(self.requester) # type: ignore[unreachable]
# ...or could be raw utf-8 bytes in the User-Agent header. user_agent = get_request_user_agent(self, "-")
# N.B. if you don't do this, the logger explodes cryptically
# with maximum recursion trying to log errors about
# the charset problem.
# c.f. https://github.com/matrix-org/synapse/issues/3471
user_agent = self.get_user_agent("-")
code = str(self.code) code = str(self.code)
if not self.finished: if not self.finished:

View File

@ -118,4 +118,4 @@ class CasHandlerTestCase(HomeserverTestCase):
def _mock_request(): def _mock_request():
"""Returns a mock which will stand in as a SynapseRequest""" """Returns a mock which will stand in as a SynapseRequest"""
return Mock(spec=["getClientIP", "get_user_agent"]) return Mock(spec=["getClientIP", "getHeader"])

View File

@ -1011,7 +1011,7 @@ def _build_callback_request(
"addCookie", "addCookie",
"requestHeaders", "requestHeaders",
"getClientIP", "getClientIP",
"get_user_agent", "getHeader",
] ]
) )
@ -1020,5 +1020,4 @@ def _build_callback_request(
request.args[b"code"] = [code.encode("utf-8")] request.args[b"code"] = [code.encode("utf-8")]
request.args[b"state"] = [state.encode("utf-8")] request.args[b"state"] = [state.encode("utf-8")]
request.getClientIP.return_value = ip_address request.getClientIP.return_value = ip_address
request.get_user_agent.return_value = user_agent
return request return request

View File

@ -262,4 +262,4 @@ class SamlHandlerTestCase(HomeserverTestCase):
def _mock_request(): def _mock_request():
"""Returns a mock which will stand in as a SynapseRequest""" """Returns a mock which will stand in as a SynapseRequest"""
return Mock(spec=["getClientIP", "get_user_agent"]) return Mock(spec=["getClientIP", "getHeader"])