Repository: snowflakedb/snowflake-connector-python

Issue:
SNOW-810810: AttributeError when using SOCKS5 Proxy
Please answer these questions before submitting your issue. Thanks!

1. What version of Python are you using?

```
Python 3.9.16 (main, May  8 2023, 20:48:22) 
[GCC 11.3.0]
```

2. What operating system and processor architecture are you using?

```
Linux-5.19.0-41-generic-x86_64-with-glibc2.35
```

3. What are the component versions in the environment (`pip freeze`)?

   Replace with the output of `python -m pip freeze`

4. What did you do?

Attempt to connect to Snowflake through a SOCKS5 proxy:

Use a minimal SOCKS5 proxy. From a second computer (`computer2`), open a SOCKS5 proxy on `computer1`

```
user@computer2:~$ ssh -R 1080 user@computer1.local
```

On `computer1`:

```
HTTPS_PROXY="socks5://localhost:1080" python3 -c "import snowflake.connector; snowflake.connector.connect(...)"
```

5. What did you expect to see?

The above test results in:

```
  File "/home/stan/Ontwikkeling/snowflake-connector-python/src/snowflake/connector/network.py", line 1042, in _request_exec
    raw_ret = session.request(
  File "/home/stan/Ontwikkeling/snowflake-connector-python/src/snowflake/connector/vendored/requests/sessions.py", line 587, in request
    resp = self.send(prep, **send_kwargs)
  File "/home/stan/Ontwikkeling/snowflake-connector-python/src/snowflake/connector/vendored/requests/sessions.py", line 701, in send
    r = adapter.send(request, **kwargs)
  File "/home/stan/Ontwikkeling/snowflake-connector-python/src/snowflake/connector/vendored/requests/adapters.py", line 455, in send
    conn = self.get_connection(request.url, proxies)
  File "/home/stan/Ontwikkeling/snowflake-connector-python/src/snowflake/connector/network.py", line 274, in get_connection
    proxy_manager.proxy_headers["Host"] = parsed_url.hostname
AttributeError: 'SOCKSProxyManager' object has no attribute 'proxy_headers'
```

