From dada376016c0255453000a6c4cbc010229ab0a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Ma=C3=AFzi?= Date: Thu, 2 Jul 2020 15:11:11 +0200 Subject: [PATCH] Ajout JWTAuthenticationBackend (from https://github.com/amitripshtos/starlette-jwt/blob/master/starlette_jwt/middleware.py). --- .gitignore | 2 + halfapi/app.py | 14 ++++- halfapi/lib/jwt_middleware.py | 102 ++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 4 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 halfapi/lib/jwt_middleware.py diff --git a/.gitignore b/.gitignore index a81c8ee..c9f3862 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,5 @@ dmypy.json # Cython debug symbols cython_debug/ + +domains/ diff --git a/halfapi/app.py b/halfapi/app.py index 920bb9e..bb891c3 100644 --- a/halfapi/app.py +++ b/halfapi/app.py @@ -9,9 +9,10 @@ from starlette.exceptions import HTTPException from starlette.middleware import Middleware from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request -from starlette.responses import Response +from starlette.responses import Response, JSONResponse from starlette.routing import Route, Match, Mount from starlette.types import ASGIApp, Receive, Scope, Send +from starlette.middleware.authentication import AuthenticationMiddleware # typing from typing import Any, Awaitable, Callable, MutableMapping @@ -26,6 +27,7 @@ from .models.api.view.route import Route as RouteView # module libraries from .lib.responses import ForbiddenResponse, NotFoundResponse +from .lib.jwt_middleware import JWTAuthenticationBackend def match_route(app: ASGIApp, scope: Scope): """ Checks all routes from "app" and checks if it matches with the one from @@ -215,7 +217,13 @@ def startup(): sys.stderr.write(str(e)) sys.exit(1) +async def root(request): + return JSONResponse({'payload': request.payload}) + app = Starlette( - middleware=[Middleware(AclCallerMiddleware)], - on_startup=[startup] + middleware=[ + Middleware(AuthenticationMiddleware, backend=JWTAuthenticationBackend(secret_key=open('/etc/half_orm/secret').read())), + Middleware(AclCallerMiddleware), + ], + on_startup=[startup], ) diff --git a/halfapi/lib/jwt_middleware.py b/halfapi/lib/jwt_middleware.py new file mode 100644 index 0000000..b4a2be2 --- /dev/null +++ b/halfapi/lib/jwt_middleware.py @@ -0,0 +1,102 @@ +__LICENSE__ = """ +BSD 3-Clause License + +Copyright (c) 2018, Amit Ripshtos +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import jwt +from uuid import UUID +from starlette.authentication import ( + AuthenticationBackend, AuthenticationError, BaseUser, AuthCredentials, + UnauthenticatedUser) + + +class JWTUser(BaseUser): + def __init__(self, id: UUID, token: str, payload: dict) -> None: + self.__id = id + self.token = token + self.payload = payload + + @property + def is_authenticated(self) -> bool: + return True + + @property + def id(self) -> str: + return self.id + + +class JWTAuthenticationBackend(AuthenticationBackend): + + def __init__(self, secret_key: str, algorithm: str = 'HS256', prefix: str = 'JWT', name: str = 'name'): + self.secret_key = secret_key + self.algorithm = algorithm + self.prefix = prefix + self.id = id + + async def authenticate(self, request): + if "Authorization" not in request.headers: + return None + + token = request.headers["Authorization"] + try: + payload = jwt.decode(token, key=self.secret_key, algorithms=self.algorithm) + except jwt.InvalidTokenError as e: + raise AuthenticationError(str(e)) + + return AuthCredentials(["authenticated"]), JWTUser( + id=payload['id'], token=token, payload=payload) + + +class JWTWebSocketAuthenticationBackend(AuthenticationBackend): + + def __init__(self, secret_key: str, algorithm: str = 'HS256', query_param_name: str = 'jwt', + id: UUID = None, audience = None, options = {}): + self.secret_key = secret_key + self.algorithm = algorithm + self.query_param_name = query_param_name + self.id = id + self.audience = audience + self.options = options + + + async def authenticate(self, request): + if self.query_param_name not in request.query_params: + return AuthCredentials(), UnauthenticatedUser() + + token = request.query_params[self.query_param_name] + + try: + payload = jwt.decode(token, key=self.secret_key, algorithms=self.algorithm, + audience=self.audience, options=self.options) + except jwt.InvalidTokenError as e: + raise AuthenticationError(str(e)) + + return AuthCredentials(["authenticated"]), JWTUser(id = payload['id'], + token=token, payload=payload) diff --git a/requirements.txt b/requirements.txt index 9f3662e..7d290d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ starlette uvicorn +jwt half_orm @ git+ssh://git@gite.lirmm.fr/newsi/halfORM.git apidb @ git+ssh://git@gite.lirmm.fr/newsi/db/hop_api.git