Skip to content

Commit f3f8729

Browse files
stanislau-arkhipenkaStanislau ArkhipenkaBrent1LT
authored
Add ca_certs argument for oauth and dropbox client (dropbox#385)
* Add ca_certs argument for oauth and dropbox client * Replace FileNotFoundError with AttributeError New error better represents actual problem (CA not set) And also compatible with python2.7 Co-authored-by: Stanislau Arkhipenka <[email protected]@gmail.com> Co-authored-by: Brent Bumann <[email protected]>
1 parent 3fa08e5 commit f3f8729

File tree

4 files changed

+54
-14
lines changed

4 files changed

+54
-14
lines changed

dropbox/dropbox_client.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def __init__(self, request_id, obj_result):
9797
self.request_id = request_id
9898
self.obj_result = obj_result
9999

100-
def create_session(max_connections=8, proxies=None):
100+
def create_session(max_connections=8, proxies=None, ca_certs=None):
101101
"""
102102
Creates a session object that can be used by multiple :class:`Dropbox` and
103103
:class:`DropboxTeam` instances. This lets you share a connection pool
@@ -112,7 +112,7 @@ def create_session(max_connections=8, proxies=None):
112112
for more details.
113113
"""
114114
# We only need as many pool_connections as we have unique hostnames.
115-
session = pinned_session(pool_maxsize=max_connections)
115+
session = pinned_session(pool_maxsize=max_connections, ca_certs=ca_certs)
116116
if proxies:
117117
session.proxies = proxies
118118
return session
@@ -151,7 +151,8 @@ def __init__(self,
151151
oauth2_access_token_expiration=None,
152152
app_key=None,
153153
app_secret=None,
154-
scope=None,):
154+
scope=None,
155+
ca_certs=None):
155156
"""
156157
:param str oauth2_access_token: OAuth2 access token for making client
157158
requests.
@@ -180,6 +181,8 @@ def __init__(self,
180181
Not required if PKCE was used to authorize the token
181182
:param list scope: list of scopes to request on refresh. If left blank,
182183
refresh will request all available scopes for application
184+
:param str ca_certs: path to CA certificate. If left blank, default certificate location \
185+
will be used
183186
"""
184187

185188
if not (oauth2_access_token or oauth2_refresh_token or (app_key and app_secret)):
@@ -212,7 +215,7 @@ def __init__(self,
212215
.format(session))
213216
self._session = session
214217
else:
215-
self._session = create_session()
218+
self._session = create_session(ca_certs=ca_certs)
216219
self._headers = headers
217220

218221
base_user_agent = 'OfficialDropboxPythonSDKv2/' + __version__

dropbox/oauth.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ def __repr__(self):
119119
class DropboxOAuth2FlowBase(object):
120120

