Skip to content

Commit cef7a0e

Browse files
committed
Migrate HTTP requests from requests to httpx
Replaced the 'requests' library with 'httpx' for all HTTP operations in the codebase, updated SSL context handling, and adjusted method signatures and usage accordingly. Updated dependencies in pyproject.toml and poetry.lock to include httpx and certifi. Also added missing __all__ to descope/__init__.py and created tests/testutils.py.
1 parent c0ff30b commit cef7a0e

36 files changed

+1777
-1642
lines changed

.gitleaksignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,27 @@ b6a2e217be5dceb6c85332d2e193619894d3a36e:README.md:generic-api-key:1349
8989
b6a2e217be5dceb6c85332d2e193619894d3a36e:README.md:generic-api-key:1372
9090
c751b1e9df5dcb6e6b3a62601174065ebf03144f:README.md:generic-api-key:1358
9191
c751b1e9df5dcb6e6b3a62601174065ebf03144f:README.md:generic-api-key:1381
92+
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_oauth.py:jwt:204
93+
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:99
94+
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:194
95+
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_sso.py:jwt:228
96+
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:403
97+
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:516
98+
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_totp.py:jwt:140
99+
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_totp.py:jwt:309
100+
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_webauthn.py:jwt:211
101+
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_webauthn.py:jwt:419
102+
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_webauthn.py:jwt:654
103+
3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_totp.py:jwt:132
104+
3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_totp.py:jwt:289
105+
3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_webauthn.py:jwt:144
106+
3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_webauthn.py:jwt:312
107+
3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_webauthn.py:jwt:482
108+
cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:104
109+
cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_oauth.py:jwt:217
110+
cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:205
111+
cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:426
112+
cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:547
113+
cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_sso.py:jwt:238
114+
3ce967512fed2bae5e89dc9ff973e67ff9bc3084:README.md:hashicorp-tf-password:248
115+
04146e7a83d6212e407c5a46008324d646878fc1:tests/test_auth.py:jwt:257

descope/__init__.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,37 @@
3939
UserPasswordFirebase,
4040
UserPasswordPbkdf2,
4141
)
42+
43+
__all__ = [
44+
"COOKIE_DATA_NAME",
45+
"REFRESH_SESSION_COOKIE_NAME",
46+
"REFRESH_SESSION_TOKEN_NAME",
47+
"SESSION_COOKIE_NAME",
48+
"SESSION_TOKEN_NAME",
49+
"AccessKeyLoginOptions",
50+
"DeliveryMethod",
51+
"LoginOptions",
52+
"SignUpOptions",
53+
"DescopeClient",
54+
"API_RATE_LIMIT_RETRY_AFTER_HEADER",
55+
"ERROR_TYPE_API_RATE_LIMIT",
56+
"ERROR_TYPE_SERVER_ERROR",
57+
"AuthException",
58+
"RateLimitException",
59+
"AssociatedTenant",
60+
"SAMLIDPAttributeMappingInfo",
61+
"SAMLIDPGroupsMappingInfo",
62+
"SAMLIDPRoleGroupMappingInfo",
63+
"AttributeMapping",
64+
"OIDCAttributeMapping",
65+
"RoleMapping",
66+
"SSOOIDCSettings",
67+
"SSOSAMLSettings",
68+
"SSOSAMLSettingsByMetadata",
69+
"UserObj",
70+
"UserPassword",
71+
"UserPasswordBcrypt",
72+
"UserPasswordDjango",
73+
"UserPasswordFirebase",
74+
"UserPasswordPbkdf2",
75+
]

descope/auth.py

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
import platform
77
import re
88
from http import HTTPStatus
9+
import ssl
910
from threading import Lock
1011
from typing import Iterable
12+
import certifi
1113

1214
import jwt
1315

@@ -16,7 +18,7 @@
1618
except ImportError:
1719
from pkg_resources import get_distribution
1820

19-
import requests
21+
import httpx
2022
from email_validator import EmailNotValidError, validate_email
2123
from jwt import ExpiredSignatureError, ImmatureSignatureError
2224

