usso 0.13.0__py3-none-any.whl → 0.15.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
usso/api.py ADDED
@@ -0,0 +1,102 @@
1
+ import requests
2
+ from singleton import Singleton
3
+
4
+ from usso.core import Usso
5
+
6
+
7
+ class UssoAPI(metaclass=Singleton):
8
+ def __init__(
9
+ self,
10
+ url: str = "https://api.usso.io",
11
+ api_key: str = None,
12
+ refresh_token: str = None,
13
+ ):
14
+ self.url = url
15
+ assert (
16
+ api_key or refresh_token
17
+ ), "Either api_key or refresh_token must be provided"
18
+ self.api_key = api_key
19
+ self.refresh_token = refresh_token
20
+ self.access_token = None
21
+
22
+ def refresh(self):
23
+ if not self.refresh_token:
24
+ return
25
+
26
+ url = f"{self.url}/auth/refresh"
27
+
28
+ if self.refresh_token:
29
+ headers = {"Authorization": f"Bearer {self.refresh_token}"}
30
+
31
+ resp = requests.post(url, headers=headers)
32
+ self.access_token = resp.json().get("access_token")
33
+
34
+ def _access_valid(self):
35
+ if not self.access_token:
36
+ return False
37
+
38
+ user_data = USSO(
39
+ jwks_url=f"{self.url}/website/jwks.json?"
40
+ ).user_data_from_token(self.access_token)
41
+ if user_data:
42
+ return True
43
+ return False
44
+
45
+ def _request(self, method="get", endpoint: str = "", data: dict = None):
46
+ url = f"{self.url}/{endpoint}"
47
+ headers = {}
48
+ if self.api_key:
49
+ headers["x-api-key"] = self.api_key
50
+ elif self.refresh_token:
51
+ if not self.access_token:
52
+ self.refresh()
53
+ headers["Authorization"] = f"Bearer {self.access_token}"
54
+
55
+ resp = requests.request(method, url, headers=headers, json=data)
56
+ return resp.json()
57
+
58
+ def get_users(self):
59
+ return self._request(endpoint="website/users/")
60
+
61
+ def get_user(self, user_id: str):
62
+ return self._request(endpoint=f"website/users/{user_id}/")
63
+
64
+ def get_user_credentials(self, user_id: str):
65
+ return self._request(endpoint=f"website/users/{user_id}/credentials/")
66
+
67
+ def get_user_by_credentials(self, credentials: dict):
68
+ return self._request(endpoint="website/users/credentials/", data=credentials)
69
+
70
+ def create_user(self, user_data: dict):
71
+ return self._request(method="post", endpoint="website/users/", data=user_data)
72
+
73
+ def create_user_credentials(self, user_id: str, credentials: dict):
74
+ return self._request(
75
+ method="post",
76
+ endpoint=f"website/users/{user_id}/credentials/",
77
+ data=credentials,
78
+ )
79
+
80
+ def create_user_by_credentials(
81
+ self, user_data: dict, credentials: dict | None = None
82
+ ):
83
+ if credentials:
84
+ user_data["authenticators"] = [credentials]
85
+ return self._request(method="post", endpoint="website/users/", data=user_data)
86
+
87
+ def get_user_payload(self, user_id: str):
88
+ return self._request(endpoint=f"website/users/{user_id}/payload/")
89
+
90
+ def update_user_payload(self, user_id: str, payload: dict):
91
+ return self._request(
92
+ method="patch",
93
+ endpoint=f"website/users/{user_id}/payload/",
94
+ data=payload,
95
+ )
96
+
97
+ def set_user_payload(self, user_id: str, payload: dict):
98
+ return self._request(
99
+ method="put",
100
+ endpoint=f"website/users/{user_id}/payload/",
101
+ data=payload,
102
+ )
usso/core.py CHANGED
@@ -3,6 +3,7 @@ import logging
3
3
  import uuid
4
4
  from functools import lru_cache
5
5
  from typing import Optional, Tuple
6
+ from singleton import Singleton
6
7
 
7
8
  import jwt
8
9
  from pydantic import BaseModel
@@ -39,59 +40,59 @@ class UserData(BaseModel):
39
40
  return b64tools.b64_encode_uuid_strip(self.uid)
