Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
24 changes: 24 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,27 @@ b6a2e217be5dceb6c85332d2e193619894d3a36e:README.md:generic-api-key:1349
b6a2e217be5dceb6c85332d2e193619894d3a36e:README.md:generic-api-key:1372
c751b1e9df5dcb6e6b3a62601174065ebf03144f:README.md:generic-api-key:1358
c751b1e9df5dcb6e6b3a62601174065ebf03144f:README.md:generic-api-key:1381
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_oauth.py:jwt:204
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:99
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:194
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_sso.py:jwt:228
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:403
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:516
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_totp.py:jwt:140
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_totp.py:jwt:309
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_webauthn.py:jwt:211
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_webauthn.py:jwt:419
7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_webauthn.py:jwt:654
3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_totp.py:jwt:132
3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_totp.py:jwt:289
3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_webauthn.py:jwt:144
3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_webauthn.py:jwt:312
3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_webauthn.py:jwt:482
cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:104
cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_oauth.py:jwt:217
cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:205
cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:426
cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:547
cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_sso.py:jwt:238
3ce967512fed2bae5e89dc9ff973e67ff9bc3084:README.md:hashicorp-tf-password:248
04146e7a83d6212e407c5a46008324d646878fc1:tests/test_auth.py:jwt:257
34 changes: 34 additions & 0 deletions descope/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,37 @@
UserPasswordFirebase,
UserPasswordPbkdf2,
)

__all__ = [
"COOKIE_DATA_NAME",
"REFRESH_SESSION_COOKIE_NAME",
"REFRESH_SESSION_TOKEN_NAME",
"SESSION_COOKIE_NAME",
"SESSION_TOKEN_NAME",
"AccessKeyLoginOptions",
"DeliveryMethod",
"LoginOptions",
"SignUpOptions",
"DescopeClient",
"API_RATE_LIMIT_RETRY_AFTER_HEADER",
"ERROR_TYPE_API_RATE_LIMIT",
"ERROR_TYPE_SERVER_ERROR",
"AuthException",
"RateLimitException",
"AssociatedTenant",
"SAMLIDPAttributeMappingInfo",
"SAMLIDPGroupsMappingInfo",
"SAMLIDPRoleGroupMappingInfo",
"AttributeMapping",
"OIDCAttributeMapping",
"RoleMapping",
"SSOOIDCSettings",
"SSOSAMLSettings",
"SSOSAMLSettingsByMetadata",
"UserObj",
"UserPassword",
"UserPasswordBcrypt",
"UserPasswordDjango",
"UserPasswordFirebase",
"UserPasswordPbkdf2",
]
78 changes: 47 additions & 31 deletions descope/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import platform
import re
from http import HTTPStatus
import ssl
from threading import Lock
from typing import Iterable
import certifi

import jwt

Expand All @@ -16,7 +18,7 @@
except ImportError:
from pkg_resources import get_distribution

import requests
import httpx
from email_validator import EmailNotValidError, validate_email
from jwt import ExpiredSignatureError, ImmatureSignatureError

Expand Down Expand Up @@ -110,6 +112,20 @@
kid, pub_key, alg = self._validate_and_load_public_key(public_key)
self.public_keys = {kid: (pub_key, alg)}

self.client_timeout = timeout_seconds
self.client_verify: bool | ssl.SSLContext = False
if not skip_verify:
# Backwards compatibility with requests
ssl_ctx = ssl.create_default_context(
cafile=os.environ.get("SSL_CERT_FILE", certifi.where()),
capath=os.environ.get("SSL_CERT_DIR"),
)
if os.environ.get("REQUESTS_CA_BUNDLE"):
# ignore - is valid string
ssl_ctx.load_cert_chain(certfile=os.environ.get("REQUESTS_CA_BUNDLE")) # type: ignore[arg-type]

Check warning on line 125 in descope/auth.py

View workflow job for this annotation

GitHub Actions / Coverage

This line has no coverage
self.client_verify = ssl_ctx
# ignore - is valid string

def _raise_rate_limit_exception(self, response):
try:
resp = response.json()
Expand Down Expand Up @@ -144,16 +160,16 @@
self,
uri: str,
params=None,
allow_redirects=None,
follow_redirects=None,
pswd: str | None = None,
) -> requests.Response:
response = requests.get(
) -> httpx.Response:
response = httpx.get(
f"{self.base_url}{uri}",
headers=self._get_default_headers(pswd),
params=params,
allow_redirects=allow_redirects,
verify=self.secure,
timeout=self.timeout_seconds,
follow_redirects=follow_redirects,
verify=self.client_verify,
timeout=self.client_timeout,
)
self._raise_from_response(response)
return response
Expand All @@ -164,15 +180,15 @@
body: dict | list[dict] | list[str] | None,
params=None,
pswd: str | None = None,
) -> requests.Response:
response = requests.post(
) -> httpx.Response:
response = httpx.post(
f"{self.base_url}{uri}",
headers=self._get_default_headers(pswd),
json=body,
allow_redirects=False,
verify=self.secure,
follow_redirects=False,
verify=self.client_verify,
params=params,
timeout=self.timeout_seconds,
timeout=self.client_timeout,
)
self._raise_from_response(response)
return response
Expand All @@ -183,29 +199,29 @@
body: dict | list[dict] | list[str] | None,
params=None,
pswd: str | None = None,
) -> requests.Response:
response = requests.patch(
) -> httpx.Response:
response = httpx.patch(
f"{self.base_url}{uri}",
headers=self._get_default_headers(pswd),
json=body,
allow_redirects=False,
verify=self.secure,
follow_redirects=False,
verify=self.client_verify,
params=params,
timeout=self.timeout_seconds,
timeout=self.client_timeout,
)
self._raise_from_response(response)
return response

