usso 0.27.22__py3-none-any.whl → 0.28.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/__init__.py +23 -2
- usso/auth/__init__.py +9 -0
- usso/auth/api_key.py +43 -0
- usso/auth/client.py +85 -0
- usso/auth/config.py +115 -0
- usso/exceptions.py +13 -0
- usso/integrations/django/__init__.py +3 -0
- usso/{django → integrations/django}/middleware.py +37 -29
- usso/integrations/fastapi/__init__.py +3 -0
- usso/integrations/fastapi/dependency.py +95 -0
- usso/models/user.py +119 -0
- usso/session/async_session.py +12 -8
- usso/session/base_session.py +26 -45
- usso/session/session.py +8 -34
- usso/utils/method_utils.py +12 -0
- usso/utils/string_utils.py +7 -0
- usso-0.28.0.dist-info/METADATA +172 -0
- usso-0.28.0.dist-info/RECORD +24 -0
- {usso-0.27.22.dist-info → usso-0.28.0.dist-info}/WHEEL +1 -1
- usso/b64tools.py +0 -20
- usso/client/__init__.py +0 -4
- usso/client/api.py +0 -174
- usso/client/async_api.py +0 -159
- usso/core.py +0 -160
- usso/fastapi/__init__.py +0 -7
- usso/fastapi/integration.py +0 -88
- usso/schemas.py +0 -67
- usso-0.27.22.dist-info/METADATA +0 -110
- usso-0.27.22.dist-info/RECORD +0 -22
- /usso/{django → utils}/__init__.py +0 -0
- {usso-0.27.22.dist-info → usso-0.28.0.dist-info}/entry_points.txt +0 -0
- {usso-0.27.22.dist-info → usso-0.28.0.dist-info/licenses}/LICENSE.txt +0 -0
- {usso-0.27.22.dist-info → usso-0.28.0.dist-info}/top_level.txt +0 -0
usso/session/base_session.py
CHANGED
@@ -1,60 +1,37 @@
|
|
1
1
|
import os
|
2
|
-
from urllib.parse import urlparse
|
3
|
-
|
4
|
-
from usso.core import is_expired
|
5
2
|
from typing import Optional
|
6
3
|
|
4
|
+
from usso_jwt.schemas import JWT, JWTConfig
|
7
5
|
|
8
|
-
class BaseUssoSession:
|
9
6
|
|
7
|
+
class BaseUssoSession:
|
10
8
|
def __init__(
|
11
9
|
self,
|
12
|
-
api_key: str | None = None,
|
13
10
|
*,
|
14
|
-
|
15
|
-
usso_refresh_url: str | None = None,
|
11
|
+
api_key: str | None = None,
|
16
12
|
refresh_token: str | None = None,
|
17
13
|
app_id: str | None = None,
|
18
14
|
app_secret: str | None = None,
|
19
|
-
|
20
|
-
usso_admin_api_key: str | None = None,
|
21
|
-
user_id: str | None = None,
|
15
|
+
usso_url: str = "https://sso.usso.io",
|
22
16
|
client: Optional["BaseUssoSession"] = None,
|
23
|
-
**kwargs,
|
24
17
|
):
|
25
18
|
if client:
|
26
19
|
self.copy_attributes_from(client)
|
27
20
|
return
|
28
21
|
|
29
|
-
if not
|
30
|
-
|
31
|
-
api_key = os.getenv("USSO_API_KEY")
|
32
|
-
elif os.getenv("USSO_URL"):
|
33
|
-
usso_base_url = os.getenv("USSO_URL")
|
34
|
-
elif os.getenv("USSO_REFRESH_URL"):
|
35
|
-
usso_refresh_url = os.getenv("USSO_REFRESH_URL")
|
36
|
-
else:
|
37
|
-
raise ValueError(
|
38
|
-
"one of api_key, usso_base_url or usso_refresh_url is required"
|
39
|
-
)
|
22
|
+
if not api_key and os.getenv("USSO_API_KEY"):
|
23
|
+
api_key = os.getenv("USSO_API_KEY")
|
40
24
|
|
41
|
-
if not (
|
42
|
-
api_key
|
43
|
-
or refresh_token
|
44
|
-
or usso_admin_api_key
|
45
|
-
or (app_id and app_secret)
|
46
|
-
or access_token
|
47
|
-
):
|
25
|
+
if not (api_key or refresh_token or (app_id and app_secret)):
|
48
26
|
if os.getenv("USSO_REFRESH_TOKEN"):
|
49
27
|
refresh_token = os.getenv("USSO_REFRESH_TOKEN")
|
50
|
-
elif os.getenv("USSO_ADMIN_API_KEY"):
|
51
|
-
usso_admin_api_key = os.getenv("USSO_ADMIN_API_KEY")
|
52
28
|
elif os.getenv("USSO_APP_ID") and os.getenv("USSO_APP_SECRET"):
|
53
29
|
app_id = os.getenv("USSO_APP_ID")
|
54
30
|
app_secret = os.getenv("USSO_APP_SECRET")
|
55
31
|
else:
|
56
32
|
raise ValueError(
|
57
|
-
"one of api_key, refresh_token, usso_admin_api_key,
|
33
|
+
"one of api_key, refresh_token, usso_admin_api_key, "
|
34
|
+
"app_id and app_secret or access_token is required"
|
58
35
|
)
|
59
36
|
|
60
37
|
if api_key:
|
@@ -64,33 +41,37 @@ class BaseUssoSession:
|
|
64
41
|
else:
|
65
42
|
self.api_key = None
|
66
43
|
|
67
|
-
if
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
44
|
+
if usso_url.endswith("/"):
|
45
|
+
usso_url = usso_url[:-1]
|
46
|
+
|
47
|
+
self.usso_url = usso_url
|
48
|
+
self.usso_refresh_url = f"{usso_url}/auth/refresh"
|
49
|
+
self._refresh_token = JWT(
|
50
|
+
token=refresh_token,
|
51
|
+
config=JWTConfig(jwk_url=f"{self.usso_url}/website/jwks.json"),
|
52
|
+
)
|
72
53
|
|
73
|
-
self.usso_base_url = usso_base_url
|
74
|
-
self.usso_refresh_url = usso_refresh_url or f"{usso_base_url}/auth/refresh"
|
75
|
-
self._refresh_token = refresh_token
|
76
54
|
self.access_token = None
|
77
|
-
self.usso_admin_api_key = usso_admin_api_key
|
78
|
-
self.user_id = user_id
|
79
55
|
self.headers = getattr(self, "headers", {})
|
80
56
|
|
81
57
|
def copy_attributes_from(self, client: "BaseUssoSession"):
|
82
|
-
self.
|
58
|
+
self.usso_url = client.usso_url
|
83
59
|
self.usso_refresh_url = client.usso_refresh_url
|
84
60
|
self._refresh_token = client._refresh_token
|
85
61
|
self.access_token = client.access_token
|
86
62
|
self.api_key = client.api_key
|
87
63
|
self.usso_admin_api_key = client.usso_admin_api_key
|
88
|
-
self.user_id = client.user_id
|
89
64
|
self.headers = client.headers.copy()
|
90
65
|
|
91
66
|
@property
|
92
67
|
def refresh_token(self):
|
93
|
-
if
|
68
|
+
if (
|
69
|
+
self._refresh_token
|
70
|
+
and self._refresh_token.verify( # noqa: W503
|
71
|
+
expected_acr="refresh",
|
72
|
+
)
|
73
|
+
and self._refresh_token.is_temporally_valid() # noqa: W503
|
74
|
+
):
|
94
75
|
self._refresh_token = None
|
95
76
|
|
96
77
|
return self._refresh_token
|
usso/session/session.py
CHANGED
@@ -1,66 +1,40 @@
|
|
1
1
|
import os
|
2
|
-
|
3
|
-
from typing import Callable, Any
|
2
|
+
|
4
3
|
import httpx
|
5
4
|
|
6
5
|
from usso.core import is_expired
|
7
6
|
|
8
7
|
from .base_session import BaseUssoSession
|
9
8
|
|
10
|
-
class UssoSession(httpx.Client, BaseUssoSession):
|
11
9
|
|
10
|
+
class UssoSession(httpx.Client, BaseUssoSession):
|
12
11
|
def __init__(
|
13
12
|
self,
|
14
13
|
*,
|
15
|
-
usso_base_url: str | None = os.getenv("USSO_URL"),
|
16
14
|
api_key: str | None = os.getenv("USSO_API_KEY"),
|
17
|
-
usso_refresh_url: str | None = os.getenv("USSO_REFRESH_URL"),
|
18
15
|
refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
|
19
|
-
|
20
|
-
user_id: str | None = None,
|
16
|
+
usso_url: str | None = os.getenv("USSO_URL"),
|
21
17
|
client: "UssoSession" = None,
|
22
18
|
**kwargs,
|
23
19
|
):
|
24
|
-
|
25
|
-
# httpx_kwargs = _filter_kwargs(kwargs, httpx.Client.__init__)
|
26
|
-
|
27
|
-
httpx.Client.__init__(self) #, **httpx_kwargs)
|
20
|
+
httpx.Client.__init__(self, **kwargs)
|
28
21
|
|
29
22
|
BaseUssoSession.__init__(
|
30
23
|
self,
|
31
|
-
usso_base_url=usso_base_url,
|
32
24
|
api_key=api_key,
|
33
|
-
usso_refresh_url=usso_refresh_url,
|
34
25
|
refresh_token=refresh_token,
|
35
|
-
|
36
|
-
user_id=user_id,
|
26
|
+
usso_url=usso_url,
|
37
27
|
client=client,
|
38
28
|
)
|
39
29
|
if not self.api_key:
|
40
30
|
self._refresh()
|
41
31
|
|
42
|
-
def _refresh_api(self):
|
43
|
-
assert self.usso_admin_api_key, "usso_api_key is required"
|
44
|
-
params = {"user_id": self.user_id} if self.user_id else {}
|
45
|
-
response = httpx.get(
|
46
|
-
f"{self.usso_refresh_url}/api",
|
47
|
-
headers={"x-api-key": self.usso_admin_api_key},
|
48
|
-
params=params,
|
49
|
-
)
|
50
|
-
response.raise_for_status()
|
51
|
-
data: dict = response.json()
|
52
|
-
self._refresh_token = data.get("token", {}).get("refresh_token")
|
53
|
-
|
54
32
|
def _refresh(self):
|
55
|
-
assert
|
56
|
-
self.refresh_token or self.usso_admin_api_key
|
57
|
-
), "refresh_token or usso_api_key is required"
|
58
|
-
|
59
|
-
if self.usso_admin_api_key and not self.refresh_token:
|
60
|
-
self._refresh_api()
|
33
|
+
assert self.refresh_token, "refresh_token is required"
|
61
34
|
|
62
35
|
response = httpx.post(
|
63
|
-
self.usso_refresh_url,
|
36
|
+
self.usso_refresh_url,
|
37
|
+
json={"refresh_token": f"{self.refresh_token}"},
|
64
38
|
)
|
65
39
|
response.raise_for_status()
|
66
40
|
self.access_token = response.json().get("access_token")
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class instance_method:
|
2
|
+
def __init__(self, func):
|
3
|
+
self.func = func
|
4
|
+
|
5
|
+
def __get__(self, instance, owner):
|
6
|
+
def wrapper(*args, **kwargs):
|
7
|
+
if instance is not None:
|
8
|
+
return self.func(instance, *args, **kwargs)
|
9
|
+
else:
|
10
|
+
return self.func(owner(), *args, **kwargs)
|
11
|
+
|
12
|
+
return wrapper
|
@@ -0,0 +1,172 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: usso
|
3
|
+
Version: 0.28.0
|
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
|
+
Author-email: Mahdi Kiani <mahdikiany@gmail.com>
|
6
|
+
Maintainer-email: Mahdi Kiani <mahdikiany@gmail.com>
|
7
|
+
License-Expression: MIT
|
8
|
+
Project-URL: Homepage, https://github.com/ussoio/usso-python
|
9
|
+
Project-URL: Bug Reports, https://github.com/ussoio/usso-python/issues
|
10
|
+
Project-URL: Funding, https://github.com/ussoio/usso-python
|
11
|
+
Project-URL: Say Thanks!, https://saythanks.io/to/mahdikiani
|
12
|
+
Project-URL: Source, https://github.com/ussoio/usso-python
|
13
|
+
Keywords: usso,sso,authentication,security,fastapi,django
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
15
|
+
Classifier: Intended Audience :: Developers
|
16
|
+
Classifier: Topic :: Software Development :: Build Tools
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
22
|
+
Requires-Python: >=3.9
|
23
|
+
Description-Content-Type: text/markdown
|
24
|
+
License-File: LICENSE.txt
|
25
|
+
Requires-Dist: pydantic>=2
|
26
|
+
Requires-Dist: cryptography>=43.0.0
|
27
|
+
Requires-Dist: cachetools
|
28
|
+
Requires-Dist: singleton_package
|
29
|
+
Requires-Dist: json-advanced
|
30
|
+
Requires-Dist: httpx
|
31
|
+
Requires-Dist: usso-jwt>=0.1.0
|
32
|
+
Provides-Extra: fastapi
|
33
|
+
Requires-Dist: fastapi>=0.65.0; extra == "fastapi"
|
34
|
+
Requires-Dist: uvicorn[standard]>=0.13.0; extra == "fastapi"
|
35
|
+
Provides-Extra: django
|
36
|
+
Requires-Dist: Django>=3.2; extra == "django"
|
37
|
+
Provides-Extra: dev
|
38
|
+
Requires-Dist: check-manifest; extra == "dev"
|
39
|
+
Provides-Extra: test
|
40
|
+
Requires-Dist: coverage; extra == "test"
|
41
|
+
Provides-Extra: all
|
42
|
+
Requires-Dist: fastapi; extra == "all"
|
43
|
+
Requires-Dist: uvicorn; extra == "all"
|
44
|
+
Requires-Dist: django; extra == "all"
|
45
|
+
Requires-Dist: dev; extra == "all"
|
46
|
+
Requires-Dist: test; extra == "all"
|
47
|
+
Dynamic: license-file
|
48
|
+
|
49
|
+
# 🛡️ USSO Python Client SDK
|
50
|
+
|
51
|
+
The **USSO Python Client SDK** (`usso`) provides a universal, secure JWT authentication layer for Python microservices and web frameworks.
|
52
|
+
It’s designed to integrate seamlessly with the [USSO Identity Platform](https://github.com/ussoio/usso) — or any standards-compliant token issuer.
|
53
|
+
|
54
|
+
---
|
55
|
+
|
56
|
+
## 🔗 Relationship to the USSO Platform
|
57
|
+
|
58
|
+
This SDK is the official verification client for the **USSO** identity service, which provides multi-tenant authentication, RBAC, token flows, and more.
|
59
|
+
You can use the SDK with:
|
60
|
+
- Self-hosted USSO via Docker
|
61
|
+
- Any identity provider that issues signed JWTs (with proper config)
|
62
|
+
|
63
|
+
---
|
64
|
+
|
65
|
+
## ✨ Features
|
66
|
+
|
67
|
+
- ✅ **Token verification** for EdDSA, RS256, HS256, and more
|
68
|
+
- ✅ **Claim validation** (`exp`, `nbf`, `aud`, `iss`)
|
69
|
+
- ✅ **Remote JWK support** for key rotation
|
70
|
+
- ✅ **Typed payload parsing** via `UserData` (Pydantic)
|
71
|
+
- ✅ **Token extraction** from:
|
72
|
+
- `Authorization` header
|
73
|
+
- Cookies
|
74
|
+
- Custom headers
|
75
|
+
- ✅ **FastAPI integration** with dependency injection
|
76
|
+
- ✅ **Django middleware** for request-based user resolution
|
77
|
+
- 🧪 90% tested with `pytest` and `tox`
|
78
|
+
|
79
|
+
---
|
80
|
+
|
81
|
+
## 📦 Installation
|
82
|
+
|
83
|
+
```bash
|
84
|
+
pip install usso
|
85
|
+
````
|
86
|
+
|
87
|
+
With framework extras:
|
88
|
+
|
89
|
+
```bash
|
90
|
+
pip install "usso[fastapi]" # for FastAPI integration
|
91
|
+
pip install "usso[django]" # for Django integration
|
92
|
+
```
|
93
|
+
|
94
|
+
---
|
95
|
+
|
96
|
+
## 🚀 Quick Start (FastAPI)
|
97
|
+
|
98
|
+
```python
|
99
|
+
from usso.fastapi.integration import get_authenticator
|
100
|
+
from usso.schemas import JWTConfig, JWTHeaderConfig, UserData
|
101
|
+
from usso.jwt.enums import Algorithm
|
102
|
+
|
103
|
+
config = JWTConfig(
|
104
|
+
key="your-ed25519-public-key",
|
105
|
+
issuer="https://sso.example.com",
|
106
|
+
audience="api.example.com",
|
107
|
+
type=Algorithm.EdDSA,
|
108
|
+
header=JWTHeaderConfig(type="Authorization")
|
109
|
+
)
|
110
|
+
|
111
|
+
authenticator = get_authenticator(config)
|
112
|
+
|
113
|
+
@app.get("/me")
|
114
|
+
def get_me(user: UserData = Depends(authenticator)):
|
115
|
+
return {"user_id": user.sub, "roles": user.roles}
|
116
|
+
```
|
117
|
+
|
118
|
+
---
|
119
|
+
|
120
|
+
## 🧱 Project Structure
|
121
|
+
|
122
|
+
```
|
123
|
+
src/usso/
|
124
|
+
├── fastapi/ # FastAPI adapter
|
125
|
+
├── django/ # Django middleware
|
126
|
+
├── jwt/ # Core JWT logic and algorithms
|
127
|
+
├── session/ # Stateless session support
|
128
|
+
├── models/ # JWTConfig, UserData, etc.
|
129
|
+
├── exceptions/ # Shared exceptions
|
130
|
+
├── authenticator.py # High-level API (token + user resolution)
|
131
|
+
```
|
132
|
+
|
133
|
+
---
|
134
|
+
|
135
|
+
## 🐳 Integrate with USSO (Docker)
|
136
|
+
|
137
|
+
Run your own identity provider:
|
138
|
+
|
139
|
+
```bash
|
140
|
+
docker run -p 8000:8000 ghcr.io/ussoio/usso:latest
|
141
|
+
```
|
142
|
+
|
143
|
+
Then configure your app to verify tokens issued by this service, using its public JWKS endpoint:
|
144
|
+
|
145
|
+
```python
|
146
|
+
JWTConfig(
|
147
|
+
jwk_url="http://localhost:8000/.well-known/jwks.json",
|
148
|
+
...
|
149
|
+
)
|
150
|
+
```
|
151
|
+
|
152
|
+
---
|
153
|
+
|
154
|
+
## 🧪 Testing
|
155
|
+
|
156
|
+
```bash
|
157
|
+
pytest
|
158
|
+
tox
|
159
|
+
```
|
160
|
+
|
161
|
+
---
|
162
|
+
|
163
|
+
## 🤝 Contributing
|
164
|
+
|
165
|
+
We welcome contributions!
|
166
|
+
|
167
|
+
---
|
168
|
+
|
169
|
+
## 📝 License
|
170
|
+
|
171
|
+
MIT License © \[mahdikiani]
|
172
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
usso/__init__.py,sha256=t3tYcw4qtGFpk7iakXTqEj5RlzIc8D2fs0I3FZcOmGs,571
|
2
|
+
usso/exceptions.py,sha256=cBzmMCwpNQKMjCUXO3bCcFwZJQQvbvJ5RxTH987ZlCI,1012
|
3
|
+
usso/auth/__init__.py,sha256=Dthv-iZTgsHTGcJrkJsnAtDCbRR5dNCx0Ut7MufoAXY,270
|
4
|
+
usso/auth/api_key.py,sha256=ec3q_mJtPiNb-eZt5MA4bantX1zRo3WwU2JfYBcGCGk,1254
|
5
|
+
usso/auth/client.py,sha256=SqZuq2ff4pbfP1bwQ0Mm9W1CEqYZEgFX7lIoAMJDMC4,2525
|
6
|
+
usso/auth/config.py,sha256=6tdnIg8iCrksuJGzpliw6QhRc8XsDUMR-dX0XY6QLHU,3833
|
7
|
+
usso/integrations/django/__init__.py,sha256=dKpbffHS5ouGtW6ooI2ivzjPmH_1rOBny85htR-KqrY,97
|
8
|
+
usso/integrations/django/middleware.py,sha256=8b-VYQ3FRhLnXSl4ZfHBpiA6VLY4b7b0-YoE_X41SFM,3443
|
9
|
+
usso/integrations/fastapi/__init__.py,sha256=ZysSeNr35ioMkviQqvfrXLYgt1ox3Wyckzee4Fv0nCk,77
|
10
|
+
usso/integrations/fastapi/dependency.py,sha256=HX2x6v9mI1r6O7SVAtSLtoTsx2uXXxxaFNwB6lSCh1Q,2954
|
11
|
+
usso/models/user.py,sha256=jG8jlt6F2pHHgqnsRRP6kCuRNK-aO9FE5Mt7iiuPPnU,3293
|
12
|
+
usso/session/__init__.py,sha256=tE4qWUdSI7iN_pywm47Mg8NKOTBa2nCNwCy3wCZWRmU,124
|
13
|
+
usso/session/async_session.py,sha256=7OKvFnJQaHnLjeQKSW6bltl0KcQGzOvUje-bJKbxFZY,3692
|
14
|
+
usso/session/base_session.py,sha256=Jggp_1JJEKwAJYxNTk4KhP15BSjq6diuSSBzf79gToI,2551
|
15
|
+
usso/session/session.py,sha256=B29Srxoq7webDvmfmfTeeh5JjtLSHvJijM2WCEZOh-8,1506
|
16
|
+
usso/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
+
usso/utils/method_utils.py,sha256=1NMN4le04PWXDSJZK-nf7q2IFqOMkwYcCnslFXAzlH8,355
|
18
|
+
usso/utils/string_utils.py,sha256=7tziAa2Cwa7xhwM_NF4DSY3BHoqVaWgJ21VuV8LvhrY,253
|
19
|
+
usso-0.28.0.dist-info/licenses/LICENSE.txt,sha256=ceC9ZJOV9H6CtQDcYmHOS46NA3dHJ_WD4J9blH513pc,1081
|
20
|
+
usso-0.28.0.dist-info/METADATA,sha256=eeqNJI0DT_JJCmXWFmMw3BMoNOKq94H_WEbPYfYvL5I,4845
|
21
|
+
usso-0.28.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
22
|
+
usso-0.28.0.dist-info/entry_points.txt,sha256=4Zgpm5ELaAWPf0jPGJFz1_X69H7un8ycT3WdGoJ0Vvk,35
|
23
|
+
usso-0.28.0.dist-info/top_level.txt,sha256=g9Jf6h1Oyidh0vPiFni7UHInTJjSvu6cUalpLTIvthg,5
|
24
|
+
usso-0.28.0.dist-info/RECORD,,
|
usso/b64tools.py
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
import base64
|
2
|
-
import uuid
|
3
|
-
|
4
|
-
|
5
|
-
def b64_encode_uuid(uuid_str: uuid.UUID | str):
|
6
|
-
uuid_UUID = uuid_str if isinstance(uuid_str, uuid.UUID) else uuid.UUID(uuid_str)
|
7
|
-
uuid_bytes = uuid_UUID.bytes
|
8
|
-
encoded_uuid = base64.urlsafe_b64encode(uuid_bytes).decode()
|
9
|
-
return encoded_uuid
|
10
|
-
|
11
|
-
|
12
|
-
def b64_encode_uuid_strip(uuid_str):
|
13
|
-
return b64_encode_uuid(uuid_str).rstrip("=")
|
14
|
-
|
15
|
-
|
16
|
-
def b64_decode_uuid(encoded_uuid):
|
17
|
-
encoded_uuid += "=" * (4 - len(encoded_uuid) % 4) # Add padding if needed
|
18
|
-
decoded_uuid = base64.urlsafe_b64decode(encoded_uuid)
|
19
|
-
uuid_obj = uuid.UUID(bytes=decoded_uuid)
|
20
|
-
return uuid_obj
|
usso/client/__init__.py
DELETED
usso/client/api.py
DELETED
@@ -1,174 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
|
3
|
-
import requests
|
4
|
-
from singleton import Singleton
|
5
|
-
|
6
|
-
from usso.core import UserData, Usso
|
7
|
-
|
8
|
-
|
9
|
-
class UssoAPI(metaclass=Singleton):
|
10
|
-
def __init__(
|
11
|
-
self,
|
12
|
-
url: str = "https://api.usso.io",
|
13
|
-
api_key: str = None,
|
14
|
-
refresh_token: str = None,
|
15
|
-
):
|
16
|
-
if url and not url.startswith("http"):
|
17
|
-
url = f"https://{url}"
|
18
|
-
url = url.rstrip("/")
|
19
|
-
self.url = url
|
20
|
-
assert (
|
21
|
-
api_key or refresh_token
|
22
|
-
), "Either api_key or refresh_token must be provided"
|
23
|
-
self.api_key = api_key
|
24
|
-
self.refresh_token = refresh_token
|
25
|
-
self.access_token = None
|
26
|
-
|
27
|
-
def _refresh(self):
|
28
|
-
if not self.refresh_token:
|
29
|
-
return
|
30
|
-
|
31
|
-
url = f"{self.url}/auth/refresh"
|
32
|
-
|
33
|
-
if self.refresh_token:
|
34
|
-
headers = {
|
35
|
-
"Authorization": f"Bearer {self.refresh_token}",
|
36
|
-
"content-type": "application/json",
|
37
|
-
}
|
38
|
-
|
39
|
-
resp = requests.post(url, headers=headers)
|
40
|
-
self.access_token = resp.json().get("access_token")
|
41
|
-
|
42
|
-
def _access_valid(self) -> bool:
|
43
|
-
if not self.access_token:
|
44
|
-
return False
|
45
|
-
|
46
|
-
user_data = Usso(
|
47
|
-
jwks_url=f"{self.url}/website/jwks.json?"
|
48
|
-
).user_data_from_token(self.access_token)
|
49
|
-
if user_data:
|
50
|
-
return True
|
51
|
-
return False
|
52
|
-
|
53
|
-
def _request(
|
54
|
-
self,
|
55
|
-
method="get",
|
56
|
-
endpoint: str = "",
|
57
|
-
data: dict = None,
|
58
|
-
**kwargs,
|
59
|
-
) -> dict:
|
60
|
-
url = f"{self.url}/{endpoint}"
|
61
|
-
headers = {"content-type": "application/json"}
|
62
|
-
if self.api_key:
|
63
|
-
headers["x-api-key"] = self.api_key
|
64
|
-
elif self.refresh_token:
|
65
|
-
if not self.access_token:
|
66
|
-
self._refresh()
|
67
|
-
headers["Authorization"] = f"Bearer {self.access_token}"
|
68
|
-
|
69
|
-
resp = requests.request(
|
70
|
-
method,
|
71
|
-
url,
|
72
|
-
headers=headers,
|
73
|
-
json=data,
|
74
|
-
)
|
75
|
-
if kwargs.get("raise_exception", True):
|
76
|
-
try:
|
77
|
-
resp_json = resp.json()
|
78
|
-
resp.raise_for_status()
|
79
|
-
except requests.HTTPError as e:
|
80
|
-
logging.error(f"Error: {e}")
|
81
|
-
logging.error(f"Response: {resp_json}")
|
82
|
-
raise e
|
83
|
-
except Exception as e:
|
84
|
-
logging.error(f"Error: {e}")
|
85
|
-
logging.error(f"Response: {resp.text}")
|
86
|
-
raise e
|
87
|
-
return resp.json()
|
88
|
-
|
89
|
-
def get_users(self, **kwargs) -> list[UserData]:
|
90
|
-
users_dict = self._request(endpoint="website/users", **kwargs)
|
91
|
-
|
92
|
-
return [UserData(user_id=user.get("uid"), **user) for user in users_dict]
|
93
|
-
|
94
|
-
def get_user(self, user_id: str, **kwargs) -> UserData:
|
95
|
-
user_dict = self._request(
|
96
|
-
endpoint=f"website/users/{user_id}",
|
97
|
-
**kwargs,
|
98
|
-
)
|
99
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
100
|
-
|
101
|
-
def get_user_by_credentials(self, credentials: dict, **kwargs) -> UserData:
|
102
|
-
user_dict = self._request(
|
103
|
-
endpoint="website/users/credentials",
|
104
|
-
data=credentials,
|
105
|
-
**kwargs,
|
106
|
-
)
|
107
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
108
|
-
|
109
|
-
def create_user(self, user_data: dict, **kwargs) -> UserData:
|
110
|
-
user_dict = self._request(
|
111
|
-
method="post",
|
112
|
-
endpoint="website/users",
|
113
|
-
data=user_data,
|
114
|
-
**kwargs,
|
115
|
-
)
|
116
|
-
|
117
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
118
|
-
|
119
|
-
def create_user_credentials(
|
120
|
-
self, user_id: str, credentials: dict, **kwargs
|
121
|
-
) -> UserData:
|
122
|
-
user_dict = self._request(
|
123
|
-
method="post",
|
124
|
-
endpoint=f"website/users/{user_id}/credentials",
|
125
|
-
data=credentials,
|
126
|
-
**kwargs,
|
127
|
-
)
|
128
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
129
|
-
|
130
|
-
def create_user_by_credentials(
|
131
|
-
self,
|
132
|
-
user_data: dict | None = None,
|
133
|
-
credentials: dict | None = None,
|
134
|
-
**kwargs,
|
135
|
-
) -> UserData:
|
136
|
-
user_data = user_data or {}
|
137
|
-
if credentials:
|
138
|
-
user_data["authenticators"] = [credentials]
|
139
|
-
user_dict = self._request(
|
140
|
-
method="post",
|
141
|
-
endpoint="website/users",
|
142
|
-
data=credentials,
|
143
|
-
**kwargs,
|
144
|
-
)
|
145
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
146
|
-
|
147
|
-
def get_user_payload(self, user_id: str, **kwargs) -> dict:
|
148
|
-
return self._request(endpoint=f"website/users/{user_id}/payload", **kwargs)
|
149
|
-
|
150
|
-
def update_user_payload(
|
151
|
-
self,
|
152
|
-
user_id: str,
|
153
|
-
payload: dict,
|
154
|
-
**kwargs,
|
155
|
-
) -> dict:
|
156
|
-
return self._request(
|
157
|
-
method="patch",
|
158
|
-
endpoint=f"website/users/{user_id}/payload",
|
159
|
-
data=payload,
|
160
|
-
**kwargs,
|
161
|
-
)
|
162
|
-
|
163
|
-
def set_user_payload(
|
164
|
-
self,
|
165
|
-
user_id: str,
|
166
|
-
payload: dict,
|
167
|
-
**kwargs,
|
168
|
-
) -> dict:
|
169
|
-
return self._request(
|
170
|
-
method="put",
|
171
|
-
endpoint=f"website/users/{user_id}/payload",
|
172
|
-
data=payload,
|
173
|
-
**kwargs,
|
174
|
-
)
|