40
41
 
41
42
 
42
- def get_authorization_scheme_param(
43
- authorization_header_value: Optional[str],
44
- ) -> Tuple[str, str]:
45
- if not authorization_header_value:
46
- return "", ""
47
- scheme, _, param = authorization_header_value.partition(" ")
48
- return scheme, param
49
-
50
-
51
- def user_data_from_token(token: str, **kwargs) -> UserData | None:
52
- """Return the user associated with a token value."""
53
- try:
54
- header = jwt.get_unverified_header(token)
55
- jwks_url = header["jwk_url"]
56
- assert os.getenv("USSO_JWKS_URL") == jwks_url
57
- jwks_client = get_jwks_keys(jwks_url)
58
- # , headers=optional_custom_headers)
59
- signing_key = jwks_client.get_signing_key_from_jwt(token)
60
- decoded = jwt.decode(
61
- token,
62
- signing_key.key,
63
- algorithms=["RS256"],
64
- )
65
- decoded["token"] = token
66
-
67
- except jwt.exceptions.ExpiredSignatureError:
68
- if kwargs.get("raise_exception"):
69
- raise USSOException(status_code=401, error="expired_signature")
70
- return None
71
- except jwt.exceptions.InvalidSignatureError:
72
- if kwargs.get("raise_exception"):
73
- raise USSOException(status_code=401, error="invalid_signature")
74
- return None
75
- except jwt.exceptions.InvalidTokenError:
76
- if kwargs.get("raise_exception"):
77
- raise USSOException(
78
- status_code=401,
79
- error="invalid_token",
43
+ class Usso(metaclass=Singleton):
44
+ def __init__(self, jwks_url: str):
45
+ self.jwks_url = jwks_url
46
+
47
+ @lru_cache
48
+ def get_jwks_keys(self):
49
+ return jwt.PyJWKClient(self.jwks_url)
50
+
51
+ def get_authorization_scheme_param(self,
52
+ authorization_header_value: Optional[str],
53
+ ) -> Tuple[str, str]:
54
+ if not authorization_header_value:
55
+ return "", ""
56
+ scheme, _, param = authorization_header_value.partition(" ")
57
+ return scheme, param
58
+
59
+ def user_data_from_token(self, token: str, **kwargs) -> UserData | None:
60
+ """Return the user associated with a token value."""
61
+ try:
62
+ # header = jwt.get_unverified_header(token)
63
+ # jwks_url = header["jwk_url"]
64
+ jwks_client = self.get_jwks_keys()
65
+ signing_key = jwks_client.get_signing_key_from_jwt(token)
66
+ decoded = jwt.decode(
67
+ token,
68
+ signing_key.key,
69
+ algorithms=["RS256"],
80
70
  )
81
- return None
82
- except Exception as e:
83
- if kwargs.get("raise_exception"):
84
- raise USSOException(
85
- status_code=401,
86
- error="error",
87
- message=str(e),
88
- )
89
- logger.error(e)
90
- return None
91
-
92
- return UserData(**decoded)
93
-
94
-
95
- @lru_cache
96
- def get_jwks_keys(jwks_url):
97
- return jwt.PyJWKClient(jwks_url)
71
+ decoded["token"] = token
72
+
73
+ except jwt.exceptions.ExpiredSignatureError:
74
+ if kwargs.get("raise_exception"):
75
+ raise USSOException(status_code=401, error="expired_signature")
76
+ return None
77
+ except jwt.exceptions.InvalidSignatureError:
78
+ if kwargs.get("raise_exception"):
79
+ raise USSOException(status_code=401, error="invalid_signature")
80
+ return None
81
+ except jwt.exceptions.InvalidTokenError:
82
+ if kwargs.get("raise_exception"):
83
+ raise USSOException(
84
+ status_code=401,
85
+ error="invalid_token",
86
+ )
87
+ return None
88
+ except Exception as e:
89
+ if kwargs.get("raise_exception"):
90
+ raise USSOException(
91
+ status_code=401,
92
+ error="error",
93
+ message=str(e),
94
+ )
95
+ logger.error(e)
96
+ return None
97
+
98
+ return UserData(**decoded)
usso/fastapi/__init__.py CHANGED
@@ -1 +1 @@
1
- from .integration import jwt_access_security
1
+ from .integration import jwt_access_security, jwt_access_security_ws
@@ -3,7 +3,7 @@ import logging
3
3
  from fastapi import Request, WebSocket
4
4
  from starlette.status import HTTP_401_UNAUTHORIZED
5
5
 
6
- from usso.core import UserData, get_authorization_scheme_param, user_data_from_token
6
+ from usso.core import UserData, Usso
7
7
  from usso.exceptions import USSOException
8
8
 
9
9
  logger = logging.getLogger("usso")
@@ -14,14 +14,14 @@ async def jwt_access_security(request: Request) -> UserData | None:
14
14
  kwargs = {}
15
15
  authorization = request.headers.get("Authorization")
16
16
  if authorization:
17
- scheme, _, credentials = get_authorization_scheme_param(authorization)
17
+ scheme, _, credentials = Usso().get_authorization_scheme_param(authorization)
18
18
  if scheme.lower() == "bearer":
19
19
  token = credentials
20
- return user_data_from_token(token, **kwargs)
20
+ return Usso().user_data_from_token(token, **kwargs)
21
21
 
22
- cookie_token = request.cookies.get("access_token")
22
+ cookie_token = request.cookies.get("usso_access_token")
23
23
  if cookie_token:
24
- return user_data_from_token(cookie_token, **kwargs)
24
+ return Usso().user_data_from_token(cookie_token, **kwargs)
25
25
 
26
26
  if kwargs.get("raise_exception", True):
27
27
  raise USSOException(
@@ -36,14 +36,14 @@ async def jwt_access_security_ws(websocket: WebSocket) -> UserData | None:
36
36
  kwargs = {}
37
37
  authorization = websocket.headers.get("Authorization")
38
38
  if authorization:
39
- scheme, _, credentials = get_authorization_scheme_param(authorization)
39
+ scheme, _, credentials = Usso().get_authorization_scheme_param(authorization)
40
40
  if scheme.lower() == "bearer":
41
41
  token = credentials
42
- return user_data_from_token(token, **kwargs)
42
+ return Usso().user_data_from_token(token, **kwargs)
43
43
 
44
- cookie_token = websocket.cookies.get("access_token")
44
+ cookie_token = websocket.cookies.get("usso_access_token")
45
45
  if cookie_token:
46
- return user_data_from_token(cookie_token, **kwargs)
46
+ return Usso().user_data_from_token(cookie_token, **kwargs)
47
47
 
48
48
  if kwargs.get("raise_exception", True):
49
49
  raise USSOException(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: usso
3
- Version: 0.13.0
3
+ Version: 0.15.0
4
4
  Summary: A plug-and-play client for integrating universal single sign-on (SSO) with Python frameworks, enabling secure and seamless authentication across microservices.
5
5
  Author-email: Mahdi Kiani <mahdikiany@gmail.com>
6
6
  Maintainer-email: Mahdi Kiani <mahdikiany@gmail.com>
@@ -24,11 +24,11 @@ License: Copyright (c) 2016 The Python Packaging Authority (PyPA)
24
24
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
25
  SOFTWARE.
26
26
 
27
- Project-URL: Homepage, https://github.com/usso-io/usso-python
28
- Project-URL: Bug Reports, https://github.com/usso-io/usso-python/issues
29
- Project-URL: Funding, https://github.com/usso-io/usso-python
30
- Project-URL: Say Thanks!, https://github.com/usso-io/usso-python
31
- Project-URL: Source, https://github.com/usso-io/usso-python
27
+ Project-URL: Homepage, https://github.com/ussoio/usso-python
28
+ Project-URL: Bug Reports, https://github.com/ussoio/usso-python/issues
29
+ Project-URL: Funding, https://github.com/ussoio/usso-python
30
+ Project-URL: Say Thanks!, https://github.com/ussoio/usso-python
31
+ Project-URL: Source, https://github.com/ussoio/usso-python
32
32
  Keywords: usso,sso,authentication,security,fastapi,django
33
33
  Classifier: Development Status :: 3 - Alpha
34
34
  Classifier: Intended Audience :: Developers
@@ -48,6 +48,7 @@ Requires-Dist: peppercorn
48
48
  Requires-Dist: pydantic >=1.8.2
49
49
  Requires-Dist: requests >=2.26.0
50
50
  Requires-Dist: pyjwt[crypto]
51
+ Requires-Dist: singleton-package
51
52
  Provides-Extra: dev
52
53
  Requires-Dist: check-manifest ; extra == 'dev'
53
54
  Provides-Extra: django
@@ -0,0 +1,14 @@
1
+ usso/__init__.py,sha256=fluJnb9X2ECUnUA0lNNR2xHdn3uSI2RVErQiFlqbOiU,49
2
+ usso/api.py,sha256=RCNBRf0TYZqDFA7NGemSrQcEUKyeKO4siiQo6kJ71HY,3263
3
+ usso/b64tools.py,sha256=6YCBx2lLaYFdk36f064VNZ1RejJTHtmgxqq0eT1sCwQ,518
4
+ usso/core.py,sha256=Su1LpCRghQ-EfLVHQiSYXuQcaHT_1VlmZuIQKHaAbqU,2932
5
+ usso/exceptions.py,sha256=hawOAuVbvQtjgRfwp1KFZ4SmV7fh720y5Gom9JVA8W8,504
6
+ usso/package_data.dat,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ usso/fastapi/__init__.py,sha256=WqTIF9BNKlq5f0H8Vk-fcQawPWZMclwuD8QwO4agW7I,69
8
+ usso/fastapi/integration.py,sha256=rkR9uZbe-DEBMgHYtIaP3QUhL6hWHM6edFhBDJQWuz0,1779
9
+ usso-0.15.0.dist-info/LICENSE.txt,sha256=ceC9ZJOV9H6CtQDcYmHOS46NA3dHJ_WD4J9blH513pc,1081
10
+ usso-0.15.0.dist-info/METADATA,sha256=SUe0o6EtDax6CypnPEGQv8uoSBS941WIZwiAtDTZIxQ,4334
11
+ usso-0.15.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
12
+ usso-0.15.0.dist-info/entry_points.txt,sha256=4Zgpm5ELaAWPf0jPGJFz1_X69H7un8ycT3WdGoJ0Vvk,35
13
+ usso-0.15.0.dist-info/top_level.txt,sha256=g9Jf6h1Oyidh0vPiFni7UHInTJjSvu6cUalpLTIvthg,5
14
+ usso-0.15.0.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- usso/__init__.py,sha256=fluJnb9X2ECUnUA0lNNR2xHdn3uSI2RVErQiFlqbOiU,49
2
- usso/b64tools.py,sha256=6YCBx2lLaYFdk36f064VNZ1RejJTHtmgxqq0eT1sCwQ,518
3
- usso/core.py,sha256=eTzztT0Daxwl3b9VzPZSk1A-QQoGaCQdW2duK8FEWoo,2690
4
- usso/exceptions.py,sha256=hawOAuVbvQtjgRfwp1KFZ4SmV7fh720y5Gom9JVA8W8,504
5
- usso/package_data.dat,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- usso/fastapi/__init__.py,sha256=3hB0_Hq4LxfIL5NTI7SdC0jsFEICcBSTv6b9RuBWdxw,45
7
- usso/fastapi/integration.py,sha256=JD5n12KYUuqvi9nuUesNyJHbR9d7CWC46qVGbXqBH10,1775
8
- usso-0.13.0.dist-info/LICENSE.txt,sha256=ceC9ZJOV9H6CtQDcYmHOS46NA3dHJ_WD4J9blH513pc,1081
9
- usso-0.13.0.dist-info/METADATA,sha256=qnQ29y0i3rryXbi0vr1WSHO2tuoNDRBQhTct1vL52A4,4306
10
- usso-0.13.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
11
- usso-0.13.0.dist-info/entry_points.txt,sha256=4Zgpm5ELaAWPf0jPGJFz1_X69H7un8ycT3WdGoJ0Vvk,35
12
- usso-0.13.0.dist-info/top_level.txt,sha256=g9Jf6h1Oyidh0vPiFni7UHInTJjSvu6cUalpLTIvthg,5
13
- usso-0.13.0.dist-info/RECORD,,
File without changes