usso 0.24.8__py3-none-any.whl → 0.24.10__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/async_session.py +20 -20
- usso/fastapi/auth_middleware.py +176 -0
- {usso-0.24.8.dist-info → usso-0.24.10.dist-info}/METADATA +1 -1
- {usso-0.24.8.dist-info → usso-0.24.10.dist-info}/RECORD +8 -7
- {usso-0.24.8.dist-info → usso-0.24.10.dist-info}/LICENSE.txt +0 -0
- {usso-0.24.8.dist-info → usso-0.24.10.dist-info}/WHEEL +0 -0
- {usso-0.24.8.dist-info → usso-0.24.10.dist-info}/entry_points.txt +0 -0
- {usso-0.24.8.dist-info → usso-0.24.10.dist-info}/top_level.txt +0 -0
usso/async_session.py
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
from contextlib import asynccontextmanager
|
1
2
|
from datetime import datetime
|
3
|
+
|
2
4
|
import aiohttp
|
3
5
|
import jwt
|
4
6
|
|
@@ -52,35 +54,33 @@ class AsyncUssoSession:
|
|
52
54
|
if self.session:
|
53
55
|
await self.session.close() # Close the session properly
|
54
56
|
|
57
|
+
@asynccontextmanager
|
55
58
|
async def _request(self, method: str, url: str, **kwargs):
|
56
59
|
await self._ensure_valid_token() # Ensure valid token before any request
|
57
|
-
|
60
|
+
async with self.session.request(method, url, **kwargs) as response:
|
61
|
+
yield response
|
62
|
+
|
63
|
+
def get(self, url: str, **kwargs):
|
64
|
+
return self._request("GET", url, **kwargs)
|
58
65
|
|
59
|
-
|
60
|
-
return
|
66
|
+
def post(self, url: str, **kwargs):
|
67
|
+
return self._request("POST", url, **kwargs)
|
61
68
|
|
62
|
-
|
63
|
-
return
|
69
|
+
def put(self, url: str, **kwargs):
|
70
|
+
return self._request("PUT", url, **kwargs)
|
64
71
|
|
65
|
-
|
66
|
-
return
|
72
|
+
def patch(self, url: str, **kwargs):
|
73
|
+
return self._request("PATCH", url, **kwargs)
|
67
74
|
|
68
|
-
|
69
|
-
return
|
75
|
+
def delete(self, url: str, **kwargs):
|
76
|
+
return self._request("DELETE", url, **kwargs)
|
70
77
|
|
71
|
-
|
72
|
-
return
|
78
|
+
def head(self, url: str, **kwargs):
|
79
|
+
return self._request("HEAD", url, **kwargs)
|
73
80
|
|
74
|
-
|
75
|
-
return
|
81
|
+
def options(self, url: str, **kwargs):
|
82
|
+
return self._request("OPTIONS", url, **kwargs)
|
76
83
|
|
77
|
-
async def options(self, url: str, **kwargs):
|
78
|
-
return await self._request("OPTIONS", url, **kwargs)
|
79
|
-
|
80
84
|
async def close(self):
|
81
85
|
await self.session.close()
|
82
86
|
self.session = None
|
83
|
-
|
84
|
-
async def __del__(self):
|
85
|
-
if self.session:
|
86
|
-
await self.session.close()
|
@@ -0,0 +1,176 @@
|
|
1
|
+
import json
|
2
|
+
import logging
|
3
|
+
import os
|
4
|
+
|
5
|
+
import cachetools.func
|
6
|
+
import jwt
|
7
|
+
from fastapi import Request, WebSocket
|
8
|
+
from pydantic import BaseModel, model_validator
|
9
|
+
from starlette.status import HTTP_401_UNAUTHORIZED
|
10
|
+
|
11
|
+
from usso.core import UserData
|
12
|
+
from usso.exceptions import USSOException
|
13
|
+
|
14
|
+
logger = logging.getLogger("usso")
|
15
|
+
|
16
|
+
|
17
|
+
class JWTConfig(BaseModel):
|
18
|
+
jwk_url: str | None = None
|
19
|
+
secret: str | None = None
|
20
|
+
type: str = "RS256"
|
21
|
+
header: dict[str, str] = {"type": "Cookie", "name": "usso_access_token"}
|
22
|
+
|
23
|
+
def __hash__(self):
|
24
|
+
return hash(self.model_dump_json())
|
25
|
+
|
26
|
+
@model_validator(mode="before")
|
27
|
+
def validate_secret(cls, data: dict):
|
28
|
+
if not data.get("jwk_url") and not data.get("secret"):
|
29
|
+
raise ValueError("Either jwk_url or secret must be provided")
|
30
|
+
return data
|
31
|
+
|
32
|
+
@classmethod
|
33
|
+
@cachetools.func.ttl_cache(maxsize=128, ttl=10 * 60)
|
34
|
+
def get_jwk_keys(cls, jwk_url):
|
35
|
+
return jwt.PyJWKClient(
|
36
|
+
jwk_url,
|
37
|
+
headers={
|
38
|
+
"User-Agent": "usso-python",
|
39
|
+
},
|
40
|
+
)
|
41
|
+
|
42
|
+
@cachetools.func.ttl_cache(maxsize=128, ttl=10 * 60)
|
43
|
+
def decode(self, token: str):
|
44
|
+
if self.jwk_url:
|
45
|
+
jwk_client = self.get_jwk_keys(self.jwk_url)
|
46
|
+
signing_key = jwk_client.get_signing_key_from_jwt(token)
|
47
|
+
return jwt.decode(token, signing_key.key, algorithms=[self.type])
|
48
|
+
|
49
|
+
return jwt.decode(token, self.secret, algorithms=[self.type])
|
50
|
+
|
51
|
+
|
52
|
+
class Usso:
|
53
|
+
|
54
|
+
def __init__(self, jwt_config: str | dict | JWTConfig | None = None):
|
55
|
+
if jwt_config is None:
|
56
|
+
self.jwk_url = os.getenv("USSO_JWK_URL")
|
57
|
+
return
|
58
|
+
|
59
|
+
if isinstance(jwt_config, str):
|
60
|
+
jwt_config = json.loads(jwt_config)
|
61
|
+
if isinstance(jwt_config, dict):
|
62
|
+
jwt_config = JWTConfig(**jwt_config)
|
63
|
+
|
64
|
+
self.jwk_url = jwt_config
|
65
|
+
self.jwt_config = jwt_config
|
66
|
+
|
67
|
+
def get_authorization_scheme_param(
|
68
|
+
self,
|
69
|
+
authorization_header_value: str | None,
|
70
|
+
) -> tuple[str, str]:
|
71
|
+
if not authorization_header_value:
|
72
|
+
return "", ""
|
73
|
+
scheme, _, param = authorization_header_value.partition(" ")
|
74
|
+
return scheme, param
|
75
|
+
|
76
|
+
def user_data_from_token(self, token: str, **kwargs) -> UserData | None:
|
77
|
+
"""Return the user associated with a token value."""
|
78
|
+
try:
|
79
|
+
decoded = self.jwk_url.decode(token)
|
80
|
+
if decoded["token_type"] != "access":
|
81
|
+
raise USSOException(
|
82
|
+
status_code=401,
|
83
|
+
error="invalid_token_type",
|
84
|
+
)
|
85
|
+
decoded["token"] = token
|
86
|
+
return UserData(**decoded)
|
87
|
+
except jwt.exceptions.ExpiredSignatureError:
|
88
|
+
if kwargs.get("raise_exception", True):
|
89
|
+
raise USSOException(status_code=401, error="expired_signature")
|
90
|
+
except jwt.exceptions.InvalidSignatureError:
|
91
|
+
if kwargs.get("raise_exception", True):
|
92
|
+
raise USSOException(status_code=401, error="invalid_signature")
|
93
|
+
except jwt.exceptions.InvalidAlgorithmError:
|
94
|
+
if kwargs.get("raise_exception", True):
|
95
|
+
raise USSOException(
|
96
|
+
status_code=401,
|
97
|
+
error="invalid_algorithm",
|
98
|
+
)
|
99
|
+
except jwt.exceptions.InvalidIssuedAtError:
|
100
|
+
if kwargs.get("raise_exception", True):
|
101
|
+
raise USSOException(
|
102
|
+
status_code=401,
|
103
|
+
error="invalid_issued_at",
|
104
|
+
)
|
105
|
+
except jwt.exceptions.InvalidTokenError:
|
106
|
+
if kwargs.get("raise_exception", True):
|
107
|
+
raise USSOException(
|
108
|
+
status_code=401,
|
109
|
+
error="invalid_token",
|
110
|
+
)
|
111
|
+
except jwt.exceptions.InvalidKeyError:
|
112
|
+
if kwargs.get("raise_exception", True):
|
113
|
+
raise USSOException(
|
114
|
+
status_code=401,
|
115
|
+
error="invalid_key",
|
116
|
+
)
|
117
|
+
except KeyError as e:
|
118
|
+
if kwargs.get("raise_exception", True):
|
119
|
+
raise USSOException(
|
120
|
+
status_code=401,
|
121
|
+
error="key_error",
|
122
|
+
message=str(e),
|
123
|
+
)
|
124
|
+
except USSOException as e:
|
125
|
+
if kwargs.get("raise_exception", True):
|
126
|
+
raise e
|
127
|
+
except Exception as e:
|
128
|
+
if kwargs.get("raise_exception", True):
|
129
|
+
raise USSOException(
|
130
|
+
status_code=401,
|
131
|
+
error="error",
|
132
|
+
message=str(e),
|
133
|
+
)
|
134
|
+
logger.error(e)
|
135
|
+
|
136
|
+
async def jwt_access_security(self, request: Request) -> UserData | None:
|
137
|
+
"""Return the user associated with a token value."""
|
138
|
+
kwargs = {}
|
139
|
+
authorization = request.headers.get("Authorization")
|
140
|
+
if authorization:
|
141
|
+
scheme, credentials = self.get_authorization_scheme_param(authorization)
|
142
|
+
if scheme.lower() == "bearer":
|
143
|
+
token = credentials
|
144
|
+
return self.user_data_from_token(token, **kwargs)
|
145
|
+
|
146
|
+
cookie_token = request.cookies.get("usso_access_token")
|
147
|
+
if cookie_token:
|
148
|
+
return self.user_data_from_token(cookie_token, **kwargs)
|
149
|
+
|
150
|
+
if kwargs.get("raise_exception", True):
|
151
|
+
raise USSOException(
|
152
|
+
status_code=HTTP_401_UNAUTHORIZED,
|
153
|
+
error="unauthorized",
|
154
|
+
)
|
155
|
+
return None
|
156
|
+
|
157
|
+
async def jwt_access_security_ws(self, websocket: WebSocket) -> UserData | None:
|
158
|
+
"""Return the user associated with a token value."""
|
159
|
+
kwargs = {}
|
160
|
+
authorization = websocket.headers.get("Authorization")
|
161
|
+
if authorization:
|
162
|
+
scheme, credentials = self.get_authorization_scheme_param(authorization)
|
163
|
+
if scheme.lower() == "bearer":
|
164
|
+
token = credentials
|
165
|
+
return self.user_data_from_token(token, **kwargs)
|
166
|
+
|
167
|
+
cookie_token = websocket.cookies.get("usso_access_token")
|
168
|
+
if cookie_token:
|
169
|
+
return self.user_data_from_token(cookie_token, **kwargs)
|
170
|
+
|
171
|
+
if kwargs.get("raise_exception", True):
|
172
|
+
raise USSOException(
|
173
|
+
status_code=HTTP_401_UNAUTHORIZED,
|
174
|
+
error="unauthorized",
|
175
|
+
)
|
176
|
+
return None
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: usso
|
3
|
-
Version: 0.24.
|
3
|
+
Version: 0.24.10
|
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>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
usso/__init__.py,sha256=NnOS_S1a-JKTOlGe1nw-kCL3m0y82mA2mDraus7BQ2o,120
|
2
2
|
usso/api.py,sha256=xlDq2nZNpq3mhAvqIbGEfANHNjJpPquSeULBfS7iMJw,5094
|
3
3
|
usso/async_api.py,sha256=rb-Xh5oudmZrPYM_iH_B75b5Z0Fvi1V1uurdcKE51w0,5551
|
4
|
-
usso/async_session.py,sha256=
|
4
|
+
usso/async_session.py,sha256=EPHT0wjwGuRg3NLvtUlLNSVZCgvAvRyUGx21XBsEBHk,3028
|
5
5
|
usso/b64tools.py,sha256=HGQ0E59vzjrQo2-4jrcY03ebtTaYwTtCZ7KgJaEmxO0,610
|
6
6
|
usso/core.py,sha256=m6Y-g70FuPYZM3AMtsEVVToQkYrf4WwNVH8C4HZRzl8,4103
|
7
7
|
usso/exceptions.py,sha256=hawOAuVbvQtjgRfwp1KFZ4SmV7fh720y5Gom9JVA8W8,504
|
@@ -10,10 +10,11 @@ usso/session.py,sha256=S3dFXzar0Pcwxj5TGqadKwGdmzmzgp4H2W3brfOwJ6A,1184
|
|
10
10
|
usso/django/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
usso/django/middleware.py,sha256=EEEpHvMQ6QiWw2HY8zQ2Aec0RCATcLWsCKeyiPWJKio,3245
|
12
12
|
usso/fastapi/__init__.py,sha256=0EcdOzb4f3yu9nILIdGWnlyUz-0VaVX2az1e3f2BusI,201
|
13
|
+
usso/fastapi/auth_middleware.py,sha256=5JnDHvBM_xfasSHpBIFlrxFsZTLS2AhMuvyzr0tyI8k,6323
|
13
14
|
usso/fastapi/integration.py,sha256=VAUWaa7ChQ1jTtn8A136VgyG6t2kDo5pGK-3RgmNDVs,1669
|
14
|
-
usso-0.24.
|
15
|
-
usso-0.24.
|
16
|
-
usso-0.24.
|
17
|
-
usso-0.24.
|
18
|
-
usso-0.24.
|
19
|
-
usso-0.24.
|
15
|
+
usso-0.24.10.dist-info/LICENSE.txt,sha256=ceC9ZJOV9H6CtQDcYmHOS46NA3dHJ_WD4J9blH513pc,1081
|
16
|
+
usso-0.24.10.dist-info/METADATA,sha256=11Of45nfG3Y5of89Q3WY8ITH3FEJBgBLCZhyLlWmEfw,4232
|
17
|
+
usso-0.24.10.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
18
|
+
usso-0.24.10.dist-info/entry_points.txt,sha256=4Zgpm5ELaAWPf0jPGJFz1_X69H7un8ycT3WdGoJ0Vvk,35
|
19
|
+
usso-0.24.10.dist-info/top_level.txt,sha256=g9Jf6h1Oyidh0vPiFni7UHInTJjSvu6cUalpLTIvthg,5
|
20
|
+
usso-0.24.10.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|