Skip to content

Commit fe1ec64

Browse files
[py] Add support for http proxy authentication to remote_connection (#10358)
* Add support for proxy authentication to remote_connection Currently, urllib3.ProxyManager is used when connecting to a Selenium Server via http proxy. The proxy url is read from environment variables. When using a proxy which requires authentication, credentials are usually passed in the url like this: 'http://username:password@proxy.com:8080' urllib3.ProxyManager does not support this, but instead provides a proxy_header field which takes a basic auth header for authentication. This commit implements support for this. If credentials are given in the proxy url, they are seperated out. Then, the url without credentials and a proxy authentication header, which is created from the url credentials, are used to create the ProxyManager instance. * Fix flake8 style violations Co-authored-by: David Burns <david.burns@theautomatedtester.co.uk>
1 parent 07f5bd9 commit fe1ec64

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed

‎py/selenium/webdriver/remote/remote_connection.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,19 @@ def _get_proxy_url(self):
128128
elif self._url.startswith('http://'):
129129
return os.environ.get('http_proxy', os.environ.get('HTTP_PROXY'))
130130

131+
def _identify_http_proxy_auth(self):
132+
url = self._proxy_url
133+
url = url[url.find(":") + 3:]
134+
return True if "@" in url and len(url[:url.find('@')]) > 0 else False
135+
136+
def _seperate_http_proxy_auth(self):
137+
url = self._proxy_url
138+
protocol = url[:url.find(":") + 3]
139+
no_protocol = url[len(protocol):]
140+
auth = no_protocol[:no_protocol.find('@')]
141+
proxy_without_auth = protocol + no_protocol[len(auth) + 1:]
142+
return proxy_without_auth, auth
143+
131144
def _get_connection_manager(self):
132145
pool_manager_init_args = {
133146
'timeout': self._timeout
@@ -140,6 +153,10 @@ def _get_connection_manager(self):
140153
if self._proxy_url.lower().startswith('sock'):
141154
from urllib3.contrib.socks import SOCKSProxyManager
142155
return SOCKSProxyManager(self._proxy_url, **pool_manager_init_args)
156+
elif self._identify_http_proxy_auth():
157+
self._proxy_url, self._basic_proxy_auth = self._seperate_http_proxy_auth()
158+
pool_manager_init_args['proxy_headers'] = urllib3.make_headers(
159+
proxy_basic_auth=self._basic_proxy_auth)
143160
return urllib3.ProxyManager(self._proxy_url, **pool_manager_init_args)
144161

145162
return urllib3.PoolManager(**pool_manager_init_args)

‎py/test/unit/selenium/webdriver/remote/remote_connection_tests.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,24 @@ def test_get_proxy_url_none(mock_proxy_settings_missing):
7272
assert proxy_url is None
7373

7474

75+
def test_get_proxy_url_http_auth(mock_proxy_auth_settings):
76+
remote_connection = RemoteConnection('http://remote', keep_alive=False)
77+
proxy_url = remote_connection._get_proxy_url()
78+
raw_proxy_url, basic_auth_string = remote_connection._seperate_http_proxy_auth()
79+
assert proxy_url == "http://user:password@http_proxy.com:8080"
80+
assert raw_proxy_url == "http://http_proxy.com:8080"
81+
assert basic_auth_string == "user:password"
82+
83+
84+
def test_get_proxy_url_https_auth(mock_proxy_auth_settings):
85+
remote_connection = RemoteConnection('https://remote', keep_alive=False)
86+
proxy_url = remote_connection._get_proxy_url()
87+
raw_proxy_url, basic_auth_string = remote_connection._seperate_http_proxy_auth()
88+
assert proxy_url == "https://user:password@https_proxy.com:8080"
89+
assert raw_proxy_url == "https://https_proxy.com:8080"
90+
assert basic_auth_string == "user:password"
91+
92+
7593
def test_get_connection_manager_without_proxy(mock_proxy_settings_missing):
7694
remote_connection = RemoteConnection('http://remote', keep_alive=False)
7795
conn = remote_connection._get_connection_manager()
@@ -102,6 +120,26 @@ def test_get_connection_manager_with_proxy(mock_proxy_settings):
102120
assert conn.proxy.port == 8080
103121

104122

123+
def test_get_connection_manager_with_auth_proxy(mock_proxy_auth_settings):
124+
proxy_auth_header = urllib3.make_headers(
125+
proxy_basic_auth="user:password"
126+
)
127+
remote_connection = RemoteConnection('http://remote', keep_alive=False)
128+
conn = remote_connection._get_connection_manager()
129+
assert type(conn) == urllib3.ProxyManager
130+
assert conn.proxy.scheme == 'http'
131+
assert conn.proxy.host == 'http_proxy.com'
132+
assert conn.proxy.port == 8080
133+
assert conn.proxy_headers == proxy_auth_header
134+
remote_connection_https = RemoteConnection('https://remote', keep_alive=False)
135+
conn = remote_connection_https._get_connection_manager()
136+
assert type(conn) == urllib3.ProxyManager
137+
assert conn.proxy.scheme == 'https'
138+
assert conn.proxy.host == 'https_proxy.com'
139+
assert conn.proxy.port == 8080
140+
assert conn.proxy_headers == proxy_auth_header
141+
142+
105143
@pytest.mark.parametrize("url",
106144
["*", ".localhost", "localhost:80", "locahost", "127.0.0.1",
107145
"LOCALHOST", "LOCALHOST:80", "http://localhost", "https://localhost",
@@ -167,6 +205,16 @@ def mock_proxy_settings(monkeypatch):
167205
monkeypatch.setenv("http_proxy", http_proxy)
168206

169207

208+
@pytest.fixture(scope="function")
209+
def mock_proxy_auth_settings(monkeypatch):
210+
http_proxy = 'http://user:password@http_proxy.com:8080'
211+
https_proxy = 'https://user:password@https_proxy.com:8080'
212+
monkeypatch.setenv("HTTPS_PROXY", https_proxy)
213+
monkeypatch.setenv("HTTP_PROXY", http_proxy)
214+
monkeypatch.setenv("https_proxy", https_proxy)
215+
monkeypatch.setenv("http_proxy", http_proxy)
216+
217+
170218
@pytest.fixture(scope="function")
171219
def mock_no_proxy_settings(monkeypatch):
172220
http_proxy = 'http://http_proxy.com:8080'

0 commit comments

Comments
 (0)