Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cert parameter to http transport params #703

Merged
merged 4 commits into from
Jul 29, 2022
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
2 changes: 2 additions & 0 deletions help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ FUNCTIONS
The username for authenticating over HTTP
password: str, optional
The password for authenticating over HTTP
cert: str/tuple, optional
If String, path to ssl client cert file (.pem). If Tuple, (‘cert’, ‘key’)
headers: dict, optional
Any headers to send in the request. If ``None``, the default headers are sent:
``{'Accept-Encoding': 'identity'}``. To use no headers at all,
Expand Down
20 changes: 15 additions & 5 deletions smart_open/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def open_uri(uri, mode, transport_params):
return open(uri, mode, **kwargs)


def open(uri, mode, kerberos=False, user=None, password=None, headers=None, timeout=None):
def open(uri, mode, kerberos=False, user=None, password=None, cert=None,
headers=None, timeout=None):
"""Implement streamed reader from a web site.

Supports Kerberos and Basic HTTP authentication.
Expand All @@ -66,6 +67,8 @@ def open(uri, mode, kerberos=False, user=None, password=None, headers=None, time
The username for authenticating over HTTP
password: str, optional
The password for authenticating over HTTP
cert: str/tuple, optional
if String, path to ssl client cert file (.pem). If Tuple, (‘cert’, ‘key’)
headers: dict, optional
Any headers to send in the request. If ``None``, the default headers are sent:
``{'Accept-Encoding': 'identity'}``. To use no headers at all,
Expand All @@ -80,7 +83,8 @@ def open(uri, mode, kerberos=False, user=None, password=None, headers=None, time
if mode == constants.READ_BINARY:
fobj = SeekableBufferedInputBase(
uri, mode, kerberos=kerberos,
user=user, password=password, headers=headers, timeout=timeout,
user=user, password=password, cert=cert,
headers=headers, timeout=timeout,
)
fobj.name = os.path.basename(urllib.parse.urlparse(uri).path)
return fobj
Expand All @@ -90,7 +94,8 @@ def open(uri, mode, kerberos=False, user=None, password=None, headers=None, time

class BufferedInputBase(io.BufferedIOBase):
def __init__(self, url, mode='r', buffer_size=DEFAULT_BUFFER_SIZE,
kerberos=False, user=None, password=None, headers=None, timeout=None):
kerberos=False, user=None, password=None, cert=None,
headers=None, timeout=None):
if kerberos:
import requests_kerberos
auth = requests_kerberos.HTTPKerberosAuth()
Expand All @@ -112,6 +117,7 @@ def __init__(self, url, mode='r', buffer_size=DEFAULT_BUFFER_SIZE,
self.response = requests.get(
url,
auth=auth,
cert=cert,
stream=True,
headers=self.headers,
timeout=self.timeout,
Expand Down Expand Up @@ -204,13 +210,15 @@ def readinto(self, b):
class SeekableBufferedInputBase(BufferedInputBase):
"""
Implement seekable streamed reader from a web site.
Supports Kerberos and Basic HTTP authentication.
Supports Kerberos, client certificate and Basic HTTP authentication.
"""

def __init__(self, url, mode='r', buffer_size=DEFAULT_BUFFER_SIZE,
kerberos=False, user=None, password=None, headers=None, timeout=None):
kerberos=False, user=None, password=None, cert=None,
headers=None, timeout=None):
"""
If Kerberos is True, will attempt to use the local Kerberos credentials.
If cert is set, will try to use a client certificate
Otherwise, will try to use "basic" HTTP authentication via username/password.

If none of those are set, will connect unauthenticated.
Expand All @@ -230,6 +238,7 @@ def __init__(self, url, mode='r', buffer_size=DEFAULT_BUFFER_SIZE,
else:
self.headers = headers

self.cert = cert
self.timeout = timeout

self.buffer_size = buffer_size
Expand Down Expand Up @@ -325,6 +334,7 @@ def _partial_request(self, start_pos=None):
self.url,
auth=self.auth,
stream=True,
cert=self.cert,
headers=self.headers,
timeout=self.timeout,
)
Expand Down
12 changes: 12 additions & 0 deletions smart_open/tests/test_smart_open.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,18 @@ def test_http_pass(self):
self.assertTrue('Authorization' in actual_request.headers)
self.assertTrue(actual_request.headers['Authorization'].startswith('Basic '))

@responses.activate
def test_http_cert(self):
"""Does cert parameter get passed to requests"""
responses.add(responses.GET, "http://127.0.0.1/index.html",
body='line1\nline2', stream=True)
cert_path = '/path/to/my/cert.pem'
tp = dict(cert=cert_path)
smart_open.open("http://127.0.0.1/index.html", transport_params=tp)
self.assertEqual(len(responses.calls), 1)
actual_request = responses.calls[0].request
self.assertEqual(cert_path, actual_request.req_kwargs['cert'])

@responses.activate
def _test_compressed_http(self, suffix, query):
"""Can open <suffix> via http?"""
Expand Down