aboutsummaryrefslogtreecommitdiff
path: root/wqflask
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2022-12-28 05:57:13 +0300
committerFrederick Muriuki Muriithi2022-12-29 03:13:31 +0300
commitbc709d1aaf1d4ce752394be9d575414de0c66307 (patch)
tree5c80ca85254dcc7656be97fa41bf247e82991867 /wqflask
parent0fa16d7ccea97cec4864983291183249da994b4c (diff)
downloadgenenetwork2-bc709d1aaf1d4ce752394be9d575414de0c66307.tar.gz
Implement "login" via OAuth2
Diffstat (limited to 'wqflask')
-rw-r--r--wqflask/utility/startup_config.py2
-rw-r--r--wqflask/utility/tools.py3
-rw-r--r--wqflask/wqflask/__init__.py34
-rw-r--r--wqflask/wqflask/oauth2/__init__.py0
-rw-r--r--wqflask/wqflask/oauth2/routes.py51
-rw-r--r--wqflask/wqflask/templates/base.html4
-rw-r--r--wqflask/wqflask/templates/oauth2/login.html41
7 files changed, 128 insertions, 7 deletions
diff --git a/wqflask/utility/startup_config.py b/wqflask/utility/startup_config.py
index 59923fa1..69cac124 100644
--- a/wqflask/utility/startup_config.py
+++ b/wqflask/utility/startup_config.py
@@ -15,7 +15,7 @@ ENDC = '\033[0m'
def app_config():
- app.config['SESSION_TYPE'] = 'filesystem'
+ app.config['SESSION_TYPE'] = app.config.get('SESSION_TYPE', 'filesystem')
if not app.config.get('SECRET_KEY'):
import os
app.config['SECRET_KEY'] = str(os.urandom(24))
diff --git a/wqflask/utility/tools.py b/wqflask/utility/tools.py
index d4c83302..5b3e9413 100644
--- a/wqflask/utility/tools.py
+++ b/wqflask/utility/tools.py
@@ -335,3 +335,6 @@ assert_dir(JS_CYTOSCAPE_PATH)
assert_file(JS_CYTOSCAPE_PATH + '/cytoscape.min.js')
# assert_file(PHEWAS_FILES+"/auwerx/PheWAS_pval_EMMA_norm.RData")
+
+OAUTH2_CLIENT_ID = get_setting('OAUTH2_CLIENT_ID')
+OAUTH2_CLIENT_SECRET = get_setting('OAUTH2_CLIENT_SECRET')
diff --git a/wqflask/wqflask/__init__.py b/wqflask/wqflask/__init__.py
index ada73867..66ed0e91 100644
--- a/wqflask/wqflask/__init__.py
+++ b/wqflask/wqflask/__init__.py
@@ -1,12 +1,16 @@
"""Entry point for flask app"""
# pylint: disable=C0413,E0611
import time
+from typing import Tuple
+from urllib.parse import urljoin, urlparse
+
+import redis
import jinja2
+from flask_session import Session
+from authlib.integrations.requests_client import OAuth2Session
+from flask import g, Flask, flash, session, url_for, redirect, current_app
+
-from flask import g
-from flask import Flask
-from typing import Tuple
-from urllib.parse import urlparse
from utility import formatting
from gn3.authentication import DataRole, AdminRole
@@ -26,6 +30,7 @@ from wqflask.api.markdown import facilities_blueprint
from wqflask.api.markdown import blogs_blueprint
from wqflask.api.markdown import news_blueprint
from wqflask.api.jobs import jobs as jobs_bp
+from wqflask.oauth2.routes import oauth2
from wqflask.jupyter_notebooks import jupyter_notebooks
@@ -47,6 +52,8 @@ app.jinja_env.globals.update(
undefined=jinja2.StrictUndefined,
numify=formatting.numify)
+app.config["SESSION_REDIS"] = redis.from_url(app.config["REDIS_URL"])
+
# Registering blueprints
app.register_blueprint(glossary_blueprint, url_prefix="/glossary")
app.register_blueprint(references_blueprint, url_prefix="/references")
@@ -62,12 +69,31 @@ app.register_blueprint(resource_management, url_prefix="/resource-management")
app.register_blueprint(metadata_edit, url_prefix="/datasets/")
app.register_blueprint(group_management, url_prefix="/group-management")
app.register_blueprint(jobs_bp, url_prefix="/jobs")
+app.register_blueprint(oauth2, url_prefix="/oauth2")
+
+server_session = Session(app)
@app.before_request
def before_request():
g.request_start_time = time.time()
g.request_time = lambda: "%.5fs" % (time.time() - g.request_start_time)
+ token = session.get("oauth2_token", False)
+ if token and not bool(session.get("user_details", False)):
+ config = current_app.config
+ client = OAuth2Session(
+ config["OAUTH2_CLIENT_ID"], config["OAUTH2_CLIENT_SECRET"],
+ token=token)
+ resp = client.get(
+ urljoin(config["GN_SERVER_URL"], "oauth2/user"))
+ user_details = resp.json()
+ session["user_details"] = user_details
+
+ if user_details.get("error") == "invalid_token":
+ flash(user_details["error_description"], "alert-danger")
+ flash("You are now logged out.", "alert-info")
+ session.pop("user_details", None)
+ session.pop("oauth2_token", None)
@app.context_processor
def include_admin_role_class():
diff --git a/wqflask/wqflask/oauth2/__init__.py b/wqflask/wqflask/oauth2/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/wqflask/wqflask/oauth2/__init__.py
diff --git a/wqflask/wqflask/oauth2/routes.py b/wqflask/wqflask/oauth2/routes.py
new file mode 100644
index 00000000..931b8b61
--- /dev/null
+++ b/wqflask/wqflask/oauth2/routes.py
@@ -0,0 +1,51 @@
+"""Routes for the OAuth2 auth system in GN3"""
+import uuid
+from urllib.parse import urljoin
+
+import redis
+from authlib.integrations.requests_client import OAuth2Session
+from authlib.integrations.base_client.errors import OAuthError
+from flask import (
+ flash, request, session, redirect, Blueprint, render_template,
+ current_app as app)
+
+oauth2 = Blueprint("oauth2", __name__)
+
+def user_logged_in():
+ """Check whether the user has logged in."""
+ return bool(session.get("oauth2_token", False))
+
+@oauth2.route("/login", methods=["GET", "POST"])
+def login():
+ """Route to allow users to sign up."""
+ if request.method == "POST":
+ config = app.config
+ form = request.form
+ scope = "profile resource"
+ client = OAuth2Session(
+ config["OAUTH2_CLIENT_ID"], config["OAUTH2_CLIENT_SECRET"],
+ scope=scope, token_endpoint_auth_method="client_secret_post")
+ try:
+ token = client.fetch_token(
+ urljoin(config["GN_SERVER_URL"], "oauth2/token"),
+ username=form.get("email_address"),
+ password=form.get("password"),
+ grant_type="password")
+ session["oauth2_token"] = token
+ except OAuthError as _oaerr:
+ flash(_oaerr.args[0], "alert-danger")
+ return render_template("oauth2/login.html")
+
+ if user_logged_in():
+ return redirect("/")
+
+ return render_template("oauth2/login.html")
+
+
+@oauth2.route("/logout", methods=["GET", "POST"])
+def logout():
+ keys = tuple(key for key in session.keys() if not key.startswith("_"))
+ for key in keys:
+ session.pop(key, default=None)
+
+ return redirect("/")
diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html
index fcd154c1..7145cd7a 100644
--- a/wqflask/wqflask/templates/base.html
+++ b/wqflask/wqflask/templates/base.html
@@ -116,8 +116,8 @@
</li>
{% endif %}
<li class="">
- {% if g.user_session.logged_in %}
- <a id="login_out" title="Signed in as {{ g.user_session.user_name }}" href="/n/logout">Sign out</a>
+ {% if g.user_session.logged_in or session.get("user_details") %}
+ <a id="login_out" title="Signed in as {{ g.user_session.user_name or session['user_details']['name']}}" href="/n/logout">Sign out</a>
{% else %}
<a id="login_in" href="/n/login">Sign in</a>
{% endif %}
diff --git a/wqflask/wqflask/templates/oauth2/login.html b/wqflask/wqflask/templates/oauth2/login.html
new file mode 100644
index 00000000..d8745bad
--- /dev/null
+++ b/wqflask/wqflask/templates/oauth2/login.html
@@ -0,0 +1,41 @@
+{%extends "base.html"%}
+{%block title%}Login{%endblock%}
+{%block content%}
+<div class="container" style="min-width: 1250px;">
+ <h3>Sign in here.</h3>
+
+ <form class="form-horizontal" action="{{url_for('oauth2.login')}}"
+ method="POST" id="oauth2-login-form">
+ <fieldset>
+ <legend>Sign in with Genenetwork</legend>
+ {{flash_me()}}
+ <div class="form-group">
+ <label class="col-xs-1 control-label" for="email_address"
+ style="text-align:left;">Email</label>
+ <div style="margin-left:20px;" class="col-xs-4">
+ <input id="email_address" name="email_address" type="email"
+ placeholder="your@email.address" size="50"
+ class="form-control" />
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label class="col-xs-1 control-label" for="password"
+ style="text-align:left;">Password</label>
+ <div style="margin-left:20px;" class="col-xs-4">
+ <input id="password" name="password" type="password"
+ size="50" class="form-control"
+ placeholder="your password" />
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div style="margin-left:20px;" class="col-xs-4 controls">
+ <input type="submit" class="btn btn-primary form-control" id="submit" name="submit"
+ value="Sign in" />
+ </div>
+ </div>
+ </fieldset>
+ </form>
+</div>
+{%endblock%}