diff options
-rw-r--r-- | gn3/oauth2/__init__.py | 1 | ||||
-rw-r--r-- | gn3/oauth2/authorisation.py | 34 | ||||
-rw-r--r-- | gn3/oauth2/errors.py | 8 | ||||
-rw-r--r-- | gn3/oauth2/jwks.py | 35 | ||||
-rw-r--r-- | gn3/settings.py | 2 |
5 files changed, 79 insertions, 1 deletions
diff --git a/gn3/oauth2/__init__.py b/gn3/oauth2/__init__.py new file mode 100644 index 0000000..8001d34 --- /dev/null +++ b/gn3/oauth2/__init__.py @@ -0,0 +1 @@ +"""Package to handle OAuth2 authorisation and other issues.""" diff --git a/gn3/oauth2/authorisation.py b/gn3/oauth2/authorisation.py new file mode 100644 index 0000000..3864455 --- /dev/null +++ b/gn3/oauth2/authorisation.py @@ -0,0 +1,34 @@ +"""Handle authorisation with auth server.""" +from functools import wraps + +from authlib.jose import jwt +from flask import request, jsonify, current_app as app + +from gn3.oauth2 import jwks +from gn3.oauth2.errors import TokenValidationError + +def require_token(func): + """Check for and verify bearer token.""" + @wraps(func) + def __auth__(*args, **kwargs): + try: + bearer = request.headers.get("Authorization", "") + if bearer.startswith("Bearer"): + # validate token and return it + _extra, token = [item.strip() for item in bearer.split(" ")] + _jwt = jwks.validate_token( + token, + jwks.fetch_jwks(app.config["AUTH_SERVER_URL"], + "auth/public-jwks")) + return func(*args, **{**kwargs, "auth_token": {"access_token": token, "jwt": _jwt}}) + error_message = "We expected a bearer token but did not get one." + except TokenValidationError as _tve: + app.logger.debug("Token validation failed.", exc_info=True) + error_message = "The token was found to be invalid." + + return jsonify({ + "error": "TokenValidationError", + "description": error_message + }), 400 + + return __auth__ diff --git a/gn3/oauth2/errors.py b/gn3/oauth2/errors.py new file mode 100644 index 0000000..f8cfd2c --- /dev/null +++ b/gn3/oauth2/errors.py @@ -0,0 +1,8 @@ +"""List of possible errors.""" + +class AuthorisationError(Exception): + """Top-level error class dealing with generic authorisation errors.""" + + +class TokenValidationError(AuthorisationError): + """Class to indicate that token validation failed.""" diff --git a/gn3/oauth2/jwks.py b/gn3/oauth2/jwks.py new file mode 100644 index 0000000..adaa3e9 --- /dev/null +++ b/gn3/oauth2/jwks.py @@ -0,0 +1,35 @@ +"""Utilities dealing with JSON Web Keys (JWK)""" +from urllib.parse import urljoin + +import requests +from flask import current_app as app +from authlib.jose.errors import BadSignatureError +from authlib.jose import KeySet, JsonWebKey, JsonWebToken + +from gn3.oauth2.errors import TokenValidationError + + +def fetch_jwks(authserveruri: str, path: str = "auth/public-jwks") -> KeySet: + """Fetch the JWKs from a particular URI""" + try: + response = requests.get(urljoin(authserveruri, path)) + if response.status_code == 200: + return KeySet([ + JsonWebKey.import_key(key) for key in response.json()["jwks"]]) + except Exception as _exc: + app.logger.debug("There was an error fetching the JSON Web Keys.", + exc_info=True) + + return KeySet([]) + + +def validate_token(token: str, keys: KeySet) -> dict: + """Validate the token against the given keys.""" + for key in keys.keys: + kd = key.as_dict() + try: + return JsonWebToken(["RS256"]).decode(token, key=key) + except BadSignatureError as _bse: + pass + + raise TokenValidationError("No key was found for validation.") diff --git a/gn3/settings.py b/gn3/settings.py index 13a2976..1e794ff 100644 --- a/gn3/settings.py +++ b/gn3/settings.py @@ -85,7 +85,7 @@ ROUND_TO = 10 MULTIPROCESSOR_PROCS = 6 # Number of processes to spawn -AUTH_SERVER_URL = "" +AUTH_SERVER_URL = "https://auth.genenetwork.org" AUTH_MIGRATIONS = "migrations/auth" AUTH_DB = os.environ.get( "AUTH_DB", f"{os.environ.get('HOME')}/genenetwork/gn3_files/db/auth.db") |