aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2024-07-26 16:43:16 -0500
committerFrederick Muriuki Muriithi2024-08-05 09:52:15 -0500
commita603fff86dfeb658a39953d12f0404cbd2b2ae87 (patch)
tree534eb2e6d4dd14d4ad9584d3fe60ac45755e12e5
parentf50c9220b4baf2f02b078eb8be0de427243377d3 (diff)
downloadgn-uploader-a603fff86dfeb658a39953d12f0404cbd2b2ae87.tar.gz
Add OAuth2/JWK-related endpoints.
-rw-r--r--uploader/__init__.py2
-rw-r--r--uploader/oauth2/views.py102
2 files changed, 104 insertions, 0 deletions
diff --git a/uploader/__init__.py b/uploader/__init__.py
index e4b5b32..787f220 100644
--- a/uploader/__init__.py
+++ b/uploader/__init__.py
@@ -14,6 +14,7 @@ from .parse import parsebp
from .samples import samples
from .base_routes import base
from .dbinsert import dbinsertbp
+from .oauth2.views import oauth2
from .errors import register_error_handlers
def override_settings_with_envvars(
@@ -53,6 +54,7 @@ def create_app():
app.register_blueprint(base, url_prefix="/")
app.register_blueprint(entrybp, url_prefix="/")
app.register_blueprint(parsebp, url_prefix="/parse")
+ app.register_blueprint(oauth2, url_prefix="/oauth2")
app.register_blueprint(upload, url_prefix="/upload")
app.register_blueprint(dbinsertbp, url_prefix="/dbinsert")
app.register_blueprint(samples, url_prefix="/samples")
diff --git a/uploader/oauth2/views.py b/uploader/oauth2/views.py
new file mode 100644
index 0000000..c33f7bc
--- /dev/null
+++ b/uploader/oauth2/views.py
@@ -0,0 +1,102 @@
+"""Views for OAuth2 related functionality."""
+import uuid
+from datetime import datetime, timedelta
+from urllib.parse import urljoin, urlparse, urlunparse
+
+from authlib.jose import jwt
+from flask import (
+ flash,
+ jsonify,
+ url_for,
+ request,
+ redirect,
+ Blueprint,
+ current_app as app)
+
+from uploader import session
+from uploader import monadic_requests as requests
+
+from . import jwks
+from .client import SCOPE, oauth2_get, oauth2_clientid, authserver_uri
+
+oauth2 = Blueprint("oauth2", __name__)
+
+@oauth2.route("/code")
+def authorisation_code():
+ """Receive authorisation code from auth server and use it to get token."""
+ def __process_error__(resp_or_exception):
+ app.logger.debug("ERROR: (%s)", resp_or_exception)
+ flash("There was an error retrieving the authorisation token.",
+ "alert-danger")
+ return redirect("/")
+
+ def __fail_set_user_details__(_failure):
+ app.logger.debug("Fetching user details fails: %s", _failure)
+ flash("Could not retrieve the user details", "alert-danger")
+ return redirect("/")
+
+ def __success_set_user_details__(_success):
+ app.logger.debug("Session info: %s", _success)
+ return redirect("/")
+
+ def __success__(token):
+ session.set_user_token(token)
+ return oauth2_get("auth/user/").then(
+ lambda usrdets: session.set_user_details({
+ "user_id": uuid.UUID(usrdets["user_id"]),
+ "name": usrdets["name"],
+ "email": usrdets["email"],
+ "token": session.user_token(),
+ "logged_in": True})).either(
+ __fail_set_user_details__,
+ __success_set_user_details__)
+
+ code = request.args.get("code", "").strip()
+ if not bool(code):
+ flash("AuthorisationError: No code was provided.", "alert-danger")
+ return redirect("/")
+
+ baseurl = urlparse(request.base_url, scheme=request.scheme)
+ issued = datetime.now()
+ jwtkey = jwks.newest_jwk_with_rotation(
+ jwks.jwks_directory(app, "UPLOADER_SECRETS"),
+ int(app.config["JWKS_ROTATION_AGE_DAYS"]))
+ return requests.post(
+ urljoin(authserver_uri(), "auth/token"),
+ json={
+ "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
+ "code": code,
+ "scope": SCOPE,
+ "redirect_uri": urljoin(
+ urlunparse(baseurl),
+ url_for("oauth2.authorisation_code")),
+ "assertion": jwt.encode(
+ header={
+ "alg": "RS256",
+ "typ": "JWT",
+ "kid": jwtkey.as_dict()["kid"]
+ },
+ payload={
+ "iss": str(oauth2_clientid()),
+ "sub": request.args["user_id"],
+ "aud": urljoin(authserver_uri(),"auth/token"),
+ "exp": (issued + timedelta(minutes=5)).timestamp(),
+ "nbf": int(issued.timestamp()),
+ "iat": int(issued.timestamp()),
+ "jti": str(uuid.uuid4())
+ },
+ key=jwtkey).decode("utf8"),
+ "client_id": oauth2_clientid()
+ }).either(__process_error__, __success__)
+
+@oauth2.route("/public-jwks")
+def public_jwks():
+ """List the available JWKs"""
+ return jsonify({
+ "documentation": (
+ "The keys are listed in order of creation, from the oldest (first) "
+ "to the newest (last)."),
+ "jwks": tuple(key.as_dict() for key
+ in jwks.list_jwks(jwks.jwks_directory(
+ app, "UPLOADER_SECRETS")))
+ })