@@ -110,6 +112,19 @@ def __init__(
110112
kid, pub_key, alg = self._validate_and_load_public_key(public_key)
111113
self.public_keys = {kid: (pub_key, alg)}
112114

115+
if skip_verify:
116+
self.ssl_ctx = False
117+
else:
118+
# Backwards compatibility with requests
119+
self.ssl_ctx = ssl.create_default_context(
120+
cafile=os.environ.get("SSL_CERT_FILE", certifi.where()),
121+
capath=os.environ.get("SSL_CERT_DIR"),
122+
)
123+
if os.environ.get("REQUESTS_CA_BUNDLE"):
124+
self.ssl_ctx.load_cert_chain(
125+
certfile=os.environ.get("REQUESTS_CA_BUNDLE")
126+
)
127+
113128
def _raise_rate_limit_exception(self, response):
114129
try:
115130
resp = response.json()
@@ -144,15 +159,15 @@ def do_get(
144159
self,
145160
uri: str,
146161
params=None,
147-
allow_redirects=None,
162+
follow_redirects=None,
148163
pswd: str | None = None,
149-
) -> requests.Response:
150-
response = requests.get(
164+
) -> httpx.Response:
165+
response = httpx.get(
151166
f"{self.base_url}{uri}",
152167
headers=self._get_default_headers(pswd),
153168
params=params,
154-
allow_redirects=allow_redirects,
155-
verify=self.secure,
169+
follow_redirects=follow_redirects,
170+
verify=self.ssl_ctx,
156171
timeout=self.timeout_seconds,
157172
)
158173
self._raise_from_response(response)
@@ -164,13 +179,13 @@ def do_post(
164179
body: dict | list[dict] | list[str] | None,
165180
params=None,
166181
pswd: str | None = None,
167-
) -> requests.Response:
168-
response = requests.post(
182+
) -> httpx.Response:
183+
response = httpx.post(
169184
f"{self.base_url}{uri}",
170185
headers=self._get_default_headers(pswd),
171186
json=body,
172-
allow_redirects=False,
173-
verify=self.secure,
187+
follow_redirects=False,
188+
verify=self.ssl_ctx,
174189
params=params,
175190
timeout=self.timeout_seconds,
176191
)
@@ -183,13 +198,13 @@ def do_patch(
183198
body: dict | list[dict] | list[str] | None,
184199
params=None,
185200
pswd: str | None = None,
186-
) -> requests.Response:
187-
response = requests.patch(
201+
) -> httpx.Response:
202+
response = httpx.patch(
188203
f"{self.base_url}{uri}",
189204
headers=self._get_default_headers(pswd),
190205
json=body,
191-
allow_redirects=False,
192-
verify=self.secure,
206+
follow_redirects=False,
207+
verify=self.ssl_ctx,
193208
params=params,
194209
timeout=self.timeout_seconds,
195210
)
@@ -198,13 +213,13 @@ def do_patch(
198213

199214
def do_delete(
200215
self, uri: str, params=None, pswd: str | None = None
201-
) -> requests.Response:
202-
response = requests.delete(
216+
) -> httpx.Response:
217+
response = httpx.delete(
203218
f"{self.base_url}{uri}",
204219
params=params,
205220
headers=self._get_default_headers(pswd),
206-
allow_redirects=False,
207-
verify=self.secure,
221+
follow_redirects=False,
222+
verify=self.ssl_ctx,
208223
timeout=self.timeout_seconds,
209224
)
210225
self._raise_from_response(response)
@@ -217,18 +232,18 @@ def do_post_with_custom_base_url(
217232
custom_base_url: str | None = None,
218233
params=None,
219234
pswd: str | None = None,
220-
) -> requests.Response:
235+
) -> httpx.Response:
221236
"""
222237
Post request with optional custom base URL.
223238
If base_url is provided, use it instead of self.base_url.
224239
"""
225240
effective_base_url = custom_base_url if custom_base_url else self.base_url
226-
response = requests.post(
241+
response = httpx.post(
227242
f"{effective_base_url}{uri}",
228243
headers=self._get_default_headers(pswd),
229244
json=body,
230-
allow_redirects=False,
231-
verify=self.secure,
245+
follow_redirects=False,
246+
verify=self.ssl_ctx,
232247
params=params,
233248
timeout=self.timeout_seconds,
234249
)
@@ -450,9 +465,9 @@ def _validate_and_load_public_key(public_key) -> tuple[str, jwt.PyJWK, str]:
450465
f"Unable to load public key {e}",
451466
)
452467

453-
def _raise_from_response(self, response):
468+
def _raise_from_response(self, response: httpx.Response):
454469
"""Raise appropriate exception from response, does nothing if response.ok is True."""
455-
if response.ok:
470+
if response.is_success:
456471
return
457472

458473
if response.status_code == HTTPStatus.TOO_MANY_REQUESTS:
@@ -466,10 +481,10 @@ def _raise_from_response(self, response):
466481

467482
def _fetch_public_keys(self) -> None:
468483
# This function called under mutex protection so no need to acquire it once again
469-
response = requests.get(
484+
response = httpx.get(
470485
f"{self.base_url}{EndpointsV2.public_key_path}/{self.project_id}",
471486
headers=self._get_default_headers(),
472-
verify=self.secure,
487+
verify=self.ssl_ctx,
473488
timeout=self.timeout_seconds,
474489
)
475490
self._raise_from_response(response)

descope/authmethod/enchantedlink.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
import requests
3+
import httpx
44

55
from descope._auth_base import AuthBase
66
from descope.auth import Auth
@@ -212,5 +212,5 @@ def _compose_get_session_body(pending_ref: str) -> dict:
212212
return {"pendingRef": pending_ref}
213213

214214
@staticmethod
215-
def _get_pending_ref_from_response(response: requests.Response) -> dict:
215+
def _get_pending_ref_from_response(response: httpx.Response) -> dict:
216216
return response.json()

descope/authmethod/webauthn.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Iterable, Optional, Union
22

3-
from requests import Response
3+
from httpx import Response
44

55
from descope._auth_base import AuthBase
66
from descope.common import (

descope/descope_client.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import Iterable
44

5-
import requests
5+
import httpx
66

77
from descope.auth import Auth # noqa: F401
88
from descope.authmethod.enchantedlink import EnchantedLink # noqa: F401
@@ -362,15 +362,15 @@ def validate_and_refresh_session(
362362
session_token, refresh_token, audience
363363
)
364364

365-
def logout(self, refresh_token: str) -> requests.Response:
365+
def logout(self, refresh_token: str) -> httpx.Response:
366366
"""
367367
Logout user from current session and revoke the refresh_token. After calling this function,
368368
you must invalidate or remove any cookies you have created.
369369
370370
Args:
371371
refresh_token (str): The refresh token
372372
373-
Return value (requests.Response): returns the response from the Descope server
373+
Return value (httpx.Response): returns the response from the Descope server
374374
375375
Raise:
376376
AuthException: Exception is raised if session is not authorized or another error occurs
@@ -385,15 +385,15 @@ def logout(self, refresh_token: str) -> requests.Response:
385385
uri = EndpointsV1.logout_path
386386
return self._auth.do_post(uri, {}, None, refresh_token)
387387

388-
def logout_all(self, refresh_token: str) -> requests.Response:
388+
def logout_all(self, refresh_token: str) -> httpx.Response:
389389
"""
390390
Logout user from all active sessions and revoke the refresh_token. After calling this function,
391391
you must invalidate or remove any cookies you have created.
392392
393393
Args:
394394
refresh_token (str): The refresh token
395395
396-
Return value (requests.Response): returns the response from the Descope server
396+
Return value (httpx.Response): returns the response from the Descope server
397397
398398
Raise:
399399
AuthException: Exception is raised if session is not authorized or another error occurs
@@ -431,7 +431,7 @@ def me(self, refresh_token: str) -> dict:
431431

432432
uri = EndpointsV1.me_path
433433
response = self._auth.do_get(
434-
uri=uri, params=None, allow_redirects=None, pswd=refresh_token
434+
uri=uri, params=None, follow_redirects=None, pswd=refresh_token
435435
)
436436
return response.json()
437437

@@ -513,7 +513,7 @@ def history(self, refresh_token: str) -> list[dict]:
513513

514514
uri = EndpointsV1.history_path
515515
response = self._auth.do_get(
516-
uri=uri, params=None, allow_redirects=None, pswd=refresh_token
516+
uri=uri, params=None, follow_redirects=None, pswd=refresh_token
517517
)
518518
return response.json()
519519

0 commit comments

Comments
 (0)