about summary refs log tree commit diff
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
parent0fa16d7ccea97cec4864983291183249da994b4c (diff)
downloadgenenetwork2-bc709d1aaf1d4ce752394be9d575414de0c66307.tar.gz
Implement "login" via OAuth2
-rw-r--r--etc/default_settings.py9
-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
8 files changed, 137 insertions, 7 deletions
diff --git a/etc/default_settings.py b/etc/default_settings.py
index 6d7ac063..38c0a110 100644
--- a/etc/default_settings.py
+++ b/etc/default_settings.py
@@ -27,6 +27,8 @@ import sys
 with open("../etc/VERSION", "r") as version_file:
     GN_VERSION = version_file.read()
 
+SECRET_KEY = "pleaseChangeThisToSomethingSecretInAnExternalConfigFileOrEnvvars"
+
 # Redis
 REDIS_URL = "redis://:@localhost:6379/0"
 
@@ -115,3 +117,10 @@ JS_GN_PATH = os.environ['HOME'] + "/genenetwork/javascript"
 # GEMMA_COMMAND = str.strip(os.popen("which gemma").read())
 REAPER_COMMAND = os.environ['GN2_PROFILE'] + "/bin/qtlreaper"
 # GEMMA_WRAPPER_COMMAND = str.strip(os.popen("which gemma-wrapper").read())
+
+OAUTH2_CLIENT_ID="0bbfca82-d73f-4bd4-a140-5ae7abb4a64d"
+OAUTH2_CLIENT_SECRET="yadabadaboo"
+
+SESSION_TYPE = "redis"
+SESSION_PERMANENT = True
+SESSION_USE_SIGNER = True
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%}