usso 0.14.0__tar.gz → 0.16.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: usso
3
- Version: 0.14.0
3
+ Version: 0.16.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: fastapi
52
53
  Requires-Dist: fastapi>=0.65.0; extra == "fastapi"
53
54
  Requires-Dist: uvicorn[standard]>=0.13.0; extra == "fastapi"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "usso"
7
- version = "0.14.0"
7
+ version = "0.16.0"
8
8
  description = "A plug-and-play client for integrating universal single sign-on (SSO) with Python frameworks, enabling secure and seamless authentication across microservices."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -33,16 +33,17 @@ dependencies = [
33
33
  "peppercorn", # Example main dependency
34
34
  "pydantic>=1.8.2",
35
35
  "requests>=2.26.0",
36
- "pyjwt[crypto]"
36
+ "pyjwt[crypto]",
37
+ "singleton_package"
37
38
  ]
38
39
  optional-dependencies = {"fastapi" = ["fastapi>=0.65.0", "uvicorn[standard]>=0.13.0"],"django" = ["Django>=3.2"],"dev" = ["check-manifest"],"test" = ["coverage"]}
39
40
 
40
41
  [project.urls]
41
- "Homepage" = "https://github.com/usso-io/usso-python"
42
- "Bug Reports" = "https://github.com/usso-io/usso-python/issues"
43
- "Funding" = "https://github.com/usso-io/usso-python"
44
- "Say Thanks!" = "https://github.com/usso-io/usso-python"
45
- "Source" = "https://github.com/usso-io/usso-python"
42
+ "Homepage" = "https://github.com/ussoio/usso-python"
43
+ "Bug Reports" = "https://github.com/ussoio/usso-python/issues"
44
+ "Funding" = "https://github.com/ussoio/usso-python"
45
+ "Say Thanks!" = "https://github.com/ussoio/usso-python"
46
+ "Source" = "https://github.com/ussoio/usso-python"
46
47
 
47
48
  [project.scripts]
48
49
  usso = "usso:main"
@@ -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
+ )
@@ -0,0 +1,100 @@
1
+ import os
2
+ import logging
3
+ import uuid
4
+ from functools import lru_cache
5
+ from typing import Optional, Tuple
6
+ from singleton import Singleton
7
+
8
+ import jwt
9
+ from pydantic import BaseModel
10
+
11
+ from . import b64tools
12
+ from .exceptions import USSOException
13
+
14
+ logger = logging.getLogger("usso")
15
+
16
+
17
+ class UserData(BaseModel):
18
+ user_id: str
19
+ email: str | None = None
20
+ phone: str | None = None
21
+ authentication_method: str | None = None
22
+ is_active: bool = False
23
+ jti: str
24
+ data: dict | None = None
25
+ token: str | None = None
26
+
27
+ @property
28
+ def uid(self) -> uuid.UUID:
29
+ user_id = self.user_id
30
+
31
+ if user_id.startswith("u_"):
32
+ user_id = user_id[2:]
33
+ if 22 <= len(user_id) <= 24:
34
+ user_id = b64tools.b64_decode_uuid(user_id)
35
+
36
+ return uuid.UUID(user_id)
37
+
38
+ @property
39
+ def b64id(self) -> uuid.UUID:
40
+ return b64tools.b64_encode_uuid_strip(self.uid)
41
+
42
+
43
+ class Usso(metaclass=Singleton):
44
+ def __init__(self, jwks_url: str|None = None):
45
+ if jwks_url is None:
46
+ jwks_url = os.getenv("USSO_JWKS_URL")
47
+ self.jwks_url = jwks_url
48
+
49
+ @lru_cache
50
+ def get_jwks_keys(self):
51
+ return jwt.PyJWKClient(self.jwks_url)
52
+
53
+ def get_authorization_scheme_param(self,
54
+ authorization_header_value: Optional[str],
55
+ ) -> Tuple[str, str]:
56
+ if not authorization_header_value:
57
+ return "", ""
58
+ scheme, _, param = authorization_header_value.partition(" ")
59
+ return scheme, param
60
+
61
+ def user_data_from_token(self, token: str, **kwargs) -> UserData | None:
62
+ """Return the user associated with a token value."""
63
+ try:
64
+ # header = jwt.get_unverified_header(token)
65
+ # jwks_url = header["jwk_url"]
66
+ jwks_client = self.get_jwks_keys()
67
+ signing_key = jwks_client.get_signing_key_from_jwt(token)
68
+ decoded = jwt.decode(
69
+ token,
70
+ signing_key.key,
71
+ algorithms=["RS256"],
72
+ )
73
+ decoded["token"] = token
74
+
75
+ except jwt.exceptions.ExpiredSignatureError:
76
+ if kwargs.get("raise_exception"):
77
+ raise USSOException(status_code=401, error="expired_signature")
78
+ return None
79
+ except jwt.exceptions.InvalidSignatureError:
80
+ if kwargs.get("raise_exception"):
81
+ raise USSOException(status_code=401, error="invalid_signature")
82
+ return None
83
+ except jwt.exceptions.InvalidTokenError:
84
+ if kwargs.get("raise_exception"):
85
+ raise USSOException(
86
+ status_code=401,
87
+ error="invalid_token",
88
+ )
89
+ return None
90
+ except Exception as e:
91
+ if kwargs.get("raise_exception"):
92
+ raise USSOException(
93
+ status_code=401,
94
+ error="error",
95
+ message=str(e),
96
+ )
97
+ logger.error(e)
98
+ return None
99
+
100
+ return UserData(**decoded)
@@ -0,0 +1 @@
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
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
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.14.0
3
+ Version: 0.16.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: fastapi
52
53
  Requires-Dist: fastapi>=0.65.0; extra == "fastapi"
53
54
  Requires-Dist: uvicorn[standard]>=0.13.0; extra == "fastapi"
@@ -2,6 +2,7 @@ LICENSE.txt
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/usso/__init__.py
5
+ src/usso/api.py
5
6
  src/usso/b64tools.py
6
7
  src/usso/core.py
7
8
  src/usso/exceptions.py
@@ -2,6 +2,7 @@ peppercorn
2
2
  pydantic>=1.8.2
3
3
  requests>=2.26.0
4
4
  pyjwt[crypto]
5
+ singleton_package
5
6
 
6
7
  [dev]
7
8
  check-manifest
@@ -1,97 +0,0 @@
1
- import os
2
- import logging
3
- import uuid
4
- from functools import lru_cache
5
- from typing import Optional, Tuple
6
-
7
- import jwt
8
- from pydantic import BaseModel
9
-
10
- from . import b64tools
11
- from .exceptions import USSOException
12
-
13
- logger = logging.getLogger("usso")
14
-
15
-
16
- class UserData(BaseModel):
17
- user_id: str
18
- email: str | None = None
19
- phone: str | None = None
20
- authentication_method: str | None = None
21
- is_active: bool = False
22
- jti: str
23
- data: dict | None = None
24
- token: str | None = None
25
-
26
- @property
27
- def uid(self) -> uuid.UUID:
28
- user_id = self.user_id
29
-
30
- if user_id.startswith("u_"):
31
- user_id = user_id[2:]
32
- if 22 <= len(user_id) <= 24:
33
- user_id = b64tools.b64_decode_uuid(user_id)
34
-
35
- return uuid.UUID(user_id)
36
-
37
- @property
38
- def b64id(self) -> uuid.UUID:
39
- return b64tools.b64_encode_uuid_strip(self.uid)
40
-
41
-
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",
80
- )
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)
@@ -1 +0,0 @@
1
- from .integration import jwt_access_security
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes