|
|
|
@ -14,7 +14,7 @@ |
|
|
|
|
import contextlib |
|
|
|
|
import logging |
|
|
|
|
import time |
|
|
|
|
from typing import Optional, Type, Union |
|
|
|
|
from typing import Optional, Tuple, Type, Union |
|
|
|
|
|
|
|
|
|
import attr |
|
|
|
|
from zope.interface import implementer |
|
|
|
@ -26,7 +26,11 @@ from twisted.web.server import Request, Site |
|
|
|
|
from synapse.config.server import ListenerConfig |
|
|
|
|
from synapse.http import get_request_user_agent, redact_uri |
|
|
|
|
from synapse.http.request_metrics import RequestMetrics, requests_counter |
|
|
|
|
from synapse.logging.context import LoggingContext, PreserveLoggingContext |
|
|
|
|
from synapse.logging.context import ( |
|
|
|
|
ContextRequest, |
|
|
|
|
LoggingContext, |
|
|
|
|
PreserveLoggingContext, |
|
|
|
|
) |
|
|
|
|
from synapse.types import Requester |
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
@ -63,7 +67,7 @@ class SynapseRequest(Request): |
|
|
|
|
|
|
|
|
|
# The requester, if authenticated. For federation requests this is the |
|
|
|
|
# server name, for client requests this is the Requester object. |
|
|
|
|
self.requester = None # type: Optional[Union[Requester, str]] |
|
|
|
|
self._requester = None # type: Optional[Union[Requester, str]] |
|
|
|
|
|
|
|
|
|
# we can't yet create the logcontext, as we don't know the method. |
|
|
|
|
self.logcontext = None # type: Optional[LoggingContext] |
|
|
|
@ -93,6 +97,31 @@ class SynapseRequest(Request): |
|
|
|
|
self.site.site_tag, |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def requester(self) -> Optional[Union[Requester, str]]: |
|
|
|
|
return self._requester |
|
|
|
|
|
|
|
|
|
@requester.setter |
|
|
|
|
def requester(self, value: Union[Requester, str]) -> None: |
|
|
|
|
# Store the requester, and update some properties based on it. |
|
|
|
|
|
|
|
|
|
# This should only be called once. |
|
|
|
|
assert self._requester is None |
|
|
|
|
|
|
|
|
|
self._requester = value |
|
|
|
|
|
|
|
|
|
# A logging context should exist by now (and have a ContextRequest). |
|
|
|
|
assert self.logcontext is not None |
|
|
|
|
assert self.logcontext.request is not None |
|
|
|
|
|
|
|
|
|
( |
|
|
|
|
requester, |
|
|
|
|
authenticated_entity, |
|
|
|
|
) = self.get_authenticated_entity() |
|
|
|
|
self.logcontext.request.requester = requester |
|
|
|
|
# If there's no authenticated entity, it was the requester. |
|
|
|
|
self.logcontext.request.authenticated_entity = authenticated_entity or requester |
|
|
|
|
|
|
|
|
|
def get_request_id(self): |
|
|
|
|
return "%s-%i" % (self.get_method(), self.request_seq) |
|
|
|
|
|
|
|
|
@ -126,13 +155,60 @@ class SynapseRequest(Request): |
|
|
|
|
return self.method.decode("ascii") |
|
|
|
|
return method |
|
|
|
|
|
|
|
|
|
def get_authenticated_entity(self) -> Tuple[Optional[str], Optional[str]]: |
|
|
|
|
""" |
|
|
|
|
Get the "authenticated" entity of the request, which might be the user |
|
|
|
|
performing the action, or a user being puppeted by a server admin. |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
A tuple: |
|
|
|
|
The first item is a string representing the user making the request. |
|
|
|
|
|
|
|
|
|
The second item is a string or None representing the user who |
|
|
|
|
authenticated when making this request. See |
|
|
|
|
Requester.authenticated_entity. |
|
|
|
|
""" |
|
|
|
|
# Convert the requester into a string that we can log |
|
|
|
|
if isinstance(self._requester, str): |
|
|
|
|
return self._requester, None |
|
|
|
|
elif isinstance(self._requester, Requester): |
|
|
|
|
requester = self._requester.user.to_string() |
|
|
|
|
authenticated_entity = self._requester.authenticated_entity |
|
|
|
|
|
|
|
|
|
# If this is a request where the target user doesn't match the user who |
|
|
|
|
# authenticated (e.g. and admin is puppetting a user) then we return both. |
|
|
|
|
if self._requester.user.to_string() != authenticated_entity: |
|
|
|
|
return requester, authenticated_entity |
|
|
|
|
|
|
|
|
|
return requester, None |
|
|
|
|
elif self._requester is not None: |
|
|
|
|
# This shouldn't happen, but we log it so we don't lose information |
|
|
|
|
# and can see that we're doing something wrong. |
|
|
|
|
return repr(self._requester), None # type: ignore[unreachable] |
|
|
|
|
|
|
|
|
|
return None, None |
|
|
|
|
|
|
|
|
|
def render(self, resrc): |
|
|
|
|
# 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. |
|
|
|
|
|
|
|
|
|
# create a LogContext for this request |
|
|
|
|
request_id = self.get_request_id() |
|
|
|
|
self.logcontext = LoggingContext(request_id, request=request_id) |
|
|
|
|
self.logcontext = LoggingContext( |
|
|
|
|
request_id, |
|
|
|
|
request=ContextRequest( |
|
|
|
|
request_id=request_id, |
|
|
|
|
ip_address=self.getClientIP(), |
|
|
|
|
site_tag=self.site.site_tag, |
|
|
|
|
# The requester is going to be unknown at this point. |
|
|
|
|
requester=None, |
|
|
|
|
authenticated_entity=None, |
|
|
|
|
method=self.get_method(), |
|
|
|
|
url=self.get_redacted_uri(), |
|
|
|
|
protocol=self.clientproto.decode("ascii", errors="replace"), |
|
|
|
|
user_agent=get_request_user_agent(self), |
|
|
|
|
), |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
# override the Server header which is set by twisted |
|
|
|
|
self.setHeader("Server", self.site.server_version_string) |
|
|
|
@ -277,25 +353,6 @@ class SynapseRequest(Request): |
|
|
|
|
# to the client (nb may be negative) |
|
|
|
|
response_send_time = self.finish_time - self._processing_finished_time |
|
|
|
|
|
|
|
|
|
# Convert the requester into a string that we can log |
|
|
|
|
authenticated_entity = None |
|
|
|
|
if isinstance(self.requester, str): |
|
|
|
|
authenticated_entity = self.requester |
|
|
|
|
elif isinstance(self.requester, Requester): |
|
|
|
|
authenticated_entity = self.requester.authenticated_entity |
|
|
|
|
|
|
|
|
|
# If this is a request where the target user doesn't match the user who |
|
|
|
|
# authenticated (e.g. and admin is puppetting a user) then we log both. |
|
|
|
|
if self.requester.user.to_string() != authenticated_entity: |
|
|
|
|
authenticated_entity = "{},{}".format( |
|
|
|
|
authenticated_entity, |
|
|
|
|
self.requester.user.to_string(), |
|
|
|
|
) |
|
|
|
|
elif self.requester is not None: |
|
|
|
|
# This shouldn't happen, but we log it so we don't lose information |
|
|
|
|
# and can see that we're doing something wrong. |
|
|
|
|
authenticated_entity = repr(self.requester) # type: ignore[unreachable] |
|
|
|
|
|
|
|
|
|
user_agent = get_request_user_agent(self, "-") |
|
|
|
|
|
|
|
|
|
code = str(self.code) |
|
|
|
@ -305,6 +362,13 @@ class SynapseRequest(Request): |
|
|
|
|
code += "!" |
|
|
|
|
|
|
|
|
|
log_level = logging.INFO if self._should_log_request() else logging.DEBUG |
|
|
|
|
|
|
|
|
|
# If this is a request where the target user doesn't match the user who |
|
|
|
|
# authenticated (e.g. and admin is puppetting a user) then we log both. |
|
|
|
|
requester, authenticated_entity = self.get_authenticated_entity() |
|
|
|
|
if authenticated_entity: |
|
|
|
|
requester = "{}.{}".format(authenticated_entity, requester) |
|
|
|
|
|
|
|
|
|
self.site.access_logger.log( |
|
|
|
|
log_level, |
|
|
|
|
"%s - %s - {%s}" |
|
|
|
@ -312,7 +376,7 @@ class SynapseRequest(Request): |
|
|
|
|
' %sB %s "%s %s %s" "%s" [%d dbevts]', |
|
|
|
|
self.getClientIP(), |
|
|
|
|
self.site.site_tag, |
|
|
|
|
authenticated_entity, |
|
|
|
|
requester, |
|
|
|
|
processing_time, |
|
|
|
|
response_send_time, |
|
|
|
|
usage.ru_utime, |
|
|
|
|