Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update AuthenticationBackend signature #581

Merged
merged 1 commit into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ The class `AuthenticationBackend` has three methods you need to override:
* `logout`: Will be called only for the logout, usually clearin the session.

```python
from typing import Optional

from sqladmin import Admin
from sqladmin.authentication import AuthenticationBackend
from starlette.requests import Request
Expand All @@ -37,13 +35,14 @@ class AdminAuth(AuthenticationBackend):
request.session.clear()
return True

async def authenticate(self, request: Request) -> Optional[RedirectResponse]:
async def authenticate(self, request: Request) -> bool:
token = request.session.get("token")

if not token:
return RedirectResponse(request.url_for("admin:login"), status_code=302)
return False

# Check the token in depth
return True


authentication_backend = AdminAuth(secret_key="...")
Expand All @@ -56,8 +55,6 @@ admin = Admin(app=..., authentication_backend=authentication_backend، ...)
??? example "Full Example"

```python
from typing import Optional

from sqladmin import Admin, ModelView
from sqladmin.authentication import AuthenticationBackend
from sqlalchemy import Column, Integer, String, create_engine
Expand Down Expand Up @@ -93,9 +90,14 @@ admin = Admin(app=..., authentication_backend=authentication_backend، ...)
request.session.clear()
return True

async def authenticate(self, request: Request) -> Optional[RedirectResponse]:
if not "token" in request.session:
return RedirectResponse(request.url_for("admin:login"), status_code=302)
async def authenticate(self, request: Request) -> bool:
token = request.session.get("token")

if not token:
return False

# Check the token in depth
return True


app = Starlette()
Expand All @@ -120,7 +122,7 @@ You can also integrate OAuth into SQLAdmin, for this example we will integrate G
If you have followed the previous example, there are only two changes required to the authentication flow:

```python
from typing import Optional
from typing import Union

from authlib.integrations.starlette_client import OAuth
from sqladmin.authentication import AuthenticationBackend
Expand Down Expand Up @@ -155,12 +157,14 @@ class AdminAuth(AuthenticationBackend):
request.session.clear()
return True

async def authenticate(self, request: Request) -> Optional[RedirectResponse]:
async def authenticate(self, request: Request) -> Union[bool, RedirectResponse]:
user = request.session.get("user")
if not user:
redirect_uri = request.url_for('login_google')
return await google.authorize_redirect(request, redirect_uri)

return True


admin = Admin(app=app, engine=engine, authentication_backend=AdminAuth("test"))

Expand Down
16 changes: 9 additions & 7 deletions sqladmin/authentication.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import functools
import inspect
from typing import Any, Callable, Optional
from typing import Any, Callable, Union

from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware
from starlette.requests import Request
from starlette.responses import Response
from starlette.responses import RedirectResponse, Response


class AuthenticationBackend:
Expand All @@ -32,14 +32,14 @@ async def logout(self, request: Request) -> bool:
"""
raise NotImplementedError()

async def authenticate(self, request: Request) -> Optional[Response]:
async def authenticate(self, request: Request) -> Union[Response, bool]:
"""Implement authenticate logic here.
This method will be called for each incoming request
to validate the authentication.

If the request is authenticated, this method should return `None` or do nothing.
Otherwise it should return a `Response` object,
like a redirect to the login page or SSO page.
If a 'Response' or `RedirectResponse` is returned,
that response is returned to the user,
otherwise a True/False is expected.
"""
raise NotImplementedError()

Expand All @@ -56,8 +56,10 @@ async def wrapper_decorator(*args: Any, **kwargs: Any) -> Any:
auth_backend = getattr(admin, "authentication_backend", None)
if auth_backend is not None:
response = await auth_backend.authenticate(request)
if response and isinstance(response, Response):
if isinstance(response, Response):
return response
if not bool(response):
return RedirectResponse(request.url_for("admin:login"), status_code=302)

if inspect.iscoroutinefunction(func):
return await func(*args, **kwargs)
Expand Down
5 changes: 3 additions & 2 deletions tests/test_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ async def logout(self, request: Request) -> bool:
request.session.clear()
return True

async def authenticate(self, request: Request) -> RedirectResponse:
if "token" not in request.session:
async def authenticate(self, request: Request) -> bool:
if "token" in request.session:
return RedirectResponse(request.url_for("admin:login"), status_code=302)
return False


app = Starlette()
Expand Down