Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Allow providing credentials to http_proxy #10360

Merged
merged 5 commits into from
Jul 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/10360.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow providing credentials to `http_proxy`.
12 changes: 11 additions & 1 deletion synapse/http/proxyagent.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ def __init__(
https_proxy = proxies["https"].encode() if "https" in proxies else None
no_proxy = proxies["no"] if "no" in proxies else None

# Parse credentials from https proxy connection string if present
# Parse credentials from http and https proxy connection string if present
self.http_proxy_creds, http_proxy = parse_username_password(http_proxy)
self.https_proxy_creds, https_proxy = parse_username_password(https_proxy)

self.http_proxy_endpoint = _http_proxy_endpoint(
Expand Down Expand Up @@ -189,6 +190,15 @@ def request(self, method, uri, headers=None, bodyProducer=None):
and self.http_proxy_endpoint
and not should_skip_proxy
):
# Determine whether we need to set Proxy-Authorization headers
if self.http_proxy_creds:
# Set a Proxy-Authorization header
if headers is None:
headers = Headers()
headers.addRawHeader(
b"Proxy-Authorization",
self.http_proxy_creds.as_proxy_authorization_value(),
)
# Cache *all* connections under the same key, since we are only
# connecting to a single destination, the proxy:
pool_key = ("http-proxy", self.http_proxy_endpoint)
Expand Down
65 changes: 52 additions & 13 deletions tests/http/test_proxyagent.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,41 @@ def test_https_request_via_no_proxy_star(self):

@patch.dict(os.environ, {"http_proxy": "proxy.com:8888", "no_proxy": "unused.com"})
def test_http_request_via_proxy(self):
"""
Tests that requests can be made through a proxy.
"""
self._do_http_request_via_proxy(auth_credentials=None)

@patch.dict(
os.environ,
{"http_proxy": "bob:pinkponies@proxy.com:8888", "no_proxy": "unused.com"},
)
def test_http_request_via_proxy_with_auth(self):
"""
Tests that authenticated requests can be made through a proxy.
"""
self._do_http_request_via_proxy(auth_credentials="bob:pinkponies")

@patch.dict(os.environ, {"https_proxy": "proxy.com", "no_proxy": "unused.com"})
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_http_request_via_proxy(
self,
auth_credentials: Optional[str] = None,
):
"""
Tests that requests can be made through a proxy.
"""
agent = ProxyAgent(self.reactor, use_proxy=True)

self.reactor.lookups["proxy.com"] = "1.2.3.5"
Expand All @@ -229,6 +264,23 @@ def test_http_request_via_proxy(self):
self.assertEqual(len(http_server.requests), 1)

request = http_server.requests[0]

# 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)

self.assertEqual(request.method, b"GET")
self.assertEqual(request.path, b"http://test.com")
self.assertEqual(request.requestHeaders.getRawHeaders(b"host"), [b"test.com"])
Expand All @@ -241,19 +293,6 @@ def test_http_request_via_proxy(self):
body = self.successResultOf(treq.content(resp))
self.assertEqual(body, b"result")

@patch.dict(os.environ, {"https_proxy": "proxy.com", "no_proxy": "unused.com"})
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,
Expand Down