aboutsummaryrefslogtreecommitdiff
path: root/uploader/oauth2
diff options
context:
space:
mode:
Diffstat (limited to 'uploader/oauth2')
-rw-r--r--uploader/oauth2/client.py26
-rw-r--r--uploader/oauth2/tokens.py47
-rw-r--r--uploader/oauth2/views.py60
3 files changed, 84 insertions, 49 deletions
diff --git a/uploader/oauth2/client.py b/uploader/oauth2/client.py
index 70a32ff..b94a044 100644
--- a/uploader/oauth2/client.py
+++ b/uploader/oauth2/client.py
@@ -1,6 +1,7 @@
"""OAuth2 client utilities."""
import json
import time
+import uuid
import random
from datetime import datetime, timedelta
from urllib.parse import urljoin, urlparse
@@ -42,7 +43,8 @@ def __fetch_auth_server_jwks__() -> KeySet:
return KeySet([
JsonWebKey.import_key(key)
for key in requests.get(
- urljoin(authserver_uri(), "auth/public-jwks")
+ urljoin(authserver_uri(), "auth/public-jwks"),
+ timeout=(9.13, 20)
).json()["jwks"]])
@@ -112,7 +114,8 @@ def oauth2_client():
try:
jwt = JsonWebToken(["RS256"]).decode(
token["access_token"], key=jwk)
- return datetime.now().timestamp() > jwt["exp"]
+ if bool(jwt.get("exp")):
+ return datetime.now().timestamp() > jwt["exp"]
except BadSignatureError as _bse:
pass
@@ -145,9 +148,24 @@ def oauth2_client():
__client__)
+def fetch_user_details() -> Either:
+ """Retrieve user details from the auth server"""
+ suser = session.session_info()["user"]
+ if suser["email"] == "anon@ymous.user":
+ udets = 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()}))
+ return udets
+ return Right(suser)
+
+
def user_logged_in():
"""Check whether the user has logged in."""
suser = session.session_info()["user"]
+ fetch_user_details()
return suser["logged_in"] and suser["token"].is_right()
@@ -191,7 +209,7 @@ def oauth2_get(url, **kwargs) -> Either:
return Right(resp.json())
return Left(resp)
except Exception as exc:#pylint: disable=[broad-except]
- app.logger.error("Error retriving data from auth server: (GET %s)",
+ app.logger.error("Error retrieving data from auth server: (GET %s)",
_uri,
exc_info=True)
return Left(exc)
@@ -223,7 +241,7 @@ def oauth2_post(url, data=None, json=None, **kwargs):#pylint: disable=[redefined
return Right(resp.json())
return Left(resp)
except Exception as exc:#pylint: disable=[broad-except]
- app.logger.error("Error retriving data from auth server: (POST %s)",
+ app.logger.error("Error retrieving data from auth server: (POST %s)",
_uri,
exc_info=True)
return Left(exc)
diff --git a/uploader/oauth2/tokens.py b/uploader/oauth2/tokens.py
new file mode 100644
index 0000000..eb650f6
--- /dev/null
+++ b/uploader/oauth2/tokens.py
@@ -0,0 +1,47 @@
+"""Utilities for dealing with tokens."""
+import uuid
+from typing import Union
+from urllib.parse import urljoin
+from datetime import datetime, timedelta
+
+from authlib.jose import jwt
+from flask import current_app as app
+
+from uploader import monadic_requests as mrequests
+
+from . import jwks
+from .client import (SCOPE, authserver_uri, oauth2_clientid)
+
+
+def request_token(token_uri: str, user_id: Union[uuid.UUID, str], **kwargs):
+ """Request token from the auth server."""
+ issued = datetime.now()
+ jwtkey = jwks.newest_jwk_with_rotation(
+ jwks.jwks_directory(app, "UPLOADER_SECRETS"),
+ int(app.config["JWKS_ROTATION_AGE_DAYS"]))
+ _mins2expiry = kwargs.get("minutes_to_expiry", 5)
+ return mrequests.post(
+ token_uri,
+ json={
+ "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
+ "scope": kwargs.get("scope", SCOPE),
+ "assertion": jwt.encode(
+ header={
+ "alg": "RS256",
+ "typ": "JWT",
+ "kid": jwtkey.as_dict()["kid"]
+ },
+ payload={
+ "iss": str(oauth2_clientid()),
+ "sub": str(user_id),
+ "aud": urljoin(authserver_uri(), "auth/token"),
+ "exp": (issued + timedelta(minutes=_mins2expiry)).timestamp(),
+ "nbf": int(issued.timestamp()),
+ "iat": int(issued.timestamp()),
+ "jti": str(uuid.uuid4())
+ },
+ key=jwtkey).decode("utf8"),
+ "client_id": oauth2_clientid(),
+ **kwargs.get("extra_params", {})
+ }
+ )
diff --git a/uploader/oauth2/views.py b/uploader/oauth2/views.py
index 61037f3..1ee4257 100644
--- a/uploader/oauth2/views.py
+++ b/uploader/oauth2/views.py
@@ -1,9 +1,6 @@
"""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,
@@ -18,28 +15,29 @@ from uploader import monadic_requests as mrequests
from uploader.monadic_requests import make_error_handler
from . import jwks
+from .tokens import request_token
from .client import (
- SCOPE,
- oauth2_get,
user_logged_in,
authserver_uri,
oauth2_clientid,
+ fetch_user_details,
oauth2_clientsecret)
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)
+ def __process_error__(error_response):
+ app.logger.debug("ERROR: (%s)", error_response.content)
flash("There was an error retrieving the authorisation token.",
- "alert-danger")
+ "alert 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")
+ flash("Could not retrieve the user details", "alert alert-danger")
return redirect("/")
def __success_set_user_details__(_success):
@@ -48,52 +46,24 @@ def authorisation_code():
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(
+ return fetch_user_details().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")
+ flash("AuthorisationError: No code was provided.", "alert 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 mrequests.post(
- urljoin(authserver_uri(), "auth/token"),
- json={
- "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
+ return request_token(
+ token_uri=urljoin(authserver_uri(), "auth/token"),
+ user_id=request.args["user_id"],
+ extra_params={
"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")
@@ -116,7 +86,7 @@ def logout():
_user = session_info["user"]
_user_str = f"{_user['name']} ({_user['email']})"
session.clear_session_info()
- flash("Successfully logged out.", "alert-success")
+ flash("Successfully signed out.", "alert alert-success")
return redirect("/")
if user_logged_in():
@@ -134,5 +104,5 @@ def logout():
cleanup_thunk=lambda: __unset_session__(
session.session_info())),
lambda res: __unset_session__(session.session_info()))
- flash("There is no user that is currently logged in.", "alert-info")
+ flash("There is no user that is currently logged in.", "alert alert-info")
return redirect("/")