From d944d45bbf86d29781ee2561eb914fd0ad5f2e4b Mon Sep 17 00:00:00 2001 From: "Maxime Alves LIRMM@home" Date: Thu, 27 Aug 2020 17:45:23 +0200 Subject: [PATCH] [auth] added "debug flag" check and wrote relative tests close #12 --- halfapi/lib/jwt_middleware.py | 37 ++++++++++++++++-- tests/test_jwt_middleware.py | 60 ++++++++++++++++++++++++++++-- tests/test_jwt_middleware_debug.py | 54 +++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 tests/test_jwt_middleware_debug.py diff --git a/halfapi/lib/jwt_middleware.py b/halfapi/lib/jwt_middleware.py index d5c9551..1d8d91c 100644 --- a/halfapi/lib/jwt_middleware.py +++ b/halfapi/lib/jwt_middleware.py @@ -30,12 +30,34 @@ 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. """ +from os import environ + import jwt from uuid import UUID from starlette.authentication import ( AuthenticationBackend, AuthenticationError, BaseUser, AuthCredentials, UnauthenticatedUser) +import logging +logger = logging.getLogger('halfapi') + +try: + from ..conf import PRODUCTION +except ImportError: + logger.warning('Could not import PRODUCTION variable from conf module,'\ + ' using HALFAPI_PROD environment variable') + PRODUCTION = environ.get('HALFAPI_PROD') or False + +try: + from ..conf import SECRET +except ImportError: + logger.warning('Could not import SECRET variable from conf module,'\ + ' using HALFAPI_SECRET environment variable') + SECRET = environ.get('HALFAPI_SECRET', False) + if not SECRET: + raise Exception('Missing HALFAPI_SECRET variable') + + class JWTUser(BaseUser): def __init__(self, id: UUID, token: str, payload: dict) -> None: @@ -64,13 +86,14 @@ class JWTUser(BaseUser): class JWTAuthenticationBackend(AuthenticationBackend): - def __init__(self, secret_key: str, algorithm: str = 'HS256', prefix: str = 'JWT', name: str = 'name'): + def __init__(self, secret_key: str = SECRET, + algorithm: str = 'HS256', prefix: str = 'JWT'): + if secret_key is None: raise Exception('Missing secret_key argument for JWTAuthenticationBackend') 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: @@ -78,7 +101,15 @@ class JWTAuthenticationBackend(AuthenticationBackend): token = request.headers["Authorization"] try: - payload = jwt.decode(token, key=self.secret_key, algorithms=self.algorithm) + payload = jwt.decode(token, + key=self.secret_key, + algorithms=self.algorithm, + verify=True) + + if PRODUCTION and 'debug' in payload.keys(): + raise AuthenticationError( + 'Trying to connect using *DEBUG* token in *PRODUCTION* mode') + except jwt.InvalidTokenError as e: raise AuthenticationError(str(e)) except Exception as e: diff --git a/tests/test_jwt_middleware.py b/tests/test_jwt_middleware.py index b009c33..ac70551 100644 --- a/tests/test_jwt_middleware.py +++ b/tests/test_jwt_middleware.py @@ -1,17 +1,33 @@ +import os import jwt -import requests +from requests import Request import pytest +from unittest.mock import patch import json from json.decoder import JSONDecodeError import sys from hashlib import sha256 from base64 import b64decode -from starlette.testclient import TestClient +from uuid import uuid4, UUID -from halfapi.app import app -from halfapi.lib.jwt_middleware import (JWTUser, JWTAuthenticationBackend, +from starlette.testclient import TestClient +from starlette.authentication import ( + AuthenticationBackend, AuthenticationError, BaseUser, AuthCredentials, + UnauthenticatedUser) + + +#from halfapi.app import app +os.environ['HALFAPI_PROD'] = 'True' +os.environ['HALFAPI_SECRET'] = 'randomsecret' + +from halfapi.lib.jwt_middleware import (PRODUCTION, SECRET, + JWTUser, JWTAuthenticationBackend, JWTWebSocketAuthenticationBackend) +def test_constants(): + assert PRODUCTION == bool(os.environ['HALFAPI_PROD']) + assert SECRET == os.environ['HALFAPI_SECRET'] + @pytest.fixture def token(): # This fixture needs to have a running auth-lirmm on 127.0.0.1:3000 @@ -33,6 +49,16 @@ def token(): return res['token'] + +@pytest.fixture +def token_builder(): + yield jwt.encode({ + 'name':'xxx', + 'id': str(uuid4())}, + key=SECRET + ) + + @pytest.fixture def token_dirser(): # This fixture needs to have a running auth-lirmm on 127.0.0.1:3000 @@ -55,6 +81,7 @@ def token_dirser(): return res['token'] +""" def test_token(token): client = TestClient(app) @@ -90,3 +117,28 @@ def test_labopers(token, token_dirser): }) assert res.status_code == 200 +""" + +def test_JWTUser(): + uid = uuid4() + token = '{}' + payload = {} + user = JWTUser(uid, token, payload) + assert user.id == uid + assert user.token == token + assert user.payload == payload + assert user.is_authenticated == True + +@pytest.mark.asyncio +async def test_JWTAuthenticationBackend(token_builder): + backend = JWTAuthenticationBackend() + assert backend.secret_key == SECRET + + req = Request( + headers={ + 'Authorization': token_builder + }) + + credentials, user = await backend.authenticate(req) + assert type(user) == JWTUser + assert type(credentials) == AuthCredentials diff --git a/tests/test_jwt_middleware_debug.py b/tests/test_jwt_middleware_debug.py new file mode 100644 index 0000000..bc4beb7 --- /dev/null +++ b/tests/test_jwt_middleware_debug.py @@ -0,0 +1,54 @@ +import os +import jwt +from requests import Request +import pytest +from unittest.mock import patch +import json +from json.decoder import JSONDecodeError +import sys +from hashlib import sha256 +from base64 import b64decode +from uuid import uuid4, UUID + +from starlette.testclient import TestClient +from starlette.authentication import ( + AuthenticationBackend, AuthenticationError, BaseUser, AuthCredentials, + UnauthenticatedUser) + + +#from halfapi.app import app +os.environ['HALFAPI_PROD'] = '' +os.environ['HALFAPI_SECRET'] = 'randomsecret' + +from halfapi.lib.jwt_middleware import (PRODUCTION, SECRET, + JWTUser, JWTAuthenticationBackend, + JWTWebSocketAuthenticationBackend) + +def test_constants(): + assert PRODUCTION == bool(os.environ['HALFAPI_PROD']) + assert SECRET == os.environ['HALFAPI_SECRET'] + + +@pytest.fixture +def token_debug_builder(): + yield jwt.encode({ + 'name':'xxx', + 'id': str(uuid4()), + 'debug': True}, + key=SECRET + ) + + +@pytest.mark.asyncio +async def test_JWTAuthenticationBackend_debug(token_debug_builder): + backend = JWTAuthenticationBackend() + + req = Request( + headers={ + 'Authorization': token_debug_builder + }) + + auth = await backend.authenticate(req) + assert(len(auth) == 2) + assert type(auth[0]) == AuthCredentials + assert type(auth[1]) == JWTUser