121121
def __init__(self, consumer_key, consumer_secret=None, locale=None, token_access_type=None,
122-
scope=None, include_granted_scopes=None, use_pkce=False, timeout=DEFAULT_TIMEOUT):
122+
scope=None, include_granted_scopes=None, use_pkce=False, timeout=DEFAULT_TIMEOUT,
123+
ca_certs=None):
123124
if scope is not None and (len(scope) == 0 or not isinstance(scope, list)):
124125
raise BadInputException("Scope list must be of type list")
125126
if token_access_type is not None and token_access_type not in TOKEN_ACCESS_TYPES:
@@ -134,7 +135,7 @@ def __init__(self, consumer_key, consumer_secret=None, locale=None, token_access
134135
self.consumer_secret = consumer_secret
135136
self.locale = locale
136137
self.token_access_type = token_access_type
137-
self.requests_session = pinned_session()
138+
self.requests_session = pinned_session(ca_certs=ca_certs)
138139
self.scope = scope
139140
self.include_granted_scopes = include_granted_scopes
140141
self._timeout = timeout
@@ -273,7 +274,8 @@ class DropboxOAuth2FlowNoRedirect(DropboxOAuth2FlowBase):
273274
"""
274275

275276
def __init__(self, consumer_key, consumer_secret=None, locale=None, token_access_type=None,
276-
scope=None, include_granted_scopes=None, use_pkce=False, timeout=DEFAULT_TIMEOUT): # noqa: E501;
277+
scope=None, include_granted_scopes=None, use_pkce=False, timeout=DEFAULT_TIMEOUT,
278+
ca_certs=None): # noqa: E501;
277279
"""
278280
Construct an instance.
279281
@@ -306,6 +308,8 @@ def __init__(self, consumer_key, consumer_secret=None, locale=None, token_access
306308
client will wait for any single packet from the server. After the timeout the client
307309
will give up on connection. If `None`, client will wait forever. Defaults
308310
to 100 seconds.
311+
:param str ca_cert: path to CA certificate. If left blank, default certificate location \
312+
will be used
309313
"""
310314
super(DropboxOAuth2FlowNoRedirect, self).__init__(
311315
consumer_key=consumer_key,
@@ -315,7 +319,8 @@ def __init__(self, consumer_key, consumer_secret=None, locale=None, token_access
315319
scope=scope,
316320
include_granted_scopes=include_granted_scopes,
317321
use_pkce=use_pkce,
318-
timeout=timeout
322+
timeout=timeout,
323+
ca_certs=ca_certs
319324
)
320325

321326
def start(self):
@@ -360,7 +365,8 @@ class DropboxOAuth2Flow(DropboxOAuth2FlowBase):
360365
def __init__(self, consumer_key, redirect_uri, session,
361366
csrf_token_session_key, consumer_secret=None, locale=None,
362367
token_access_type=None, scope=None,
363-
include_granted_scopes=None, use_pkce=False, timeout=DEFAULT_TIMEOUT):
368+
include_granted_scopes=None, use_pkce=False, timeout=DEFAULT_TIMEOUT,
369+
ca_certs=None):
364370
"""
365371
Construct an instance.
366372
@@ -399,6 +405,8 @@ def __init__(self, consumer_key, redirect_uri, session,
399405
:param Optional[float] timeout: Maximum duration in seconds that client will wait for any
400406
single packet from the server. After the timeout the client will give up on connection.
401407
If `None`, client will wait forever. Defaults to 100 seconds.
408+
:param str ca_cert: path to CA certificate. If left blank, default certificate location \
409+
will be used
402410
"""
403411

404412
super(DropboxOAuth2Flow, self).__init__(
@@ -409,7 +417,8 @@ def __init__(self, consumer_key, redirect_uri, session,
409417
scope=scope,
410418
include_granted_scopes=include_granted_scopes,
411419
use_pkce=use_pkce,
412-
timeout=timeout
420+
timeout=timeout,
421+
ca_certs=ca_certs
413422
)
414423
self.redirect_uri = redirect_uri
415424
self.session = session

dropbox/session.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,33 @@
3232
# This is the default longest time we'll block on receiving data from the server
3333
DEFAULT_TIMEOUT = 100
3434

35-
_TRUSTED_CERT_FILE = pkg_resources.resource_filename(__name__, 'trusted-certs.crt')
35+
try:
36+
_TRUSTED_CERT_FILE = pkg_resources.resource_filename(__name__, 'trusted-certs.crt')
37+
except NotImplementedError: # Package is used inside python archive
38+
_TRUSTED_CERT_FILE = None
39+
3640

3741
# TODO(kelkabany): We probably only want to instantiate this once so that even
3842
# if multiple Dropbox objects are instantiated, they all share the same pool.
3943
class _SSLAdapter(HTTPAdapter):
44+
45+
def __init__(self, *args, **kwargs):
46+
self._ca_certs = kwargs.pop("ca_certs", None) or _TRUSTED_CERT_FILE
47+
if not self._ca_certs:
48+
raise AttributeError("CA certificate not set")
49+
super(_SSLAdapter, self).__init__(*args, **kwargs)
50+
4051
def init_poolmanager(self, connections, maxsize, block=False, **_):
4152
self.poolmanager = PoolManager(
4253
num_pools=connections,
4354
maxsize=maxsize,
4455
block=block,
4556
cert_reqs=ssl.CERT_REQUIRED,
46-
ca_certs=_TRUSTED_CERT_FILE,
57+
ca_certs=self._ca_certs,
4758
)
4859

49-
def pinned_session(pool_maxsize=8):
50-
http_adapter = _SSLAdapter(pool_connections=4, pool_maxsize=pool_maxsize)
60+
def pinned_session(pool_maxsize=8, ca_certs=None):
61+
http_adapter = _SSLAdapter(pool_connections=4, pool_maxsize=pool_maxsize, ca_certs=ca_certs)
5162
_session = requests.session()
5263
_session.mount('https://', http_adapter)
5364

test/unit/test_dropbox_unit.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
TEAM_MEMBER_ID = 'dummy_team_member_id'
2323
SCOPE_LIST = ['files.metadata.read', 'files.metadata.write']
2424
EXPIRATION = datetime.utcnow() + timedelta(seconds=EXPIRES_IN)
25+
CA_CERTS = "/dummy/path/ca.crt"
2526

2627
EXPIRATION_BUFFER = timedelta(minutes=5)
2728

@@ -85,6 +86,10 @@ def test_authorization_url(self):
8586
assert 'code_challenge_method' not in authorization_url
8687
assert 'code_challenge' not in authorization_url
8788

89+
def test_authorization_with_ca_certs(self):
90+
DropboxOAuth2Flow(APP_KEY, APP_SECRET, 'http://localhost/dummy', 'dummy_session',
91+
'dbx-auth-csrf-token', ca_certs=CA_CERTS)
92+
8893
def test_authorization_url_legacy_default(self):
8994
flow_obj = DropboxOAuth2Flow(APP_KEY, APP_SECRET, 'http://localhost/dummy',
9095
'dummy_session', 'dbx-auth-csrf-token')
@@ -233,6 +238,14 @@ def session_instance(self, mocker):
233238
mocker.patch.object(session_obj, 'post', return_value=post_response)
234239
return session_obj
235240

241+
@pytest.fixture(scope='function')
242+
def session_instance_with_ca_certs(self, mocker):
243+
session_obj = create_session(ca_certs=CA_CERTS)
244+
post_response = mock.MagicMock(status_code=200)
245+
post_response.json.return_value = {"access_token": ACCESS_TOKEN, "expires_in": EXPIRES_IN}
246+
mocker.patch.object(session_obj, 'post', return_value=post_response)
247+
return session_obj
248+
236249
@pytest.fixture(scope='function')
237250
def invalid_grant_session_instance(self, mocker):
238251
session_obj = create_session()
@@ -366,6 +379,10 @@ def test_check_refresh_with_invalid_grant(self, invalid_grant_session_instance):
366379
assert invalid_grant_session_instance.post.call_count == 1
367380
assert e.error.is_invalid_access_token()
368381

382+
def test_check_Dropbox_with_ca_certs(self, session_instance_with_ca_certs):
383+
Dropbox(oauth2_access_token=ACCESS_TOKEN, oauth2_access_token_expiration=EXPIRATION,
384+
session=session_instance_with_ca_certs)
385+
369386
def test_team_client_refresh(self, session_instance):
370387
dbx = DropboxTeam(oauth2_refresh_token=REFRESH_TOKEN,
371388
app_key=APP_KEY,

0 commit comments

Comments
 (0)