diff options
Diffstat (limited to 'gn3/oauth2')
-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 | 36 |
4 files changed, 79 insertions, 0 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..b2dd1ae --- /dev/null +++ b/gn3/oauth2/authorisation.py @@ -0,0 +1,34 @@ +"""Handle authorisation with auth server.""" +from functools import wraps + +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..8798a3f --- /dev/null +++ b/gn3/oauth2/jwks.py @@ -0,0 +1,36 @@ +"""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"]]) + # XXXX: TODO: Catch specific exception we need. + # pylint: disable=W0703 + 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: + try: + return JsonWebToken(["RS256"]).decode(token, key=key) + except BadSignatureError as _bse: + pass + + raise TokenValidationError("No key was found for validation.") |