From c0be43af6e05e34f3c079925acfcde4718414def Mon Sep 17 00:00:00 2001 From: Amin Alaee Date: Fri, 18 Aug 2023 09:43:13 +0200 Subject: [PATCH] Update AuthenticationBackend signature --- docs/authentication.md | 26 +++++++++++++++----------- sqladmin/authentication.py | 16 +++++++++------- tests/test_authentication.py | 5 +++-- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/docs/authentication.md b/docs/authentication.md index f7ea61aa..4574d899 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -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 @@ -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="...") @@ -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 @@ -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() @@ -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 @@ -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")) diff --git a/sqladmin/authentication.py b/sqladmin/authentication.py index 25772ff4..af139748 100644 --- a/sqladmin/authentication.py +++ b/sqladmin/authentication.py @@ -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: @@ -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() @@ -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) diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 1549edc8..747213b2 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -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()