usso 0.27.22__tar.gz → 0.28.1__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.28.1/MANIFEST.in +1 -0
- usso-0.28.1/PKG-INFO +172 -0
- usso-0.28.1/README.md +124 -0
- {usso-0.27.22 → usso-0.28.1}/pyproject.toml +37 -12
- usso-0.28.1/pytest.ini +22 -0
- usso-0.28.1/src/usso/__init__.py +25 -0
- usso-0.28.1/src/usso/auth/__init__.py +9 -0
- usso-0.28.1/src/usso/auth/api_key.py +43 -0
- usso-0.28.1/src/usso/auth/client.py +87 -0
- usso-0.28.1/src/usso/auth/config.py +115 -0
- {usso-0.27.22 → usso-0.28.1}/src/usso/exceptions.py +13 -0
- usso-0.28.1/src/usso/integrations/django/__init__.py +3 -0
- {usso-0.27.22/src/usso → usso-0.28.1/src/usso/integrations}/django/middleware.py +37 -29
- usso-0.28.1/src/usso/integrations/fastapi/__init__.py +8 -0
- usso-0.28.1/src/usso/integrations/fastapi/dependency.py +80 -0
- usso-0.28.1/src/usso/integrations/fastapi/handler.py +16 -0
- usso-0.28.1/src/usso/models/user.py +119 -0
- {usso-0.27.22 → usso-0.28.1}/src/usso/session/async_session.py +12 -8
- usso-0.28.1/src/usso/session/base_session.py +77 -0
- usso-0.28.1/src/usso/session/session.py +54 -0
- usso-0.28.1/src/usso/utils/method_utils.py +12 -0
- usso-0.28.1/src/usso/utils/string_utils.py +7 -0
- usso-0.28.1/src/usso.egg-info/PKG-INFO +172 -0
- usso-0.28.1/src/usso.egg-info/SOURCES.txt +31 -0
- {usso-0.27.22 → usso-0.28.1}/src/usso.egg-info/requires.txt +2 -5
- usso-0.28.1/tests/test_fastapi.py +102 -0
- usso-0.27.22/PKG-INFO +0 -110
- usso-0.27.22/README.md +0 -41
- usso-0.27.22/src/usso/__init__.py +0 -4
- usso-0.27.22/src/usso/b64tools.py +0 -20
- usso-0.27.22/src/usso/client/__init__.py +0 -4
- usso-0.27.22/src/usso/client/api.py +0 -174
- usso-0.27.22/src/usso/client/async_api.py +0 -159
- usso-0.27.22/src/usso/core.py +0 -160
- usso-0.27.22/src/usso/fastapi/__init__.py +0 -7
- usso-0.27.22/src/usso/fastapi/integration.py +0 -88
- usso-0.27.22/src/usso/schemas.py +0 -67
- usso-0.27.22/src/usso/session/base_session.py +0 -96
- usso-0.27.22/src/usso/session/session.py +0 -80
- usso-0.27.22/src/usso.egg-info/PKG-INFO +0 -110
- usso-0.27.22/src/usso.egg-info/SOURCES.txt +0 -25
- {usso-0.27.22 → usso-0.28.1}/LICENSE.txt +0 -0
- {usso-0.27.22 → usso-0.28.1}/setup.cfg +0 -0
- {usso-0.27.22 → usso-0.28.1}/src/usso/session/__init__.py +0 -0
- {usso-0.27.22/src/usso/django → usso-0.28.1/src/usso/utils}/__init__.py +0 -0
- {usso-0.27.22 → usso-0.28.1}/src/usso.egg-info/dependency_links.txt +0 -0
- {usso-0.27.22 → usso-0.28.1}/src/usso.egg-info/entry_points.txt +0 -0
- {usso-0.27.22 → usso-0.28.1}/src/usso.egg-info/top_level.txt +0 -0
usso-0.28.1/MANIFEST.in
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
include pytest.ini
|
usso-0.28.1/PKG-INFO
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: usso
|
3
|
+
Version: 0.28.1
|
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
|
+
|
usso-0.28.1/README.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# 🛡️ USSO Python Client SDK
|
2
|
+
|
3
|
+
The **USSO Python Client SDK** (`usso`) provides a universal, secure JWT authentication layer for Python microservices and web frameworks.
|
4
|
+
It’s designed to integrate seamlessly with the [USSO Identity Platform](https://github.com/ussoio/usso) — or any standards-compliant token issuer.
|
5
|
+
|
6
|
+
---
|
7
|
+
|
8
|
+
## 🔗 Relationship to the USSO Platform
|
9
|
+
|
10
|
+
This SDK is the official verification client for the **USSO** identity service, which provides multi-tenant authentication, RBAC, token flows, and more.
|
11
|
+
You can use the SDK with:
|
12
|
+
- Self-hosted USSO via Docker
|
13
|
+
- Any identity provider that issues signed JWTs (with proper config)
|
14
|
+
|
15
|
+
---
|
16
|
+
|
17
|
+
## ✨ Features
|
18
|
+
|
19
|
+
- ✅ **Token verification** for EdDSA, RS256, HS256, and more
|
20
|
+
- ✅ **Claim validation** (`exp`, `nbf`, `aud`, `iss`)
|
21
|
+
- ✅ **Remote JWK support** for key rotation
|
22
|
+
- ✅ **Typed payload parsing** via `UserData` (Pydantic)
|
23
|
+
- ✅ **Token extraction** from:
|
24
|
+
- `Authorization` header
|
25
|
+
- Cookies
|
26
|
+
- Custom headers
|
27
|
+
- ✅ **FastAPI integration** with dependency injection
|
28
|
+
- ✅ **Django middleware** for request-based user resolution
|
29
|
+
- 🧪 90% tested with `pytest` and `tox`
|
30
|
+
|
31
|
+
---
|
32
|
+
|
33
|
+
## 📦 Installation
|
34
|
+
|
35
|
+
```bash
|
36
|
+
pip install usso
|
37
|
+
````
|
38
|
+
|
39
|
+
With framework extras:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
pip install "usso[fastapi]" # for FastAPI integration
|
43
|
+
pip install "usso[django]" # for Django integration
|
44
|
+
```
|
45
|
+
|
46
|
+
---
|
47
|
+
|
48
|
+
## 🚀 Quick Start (FastAPI)
|
49
|
+
|
50
|
+
```python
|
51
|
+
from usso.fastapi.integration import get_authenticator
|
52
|
+
from usso.schemas import JWTConfig, JWTHeaderConfig, UserData
|
53
|
+
from usso.jwt.enums import Algorithm
|
54
|
+
|
55
|
+
config = JWTConfig(
|
56
|
+
key="your-ed25519-public-key",
|
57
|
+
issuer="https://sso.example.com",
|
58
|
+
audience="api.example.com",
|
59
|
+
type=Algorithm.EdDSA,
|
60
|
+
header=JWTHeaderConfig(type="Authorization")
|
61
|
+
)
|
62
|
+
|
63
|
+
authenticator = get_authenticator(config)
|
64
|
+
|
65
|
+
@app.get("/me")
|
66
|
+
def get_me(user: UserData = Depends(authenticator)):
|
67
|
+
return {"user_id": user.sub, "roles": user.roles}
|
68
|
+
```
|
69
|
+
|
70
|
+
---
|
71
|
+
|
72
|
+
## 🧱 Project Structure
|
73
|
+
|
74
|
+
```
|
75
|
+
src/usso/
|
76
|
+
├── fastapi/ # FastAPI adapter
|
77
|
+
├── django/ # Django middleware
|
78
|
+
├── jwt/ # Core JWT logic and algorithms
|
79
|
+
├── session/ # Stateless session support
|
80
|
+
├── models/ # JWTConfig, UserData, etc.
|
81
|
+
├── exceptions/ # Shared exceptions
|
82
|
+
├── authenticator.py # High-level API (token + user resolution)
|
83
|
+
```
|
84
|
+
|
85
|
+
---
|
86
|
+
|
87
|
+
## 🐳 Integrate with USSO (Docker)
|
88
|
+
|
89
|
+
Run your own identity provider:
|
90
|
+
|
91
|
+
```bash
|
92
|
+
docker run -p 8000:8000 ghcr.io/ussoio/usso:latest
|
93
|
+
```
|
94
|
+
|
95
|
+
Then configure your app to verify tokens issued by this service, using its public JWKS endpoint:
|
96
|
+
|
97
|
+
```python
|
98
|
+
JWTConfig(
|
99
|
+
jwk_url="http://localhost:8000/.well-known/jwks.json",
|
100
|
+
...
|
101
|
+
)
|
102
|
+
```
|
103
|
+
|
104
|
+
---
|
105
|
+
|
106
|
+
## 🧪 Testing
|
107
|
+
|
108
|
+
```bash
|
109
|
+
pytest
|
110
|
+
tox
|
111
|
+
```
|
112
|
+
|
113
|
+
---
|
114
|
+
|
115
|
+
## 🤝 Contributing
|
116
|
+
|
117
|
+
We welcome contributions!
|
118
|
+
|
119
|
+
---
|
120
|
+
|
121
|
+
## 📝 License
|
122
|
+
|
123
|
+
MIT License © \[mahdikiani]
|
124
|
+
|
@@ -4,23 +4,19 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "usso"
|
7
|
-
version = "0.
|
7
|
+
version = "0.28.1"
|
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"
|
11
|
-
license =
|
11
|
+
license = "MIT"
|
12
|
+
license-files = ["LICENSE.txt"]
|
12
13
|
keywords = ["usso", "sso", "authentication", "security", "fastapi", "django"]
|
13
|
-
authors = [
|
14
|
-
|
15
|
-
]
|
16
|
-
maintainers = [
|
17
|
-
{name = "Mahdi Kiani", email = "mahdikiany@gmail.com"}
|
18
|
-
]
|
14
|
+
authors = [{ name = "Mahdi Kiani", email = "mahdikiany@gmail.com" }]
|
15
|
+
maintainers = [{ name = "Mahdi Kiani", email = "mahdikiany@gmail.com" }]
|
19
16
|
classifiers = [
|
20
17
|
"Development Status :: 3 - Alpha",
|
21
18
|
"Intended Audience :: Developers",
|
22
19
|
"Topic :: Software Development :: Build Tools",
|
23
|
-
"License :: OSI Approved :: MIT License",
|
24
20
|
"Programming Language :: Python :: 3",
|
25
21
|
"Programming Language :: Python :: 3.10",
|
26
22
|
"Programming Language :: Python :: 3.11",
|
@@ -29,13 +25,29 @@ classifiers = [
|
|
29
25
|
]
|
30
26
|
dependencies = [
|
31
27
|
"pydantic>=2",
|
32
|
-
"
|
28
|
+
"cryptography>=43.0.0",
|
33
29
|
"cachetools",
|
34
30
|
"singleton_package",
|
35
31
|
"json-advanced",
|
36
32
|
"httpx",
|
33
|
+
"usso-jwt>=0.1.0",
|
37
34
|
]
|
38
|
-
optional-dependencies = {"fastapi"
|
35
|
+
optional-dependencies = { "fastapi" = [
|
36
|
+
"fastapi>=0.65.0",
|
37
|
+
"uvicorn[standard]>=0.13.0",
|
38
|
+
], "django" = [
|
39
|
+
"Django>=3.2",
|
40
|
+
], "dev" = [
|
41
|
+
"check-manifest",
|
42
|
+
], "test" = [
|
43
|
+
"coverage",
|
44
|
+
], "all" = [
|
45
|
+
"fastapi",
|
46
|
+
"uvicorn",
|
47
|
+
"django",
|
48
|
+
"dev",
|
49
|
+
"test",
|
50
|
+
] }
|
39
51
|
|
40
52
|
[project.urls]
|
41
53
|
"Homepage" = "https://github.com/ussoio/usso-python"
|
@@ -48,4 +60,17 @@ optional-dependencies = {"fastapi"=["fastapi>=0.65.0", "uvicorn[standard]>=0.13.
|
|
48
60
|
usso = "usso:main"
|
49
61
|
|
50
62
|
[tool.setuptools]
|
51
|
-
package-data = {"
|
63
|
+
package-data = { "usso_jwt" = ["*.dat"] }
|
64
|
+
|
65
|
+
[tool.ruff]
|
66
|
+
line-length = 79
|
67
|
+
target-version = "py313"
|
68
|
+
fix = true
|
69
|
+
unsafe-fixes = true
|
70
|
+
preview = true
|
71
|
+
|
72
|
+
[tool.ruff.lint]
|
73
|
+
select = ["E", "F", "W", "I", "UP", "B"]
|
74
|
+
|
75
|
+
[tool.ruff.format]
|
76
|
+
quote-style = "double"
|
usso-0.28.1/pytest.ini
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
[pytest]
|
2
|
+
pythonpath = .
|
3
|
+
testpaths = tests
|
4
|
+
|
5
|
+
asyncio_mode = auto
|
6
|
+
asyncio_default_fixture_loop_scope = session
|
7
|
+
|
8
|
+
|
9
|
+
addopts =
|
10
|
+
--cov=src/usso/integrations/fastapi
|
11
|
+
--cov=src/usso/auth
|
12
|
+
--cov=src/usso/models
|
13
|
+
; --cov=src/usso/session
|
14
|
+
--cov=src/usso/utils
|
15
|
+
; --cov=src/usso
|
16
|
+
--cov-report=term-missing
|
17
|
+
--cov-report=html
|
18
|
+
--cov-fail-under=75
|
19
|
+
|
20
|
+
filterwarnings =
|
21
|
+
ignore:.*pkg_resources.*:DeprecationWarning
|
22
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
"""USSO - Universal Single Sign-On Client
|
2
|
+
|
3
|
+
A plug-and-play client for integrating universal single sign-on (SSO)
|
4
|
+
with Python frameworks, enabling secure and seamless authentication
|
5
|
+
across microservices.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from .auth import APIHeaderConfig, AuthConfig, HeaderConfig, UssoAuth
|
9
|
+
from .exceptions import USSOException
|
10
|
+
from .models.user import UserData
|
11
|
+
|
12
|
+
__version__ = "0.28.0"
|
13
|
+
|
14
|
+
__all__ = [
|
15
|
+
# Main client
|
16
|
+
"UssoAuth",
|
17
|
+
# Configuration
|
18
|
+
"AuthConfig",
|
19
|
+
"HeaderConfig",
|
20
|
+
"APIHeaderConfig",
|
21
|
+
# Models
|
22
|
+
"UserData",
|
23
|
+
# Exceptions
|
24
|
+
"USSOException",
|
25
|
+
]
|
@@ -0,0 +1,9 @@
|
|
1
|
+
"""USSO Authentication Module.
|
2
|
+
|
3
|
+
This module provides the core authentication functionality for USSO.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from .client import UssoAuth
|
7
|
+
from .config import APIHeaderConfig, AuthConfig, HeaderConfig
|
8
|
+
|
9
|
+
__all__ = ["UssoAuth", "AuthConfig", "HeaderConfig", "APIHeaderConfig"]
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import logging
|
2
|
+
from urllib.parse import urlparse
|
3
|
+
|
4
|
+
import cachetools.func
|
5
|
+
import httpx
|
6
|
+
|
7
|
+
from ..exceptions import USSOException
|
8
|
+
from ..models.user import UserData
|
9
|
+
|
10
|
+
logger = logging.getLogger("usso")
|
11
|
+
|
12
|
+
|
13
|
+
def _handle_exception(error_type: str, **kwargs):
|
14
|
+
"""Handle API key related exceptions."""
|
15
|
+
if kwargs.get("raise_exception", True):
|
16
|
+
raise USSOException(
|
17
|
+
status_code=401, error=error_type, message=kwargs.get("message")
|
18
|
+
)
|
19
|
+
logger.error(kwargs.get("message") or error_type)
|
20
|
+
|
21
|
+
|
22
|
+
@cachetools.func.ttl_cache(maxsize=128, ttl=10 * 60)
|
23
|
+
def fetch_api_key_data(jwk_url: str, api_key: str):
|
24
|
+
"""Fetch user data using an API key.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
jwk_url: The JWK URL to use for verification
|
28
|
+
api_key: The API key to verify
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
UserData: The user data associated with the API key
|
32
|
+
|
33
|
+
Raises:
|
34
|
+
USSOException: If the API key is invalid or verification fails
|
35
|
+
"""
|
36
|
+
try:
|
37
|
+
parsed = urlparse(jwk_url)
|
38
|
+
url = f"{parsed.scheme}://{parsed.netloc}/api_key/verify"
|
39
|
+
response = httpx.post(url, json={"api_key": api_key})
|
40
|
+
response.raise_for_status()
|
41
|
+
return UserData(**response.json())
|
42
|
+
except Exception as e:
|
43
|
+
_handle_exception("error", message=str(e))
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
import usso_jwt.exceptions
|
4
|
+
import usso_jwt.schemas
|
5
|
+
|
6
|
+
from ..exceptions import _handle_exception
|
7
|
+
from ..models.user import UserData
|
8
|
+
from .api_key import fetch_api_key_data
|
9
|
+
from .config import AuthConfig, AvailableJwtConfigs
|
10
|
+
|
11
|
+
logger = logging.getLogger("usso")
|
12
|
+
|
13
|
+
|
14
|
+
class UssoAuth:
|
15
|
+
"""Main authentication client for USSO.
|
16
|
+
|
17
|
+
This client handles token validation, user data retrieval,
|
18
|
+
and API key verification.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(
|
22
|
+
self,
|
23
|
+
*,
|
24
|
+
jwt_config: AvailableJwtConfigs | None = None,
|
25
|
+
):
|
26
|
+
"""Initialize the USSO authentication client.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
jwt_config: JWT configuration(s) to use for token validation
|
30
|
+
"""
|
31
|
+
if jwt_config is None:
|
32
|
+
jwt_config = AuthConfig()
|
33
|
+
self.jwt_configs = AuthConfig.validate_jwt_configs(jwt_config)
|
34
|
+
|
35
|
+
def user_data_from_token(
|
36
|
+
self,
|
37
|
+
token: str,
|
38
|
+
*,
|
39
|
+
expected_acr: str | None = "access",
|
40
|
+
raise_exception: bool = True,
|
41
|
+
**kwargs,
|
42
|
+
) -> UserData | None:
|
43
|
+
"""Get user data from a JWT token.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
token: The JWT token to validate
|
47
|
+
expected_acr: Expected authentication context reference
|
48
|
+
raise_exception: Whether to raise exception on error
|
49
|
+
**kwargs: Additional arguments to pass to token verification
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
UserData if token is valid, None otherwise
|
53
|
+
|
54
|
+
Raises:
|
55
|
+
USSOException: If token is invalid and raise_exception is True
|
56
|
+
"""
|
57
|
+
exp = None
|
58
|
+
for jwk_config in self.jwt_configs:
|
59
|
+
try:
|
60
|
+
jwt_obj = usso_jwt.schemas.JWT(
|
61
|
+
token=token, config=jwk_config, payload_class=UserData
|
62
|
+
)
|
63
|
+
if jwt_obj.verify(expected_acr=expected_acr, **kwargs):
|
64
|
+
return jwt_obj.payload
|
65
|
+
except usso_jwt.exceptions.JWTError as e:
|
66
|
+
exp = e
|
67
|
+
|
68
|
+
_handle_exception(
|
69
|
+
"Unauthorized",
|
70
|
+
message=str(exp) if exp else None,
|
71
|
+
raise_exception=raise_exception,
|
72
|
+
**kwargs,
|
73
|
+
)
|
74
|
+
|
75
|
+
def user_data_from_api_key(self, api_key: str) -> UserData:
|
76
|
+
"""Get user data from an API key.
|
77
|
+
|
78
|
+
Args:
|
79
|
+
api_key: The API key to verify
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
UserData: The user data associated with the API key
|
83
|
+
|
84
|
+
Raises:
|
85
|
+
USSOException: If the API key is invalid
|
86
|
+
"""
|
87
|
+
return fetch_api_key_data(self.jwt_configs[0].jwk_url, api_key)
|
@@ -0,0 +1,115 @@
|
|
1
|
+
import json
|
2
|
+
from typing import Any, Literal, Union
|
3
|
+
|
4
|
+
import usso_jwt.config
|
5
|
+
from pydantic import BaseModel, model_validator
|
6
|
+
|
7
|
+
from ..models.user import UserData
|
8
|
+
from ..utils.string_utils import get_authorization_scheme_param
|
9
|
+
|
10
|
+
|
11
|
+
class HeaderConfig(BaseModel):
|
12
|
+
type: Literal["Authorization", "Cookie", "CustomHeader"] = "Cookie"
|
13
|
+
name: str = "usso_access_token"
|
14
|
+
|
15
|
+
@model_validator(mode="before")
|
16
|
+
def validate_header(cls, data: dict):
|
17
|
+
if data.get("type") == "Authorization" and not data.get("name"):
|
18
|
+
data["name"] = "Bearer"
|
19
|
+
elif data.get("type") == "Cookie":
|
20
|
+
data["name"] = data.get("name", "usso_access_token")
|
21
|
+
elif data.get("type") == "CustomHeader":
|
22
|
+
data["name"] = data.get("name", "x-usso-access-token")
|
23
|
+
return data
|
24
|
+
|
25
|
+
def __hash__(self):
|
26
|
+
return hash(self.model_dump_json())
|
27
|
+
|
28
|
+
def get_key(self, request) -> str | None:
|
29
|
+
headers: dict[str, Any] = getattr(request, "headers", {})
|
30
|
+
cookies: dict[str, str] = getattr(
|
31
|
+
request, "cookies", headers.get("Cookie", {})
|
32
|
+
)
|
33
|
+
if self.type == "CustomHeader":
|
34
|
+
return headers.get(self.name)
|
35
|
+
elif self.type == "Cookie":
|
36
|
+
return cookies.get(self.name)
|
37
|
+
elif self.type == "Authorization":
|
38
|
+
authorization = headers.get("Authorization")
|
39
|
+
if self.type == "Authorization":
|
40
|
+
authorization = headers.get("Authorization")
|
41
|
+
scheme, credentials = get_authorization_scheme_param(authorization)
|
42
|
+
if scheme.lower() == self.name.lower():
|
43
|
+
return credentials
|
44
|
+
|
45
|
+
|
46
|
+
class APIHeaderConfig(HeaderConfig):
|
47
|
+
verify_endpoint: str = "https://sso.usso.io/api_key/verify"
|
48
|
+
|
49
|
+
|
50
|
+
class AuthConfig(usso_jwt.config.JWTConfig):
|
51
|
+
"""Configuration for JWT processing."""
|
52
|
+
|
53
|
+
api_key_header: APIHeaderConfig | None = APIHeaderConfig(
|
54
|
+
type="CustomHeader", name="x-api-key"
|
55
|
+
)
|
56
|
+
jwt_header: HeaderConfig | None = HeaderConfig()
|
57
|
+
static_api_keys: list[str] | None = None
|
58
|
+
|
59
|
+
def get_api_key(self, request) -> str | None:
|
60
|
+
if self.api_key_header:
|
61
|
+
return self.api_key_header.get_key(request)
|
62
|
+
return None
|
63
|
+
|
64
|
+
def get_jwt(self, request) -> str | None:
|
65
|
+
if self.jwt_header:
|
66
|
+
return self.jwt_header.get_key(request)
|
67
|
+
return None
|
68
|
+
|
69
|
+
def verify_token(
|
70
|
+
self, token: str, *, raise_exception: bool = True, **kwargs
|
71
|
+
) -> bool:
|
72
|
+
from usso_jwt import exceptions as jwt_exceptions
|
73
|
+
from usso_jwt import schemas
|
74
|
+
|
75
|
+
try:
|
76
|
+
return schemas.JWT(
|
77
|
+
token=token,
|
78
|
+
config=self,
|
79
|
+
payload_class=UserData,
|
80
|
+
).verify(**kwargs)
|
81
|
+
except jwt_exceptions.JWTError as e:
|
82
|
+
if raise_exception:
|
83
|
+
raise e
|
84
|
+
return False
|
85
|
+
|
86
|
+
@classmethod
|
87
|
+
def _parse_config(
|
88
|
+
cls, config: Union[str, dict, "AuthConfig"]
|
89
|
+
) -> "AuthConfig":
|
90
|
+
"""Parse a single JWT configuration."""
|
91
|
+
if isinstance(config, str):
|
92
|
+
config = json.loads(config)
|
93
|
+
if isinstance(config, dict):
|
94
|
+
return cls(**config)
|
95
|
+
if isinstance(config, cls):
|
96
|
+
return config
|
97
|
+
raise ValueError("Invalid JWT configuration")
|
98
|
+
|
99
|
+
@classmethod
|
100
|
+
def validate_jwt_configs(
|
101
|
+
cls,
|
102
|
+
jwt_config: Union[
|
103
|
+
str, dict, "AuthConfig", list[str], list[dict], list["AuthConfig"]
|
104
|
+
],
|
105
|
+
) -> list["AuthConfig"]:
|
106
|
+
if isinstance(jwt_config, (str, dict, cls)):
|
107
|
+
return [cls._parse_config(jwt_config)]
|
108
|
+
if isinstance(jwt_config, list):
|
109
|
+
return [cls._parse_config(config) for config in jwt_config]
|
110
|
+
raise ValueError("Invalid jwt_config format")
|
111
|
+
|
112
|
+
|
113
|
+
AvailableJwtConfigs = (
|
114
|
+
str | dict | AuthConfig | list[str] | list[dict] | list[AuthConfig]
|
115
|
+
)
|