This is because the [SOCKSProxyManager](https://github.com/snowflakedb/snowflake-connector-python/blob/97c25993eb3fb43e0df634fd41f98f9e9ebf20bd/src/snowflake/connector/vendored/urllib3/contrib/socks.py#L159) is a subclass of [PoolManager](https://github.com/snowflakedb/snowflake-connector-python/blob/97c25993eb3fb43e0df634fd41f98f9e9ebf20bd/src/snowflake/connector/vendored/urllib3/poolmanager.py#L140) Which does not have the property `proxy_headers`. It does have a property `headers`.

I modified the code to not set the header when using a SOCKS5 proxy, which seems to work. The connection succeeds over the SOCKS5 proxy. It also works if I set the `headers` property instead of the `proxy_headers` property.

I offer a pull request for the first solution (not setting the `proxy_headers` when using a SOCKS5 proxy), but I leave it up to someone more knowledgable to decide whether this is the desired solution to this.



Discussion:
github-actions[bot]: **CLA Assistant Lite bot:** <br/>Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our [Contributor License Agreement](https://github.com/Snowflake-Labs/CLA/blob/main/README.md) before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.<br/>
   - - -
   I have read the CLA Document and I hereby sign the CLA
   - - -
   <sub>You can retrigger this bot by commenting **recheck** in this Pull Request</sub>
stan-janssen: I have read the CLA Document and I hereby sign the CLA
sfc-gh-yixie: @sfc-gh-jdu @sfc-gh-aling Can you review if this PR makes sense to you?

Patch:
diff --git a/DESCRIPTION.md b/DESCRIPTION.md
index 2ceb3382..91470757 100644
--- a/DESCRIPTION.md
+++ b/DESCRIPTION.md
@@ -12,6 +12,7 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne
 
   - Fixed a bug in retry logic for okta authentication to refresh token.
   - Support `RSAPublicKey` when constructing `AuthByKeyPair` in addition to raw bytes.
+  - Fixed a bug when connecting through SOCKS5 proxy, the attribute `proxy_header` is missing on `SOCKSProxyManager`.
 
 - v3.1.0(July 31,2023)
 
diff --git a/src/snowflake/connector/network.py b/src/snowflake/connector/network.py
index 7d041736..4140e707 100644
--- a/src/snowflake/connector/network.py
+++ b/src/snowflake/connector/network.py
@@ -111,6 +111,7 @@ from .vendored.requests.exceptions import (
 )
 from .vendored.requests.utils import prepend_scheme_if_needed, select_proxy
 from .vendored.urllib3.exceptions import ProtocolError
+from .vendored.urllib3.poolmanager import ProxyManager
 from .vendored.urllib3.util.url import parse_url
 
 if TYPE_CHECKING:
@@ -268,8 +269,14 @@ class ProxySupportAdapter(HTTPAdapter):
                 )
             proxy_manager = self.proxy_manager_for(proxy)
 
-            # Add Host to proxy header SNOW-232777
-            proxy_manager.proxy_headers["Host"] = parsed_url.hostname
+            if isinstance(proxy_manager, ProxyManager):
+                # Add Host to proxy header SNOW-232777
+                proxy_manager.proxy_headers["Host"] = parsed_url.hostname
+            else:
+                logger.debug(
+                    f"Unable to set 'Host' to proxy manager of type {type(proxy_manager)} as"
+                    f" it does not have attribute 'proxy_headers'."
+                )
             conn = proxy_manager.connection_from_url(url)
         else:
             # Only scheme should be lower case


Test patch:
diff --git a/test/unit/test_proxies.py b/test/unit/test_proxies.py
index 2880f0ad..55aff685 100644
--- a/test/unit/test_proxies.py
+++ b/test/unit/test_proxies.py
@@ -5,7 +5,14 @@
 
 from __future__ import annotations
 
+import logging
 import os
+import unittest.mock
+
+import pytest
+
+import snowflake.connector
+from snowflake.connector.errors import OperationalError
 
 
 def test_set_proxies():
@@ -31,3 +38,60 @@ def test_set_proxies():
     # NOTE environment variable is set if the proxy parameter is specified.
     del os.environ["HTTP_PROXY"]
     del os.environ["HTTPS_PROXY"]
+
+
+@pytest.mark.skipolddriver
+def test_socks_5_proxy_missing_proxy_header_attribute(caplog):
+    from snowflake.connector.vendored.urllib3.poolmanager import ProxyManager
+
+    os.environ["HTTPS_PROXY"] = "socks5://localhost:8080"
+
+    class MockSOCKSProxyManager:
+        def __init__(self):
+            pass
+
+        def connection_from_url(self, url):
+            pass
+
+    def mock_proxy_manager_for_url_no_header(*args, **kwargs):
+        return MockSOCKSProxyManager()
+
+    def mock_proxy_manager_for_url_wiht_header(*args, **kwargs):
+        return ProxyManager("testurl")
+
+    # connection
+    caplog.set_level(logging.DEBUG, "snowflake.connector")
+
+    # bad path
+    with unittest.mock.patch(
+        "snowflake.connector.network.ProxySupportAdapter.proxy_manager_for",
+        mock_proxy_manager_for_url_no_header,
+    ):
+        with pytest.raises(OperationalError):
+            snowflake.connector.connect(
+                account="testaccount",
+                user="testuser",
+                password="testpassword",
+                database="TESTDB",
+                warehouse="TESTWH",
+            )
+    assert "Unable to set 'Host' to proxy manager of type" in caplog.text
+
+    caplog.clear()
+
+    # happy path
+    with unittest.mock.patch(
+        "snowflake.connector.network.ProxySupportAdapter.proxy_manager_for",
+        mock_proxy_manager_for_url_wiht_header,
+    ):
+        with pytest.raises(OperationalError):
+            snowflake.connector.connect(
+                account="testaccount",
+                user="testuser",
+                password="testpassword",
+                database="TESTDB",
+                warehouse="TESTWH",
+            )
+    assert "Unable to set 'Host' to proxy manager of type" not in caplog.text
+
+    del os.environ["HTTPS_PROXY"]