usso 0.21.1__tar.gz → 0.22.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.
- {usso-0.21.1/src/usso.egg-info → usso-0.22.0}/PKG-INFO +1 -1
- {usso-0.21.1 → usso-0.22.0}/pyproject.toml +1 -1
- {usso-0.21.1 → usso-0.22.0}/src/usso/api.py +0 -1
- usso-0.22.0/src/usso/async_api.py +179 -0
- {usso-0.21.1 → usso-0.22.0}/src/usso/core.py +8 -8
- {usso-0.21.1 → usso-0.22.0}/src/usso/fastapi/integration.py +0 -1
- {usso-0.21.1 → usso-0.22.0}/src/usso/session.py +5 -2
- {usso-0.21.1 → usso-0.22.0/src/usso.egg-info}/PKG-INFO +1 -1
- {usso-0.21.1 → usso-0.22.0}/src/usso.egg-info/SOURCES.txt +1 -0
- {usso-0.21.1 → usso-0.22.0}/tests/test_simple.py +2 -2
- {usso-0.21.1 → usso-0.22.0}/LICENSE.txt +0 -0
- {usso-0.21.1 → usso-0.22.0}/README.md +0 -0
- {usso-0.21.1 → usso-0.22.0}/setup.cfg +0 -0
- {usso-0.21.1 → usso-0.22.0}/src/usso/__init__.py +0 -0
- {usso-0.21.1 → usso-0.22.0}/src/usso/b64tools.py +0 -0
- {usso-0.21.1 → usso-0.22.0}/src/usso/exceptions.py +0 -0
- {usso-0.21.1 → usso-0.22.0}/src/usso/fastapi/__init__.py +0 -0
- {usso-0.21.1 → usso-0.22.0}/src/usso/package_data.dat +0 -0
- {usso-0.21.1 → usso-0.22.0}/src/usso.egg-info/dependency_links.txt +0 -0
- {usso-0.21.1 → usso-0.22.0}/src/usso.egg-info/entry_points.txt +0 -0
- {usso-0.21.1 → usso-0.22.0}/src/usso.egg-info/requires.txt +0 -0
- {usso-0.21.1 → usso-0.22.0}/src/usso.egg-info/top_level.txt +0 -0
- {usso-0.21.1 → usso-0.22.0}/tests/test_api.py +0 -0
- {usso-0.21.1 → usso-0.22.0}/tests/test_core.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: usso
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.22.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>
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "usso"
|
7
|
-
version = "0.
|
7
|
+
version = "0.22.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.9"
|
@@ -0,0 +1,179 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
import aiohttp
|
4
|
+
from singleton import Singleton
|
5
|
+
from usso.core import UserData, Usso
|
6
|
+
|
7
|
+
|
8
|
+
class AsyncUssoAPI(metaclass=Singleton):
|
9
|
+
def __init__(
|
10
|
+
self,
|
11
|
+
url: str = "https://api.usso.io",
|
12
|
+
api_key: str = None,
|
13
|
+
refresh_token: str = None,
|
14
|
+
):
|
15
|
+
if url and not url.startswith("http"):
|
16
|
+
url = f"https://{url}"
|
17
|
+
self.url = url
|
18
|
+
assert (
|
19
|
+
api_key or refresh_token
|
20
|
+
), "Either api_key or refresh_token must be provided"
|
21
|
+
self.api_key = api_key
|
22
|
+
self.refresh_token = refresh_token
|
23
|
+
self.access_token = None
|
24
|
+
|
25
|
+
async def _refresh(self, **kwargs):
|
26
|
+
if not self.refresh_token:
|
27
|
+
return
|
28
|
+
|
29
|
+
url = f"{self.url}/auth/refresh"
|
30
|
+
|
31
|
+
if self.refresh_token:
|
32
|
+
headers = {
|
33
|
+
"Authorization": f"Bearer {self.refresh_token}",
|
34
|
+
"content-type": "application/json",
|
35
|
+
}
|
36
|
+
|
37
|
+
async with aiohttp.ClientSession() as session:
|
38
|
+
async with session.post(url, headers=headers) as resp:
|
39
|
+
if kwargs.get("raise_exception", True):
|
40
|
+
resp.raise_for_status()
|
41
|
+
self.access_token = (await resp.json()).get("access_token")
|
42
|
+
|
43
|
+
def _access_valid(self) -> bool:
|
44
|
+
if not self.access_token:
|
45
|
+
return False
|
46
|
+
|
47
|
+
user_data = Usso(
|
48
|
+
jwks_url=f"{self.url}/website/jwks.json?"
|
49
|
+
).user_data_from_token(self.access_token)
|
50
|
+
if user_data:
|
51
|
+
return True
|
52
|
+
return False
|
53
|
+
|
54
|
+
async def _request(
|
55
|
+
self,
|
56
|
+
method="get",
|
57
|
+
endpoint: str = "",
|
58
|
+
data: dict = None,
|
59
|
+
**kwargs,
|
60
|
+
) -> dict:
|
61
|
+
url = f"{self.url}/{endpoint}"
|
62
|
+
headers = {"content-type": "application/json"}
|
63
|
+
if self.api_key:
|
64
|
+
headers["x-api-key"] = self.api_key
|
65
|
+
elif self.refresh_token:
|
66
|
+
if not self.access_token:
|
67
|
+
await self._refresh()
|
68
|
+
headers["Authorization"] = f"Bearer {self.access_token}"
|
69
|
+
|
70
|
+
async with aiohttp.ClientSession() as session:
|
71
|
+
async with session.request(
|
72
|
+
method,
|
73
|
+
url,
|
74
|
+
headers=headers,
|
75
|
+
json=data,
|
76
|
+
) as resp:
|
77
|
+
try:
|
78
|
+
resp_json = resp.json()
|
79
|
+
resp.raise_for_status()
|
80
|
+
except aiohttp.ClientError as e:
|
81
|
+
logging.error(f"Error: {e}")
|
82
|
+
logging.error(f"Response: {resp_json}")
|
83
|
+
raise e
|
84
|
+
except Exception as e:
|
85
|
+
logging.error(f"Error: {e}")
|
86
|
+
logging.error(f"Response: {resp.text}")
|
87
|
+
raise e
|
88
|
+
return await resp.json()
|
89
|
+
|
90
|
+
async def get_users(self, **kwargs) -> list[UserData]:
|
91
|
+
users_dict = await self._request(endpoint="website/users", **kwargs)
|
92
|
+
|
93
|
+
return [
|
94
|
+
UserData(user_id=user.get("uid"), **user) for user in users_dict
|
95
|
+
]
|
96
|
+
|
97
|
+
async def get_user(self, user_id: str, **kwargs) -> UserData:
|
98
|
+
user_dict = await self._request(
|
99
|
+
endpoint=f"website/users/{user_id}",
|
100
|
+
**kwargs,
|
101
|
+
)
|
102
|
+
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
103
|
+
|
104
|
+
async def get_user_by_credentials(self, credentials: dict, **kwargs) -> UserData:
|
105
|
+
user_dict = await self._request(
|
106
|
+
endpoint="website/users/credentials",
|
107
|
+
data=credentials,
|
108
|
+
**kwargs,
|
109
|
+
)
|
110
|
+
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
111
|
+
|
112
|
+
async def create_user(self, user_data: dict, **kwargs) -> UserData:
|
113
|
+
user_dict = await self._request(
|
114
|
+
method="post",
|
115
|
+
endpoint="website/users",
|
116
|
+
data=user_data,
|
117
|
+
**kwargs,
|
118
|
+
)
|
119
|
+
|
120
|
+
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
121
|
+
|
122
|
+
async def create_user_credentials(
|
123
|
+
self, user_id: str, credentials: dict, **kwargs
|
124
|
+
) -> UserData:
|
125
|
+
user_dict = await self._request(
|
126
|
+
method="post",
|
127
|
+
endpoint=f"website/users/{user_id}/credentials",
|
128
|
+
data=credentials,
|
129
|
+
**kwargs,
|
130
|
+
)
|
131
|
+
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
132
|
+
|
133
|
+
async def create_user_by_credentials(
|
134
|
+
self,
|
135
|
+
user_data: dict | None = None,
|
136
|
+
credentials: dict | None = None,
|
137
|
+
**kwargs,
|
138
|
+
) -> UserData:
|
139
|
+
user_data = user_data or {}
|
140
|
+
if credentials:
|
141
|
+
user_data["authenticators"] = [credentials]
|
142
|
+
user_dict = await self._request(
|
143
|
+
method="post",
|
144
|
+
endpoint="website/users",
|
145
|
+
data=credentials,
|
146
|
+
**kwargs,
|
147
|
+
)
|
148
|
+
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
149
|
+
|
150
|
+
async def get_user_payload(self, user_id: str, **kwargs) -> dict:
|
151
|
+
return await self._request(
|
152
|
+
endpoint=f"website/users/{user_id}/payload", **kwargs
|
153
|
+
)
|
154
|
+
|
155
|
+
async def update_user_payload(
|
156
|
+
self,
|
157
|
+
user_id: str,
|
158
|
+
payload: dict,
|
159
|
+
**kwargs,
|
160
|
+
) -> dict:
|
161
|
+
return await self._request(
|
162
|
+
method="patch",
|
163
|
+
endpoint=f"website/users/{user_id}/payload",
|
164
|
+
data=payload,
|
165
|
+
**kwargs,
|
166
|
+
)
|
167
|
+
|
168
|
+
async def set_user_payload(
|
169
|
+
self,
|
170
|
+
user_id: str,
|
171
|
+
payload: dict,
|
172
|
+
**kwargs,
|
173
|
+
) -> dict:
|
174
|
+
return await self._request(
|
175
|
+
method="put",
|
176
|
+
endpoint=f"website/users/{user_id}/payload",
|
177
|
+
data=payload,
|
178
|
+
**kwargs,
|
179
|
+
)
|
@@ -79,40 +79,40 @@ class Usso(metaclass=Singleton):
|
|
79
79
|
decoded["token"] = token
|
80
80
|
return UserData(**decoded)
|
81
81
|
except jwt.exceptions.ExpiredSignatureError:
|
82
|
-
if kwargs.get("raise_exception"):
|
82
|
+
if kwargs.get("raise_exception", True):
|
83
83
|
raise USSOException(status_code=401, error="expired_signature")
|
84
84
|
except jwt.exceptions.InvalidSignatureError:
|
85
|
-
if kwargs.get("raise_exception"):
|
85
|
+
if kwargs.get("raise_exception", True):
|
86
86
|
raise USSOException(status_code=401, error="invalid_signature")
|
87
87
|
except jwt.exceptions.InvalidAlgorithmError:
|
88
|
-
if kwargs.get("raise_exception"):
|
88
|
+
if kwargs.get("raise_exception", True):
|
89
89
|
raise USSOException(
|
90
90
|
status_code=401,
|
91
91
|
error="invalid_algorithm",
|
92
92
|
)
|
93
93
|
except jwt.exceptions.InvalidIssuedAtError:
|
94
|
-
if kwargs.get("raise_exception"):
|
94
|
+
if kwargs.get("raise_exception", True):
|
95
95
|
raise USSOException(
|
96
96
|
status_code=401,
|
97
97
|
error="invalid_issued_at",
|
98
98
|
)
|
99
99
|
except jwt.exceptions.InvalidTokenError:
|
100
|
-
if kwargs.get("raise_exception"):
|
100
|
+
if kwargs.get("raise_exception", True):
|
101
101
|
raise USSOException(
|
102
102
|
status_code=401,
|
103
103
|
error="invalid_token",
|
104
104
|
)
|
105
105
|
except jwt.exceptions.InvalidKeyError:
|
106
|
-
if kwargs.get("raise_exception"):
|
106
|
+
if kwargs.get("raise_exception", True):
|
107
107
|
raise USSOException(
|
108
108
|
status_code=401,
|
109
109
|
error="invalid_key",
|
110
110
|
)
|
111
111
|
except USSOException as e:
|
112
|
-
if kwargs.get("raise_exception"):
|
112
|
+
if kwargs.get("raise_exception", True):
|
113
113
|
raise e
|
114
114
|
except Exception as e:
|
115
|
-
if kwargs.get("raise_exception"):
|
115
|
+
if kwargs.get("raise_exception", True):
|
116
116
|
raise USSOException(
|
117
117
|
status_code=401,
|
118
118
|
error="error",
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from datetime import datetime
|
2
|
-
|
2
|
+
|
3
3
|
import jwt
|
4
|
+
import requests
|
4
5
|
|
5
6
|
|
6
7
|
class UssoSession:
|
@@ -23,7 +24,9 @@ class UssoSession:
|
|
23
24
|
|
24
25
|
def get_session(self):
|
25
26
|
if self.access_token:
|
26
|
-
decoded_token = jwt.decode(
|
27
|
+
decoded_token = jwt.decode(
|
28
|
+
self.access_token, options={"verify_signature": False}
|
29
|
+
)
|
27
30
|
exp = datetime.fromtimestamp(decoded_token.get("exp"))
|
28
31
|
if exp < datetime.now():
|
29
32
|
self.access_token = None
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: usso
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.22.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>
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|