def do_delete(
self, uri: str, params=None, pswd: str | None = None
) -> requests.Response:
response = requests.delete(
) -> httpx.Response:
response = httpx.delete(
f"{self.base_url}{uri}",
params=params,
headers=self._get_default_headers(pswd),
allow_redirects=False,
verify=self.secure,
timeout=self.timeout_seconds,
follow_redirects=False,
verify=self.client_verify,
timeout=self.client_timeout,
)
self._raise_from_response(response)
return response
Expand All @@ -217,20 +233,20 @@
custom_base_url: str | None = None,
params=None,
pswd: str | None = None,
) -> requests.Response:
) -> httpx.Response:
"""
Post request with optional custom base URL.
If base_url is provided, use it instead of self.base_url.
"""
effective_base_url = custom_base_url if custom_base_url else self.base_url
response = requests.post(
response = httpx.post(
f"{effective_base_url}{uri}",
headers=self._get_default_headers(pswd),
json=body,
allow_redirects=False,
verify=self.secure,
follow_redirects=False,
verify=self.client_verify,
params=params,
timeout=self.timeout_seconds,
timeout=self.client_timeout,
)
self._raise_from_response(response)
return response
Expand Down Expand Up @@ -450,9 +466,9 @@
f"Unable to load public key {e}",
)

def _raise_from_response(self, response):
def _raise_from_response(self, response: httpx.Response):
"""Raise appropriate exception from response, does nothing if response.ok is True."""
if response.ok:
if response.is_success:
return

if response.status_code == HTTPStatus.TOO_MANY_REQUESTS:
Expand All @@ -466,10 +482,10 @@

def _fetch_public_keys(self) -> None:
# This function called under mutex protection so no need to acquire it once again
response = requests.get(
response = httpx.get(
f"{self.base_url}{EndpointsV2.public_key_path}/{self.project_id}",
headers=self._get_default_headers(),
verify=self.secure,
verify=self.client_verify,
timeout=self.timeout_seconds,
)
self._raise_from_response(response)
Expand Down
6 changes: 4 additions & 2 deletions descope/authmethod/enchantedlink.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations

import requests
from typing import TYPE_CHECKING

if TYPE_CHECKING:
import httpx

Check warning on line 6 in descope/authmethod/enchantedlink.py

View workflow job for this annotation

GitHub Actions / Coverage

This line has no coverage
from descope._auth_base import AuthBase
from descope.auth import Auth
from descope.common import (
Expand Down Expand Up @@ -212,5 +214,5 @@
return {"pendingRef": pending_ref}

@staticmethod
def _get_pending_ref_from_response(response: requests.Response) -> dict:
def _get_pending_ref_from_response(response: httpx.Response) -> dict:
return response.json()
2 changes: 1 addition & 1 deletion descope/authmethod/webauthn.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Iterable, Optional, Union

from requests import Response
from httpx import Response

from descope._auth_base import AuthBase
from descope.common import (
Expand Down
14 changes: 7 additions & 7 deletions descope/descope_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import Iterable

import requests
import httpx

from descope.auth import Auth # noqa: F401
from descope.authmethod.enchantedlink import EnchantedLink # noqa: F401
Expand Down Expand Up @@ -362,15 +362,15 @@ def validate_and_refresh_session(
session_token, refresh_token, audience
)

def logout(self, refresh_token: str) -> requests.Response:
def logout(self, refresh_token: str) -> httpx.Response:
"""
Logout user from current session and revoke the refresh_token. After calling this function,
you must invalidate or remove any cookies you have created.

Args:
refresh_token (str): The refresh token

Return value (requests.Response): returns the response from the Descope server
Return value (httpx.Response): returns the response from the Descope server

Raise:
AuthException: Exception is raised if session is not authorized or another error occurs
Expand All @@ -385,15 +385,15 @@ def logout(self, refresh_token: str) -> requests.Response:
uri = EndpointsV1.logout_path
return self._auth.do_post(uri, {}, None, refresh_token)

def logout_all(self, refresh_token: str) -> requests.Response:
def logout_all(self, refresh_token: str) -> httpx.Response:
"""
Logout user from all active sessions and revoke the refresh_token. After calling this function,
you must invalidate or remove any cookies you have created.

Args:
refresh_token (str): The refresh token

Return value (requests.Response): returns the response from the Descope server
Return value (httpx.Response): returns the response from the Descope server

Raise:
AuthException: Exception is raised if session is not authorized or another error occurs
Expand Down Expand Up @@ -431,7 +431,7 @@ def me(self, refresh_token: str) -> dict:

uri = EndpointsV1.me_path
response = self._auth.do_get(
uri=uri, params=None, allow_redirects=None, pswd=refresh_token
uri=uri, params=None, follow_redirects=None, pswd=refresh_token
)
return response.json()

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

uri = EndpointsV1.history_path
response = self._auth.do_get(
uri=uri, params=None, allow_redirects=None, pswd=refresh_token
uri=uri, params=None, follow_redirects=None, pswd=refresh_token
)
return response.json()

Expand Down
Loading
Loading