aboutsummaryrefslogtreecommitdiff
path: root/gn2
diff options
context:
space:
mode:
Diffstat (limited to 'gn2')
-rw-r--r--gn2/wqflask/__init__.py59
-rw-r--r--gn2/wqflask/app_errors.py1
-rw-r--r--gn2/wqflask/marker_regression/display_mapping_results.py4
-rw-r--r--gn2/wqflask/oauth2/request_utils.py3
-rw-r--r--gn2/wqflask/oauth2/toplevel.py65
5 files changed, 102 insertions, 30 deletions
diff --git a/gn2/wqflask/__init__.py b/gn2/wqflask/__init__.py
index 1f69c085..4dee4fea 100644
--- a/gn2/wqflask/__init__.py
+++ b/gn2/wqflask/__init__.py
@@ -1,7 +1,9 @@
"""Entry point for flask app"""
# pylint: disable=C0413,E0611
import os
+import sys
import time
+import logging
import datetime
from typing import Tuple
from pathlib import Path
@@ -10,6 +12,7 @@ from urllib.parse import urljoin, urlparse
import redis
import jinja2
from flask_session import Session
+from authlib.jose import JsonWebKey
from authlib.integrations.requests_client import OAuth2Session
from flask import g, Flask, flash, session, url_for, redirect, current_app
@@ -44,13 +47,6 @@ from gn2.wqflask.startup import (
startup_errors,
check_mandatory_configs)
-app = Flask(__name__)
-
-
-# See http://flask.pocoo.org/docs/config/#configuring-from-files
-# Note no longer use the badly named WQFLASK_OVERRIDES (nyi)
-app.config.from_object('gn2.default_settings')
-app.config.from_envvar('GN2_SETTINGS')
def numcoll():
"""Handle possible errors."""
@@ -59,6 +55,52 @@ def numcoll():
except Exception as _exc:
return "ERROR"
+
+def parse_ssl_key(app: Flask, keyconfig: str):
+ """Parse key file paths into objects"""
+ keypath = app.config.get(keyconfig, "").strip()
+ if not bool(keypath):
+ app.logger.error("Expected configuration '%s'", keyconfig)
+ return
+
+ with open(keypath) as _sslkey:
+ app.config[keyconfig] = JsonWebKey.import_key(_sslkey.read())
+
+
+def dev_loggers(appl: Flask) -> None:
+ """Default development logging."""
+ formatter = logging.Formatter(
+ fmt="[%(asctime)s] %(levelname)s in %(module)s: %(message)s")
+ stderr_handler = logging.StreamHandler(stream=sys.stderr)
+ stderr_handler.setFormatter(formatter)
+ appl.logger.addHandler(stderr_handler)
+
+ root_logger = logging.getLogger()
+ root_logger.addHandler(stderr_handler)
+ root_logger.setLevel(appl.config.get("LOGLEVEL", "WARNING"))
+
+
+def gunicorn_loggers(appl: Flask) -> None:
+ """Logging with gunicorn WSGI server."""
+ logger = logging.getLogger("gunicorn.error")
+ appl.logger.handlers = logger.handlers
+ appl.logger.setLevel(logger.level)
+
+def setup_logging(appl: Flask) -> None:
+ """Setup appropriate logging"""
+ software, *_version_and_comments = os.environ.get(
+ "SERVER_SOFTWARE", "").split('/')
+ gunicorn_loggers(app) if software == "gunicorn" else dev_loggers(app)
+
+
+app = Flask(__name__)
+setup_logging(app)
+
+# See http://flask.pocoo.org/docs/config/#configuring-from-files
+# Note no longer use the badly named WQFLASK_OVERRIDES (nyi)
+app.config.from_object('gn2.default_settings')
+app.config.from_envvar('GN2_SETTINGS')
+
app.jinja_env.globals.update(
undefined=jinja2.StrictUndefined,
numify=formatting.numify,
@@ -107,6 +149,9 @@ except StartupError as serr:
server_session = Session(app)
+parse_ssl_key(app, "SSL_PRIVATE_KEY")
+parse_ssl_key(app, "AUTH_SERVER_SSL_PUBLIC_KEY")
+
@app.before_request
def before_request():
g.request_start_time = time.time()
diff --git a/gn2/wqflask/app_errors.py b/gn2/wqflask/app_errors.py
index b0e3b665..503f4e1c 100644
--- a/gn2/wqflask/app_errors.py
+++ b/gn2/wqflask/app_errors.py
@@ -3,6 +3,7 @@ import os
import random
import datetime
import traceback
+from uuid import uuid4
from werkzeug.exceptions import InternalServerError
from authlib.integrations.base_client.errors import InvalidTokenError
diff --git a/gn2/wqflask/marker_regression/display_mapping_results.py b/gn2/wqflask/marker_regression/display_mapping_results.py
index 0f1df3ec..dec414c0 100644
--- a/gn2/wqflask/marker_regression/display_mapping_results.py
+++ b/gn2/wqflask/marker_regression/display_mapping_results.py
@@ -214,8 +214,8 @@ class DisplayMappingResults:
# BEGIN HaplotypeAnalyst
HAPLOTYPE_POSITIVE = BLUE
- HAPLOTYPE_NEGATIVE = YELLOW
- HAPLOTYPE_HETEROZYGOUS = GREEN
+ HAPLOTYPE_NEGATIVE = RED
+ HAPLOTYPE_HETEROZYGOUS = ORANGE
HAPLOTYPE_RECOMBINATION = DARKGRAY
# END HaplotypeAnalyst
diff --git a/gn2/wqflask/oauth2/request_utils.py b/gn2/wqflask/oauth2/request_utils.py
index a453d18e..1cdc465f 100644
--- a/gn2/wqflask/oauth2/request_utils.py
+++ b/gn2/wqflask/oauth2/request_utils.py
@@ -35,7 +35,8 @@ def process_error(error: Response,
if error.status_code in range(400, 500):
try:
err = error.json()
- msg = err.get("error_description", f"{error.reason}")
+ msg = err.get(
+ "error", err.get("error_description", f"{error.reason}"))
except simplejson.errors.JSONDecodeError as _jde:
msg = message
return {
diff --git a/gn2/wqflask/oauth2/toplevel.py b/gn2/wqflask/oauth2/toplevel.py
index dffc0a7c..23965cc1 100644
--- a/gn2/wqflask/oauth2/toplevel.py
+++ b/gn2/wqflask/oauth2/toplevel.py
@@ -1,14 +1,18 @@
"""Authentication endpoints."""
-from uuid import UUID
+import uuid
+import datetime
from urllib.parse import urljoin, urlparse, urlunparse
+
+from authlib.jose import jwt
from flask import (
flash, request, Blueprint, url_for, redirect, render_template,
current_app as app)
from . import session
-from .client import SCOPE, no_token_post, user_logged_in
from .checks import require_oauth2
from .request_utils import user_details, process_error
+from .client import (
+ SCOPE, no_token_post, user_logged_in, authserver_uri, oauth2_clientid)
toplevel = Blueprint("toplevel", __name__)
@@ -18,38 +22,59 @@ def register_client():
"""Register an OAuth2 client."""
return "USER IS LOGGED IN AND SUCCESSFULLY ACCESSED THIS ENDPOINT!"
+
@toplevel.route("/code", methods=["GET"])
def authorisation_code():
"""Use authorisation code to get token."""
- def __error__(error):
- flash(f"{error['error']}: {error['error_description']}",
- "alert-danger")
- return redirect("/")
-
- def __success__(token):
- session.set_user_token(token)
- udets = user_details()
- session.set_user_details({
- "user_id": UUID(udets["user_id"]),
- "name": udets["name"],
- "email": udets["email"],
- "token": session.user_token(),
- "logged_in": True
- })
- return redirect("/")
-
code = request.args.get("code", "")
if bool(code):
base_url = urlparse(request.base_url, scheme=request.scheme)
+ jwtkey = app.config["SSL_PRIVATE_KEY"]
+ issued = datetime.datetime.now()
request_data = {
- "grant_type": "authorization_code",
+ "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"code": code,
"scope": SCOPE,
"redirect_uri": urljoin(
urlunparse(base_url),
url_for("oauth2.toplevel.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 + datetime.timedelta(minutes=5)),
+ "nbf": int(issued.timestamp()),
+ "iat": int(issued.timestamp()),
+ "jti": str(uuid.uuid4())},
+ key=jwtkey).decode("utf8"),
"client_id": app.config["OAUTH2_CLIENT_ID"]
}
+
+ def __error__(error):
+ flash(f"{error['error']}: {error['error_description']}",
+ "alert-danger")
+ app.logger.debug("Request error (%s) %s: %s",
+ error["status_code"], error["error_description"],
+ request_data)
+ return redirect("/")
+
+ def __success__(token):
+ session.set_user_token(token)
+ udets = user_details()
+ session.set_user_details({
+ "user_id": uuid.UUID(udets["user_id"]),
+ "name": udets["name"],
+ "email": udets["email"],
+ "token": session.user_token(),
+ "logged_in": True
+ })
+ return redirect("/")
+
return no_token_post(
"auth/token", data=request_data).either(
lambda err: __error__(process_error(err)), __success__)