about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--gn3/oauth2/__init__.py1
-rw-r--r--gn3/oauth2/authorisation.py34
-rw-r--r--gn3/oauth2/errors.py8
-rw-r--r--gn3/oauth2/jwks.py35
-rw-r--r--gn3/settings.py2
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")