Allow providing credentials to HTTPS_PROXY (#9657)
Addresses https://github.com/matrix-org/synapse-dinsic/issues/70 This PR causes `ProxyAgent` to attempt to extract credentials from an `HTTPS_PROXY` env var. If credentials are found, a `Proxy-Authorization` header ([details](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authorization)) is sent to the proxy server to authenticate against it. The headers are *not* passed to the remote server. Also added some type hints.pull/9665/head
parent
4612302399
commit
5b268997bd
|
@ -0,0 +1 @@
|
||||||
|
Add support for credentials for proxy authentication in the `HTTPS_PROXY` environment variable.
|
|
@ -19,9 +19,10 @@ from zope.interface import implementer
|
||||||
|
|
||||||
from twisted.internet import defer, protocol
|
from twisted.internet import defer, protocol
|
||||||
from twisted.internet.error import ConnectError
|
from twisted.internet.error import ConnectError
|
||||||
from twisted.internet.interfaces import IStreamClientEndpoint
|
from twisted.internet.interfaces import IReactorCore, IStreamClientEndpoint
|
||||||
from twisted.internet.protocol import connectionDone
|
from twisted.internet.protocol import ClientFactory, Protocol, connectionDone
|
||||||
from twisted.web import http
|
from twisted.web import http
|
||||||
|
from twisted.web.http_headers import Headers
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -43,23 +44,33 @@ class HTTPConnectProxyEndpoint:
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reactor: the Twisted reactor to use for the connection
|
reactor: the Twisted reactor to use for the connection
|
||||||
proxy_endpoint (IStreamClientEndpoint): the endpoint to use to connect to the
|
proxy_endpoint: the endpoint to use to connect to the proxy
|
||||||
proxy
|
host: hostname that we want to CONNECT to
|
||||||
host (bytes): hostname that we want to CONNECT to
|
port: port that we want to connect to
|
||||||
port (int): port that we want to connect to
|
headers: Extra HTTP headers to include in the CONNECT request
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, reactor, proxy_endpoint, host, port):
|
def __init__(
|
||||||
|
self,
|
||||||
|
reactor: IReactorCore,
|
||||||
|
proxy_endpoint: IStreamClientEndpoint,
|
||||||
|
host: bytes,
|
||||||
|
port: int,
|
||||||
|
headers: Headers,
|
||||||
|
):
|
||||||
self._reactor = reactor
|
self._reactor = reactor
|
||||||
self._proxy_endpoint = proxy_endpoint
|
self._proxy_endpoint = proxy_endpoint
|
||||||
self._host = host
|
self._host = host
|
||||||
self._port = port
|
self._port = port
|
||||||
|
self._headers = headers
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<HTTPConnectProxyEndpoint %s>" % (self._proxy_endpoint,)
|
return "<HTTPConnectProxyEndpoint %s>" % (self._proxy_endpoint,)
|
||||||
|
|
||||||
def connect(self, protocolFactory):
|
def connect(self, protocolFactory: ClientFactory):
|
||||||
f = HTTPProxiedClientFactory(self._host, self._port, protocolFactory)
|
f = HTTPProxiedClientFactory(
|
||||||
|
self._host, self._port, protocolFactory, self._headers
|
||||||
|
)
|
||||||
d = self._proxy_endpoint.connect(f)
|
d = self._proxy_endpoint.connect(f)
|
||||||
# once the tcp socket connects successfully, we need to wait for the
|
# once the tcp socket connects successfully, we need to wait for the
|
||||||
# CONNECT to complete.
|
# CONNECT to complete.
|
||||||
|
@ -74,15 +85,23 @@ class HTTPProxiedClientFactory(protocol.ClientFactory):
|
||||||
HTTP Protocol object and run the rest of the connection.
|
HTTP Protocol object and run the rest of the connection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dst_host (bytes): hostname that we want to CONNECT to
|
dst_host: hostname that we want to CONNECT to
|
||||||
dst_port (int): port that we want to connect to
|
dst_port: port that we want to connect to
|
||||||
wrapped_factory (protocol.ClientFactory): The original Factory
|
wrapped_factory: The original Factory
|
||||||
|
headers: Extra HTTP headers to include in the CONNECT request
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, dst_host, dst_port, wrapped_factory):
|
def __init__(
|
||||||
|
self,
|
||||||
|
dst_host: bytes,
|
||||||
|
dst_port: int,
|
||||||
|
wrapped_factory: ClientFactory,
|
||||||
|
headers: Headers,
|
||||||
|
):
|
||||||
self.dst_host = dst_host
|
self.dst_host = dst_host
|
||||||
self.dst_port = dst_port
|
self.dst_port = dst_port
|
||||||
self.wrapped_factory = wrapped_factory
|
self.wrapped_factory = wrapped_factory
|
||||||
|
self.headers = headers
|
||||||
self.on_connection = defer.Deferred()
|
self.on_connection = defer.Deferred()
|
||||||
|
|
||||||
def startedConnecting(self, connector):
|
def startedConnecting(self, connector):
|
||||||
|
@ -92,7 +111,11 @@ class HTTPProxiedClientFactory(protocol.ClientFactory):
|
||||||
wrapped_protocol = self.wrapped_factory.buildProtocol(addr)
|
wrapped_protocol = self.wrapped_factory.buildProtocol(addr)
|
||||||
|
|
||||||
return HTTPConnectProtocol(
|
return HTTPConnectProtocol(
|
||||||
self.dst_host, self.dst_port, wrapped_protocol, self.on_connection
|
self.dst_host,
|
||||||
|
self.dst_port,
|
||||||
|
wrapped_protocol,
|
||||||
|
self.on_connection,
|
||||||
|
self.headers,
|
||||||
)
|
)
|
||||||
|
|
||||||
def clientConnectionFailed(self, connector, reason):
|
def clientConnectionFailed(self, connector, reason):
|
||||||
|
@ -112,24 +135,37 @@ class HTTPConnectProtocol(protocol.Protocol):
|
||||||
"""Protocol that wraps an existing Protocol to do a CONNECT handshake at connect
|
"""Protocol that wraps an existing Protocol to do a CONNECT handshake at connect
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
host (bytes): The original HTTP(s) hostname or IPv4 or IPv6 address literal
|
host: The original HTTP(s) hostname or IPv4 or IPv6 address literal
|
||||||
to put in the CONNECT request
|
to put in the CONNECT request
|
||||||
|
|
||||||
port (int): The original HTTP(s) port to put in the CONNECT request
|
port: The original HTTP(s) port to put in the CONNECT request
|
||||||
|
|
||||||
wrapped_protocol (interfaces.IProtocol): the original protocol (probably
|
wrapped_protocol: the original protocol (probably HTTPChannel or
|
||||||
HTTPChannel or TLSMemoryBIOProtocol, but could be anything really)
|
TLSMemoryBIOProtocol, but could be anything really)
|
||||||
|
|
||||||
connected_deferred (Deferred): a Deferred which will be callbacked with
|
connected_deferred: a Deferred which will be callbacked with
|
||||||
wrapped_protocol when the CONNECT completes
|
wrapped_protocol when the CONNECT completes
|
||||||
|
|
||||||
|
headers: Extra HTTP headers to include in the CONNECT request
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host, port, wrapped_protocol, connected_deferred):
|
def __init__(
|
||||||
|
self,
|
||||||
|
host: bytes,
|
||||||
|
port: int,
|
||||||
|
wrapped_protocol: Protocol,
|
||||||
|
connected_deferred: defer.Deferred,
|
||||||
|
headers: Headers,
|
||||||
|
):
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
self.wrapped_protocol = wrapped_protocol
|
self.wrapped_protocol = wrapped_protocol
|
||||||
self.connected_deferred = connected_deferred
|
self.connected_deferred = connected_deferred
|
||||||
self.http_setup_client = HTTPConnectSetupClient(self.host, self.port)
|
self.headers = headers
|
||||||
|
|
||||||
|
self.http_setup_client = HTTPConnectSetupClient(
|
||||||
|
self.host, self.port, self.headers
|
||||||
|
)
|
||||||
self.http_setup_client.on_connected.addCallback(self.proxyConnected)
|
self.http_setup_client.on_connected.addCallback(self.proxyConnected)
|
||||||
|
|
||||||
def connectionMade(self):
|
def connectionMade(self):
|
||||||
|
@ -154,7 +190,7 @@ class HTTPConnectProtocol(protocol.Protocol):
|
||||||
if buf:
|
if buf:
|
||||||
self.wrapped_protocol.dataReceived(buf)
|
self.wrapped_protocol.dataReceived(buf)
|
||||||
|
|
||||||
def dataReceived(self, data):
|
def dataReceived(self, data: bytes):
|
||||||
# if we've set up the HTTP protocol, we can send the data there
|
# if we've set up the HTTP protocol, we can send the data there
|
||||||
if self.wrapped_protocol.connected:
|
if self.wrapped_protocol.connected:
|
||||||
return self.wrapped_protocol.dataReceived(data)
|
return self.wrapped_protocol.dataReceived(data)
|
||||||
|
@ -168,21 +204,29 @@ class HTTPConnectSetupClient(http.HTTPClient):
|
||||||
"""HTTPClient protocol to send a CONNECT message for proxies and read the response.
|
"""HTTPClient protocol to send a CONNECT message for proxies and read the response.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
host (bytes): The hostname to send in the CONNECT message
|
host: The hostname to send in the CONNECT message
|
||||||
port (int): The port to send in the CONNECT message
|
port: The port to send in the CONNECT message
|
||||||
|
headers: Extra headers to send with the CONNECT message
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host, port):
|
def __init__(self, host: bytes, port: int, headers: Headers):
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
|
self.headers = headers
|
||||||
self.on_connected = defer.Deferred()
|
self.on_connected = defer.Deferred()
|
||||||
|
|
||||||
def connectionMade(self):
|
def connectionMade(self):
|
||||||
logger.debug("Connected to proxy, sending CONNECT")
|
logger.debug("Connected to proxy, sending CONNECT")
|
||||||
self.sendCommand(b"CONNECT", b"%s:%d" % (self.host, self.port))
|
self.sendCommand(b"CONNECT", b"%s:%d" % (self.host, self.port))
|
||||||
|
|
||||||
|
# Send any additional specified headers
|
||||||
|
for name, values in self.headers.getAllRawHeaders():
|
||||||
|
for value in values:
|
||||||
|
self.sendHeader(name, value)
|
||||||
|
|
||||||
self.endHeaders()
|
self.endHeaders()
|
||||||
|
|
||||||
def handleStatus(self, version, status, message):
|
def handleStatus(self, version: bytes, status: bytes, message: bytes):
|
||||||
logger.debug("Got Status: %s %s %s", status, message, version)
|
logger.debug("Got Status: %s %s %s", status, message, version)
|
||||||
if status != b"200":
|
if status != b"200":
|
||||||
raise ProxyConnectError("Unexpected status on CONNECT: %s" % status)
|
raise ProxyConnectError("Unexpected status on CONNECT: %s" % status)
|
||||||
|
|
|
@ -12,10 +12,13 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import base64
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from typing import Optional, Tuple
|
||||||
from urllib.request import getproxies_environment, proxy_bypass_environment
|
from urllib.request import getproxies_environment, proxy_bypass_environment
|
||||||
|
|
||||||
|
import attr
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
@ -23,6 +26,7 @@ from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
from twisted.web.client import URI, BrowserLikePolicyForHTTPS, _AgentBase
|
from twisted.web.client import URI, BrowserLikePolicyForHTTPS, _AgentBase
|
||||||
from twisted.web.error import SchemeNotSupported
|
from twisted.web.error import SchemeNotSupported
|
||||||
|
from twisted.web.http_headers import Headers
|
||||||
from twisted.web.iweb import IAgent
|
from twisted.web.iweb import IAgent
|
||||||
|
|
||||||
from synapse.http.connectproxyclient import HTTPConnectProxyEndpoint
|
from synapse.http.connectproxyclient import HTTPConnectProxyEndpoint
|
||||||
|
@ -32,6 +36,22 @@ logger = logging.getLogger(__name__)
|
||||||
_VALID_URI = re.compile(br"\A[\x21-\x7e]+\Z")
|
_VALID_URI = re.compile(br"\A[\x21-\x7e]+\Z")
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class ProxyCredentials:
|
||||||
|
username_password = attr.ib(type=bytes)
|
||||||
|
|
||||||
|
def as_proxy_authorization_value(self) -> bytes:
|
||||||
|
"""
|
||||||
|
Return the value for a Proxy-Authorization header (i.e. 'Basic abdef==').
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A transformation of the authentication string the encoded value for
|
||||||
|
a Proxy-Authorization header.
|
||||||
|
"""
|
||||||
|
# Encode as base64 and prepend the authorization type
|
||||||
|
return b"Basic " + base64.encodebytes(self.username_password)
|
||||||
|
|
||||||
|
|
||||||
@implementer(IAgent)
|
@implementer(IAgent)
|
||||||
class ProxyAgent(_AgentBase):
|
class ProxyAgent(_AgentBase):
|
||||||
"""An Agent implementation which will use an HTTP proxy if one was requested
|
"""An Agent implementation which will use an HTTP proxy if one was requested
|
||||||
|
@ -96,6 +116,9 @@ class ProxyAgent(_AgentBase):
|
||||||
https_proxy = proxies["https"].encode() if "https" in proxies else None
|
https_proxy = proxies["https"].encode() if "https" in proxies else None
|
||||||
no_proxy = proxies["no"] if "no" in proxies else None
|
no_proxy = proxies["no"] if "no" in proxies else None
|
||||||
|
|
||||||
|
# Parse credentials from https proxy connection string if present
|
||||||
|
self.https_proxy_creds, https_proxy = parse_username_password(https_proxy)
|
||||||
|
|
||||||
self.http_proxy_endpoint = _http_proxy_endpoint(
|
self.http_proxy_endpoint = _http_proxy_endpoint(
|
||||||
http_proxy, self.proxy_reactor, **self._endpoint_kwargs
|
http_proxy, self.proxy_reactor, **self._endpoint_kwargs
|
||||||
)
|
)
|
||||||
|
@ -175,11 +198,22 @@ class ProxyAgent(_AgentBase):
|
||||||
and self.https_proxy_endpoint
|
and self.https_proxy_endpoint
|
||||||
and not should_skip_proxy
|
and not should_skip_proxy
|
||||||
):
|
):
|
||||||
|
connect_headers = Headers()
|
||||||
|
|
||||||
|
# Determine whether we need to set Proxy-Authorization headers
|
||||||
|
if self.https_proxy_creds:
|
||||||
|
# Set a Proxy-Authorization header
|
||||||
|
connect_headers.addRawHeader(
|
||||||
|
b"Proxy-Authorization",
|
||||||
|
self.https_proxy_creds.as_proxy_authorization_value(),
|
||||||
|
)
|
||||||
|
|
||||||
endpoint = HTTPConnectProxyEndpoint(
|
endpoint = HTTPConnectProxyEndpoint(
|
||||||
self.proxy_reactor,
|
self.proxy_reactor,
|
||||||
self.https_proxy_endpoint,
|
self.https_proxy_endpoint,
|
||||||
parsed_uri.host,
|
parsed_uri.host,
|
||||||
parsed_uri.port,
|
parsed_uri.port,
|
||||||
|
headers=connect_headers,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# not using a proxy
|
# not using a proxy
|
||||||
|
@ -208,12 +242,16 @@ class ProxyAgent(_AgentBase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _http_proxy_endpoint(proxy, reactor, **kwargs):
|
def _http_proxy_endpoint(proxy: Optional[bytes], reactor, **kwargs):
|
||||||
"""Parses an http proxy setting and returns an endpoint for the proxy
|
"""Parses an http proxy setting and returns an endpoint for the proxy
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
proxy (bytes|None): the proxy setting
|
proxy: the proxy setting in the form: [<username>:<password>@]<host>[:<port>]
|
||||||
|
Note that compared to other apps, this function currently lacks support
|
||||||
|
for specifying a protocol schema (i.e. protocol://...).
|
||||||
|
|
||||||
reactor: reactor to be used to connect to the proxy
|
reactor: reactor to be used to connect to the proxy
|
||||||
|
|
||||||
kwargs: other args to be passed to HostnameEndpoint
|
kwargs: other args to be passed to HostnameEndpoint
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -223,16 +261,43 @@ def _http_proxy_endpoint(proxy, reactor, **kwargs):
|
||||||
if proxy is None:
|
if proxy is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# currently we only support hostname:port. Some apps also support
|
# Parse the connection string
|
||||||
# protocol://<host>[:port], which allows a way of requiring a TLS connection to the
|
|
||||||
# proxy.
|
|
||||||
|
|
||||||
host, port = parse_host_port(proxy, default_port=1080)
|
host, port = parse_host_port(proxy, default_port=1080)
|
||||||
return HostnameEndpoint(reactor, host, port, **kwargs)
|
return HostnameEndpoint(reactor, host, port, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def parse_host_port(hostport, default_port=None):
|
def parse_username_password(proxy: bytes) -> Tuple[Optional[ProxyCredentials], bytes]:
|
||||||
# could have sworn we had one of these somewhere else...
|
"""
|
||||||
|
Parses the username and password from a proxy declaration e.g
|
||||||
|
username:password@hostname:port.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
proxy: The proxy connection string.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
An instance of ProxyCredentials and the proxy connection string with any credentials
|
||||||
|
stripped, i.e u:p@host:port -> host:port. If no credentials were found, the
|
||||||
|
ProxyCredentials instance is replaced with None.
|
||||||
|
"""
|
||||||
|
if proxy and b"@" in proxy:
|
||||||
|
# We use rsplit here as the password could contain an @ character
|
||||||
|
credentials, proxy_without_credentials = proxy.rsplit(b"@", 1)
|
||||||
|
return ProxyCredentials(credentials), proxy_without_credentials
|
||||||
|
|
||||||
|
return None, proxy
|
||||||
|
|
||||||
|
|
||||||
|
def parse_host_port(hostport: bytes, default_port: int = None) -> Tuple[bytes, int]:
|
||||||
|
"""
|
||||||
|
Parse the hostname and port from a proxy connection byte string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hostport: The proxy connection string. Must be in the form 'host[:port]'.
|
||||||
|
default_port: The default port to return if one is not found in `hostport`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A tuple containing the hostname and port. Uses `default_port` if one was not found.
|
||||||
|
"""
|
||||||
if b":" in hostport:
|
if b":" in hostport:
|
||||||
host, port = hostport.rsplit(b":", 1)
|
host, port = hostport.rsplit(b":", 1)
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -12,8 +12,10 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import base64
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from typing import Optional
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import treq
|
import treq
|
||||||
|
@ -242,6 +244,21 @@ class MatrixFederationAgentTests(TestCase):
|
||||||
|
|
||||||
@patch.dict(os.environ, {"https_proxy": "proxy.com", "no_proxy": "unused.com"})
|
@patch.dict(os.environ, {"https_proxy": "proxy.com", "no_proxy": "unused.com"})
|
||||||
def test_https_request_via_proxy(self):
|
def test_https_request_via_proxy(self):
|
||||||
|
"""Tests that TLS-encrypted requests can be made through a proxy"""
|
||||||
|
self._do_https_request_via_proxy(auth_credentials=None)
|
||||||
|
|
||||||
|
@patch.dict(
|
||||||
|
os.environ,
|
||||||
|
{"https_proxy": "bob:pinkponies@proxy.com", "no_proxy": "unused.com"},
|
||||||
|
)
|
||||||
|
def test_https_request_via_proxy_with_auth(self):
|
||||||
|
"""Tests that authenticated, TLS-encrypted requests can be made through a proxy"""
|
||||||
|
self._do_https_request_via_proxy(auth_credentials="bob:pinkponies")
|
||||||
|
|
||||||
|
def _do_https_request_via_proxy(
|
||||||
|
self,
|
||||||
|
auth_credentials: Optional[str] = None,
|
||||||
|
):
|
||||||
agent = ProxyAgent(
|
agent = ProxyAgent(
|
||||||
self.reactor,
|
self.reactor,
|
||||||
contextFactory=get_test_https_policy(),
|
contextFactory=get_test_https_policy(),
|
||||||
|
@ -278,6 +295,22 @@ class MatrixFederationAgentTests(TestCase):
|
||||||
self.assertEqual(request.method, b"CONNECT")
|
self.assertEqual(request.method, b"CONNECT")
|
||||||
self.assertEqual(request.path, b"test.com:443")
|
self.assertEqual(request.path, b"test.com:443")
|
||||||
|
|
||||||
|
# Check whether auth credentials have been supplied to the proxy
|
||||||
|
proxy_auth_header_values = request.requestHeaders.getRawHeaders(
|
||||||
|
b"Proxy-Authorization"
|
||||||
|
)
|
||||||
|
|
||||||
|
if auth_credentials is not None:
|
||||||
|
# Compute the correct header value for Proxy-Authorization
|
||||||
|
encoded_credentials = base64.b64encode(b"bob:pinkponies")
|
||||||
|
expected_header_value = b"Basic " + encoded_credentials
|
||||||
|
|
||||||
|
# Validate the header's value
|
||||||
|
self.assertIn(expected_header_value, proxy_auth_header_values)
|
||||||
|
else:
|
||||||
|
# Check that the Proxy-Authorization header has not been supplied to the proxy
|
||||||
|
self.assertIsNone(proxy_auth_header_values)
|
||||||
|
|
||||||
# tell the proxy server not to close the connection
|
# tell the proxy server not to close the connection
|
||||||
proxy_server.persistent = True
|
proxy_server.persistent = True
|
||||||
|
|
||||||
|
@ -312,6 +345,13 @@ class MatrixFederationAgentTests(TestCase):
|
||||||
self.assertEqual(request.method, b"GET")
|
self.assertEqual(request.method, b"GET")
|
||||||
self.assertEqual(request.path, b"/abc")
|
self.assertEqual(request.path, b"/abc")
|
||||||
self.assertEqual(request.requestHeaders.getRawHeaders(b"host"), [b"test.com"])
|
self.assertEqual(request.requestHeaders.getRawHeaders(b"host"), [b"test.com"])
|
||||||
|
|
||||||
|
# Check that the destination server DID NOT receive proxy credentials
|
||||||
|
proxy_auth_header_values = request.requestHeaders.getRawHeaders(
|
||||||
|
b"Proxy-Authorization"
|
||||||
|
)
|
||||||
|
self.assertIsNone(proxy_auth_header_values)
|
||||||
|
|
||||||
request.write(b"result")
|
request.write(b"result")
|
||||||
request.finish()
|
request.finish()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue