Merge commit 'ff5c4da12' into anoa/dinsic_release_1_31_0
This commit is contained in:
1
changelog.d/8941.feature
Normal file
1
changelog.d/8941.feature
Normal file
@@ -0,0 +1 @@
|
||||
Add support for allowing users to pick their own user ID during a single-sign-on login.
|
||||
1
changelog.d/8950.misc
Normal file
1
changelog.d/8950.misc
Normal file
@@ -0,0 +1 @@
|
||||
Add a maximum size of 50 kilobytes to .well-known lookups.
|
||||
1
changelog.d/8954.feature
Normal file
1
changelog.d/8954.feature
Normal file
@@ -0,0 +1 @@
|
||||
Apply an IP range blacklist to push and key revocation requests.
|
||||
@@ -173,6 +173,18 @@ pid_file: DATADIR/homeserver.pid
|
||||
# - 'fe80::/10'
|
||||
# - 'fc00::/7'
|
||||
|
||||
# List of IP address CIDR ranges that should be allowed for federation,
|
||||
# identity servers, push servers, and for checking key validity for
|
||||
# third-party invite events. This is useful for specifying exceptions to
|
||||
# wide-ranging blacklisted target IP ranges - e.g. for communication with
|
||||
# a push server only visible in your network.
|
||||
#
|
||||
# This whitelist overrides ip_range_blacklist and defaults to an empty
|
||||
# list.
|
||||
#
|
||||
#ip_range_whitelist:
|
||||
# - '192.168.1.1'
|
||||
|
||||
# List of ports that Synapse should listen on, their purpose and their
|
||||
# configuration.
|
||||
#
|
||||
@@ -739,18 +751,6 @@ acme:
|
||||
# - nyc.example.com
|
||||
# - syd.example.com
|
||||
|
||||
# List of IP address CIDR ranges that should be allowed for federation,
|
||||
# identity servers, push servers, and for checking key validity for
|
||||
# third-party invite events. This is useful for specifying exceptions to
|
||||
# wide-ranging blacklisted target IP ranges - e.g. for communication with
|
||||
# a push server only visible in your network.
|
||||
#
|
||||
# This whitelist overrides ip_range_blacklist and defaults to an empty
|
||||
# list.
|
||||
#
|
||||
#ip_range_whitelist:
|
||||
# - '192.168.1.1'
|
||||
|
||||
# Report prometheus metrics on the age of PDUs being sent to and received from
|
||||
# the following domains. This can be used to give an idea of "delay" on inbound
|
||||
# and outbound federation, though be aware that any delay can be due to problems
|
||||
|
||||
@@ -56,18 +56,6 @@ class FederationConfig(Config):
|
||||
# - nyc.example.com
|
||||
# - syd.example.com
|
||||
|
||||
# List of IP address CIDR ranges that should be allowed for federation,
|
||||
# identity servers, push servers, and for checking key validity for
|
||||
# third-party invite events. This is useful for specifying exceptions to
|
||||
# wide-ranging blacklisted target IP ranges - e.g. for communication with
|
||||
# a push server only visible in your network.
|
||||
#
|
||||
# This whitelist overrides ip_range_blacklist and defaults to an empty
|
||||
# list.
|
||||
#
|
||||
#ip_range_whitelist:
|
||||
# - '192.168.1.1'
|
||||
|
||||
# Report prometheus metrics on the age of PDUs being sent to and received from
|
||||
# the following domains. This can be used to give an idea of "delay" on inbound
|
||||
# and outbound federation, though be aware that any delay can be due to problems
|
||||
|
||||
@@ -838,6 +838,18 @@ class ServerConfig(Config):
|
||||
#ip_range_blacklist:
|
||||
%(ip_range_blacklist)s
|
||||
|
||||
# List of IP address CIDR ranges that should be allowed for federation,
|
||||
# identity servers, push servers, and for checking key validity for
|
||||
# third-party invite events. This is useful for specifying exceptions to
|
||||
# wide-ranging blacklisted target IP ranges - e.g. for communication with
|
||||
# a push server only visible in your network.
|
||||
#
|
||||
# This whitelist overrides ip_range_blacklist and defaults to an empty
|
||||
# list.
|
||||
#
|
||||
#ip_range_whitelist:
|
||||
# - '192.168.1.1'
|
||||
|
||||
# List of ports that Synapse should listen on, their purpose and their
|
||||
# configuration.
|
||||
#
|
||||
|
||||
@@ -115,8 +115,6 @@ class OidcHandler(BaseHandler):
|
||||
self._allow_existing_users = hs.config.oidc_allow_existing_users # type: bool
|
||||
|
||||
self._http_client = hs.get_proxied_http_client()
|
||||
self._auth_handler = hs.get_auth_handler()
|
||||
self._registration_handler = hs.get_registration_handler()
|
||||
self._server_name = hs.config.server_name # type: str
|
||||
self._macaroon_secret_key = hs.config.macaroon_secret_key
|
||||
|
||||
@@ -689,33 +687,14 @@ class OidcHandler(BaseHandler):
|
||||
|
||||
# otherwise, it's a login
|
||||
|
||||
# Pull out the user-agent and IP from the request.
|
||||
user_agent = request.get_user_agent("")
|
||||
ip_address = self.hs.get_ip_from_request(request)
|
||||
|
||||
# Call the mapper to register/login the user
|
||||
try:
|
||||
user_id = await self._map_userinfo_to_user(
|
||||
userinfo, token, user_agent, ip_address
|
||||
await self._complete_oidc_login(
|
||||
userinfo, token, request, client_redirect_url
|
||||
)
|
||||
except MappingException as e:
|
||||
logger.exception("Could not map user")
|
||||
self._sso_handler.render_error(request, "mapping_error", str(e))
|
||||
return
|
||||
|
||||
# Mapping providers might not have get_extra_attributes: only call this
|
||||
# method if it exists.
|
||||
extra_attributes = None
|
||||
get_extra_attributes = getattr(
|
||||
self._user_mapping_provider, "get_extra_attributes", None
|
||||
)
|
||||
if get_extra_attributes:
|
||||
extra_attributes = await get_extra_attributes(userinfo, token)
|
||||
|
||||
# and finally complete the login
|
||||
await self._auth_handler.complete_sso_login(
|
||||
user_id, request, client_redirect_url, extra_attributes
|
||||
)
|
||||
|
||||
def _generate_oidc_session_token(
|
||||
self,
|
||||
@@ -838,10 +817,14 @@ class OidcHandler(BaseHandler):
|
||||
now = self.clock.time_msec()
|
||||
return now < expiry
|
||||
|
||||
async def _map_userinfo_to_user(
|
||||
self, userinfo: UserInfo, token: Token, user_agent: str, ip_address: str
|
||||
) -> str:
|
||||
"""Maps a UserInfo object to a mxid.
|
||||
async def _complete_oidc_login(
|
||||
self,
|
||||
userinfo: UserInfo,
|
||||
token: Token,
|
||||
request: SynapseRequest,
|
||||
client_redirect_url: str,
|
||||
) -> None:
|
||||
"""Given a UserInfo response, complete the login flow
|
||||
|
||||
UserInfo should have a claim that uniquely identifies users. This claim
|
||||
is usually `sub`, but can be configured with `oidc_config.subject_claim`.
|
||||
@@ -853,17 +836,16 @@ class OidcHandler(BaseHandler):
|
||||
If a user already exists with the mxid we've mapped and allow_existing_users
|
||||
is disabled, raise an exception.
|
||||
|
||||
Otherwise, render a redirect back to the client_redirect_url with a loginToken.
|
||||
|
||||
Args:
|
||||
userinfo: an object representing the user
|
||||
token: a dict with the tokens obtained from the provider
|
||||
user_agent: The user agent of the client making the request.
|
||||
ip_address: The IP address of the client making the request.
|
||||
request: The request to respond to
|
||||
client_redirect_url: The redirect URL passed in by the client.
|
||||
|
||||
Raises:
|
||||
MappingException: if there was an error while mapping some properties
|
||||
|
||||
Returns:
|
||||
The mxid of the user
|
||||
"""
|
||||
try:
|
||||
remote_user_id = self._remote_id_from_userinfo(userinfo)
|
||||
@@ -931,13 +913,23 @@ class OidcHandler(BaseHandler):
|
||||
|
||||
return None
|
||||
|
||||
return await self._sso_handler.get_mxid_from_sso(
|
||||
# Mapping providers might not have get_extra_attributes: only call this
|
||||
# method if it exists.
|
||||
extra_attributes = None
|
||||
get_extra_attributes = getattr(
|
||||
self._user_mapping_provider, "get_extra_attributes", None
|
||||
)
|
||||
if get_extra_attributes:
|
||||
extra_attributes = await get_extra_attributes(userinfo, token)
|
||||
|
||||
await self._sso_handler.complete_sso_login_request(
|
||||
self._auth_provider_id,
|
||||
remote_user_id,
|
||||
user_agent,
|
||||
ip_address,
|
||||
request,
|
||||
client_redirect_url,
|
||||
oidc_response_to_user_attributes,
|
||||
grandfather_existing_users,
|
||||
extra_attributes,
|
||||
)
|
||||
|
||||
def _remote_id_from_userinfo(self, userinfo: UserInfo) -> str:
|
||||
|
||||
@@ -58,8 +58,6 @@ class SamlHandler(BaseHandler):
|
||||
super().__init__(hs)
|
||||
self._saml_client = Saml2Client(hs.config.saml2_sp_config)
|
||||
self._saml_idp_entityid = hs.config.saml2_idp_entityid
|
||||
self._auth_handler = hs.get_auth_handler()
|
||||
self._registration_handler = hs.get_registration_handler()
|
||||
|
||||
self._saml2_session_lifetime = hs.config.saml2_session_lifetime
|
||||
self._grandfathered_mxid_source_attribute = (
|
||||
@@ -229,40 +227,29 @@ class SamlHandler(BaseHandler):
|
||||
)
|
||||
return
|
||||
|
||||
# Pull out the user-agent and IP from the request.
|
||||
user_agent = request.get_user_agent("")
|
||||
ip_address = self.hs.get_ip_from_request(request)
|
||||
|
||||
# Call the mapper to register/login the user
|
||||
try:
|
||||
user_id = await self._map_saml_response_to_user(
|
||||
saml2_auth, relay_state, user_agent, ip_address
|
||||
)
|
||||
await self._complete_saml_login(saml2_auth, request, relay_state)
|
||||
except MappingException as e:
|
||||
logger.exception("Could not map user")
|
||||
self._sso_handler.render_error(request, "mapping_error", str(e))
|
||||
return
|
||||
|
||||
await self._auth_handler.complete_sso_login(user_id, request, relay_state)
|
||||
|
||||
async def _map_saml_response_to_user(
|
||||
async def _complete_saml_login(
|
||||
self,
|
||||
saml2_auth: saml2.response.AuthnResponse,
|
||||
request: SynapseRequest,
|
||||
client_redirect_url: str,
|
||||
user_agent: str,
|
||||
ip_address: str,
|
||||
) -> str:
|
||||
) -> None:
|
||||
"""
|
||||
Given a SAML response, retrieve the user ID for it and possibly register the user.
|
||||
Given a SAML response, complete the login flow
|
||||
|
||||
Retrieves the remote user ID, registers the user if necessary, and serves
|
||||
a redirect back to the client with a login-token.
|
||||
|
||||
Args:
|
||||
saml2_auth: The parsed SAML2 response.
|
||||
request: The request to respond to
|
||||
client_redirect_url: The redirect URL passed in by the client.
|
||||
user_agent: The user agent of the client making the request.
|
||||
ip_address: The IP address of the client making the request.
|
||||
|
||||
Returns:
|
||||
The user ID associated with this response.
|
||||
|
||||
Raises:
|
||||
MappingException if there was a problem mapping the response to a user.
|
||||
@@ -318,11 +305,11 @@ class SamlHandler(BaseHandler):
|
||||
|
||||
return None
|
||||
|
||||
return await self._sso_handler.get_mxid_from_sso(
|
||||
await self._sso_handler.complete_sso_login_request(
|
||||
self._auth_provider_id,
|
||||
remote_user_id,
|
||||
user_agent,
|
||||
ip_address,
|
||||
request,
|
||||
client_redirect_url,
|
||||
saml_response_to_remapped_user_attributes,
|
||||
grandfather_existing_users,
|
||||
)
|
||||
|
||||
@@ -21,7 +21,8 @@ from twisted.web.http import Request
|
||||
|
||||
from synapse.api.errors import RedirectException
|
||||
from synapse.http.server import respond_with_html
|
||||
from synapse.types import UserID, contains_invalid_mxid_characters
|
||||
from synapse.http.site import SynapseRequest
|
||||
from synapse.types import JsonDict, UserID, contains_invalid_mxid_characters
|
||||
from synapse.util.async_helpers import Linearizer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -119,15 +120,16 @@ class SsoHandler:
|
||||
# No match.
|
||||
return None
|
||||
|
||||
async def get_mxid_from_sso(
|
||||
async def complete_sso_login_request(
|
||||
self,
|
||||
auth_provider_id: str,
|
||||
remote_user_id: str,
|
||||
user_agent: str,
|
||||
ip_address: str,
|
||||
request: SynapseRequest,
|
||||
client_redirect_url: str,
|
||||
sso_to_matrix_id_mapper: Callable[[int], Awaitable[UserAttributes]],
|
||||
grandfather_existing_users: Optional[Callable[[], Awaitable[Optional[str]]]],
|
||||
) -> str:
|
||||
extra_login_attributes: Optional[JsonDict] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Given an SSO ID, retrieve the user ID for it and possibly register the user.
|
||||
|
||||
@@ -146,12 +148,18 @@ class SsoHandler:
|
||||
given user-agent and IP address and the SSO ID is linked to this matrix
|
||||
ID for subsequent calls.
|
||||
|
||||
Finally, we generate a redirect to the supplied redirect uri, with a login token
|
||||
|
||||
Args:
|
||||
auth_provider_id: A unique identifier for this SSO provider, e.g.
|
||||
"oidc" or "saml".
|
||||
|
||||
remote_user_id: The unique identifier from the SSO provider.
|
||||
user_agent: The user agent of the client making the request.
|
||||
ip_address: The IP address of the client making the request.
|
||||
|
||||
request: The request to respond to
|
||||
|
||||
client_redirect_url: The redirect URL passed in by the client.
|
||||
|
||||
sso_to_matrix_id_mapper: A callable to generate the user attributes.
|
||||
The only parameter is an integer which represents the amount of
|
||||
times the returned mxid localpart mapping has failed.
|
||||
@@ -163,12 +171,13 @@ class SsoHandler:
|
||||
to the user.
|
||||
RedirectException to redirect to an additional page (e.g.
|
||||
to prompt the user for more information).
|
||||
|
||||
grandfather_existing_users: A callable which can return an previously
|
||||
existing matrix ID. The SSO ID is then linked to the returned
|
||||
matrix ID.
|
||||
|
||||
Returns:
|
||||
The user ID associated with the SSO response.
|
||||
extra_login_attributes: An optional dictionary of extra
|
||||
attributes to be provided to the client in the login response.
|
||||
|
||||
Raises:
|
||||
MappingException if there was a problem mapping the response to a user.
|
||||
@@ -181,28 +190,33 @@ class SsoHandler:
|
||||
# interstitial pages.
|
||||
with await self._mapping_lock.queue(auth_provider_id):
|
||||
# first of all, check if we already have a mapping for this user
|
||||
previously_registered_user_id = await self.get_sso_user_by_remote_user_id(
|
||||
user_id = await self.get_sso_user_by_remote_user_id(
|
||||
auth_provider_id, remote_user_id,
|
||||
)
|
||||
if previously_registered_user_id:
|
||||
return previously_registered_user_id
|
||||
|
||||
# Check for grandfathering of users.
|
||||
if grandfather_existing_users:
|
||||
previously_registered_user_id = await grandfather_existing_users()
|
||||
if previously_registered_user_id:
|
||||
if not user_id and grandfather_existing_users:
|
||||
user_id = await grandfather_existing_users()
|
||||
if user_id:
|
||||
# Future logins should also match this user ID.
|
||||
await self._store.record_user_external_id(
|
||||
auth_provider_id, remote_user_id, previously_registered_user_id
|
||||
auth_provider_id, remote_user_id, user_id
|
||||
)
|
||||
return previously_registered_user_id
|
||||
|
||||
# Otherwise, generate a new user.
|
||||
attributes = await self._call_attribute_mapper(sso_to_matrix_id_mapper)
|
||||
user_id = await self._register_mapped_user(
|
||||
attributes, auth_provider_id, remote_user_id, user_agent, ip_address,
|
||||
)
|
||||
return user_id
|
||||
if not user_id:
|
||||
attributes = await self._call_attribute_mapper(sso_to_matrix_id_mapper)
|
||||
user_id = await self._register_mapped_user(
|
||||
attributes,
|
||||
auth_provider_id,
|
||||
remote_user_id,
|
||||
request.get_user_agent(""),
|
||||
request.getClientIP(),
|
||||
)
|
||||
|
||||
await self._auth_handler.complete_sso_login(
|
||||
user_id, request, client_redirect_url, extra_login_attributes
|
||||
)
|
||||
|
||||
async def _call_attribute_mapper(
|
||||
self, sso_to_matrix_id_mapper: Callable[[int], Awaitable[UserAttributes]],
|
||||
|
||||
@@ -719,11 +719,14 @@ class SimpleHttpClient:
|
||||
|
||||
try:
|
||||
length = await make_deferred_yieldable(
|
||||
readBodyToFile(response, output_stream, max_size)
|
||||
read_body_with_max_size(response, output_stream, max_size)
|
||||
)
|
||||
except BodyExceededMaxSize:
|
||||
SynapseError(
|
||||
502,
|
||||
"Requested file is too large > %r bytes" % (max_size,),
|
||||
Codes.TOO_LARGE,
|
||||
)
|
||||
except SynapseError:
|
||||
# This can happen e.g. because the body is too large.
|
||||
raise
|
||||
except Exception as e:
|
||||
raise SynapseError(502, ("Failed to download remote body: %s" % e)) from e
|
||||
|
||||
@@ -747,7 +750,11 @@ def _timeout_to_request_timed_out_error(f: Failure):
|
||||
return f
|
||||
|
||||
|
||||
class _ReadBodyToFileProtocol(protocol.Protocol):
|
||||
class BodyExceededMaxSize(Exception):
|
||||
"""The maximum allowed size of the HTTP body was exceeded."""
|
||||
|
||||
|
||||
class _ReadBodyWithMaxSizeProtocol(protocol.Protocol):
|
||||
def __init__(
|
||||
self, stream: BinaryIO, deferred: defer.Deferred, max_size: Optional[int]
|
||||
):
|
||||
@@ -760,13 +767,7 @@ class _ReadBodyToFileProtocol(protocol.Protocol):
|
||||
self.stream.write(data)
|
||||
self.length += len(data)
|
||||
if self.max_size is not None and self.length >= self.max_size:
|
||||
self.deferred.errback(
|
||||
SynapseError(
|
||||
502,
|
||||
"Requested file is too large > %r bytes" % (self.max_size,),
|
||||
Codes.TOO_LARGE,
|
||||
)
|
||||
)
|
||||
self.deferred.errback(BodyExceededMaxSize())
|
||||
self.deferred = defer.Deferred()
|
||||
self.transport.loseConnection()
|
||||
|
||||
@@ -781,12 +782,15 @@ class _ReadBodyToFileProtocol(protocol.Protocol):
|
||||
self.deferred.errback(reason)
|
||||
|
||||
|
||||
def readBodyToFile(
|
||||
def read_body_with_max_size(
|
||||
response: IResponse, stream: BinaryIO, max_size: Optional[int]
|
||||
) -> defer.Deferred:
|
||||
"""
|
||||
Read a HTTP response body to a file-object. Optionally enforcing a maximum file size.
|
||||
|
||||
If the maximum file size is reached, the returned Deferred will resolve to a
|
||||
Failure with a BodyExceededMaxSize exception.
|
||||
|
||||
Args:
|
||||
response: The HTTP response to read from.
|
||||
stream: The file-object to write to.
|
||||
@@ -797,7 +801,7 @@ def readBodyToFile(
|
||||
"""
|
||||
|
||||
d = defer.Deferred()
|
||||
response.deliverBody(_ReadBodyToFileProtocol(stream, d, max_size))
|
||||
response.deliverBody(_ReadBodyWithMaxSizeProtocol(stream, d, max_size))
|
||||
return d
|
||||
|
||||
|
||||
|
||||
@@ -15,17 +15,19 @@
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
from io import BytesIO
|
||||
from typing import Callable, Dict, Optional, Tuple
|
||||
|
||||
import attr
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.interfaces import IReactorTime
|
||||
from twisted.web.client import RedirectAgent, readBody
|
||||
from twisted.web.client import RedirectAgent
|
||||
from twisted.web.http import stringToDatetime
|
||||
from twisted.web.http_headers import Headers
|
||||
from twisted.web.iweb import IAgent, IResponse
|
||||
|
||||
from synapse.http.client import BodyExceededMaxSize, read_body_with_max_size
|
||||
from synapse.logging.context import make_deferred_yieldable
|
||||
from synapse.util import Clock, json_decoder
|
||||
from synapse.util.caches.ttlcache import TTLCache
|
||||
@@ -53,6 +55,9 @@ WELL_KNOWN_MAX_CACHE_PERIOD = 48 * 3600
|
||||
# lower bound for .well-known cache period
|
||||
WELL_KNOWN_MIN_CACHE_PERIOD = 5 * 60
|
||||
|
||||
# The maximum size (in bytes) to allow a well-known file to be.
|
||||
WELL_KNOWN_MAX_SIZE = 50 * 1024 # 50 KiB
|
||||
|
||||
# Attempt to refetch a cached well-known N% of the TTL before it expires.
|
||||
# e.g. if set to 0.2 and we have a cached entry with a TTL of 5mins, then
|
||||
# we'll start trying to refetch 1 minute before it expires.
|
||||
@@ -229,6 +234,9 @@ class WellKnownResolver:
|
||||
server_name: name of the server, from the requested url
|
||||
retry: Whether to retry the request if it fails.
|
||||
|
||||
Raises:
|
||||
_FetchWellKnownFailure if we fail to lookup a result
|
||||
|
||||
Returns:
|
||||
Returns the response object and body. Response may be a non-200 response.
|
||||
"""
|
||||
@@ -250,7 +258,11 @@ class WellKnownResolver:
|
||||
b"GET", uri, headers=Headers(headers)
|
||||
)
|
||||
)
|
||||
body = await make_deferred_yieldable(readBody(response))
|
||||
body_stream = BytesIO()
|
||||
await make_deferred_yieldable(
|
||||
read_body_with_max_size(response, body_stream, WELL_KNOWN_MAX_SIZE)
|
||||
)
|
||||
body = body_stream.getvalue()
|
||||
|
||||
if 500 <= response.code < 600:
|
||||
raise Exception("Non-200 response %s" % (response.code,))
|
||||
@@ -259,6 +271,15 @@ class WellKnownResolver:
|
||||
except defer.CancelledError:
|
||||
# Bail if we've been cancelled
|
||||
raise
|
||||
except BodyExceededMaxSize:
|
||||
# If the well-known file was too large, do not keep attempting
|
||||
# to download it, but consider it a temporary error.
|
||||
logger.warning(
|
||||
"Requested .well-known file for %s is too large > %r bytes",
|
||||
server_name.decode("ascii"),
|
||||
WELL_KNOWN_MAX_SIZE,
|
||||
)
|
||||
raise _FetchWellKnownFailure(temporary=True)
|
||||
except Exception as e:
|
||||
if not retry or i >= WELL_KNOWN_RETRY_ATTEMPTS:
|
||||
logger.info("Error fetching %s: %s", uri_str, e)
|
||||
|
||||
@@ -37,16 +37,19 @@ from twisted.web.iweb import IBodyProducer, IResponse
|
||||
import synapse.metrics
|
||||
import synapse.util.retryutils
|
||||
from synapse.api.errors import (
|
||||
Codes,
|
||||
FederationDeniedError,
|
||||
HttpResponseException,
|
||||
RequestSendFailed,
|
||||
SynapseError,
|
||||
)
|
||||
from synapse.http import QuieterFileBodyProducer
|
||||
from synapse.http.client import (
|
||||
BlacklistingAgentWrapper,
|
||||
BlacklistingReactorWrapper,
|
||||
BodyExceededMaxSize,
|
||||
encode_query_args,
|
||||
readBodyToFile,
|
||||
read_body_with_max_size,
|
||||
)
|
||||
from synapse.http.federation.matrix_federation_agent import MatrixFederationAgent
|
||||
from synapse.logging.context import make_deferred_yieldable
|
||||
@@ -975,9 +978,15 @@ class MatrixFederationHttpClient:
|
||||
headers = dict(response.headers.getAllRawHeaders())
|
||||
|
||||
try:
|
||||
d = readBodyToFile(response, output_stream, max_size)
|
||||
d = read_body_with_max_size(response, output_stream, max_size)
|
||||
d.addTimeout(self.default_timeout, self.reactor)
|
||||
length = await make_deferred_yieldable(d)
|
||||
except BodyExceededMaxSize:
|
||||
msg = "Requested file is too large > %r bytes" % (max_size,)
|
||||
logger.warning(
|
||||
"{%s} [%s] %s", request.txn_id, request.destination, msg,
|
||||
)
|
||||
SynapseError(502, msg, Codes.TOO_LARGE)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"{%s} [%s] Error reading response: %s",
|
||||
|
||||
@@ -131,7 +131,7 @@ class SamlHandlerTestCase(HomeserverTestCase):
|
||||
|
||||
# check that the auth handler got called as expected
|
||||
auth_handler.complete_sso_login.assert_called_once_with(
|
||||
"@test_user:test", request, "redirect_uri"
|
||||
"@test_user:test", request, "redirect_uri", None
|
||||
)
|
||||
|
||||
@override_config({"saml2_config": {"grandfathered_mxid_source_attribute": "mxid"}})
|
||||
@@ -157,7 +157,7 @@ class SamlHandlerTestCase(HomeserverTestCase):
|
||||
|
||||
# check that the auth handler got called as expected
|
||||
auth_handler.complete_sso_login.assert_called_once_with(
|
||||
"@test_user:test", request, ""
|
||||
"@test_user:test", request, "", None
|
||||
)
|
||||
|
||||
# Subsequent calls should map to the same mxid.
|
||||
@@ -166,7 +166,7 @@ class SamlHandlerTestCase(HomeserverTestCase):
|
||||
self.handler._handle_authn_response(request, saml_response, "")
|
||||
)
|
||||
auth_handler.complete_sso_login.assert_called_once_with(
|
||||
"@test_user:test", request, ""
|
||||
"@test_user:test", request, "", None
|
||||
)
|
||||
|
||||
def test_map_saml_response_to_invalid_localpart(self):
|
||||
@@ -214,7 +214,7 @@ class SamlHandlerTestCase(HomeserverTestCase):
|
||||
|
||||
# test_user is already taken, so test_user1 gets registered instead.
|
||||
auth_handler.complete_sso_login.assert_called_once_with(
|
||||
"@test_user1:test", request, ""
|
||||
"@test_user1:test", request, "", None
|
||||
)
|
||||
auth_handler.complete_sso_login.reset_mock()
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ from synapse.crypto.context_factory import FederationPolicyForHTTPS
|
||||
from synapse.http.federation.matrix_federation_agent import MatrixFederationAgent
|
||||
from synapse.http.federation.srv_resolver import Server
|
||||
from synapse.http.federation.well_known_resolver import (
|
||||
WELL_KNOWN_MAX_SIZE,
|
||||
WellKnownResolver,
|
||||
_cache_period_from_headers,
|
||||
)
|
||||
@@ -1107,6 +1108,32 @@ class MatrixFederationAgentTests(unittest.TestCase):
|
||||
r = self.successResultOf(fetch_d)
|
||||
self.assertEqual(r.delegated_server, None)
|
||||
|
||||
def test_well_known_too_large(self):
|
||||
"""A well-known query that returns a result which is too large should be rejected."""
|
||||
self.reactor.lookups["testserv"] = "1.2.3.4"
|
||||
|
||||
fetch_d = defer.ensureDeferred(
|
||||
self.well_known_resolver.get_well_known(b"testserv")
|
||||
)
|
||||
|
||||
# there should be an attempt to connect on port 443 for the .well-known
|
||||
clients = self.reactor.tcpClients
|
||||
self.assertEqual(len(clients), 1)
|
||||
(host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
|
||||
self.assertEqual(host, "1.2.3.4")
|
||||
self.assertEqual(port, 443)
|
||||
|
||||
self._handle_well_known_connection(
|
||||
client_factory,
|
||||
expected_sni=b"testserv",
|
||||
response_headers={b"Cache-Control": b"max-age=1000"},
|
||||
content=b'{ "m.server": "' + (b"a" * WELL_KNOWN_MAX_SIZE) + b'" }',
|
||||
)
|
||||
|
||||
# The result is sucessful, but disabled delegation.
|
||||
r = self.successResultOf(fetch_d)
|
||||
self.assertIsNone(r.delegated_server)
|
||||
|
||||
def test_srv_fallbacks(self):
|
||||
"""Test that other SRV results are tried if the first one fails.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user