usso 0.28.0__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.0/src/usso.egg-info → usso-0.28.1}/PKG-INFO +1 -1
- {usso-0.28.0 → usso-0.28.1}/pyproject.toml +1 -1
- {usso-0.28.0 → usso-0.28.1}/src/usso/auth/client.py +6 -4
- usso-0.28.1/src/usso/integrations/fastapi/__init__.py +8 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/integrations/fastapi/dependency.py +17 -32
- usso-0.28.1/src/usso/integrations/fastapi/handler.py +16 -0
- {usso-0.28.0 → usso-0.28.1/src/usso.egg-info}/PKG-INFO +1 -1
- {usso-0.28.0 → usso-0.28.1}/src/usso.egg-info/SOURCES.txt +1 -0
- usso-0.28.1/tests/test_fastapi.py +102 -0
- usso-0.28.0/src/usso/integrations/fastapi/__init__.py +0 -3
- usso-0.28.0/tests/test_fastapi.py +0 -79
- {usso-0.28.0 → usso-0.28.1}/LICENSE.txt +0 -0
- {usso-0.28.0 → usso-0.28.1}/MANIFEST.in +0 -0
- {usso-0.28.0 → usso-0.28.1}/README.md +0 -0
- {usso-0.28.0 → usso-0.28.1}/pytest.ini +0 -0
- {usso-0.28.0 → usso-0.28.1}/setup.cfg +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/__init__.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/auth/__init__.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/auth/api_key.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/auth/config.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/exceptions.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/integrations/django/__init__.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/integrations/django/middleware.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/models/user.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/session/__init__.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/session/async_session.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/session/base_session.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/session/session.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/utils/__init__.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/utils/method_utils.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso/utils/string_utils.py +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso.egg-info/dependency_links.txt +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso.egg-info/entry_points.txt +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso.egg-info/requires.txt +0 -0
- {usso-0.28.0 → usso-0.28.1}/src/usso.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: usso
|
3
|
-
Version: 0.28.
|
3
|
+
Version: 0.28.1
|
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.28.
|
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"
|
@@ -65,10 +65,12 @@ class UssoAuth:
|
|
65
65
|
except usso_jwt.exceptions.JWTError as e:
|
66
66
|
exp = e
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
68
|
+
_handle_exception(
|
69
|
+
"Unauthorized",
|
70
|
+
message=str(exp) if exp else None,
|
71
|
+
raise_exception=raise_exception,
|
72
|
+
**kwargs,
|
73
|
+
)
|
72
74
|
|
73
75
|
def user_data_from_api_key(self, api_key: str) -> UserData:
|
74
76
|
"""Get user data from an API key.
|
@@ -1,13 +1,10 @@
|
|
1
1
|
import logging
|
2
2
|
|
3
|
-
from starlette.status import HTTP_401_UNAUTHORIZED
|
4
|
-
|
5
3
|
from fastapi import Request, WebSocket
|
6
|
-
from fastapi.responses import JSONResponse
|
7
4
|
|
8
5
|
from ...auth import UssoAuth
|
9
6
|
from ...auth.config import AuthConfig, AvailableJwtConfigs
|
10
|
-
from ...exceptions import
|
7
|
+
from ...exceptions import _handle_exception
|
11
8
|
from ...models.user import UserData
|
12
9
|
from ...utils.method_utils import instance_method
|
13
10
|
|
@@ -26,6 +23,9 @@ class USSOAuthentication(UssoAuth):
|
|
26
23
|
super().__init__(jwt_config=jwt_config)
|
27
24
|
self.raise_exception = raise_exception
|
28
25
|
|
26
|
+
def __call__(self, request: Request) -> UserData:
|
27
|
+
return self.usso_access_security(request)
|
28
|
+
|
29
29
|
@instance_method
|
30
30
|
def get_request_jwt(self, request: Request | WebSocket) -> str | None:
|
31
31
|
for jwt_config in self.jwt_configs:
|
@@ -42,7 +42,7 @@ class USSOAuthentication(UssoAuth):
|
|
42
42
|
return token
|
43
43
|
return None
|
44
44
|
|
45
|
-
@instance_method
|
45
|
+
# @instance_method
|
46
46
|
def usso_access_security(self, request: Request) -> UserData | None:
|
47
47
|
"""Return the user associated with a token value."""
|
48
48
|
api_key = self.get_request_api_key(request)
|
@@ -54,15 +54,14 @@ class USSOAuthentication(UssoAuth):
|
|
54
54
|
return self.user_data_from_token(
|
55
55
|
token, raise_exception=self.raise_exception
|
56
56
|
)
|
57
|
-
if self.raise_exception:
|
58
|
-
raise USSOException(
|
59
|
-
status_code=HTTP_401_UNAUTHORIZED,
|
60
|
-
error="unauthorized",
|
61
|
-
message="No token provided",
|
62
|
-
)
|
63
|
-
return None
|
64
57
|
|
65
|
-
|
58
|
+
_handle_exception(
|
59
|
+
"Unauthorized",
|
60
|
+
message="No token provided",
|
61
|
+
raise_exception=self.raise_exception,
|
62
|
+
)
|
63
|
+
|
64
|
+
# @instance_method
|
66
65
|
def jwt_access_security_ws(self, websocket: WebSocket) -> UserData | None:
|
67
66
|
"""Return the user associated with a token value."""
|
68
67
|
api_key = self.get_request_api_key(websocket)
|
@@ -74,22 +73,8 @@ class USSOAuthentication(UssoAuth):
|
|
74
73
|
return self.user_data_from_token(
|
75
74
|
token, raise_exception=self.raise_exception
|
76
75
|
)
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
)
|
83
|
-
return None
|
84
|
-
|
85
|
-
|
86
|
-
async def usso_exception_handler(request: Request, exc: USSOException):
|
87
|
-
return JSONResponse(
|
88
|
-
status_code=exc.status_code,
|
89
|
-
content={"message": exc.message, "error": exc.error},
|
90
|
-
)
|
91
|
-
|
92
|
-
|
93
|
-
EXCEPTION_HANDLERS = {
|
94
|
-
USSOException: usso_exception_handler,
|
95
|
-
}
|
76
|
+
_handle_exception(
|
77
|
+
"Unauthorized",
|
78
|
+
message="No token provided",
|
79
|
+
raise_exception=self.raise_exception,
|
80
|
+
)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from fastapi import Request
|
2
|
+
from fastapi.responses import JSONResponse
|
3
|
+
|
4
|
+
from ...exceptions import USSOException
|
5
|
+
|
6
|
+
|
7
|
+
async def usso_exception_handler(request: Request, exc: USSOException):
|
8
|
+
return JSONResponse(
|
9
|
+
status_code=exc.status_code,
|
10
|
+
content={"message": exc.message, "error": exc.error},
|
11
|
+
)
|
12
|
+
|
13
|
+
|
14
|
+
EXCEPTION_HANDLERS = {
|
15
|
+
USSOException: usso_exception_handler,
|
16
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: usso
|
3
|
-
Version: 0.28.
|
3
|
+
Version: 0.28.1
|
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>
|
@@ -19,6 +19,7 @@ src/usso/integrations/django/__init__.py
|
|
19
19
|
src/usso/integrations/django/middleware.py
|
20
20
|
src/usso/integrations/fastapi/__init__.py
|
21
21
|
src/usso/integrations/fastapi/dependency.py
|
22
|
+
src/usso/integrations/fastapi/handler.py
|
22
23
|
src/usso/models/user.py
|
23
24
|
src/usso/session/__init__.py
|
24
25
|
src/usso/session/async_session.py
|
@@ -0,0 +1,102 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
from collections.abc import AsyncGenerator
|
4
|
+
|
5
|
+
import httpx
|
6
|
+
import pytest
|
7
|
+
import pytest_asyncio
|
8
|
+
from fastapi import Depends, WebSocket
|
9
|
+
from starlette.testclient import TestClient
|
10
|
+
from usso_jwt.algorithms import AbstractKey
|
11
|
+
|
12
|
+
from src.usso import UserData
|
13
|
+
from src.usso.exceptions import USSOException
|
14
|
+
from src.usso.integrations.fastapi import (
|
15
|
+
USSOAuthentication,
|
16
|
+
usso_exception_handler,
|
17
|
+
)
|
18
|
+
|
19
|
+
|
20
|
+
@pytest.fixture(scope="session")
|
21
|
+
def app(test_key: AbstractKey):
|
22
|
+
import fastapi
|
23
|
+
|
24
|
+
os.environ["JWT_CONFIG"] = json.dumps({
|
25
|
+
"type": "EDDSA",
|
26
|
+
"key": test_key.public_pem().decode(),
|
27
|
+
"jwt_header": {"type": "Authorization"},
|
28
|
+
})
|
29
|
+
|
30
|
+
app = fastapi.FastAPI()
|
31
|
+
|
32
|
+
app.add_exception_handler(USSOException, usso_exception_handler)
|
33
|
+
|
34
|
+
usso = USSOAuthentication()
|
35
|
+
|
36
|
+
@app.get("/user")
|
37
|
+
async def get_user(
|
38
|
+
user: UserData = Depends(usso.usso_access_security), # noqa: B008
|
39
|
+
):
|
40
|
+
return user.model_dump()
|
41
|
+
|
42
|
+
@app.websocket("/ws")
|
43
|
+
async def websocket_endpoint(
|
44
|
+
websocket: WebSocket,
|
45
|
+
user: UserData = Depends(usso.jwt_access_security_ws),
|
46
|
+
):
|
47
|
+
await websocket.accept()
|
48
|
+
await websocket.send_json({"msg": user.model_dump()})
|
49
|
+
# await websocket.send_json({"msg": "Hello WebSocket"})
|
50
|
+
await websocket.close()
|
51
|
+
|
52
|
+
return app
|
53
|
+
|
54
|
+
|
55
|
+
@pytest_asyncio.fixture(scope="session")
|
56
|
+
async def client(app) -> AsyncGenerator[httpx.AsyncClient]:
|
57
|
+
"""Fixture to provide an AsyncClient for FastAPI app."""
|
58
|
+
|
59
|
+
async with httpx.AsyncClient(
|
60
|
+
transport=httpx.ASGITransport(app=app),
|
61
|
+
base_url="http://test.uln.me",
|
62
|
+
) as ac:
|
63
|
+
yield ac
|
64
|
+
|
65
|
+
|
66
|
+
@pytest.mark.asyncio
|
67
|
+
async def test_get_user_no_token(client: httpx.AsyncClient):
|
68
|
+
response = await client.get("/user")
|
69
|
+
print(response.json())
|
70
|
+
assert response.status_code == 401
|
71
|
+
|
72
|
+
|
73
|
+
@pytest.mark.asyncio
|
74
|
+
async def test_get_user_with_invalid_token(client: httpx.AsyncClient):
|
75
|
+
response = await client.get(
|
76
|
+
"/user",
|
77
|
+
headers={"Authorization": "Bearer test"},
|
78
|
+
)
|
79
|
+
assert response.status_code == 401
|
80
|
+
|
81
|
+
|
82
|
+
@pytest.mark.asyncio
|
83
|
+
async def test_get_user_with_token(
|
84
|
+
client: httpx.AsyncClient,
|
85
|
+
test_valid_token: str,
|
86
|
+
test_valid_payload: dict,
|
87
|
+
):
|
88
|
+
response = await client.get(
|
89
|
+
"/user",
|
90
|
+
headers={"Authorization": f"Bearer {test_valid_token}"},
|
91
|
+
)
|
92
|
+
assert response.status_code == 200
|
93
|
+
assert response.json().get("claims") == test_valid_payload
|
94
|
+
|
95
|
+
|
96
|
+
def test_websocket(app, test_valid_token: str, test_valid_payload: dict):
|
97
|
+
client = TestClient(app)
|
98
|
+
with client.websocket_connect(
|
99
|
+
"/ws", headers={"Authorization": f"Bearer {test_valid_token}"}
|
100
|
+
) as websocket:
|
101
|
+
data = websocket.receive_json()
|
102
|
+
assert data.get("msg").get("claims") == test_valid_payload
|
@@ -1,79 +0,0 @@
|
|
1
|
-
from collections.abc import AsyncGenerator
|
2
|
-
|
3
|
-
import httpx
|
4
|
-
import pytest
|
5
|
-
import pytest_asyncio
|
6
|
-
from fastapi import Depends
|
7
|
-
from usso_jwt.algorithms import AbstractKey
|
8
|
-
|
9
|
-
from src.usso import AuthConfig, UserData
|
10
|
-
from src.usso.exceptions import USSOException
|
11
|
-
from src.usso.integrations.fastapi import USSOAuthentication
|
12
|
-
|
13
|
-
|
14
|
-
@pytest.fixture(scope="session")
|
15
|
-
def app(test_key: AbstractKey):
|
16
|
-
import fastapi
|
17
|
-
|
18
|
-
app = fastapi.FastAPI()
|
19
|
-
config = AuthConfig(
|
20
|
-
key=test_key.public_pem(),
|
21
|
-
jwt_header={"type": "Authorization"},
|
22
|
-
)
|
23
|
-
usso = USSOAuthentication(jwt_config=config)
|
24
|
-
|
25
|
-
async def get_current_user(request: fastapi.Request) -> UserData:
|
26
|
-
return usso.usso_access_security(request)
|
27
|
-
|
28
|
-
@app.get("/user")
|
29
|
-
async def get_user(
|
30
|
-
user: UserData = Depends(get_current_user), # noqa: B008
|
31
|
-
):
|
32
|
-
print(user)
|
33
|
-
return user.model_dump()
|
34
|
-
|
35
|
-
return app
|
36
|
-
|
37
|
-
|
38
|
-
@pytest_asyncio.fixture(scope="session")
|
39
|
-
async def client(app) -> AsyncGenerator[httpx.AsyncClient]:
|
40
|
-
"""Fixture to provide an AsyncClient for FastAPI app."""
|
41
|
-
|
42
|
-
async with httpx.AsyncClient(
|
43
|
-
transport=httpx.ASGITransport(app=app),
|
44
|
-
base_url="http://test.uln.me",
|
45
|
-
) as ac:
|
46
|
-
yield ac
|
47
|
-
|
48
|
-
|
49
|
-
@pytest.mark.asyncio
|
50
|
-
async def test_get_user_no_token(client: httpx.AsyncClient):
|
51
|
-
with pytest.raises(USSOException):
|
52
|
-
response = await client.get("/user")
|
53
|
-
print(response.json())
|
54
|
-
assert response.status_code == 401
|
55
|
-
|
56
|
-
|
57
|
-
@pytest.mark.asyncio
|
58
|
-
async def test_get_user_with_invalid_token(client: httpx.AsyncClient):
|
59
|
-
with pytest.raises(USSOException):
|
60
|
-
response = await client.get(
|
61
|
-
"/user",
|
62
|
-
headers={"Authorization": "Bearer test"},
|
63
|
-
)
|
64
|
-
assert response.status_code == 401
|
65
|
-
|
66
|
-
|
67
|
-
@pytest.mark.asyncio
|
68
|
-
async def test_get_user_with_token(
|
69
|
-
client: httpx.AsyncClient,
|
70
|
-
test_valid_token: str,
|
71
|
-
test_valid_payload: dict,
|
72
|
-
):
|
73
|
-
response = await client.get(
|
74
|
-
"/user",
|
75
|
-
headers={"Authorization": f"Bearer {test_valid_token}"},
|
76
|
-
)
|
77
|
-
assert response.status_code == 200
|
78
|
-
print(response.json(), test_valid_payload)
|
79
|
-
assert response.json().get("claims") == test_valid_payload
|
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
|
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
|