summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBryan Newbold <bnewbold@robocracy.org>2019-01-03 20:45:29 -0800
committerBryan Newbold <bnewbold@robocracy.org>2019-01-03 20:45:29 -0800
commit422a8cc47489aa44b852ff0add1ef6ea63cfc1ff (patch)
tree6640c13f10271cffe8e442e1fc75202d032121ca
parent1cb3b1afa8df555d890cc556751222cf22c22696 (diff)
downloadfatcat-422a8cc47489aa44b852ff0add1ef6ea63cfc1ff.tar.gz
fatcat-422a8cc47489aa44b852ff0add1ef6ea63cfc1ff.zip
several auth improvements
-rw-r--r--python/fatcat_web/__init__.py11
-rw-r--r--python/fatcat_web/auth.py60
-rw-r--r--python/fatcat_web/routes.py45
-rw-r--r--python/fatcat_web/templates/auth_login.html17
-rw-r--r--python/fatcat_web/templates/auth_logout.html8
-rw-r--r--python/fatcat_web/templates/auth_token_login.html29
-rw-r--r--python/fatcat_web/templates/base.html15
-rw-r--r--python/web_config.py9
8 files changed, 170 insertions, 24 deletions
diff --git a/python/fatcat_web/__init__.py b/python/fatcat_web/__init__.py
index f8b72fd0..9cd5f812 100644
--- a/python/fatcat_web/__init__.py
+++ b/python/fatcat_web/__init__.py
@@ -26,6 +26,17 @@ conf = fatcat_client.Configuration()
conf.host = "http://localhost:9411/v0"
api = fatcat_client.DefaultApi(fatcat_client.ApiClient(conf))
+if Config.FATCAT_API_AUTH_TOKEN:
+ print("Found and using privileged token (eg, for account signup)")
+ priv_conf = fatcat_client.Configuration()
+ priv_conf.api_key["Authorization"] = Config.FATCAT_API_AUTH_TOKEN
+ priv_conf.api_key_prefix["Authorization"] = "Bearer"
+ priv_conf.host = 'http://localhost:9411/v0'
+ priv_api = fatcat_client.DefaultApi(fatcat_client.ApiClient(local_conf))
+else:
+ print("No privileged token found")
+ priv_api = None
+
from fatcat_web import routes, auth
gitlab_bp = create_flask_blueprint(Gitlab, oauth, auth.handle_oauth)
diff --git a/python/fatcat_web/auth.py b/python/fatcat_web/auth.py
index f6672e87..385f5c49 100644
--- a/python/fatcat_web/auth.py
+++ b/python/fatcat_web/auth.py
@@ -1,27 +1,75 @@
from flask import Flask, render_template, send_from_directory, request, \
url_for, abort, g, redirect, jsonify, session
-from fatcat_web import login_manager
+from fatcat_web import login_manager, api
+from flask_login import logout_user, login_user, UserMixin
+import pymacaroons
+def handle_logout():
+ logout_user()
+ for k in ('editor', 'token'):
+ if k in session:
+ session.pop(k)
+
+def handle_token_login(token):
+ try:
+ m = pymacaroons.Macaroon.deserialize(token)
+ except pymacaroons.exceptions.MacaroonDeserializationException:
+ # TODO: what kind of Exceptions?
+ return abort(400)
+ # extract editor_id
+ editor_id = None
+ for caveat in m.first_party_caveats():
+ caveat = caveat.caveat_id
+ if caveat.startswith(b"editor_id = "):
+ editor_id = caveat[12:].decode('utf-8')
+ if not editor_id:
+ abort(400)
+ # fetch editor info
+ editor = api.get_editor(editor_id).to_dict()
+ session['api_token'] = token
+ session['editor'] = editor
+ login_user(load_user(editor_id))
+ return redirect("/")
+
# This will need to login/signup via fatcatd API, then set token in session
def handle_oauth(remote, token, user_info):
print(remote)
if token:
print(remote.name, token)
if user_info:
- # TODO: fetch api login/signup using user_info
print(user_info)
- # TODO: write token and username to session
- # TODO: call login_user(load_user(editor_id))
+ print(user_info.iss)
+ print(user_info.prefered_username)
+
+ # fetch api login/signup using user_info
+ params = AuthOidc(remote.name, user_info.sub, user_info.iss)
+ resp = api.auth_oidc(params)
+ editor = resp['editor']
+ api_token = resp['token']
+
+ # write token and username to session
+ session['api_token'] = api_token
+ session['editor'] = editor.editor_id
+
+ # call login_user(load_user(editor_id))
+ login_user(load_user(editor_id))
return redirect("/")
+
raise some_error
@login_manager.user_loader
def load_user(editor_id):
- # NOTE: this should look for extra info in session, and update the user
- # object with that. If session isn't loaded/valid, should return None
+ # looks for extra info in session, and updates the user object with that.
+ # If session isn't loaded/valid, should return None
+ if not 'editor' in session or not 'api_token' in session:
+ return None
+ editor = session['editor']
+ token = session['api_token']
user = UserMixin()
user.id = editor_id
+ user.username = editor['username']
+ user.token = token
return user
diff --git a/python/fatcat_web/routes.py b/python/fatcat_web/routes.py
index 51533a2f..5d46fe0b 100644
--- a/python/fatcat_web/routes.py
+++ b/python/fatcat_web/routes.py
@@ -4,6 +4,7 @@ import json
from flask import Flask, render_template, send_from_directory, request, \
url_for, abort, g, redirect, jsonify, session
from fatcat_web import app, api
+from fatcat_web.auth import handle_token_login, handle_logout
from fatcat_client.rest import ApiException
from fatcat_web.search import do_search
@@ -295,12 +296,6 @@ def work_view(ident):
return render_template('deleted_entity.html', entity=entity)
return render_template('work_view.html', work=entity, releases=releases)
-@app.route('/editgroup/current', methods=['GET'])
-def editgroup_current():
- raise NotImplementedError
- #eg = api.get_or_create_editgroup()
- #return redirect('/editgroup/{}'.format(eg.id))
-
@app.route('/editgroup/<ident>', methods=['GET'])
def editgroup_view(ident):
try:
@@ -327,6 +322,17 @@ def editor_changelog(ident):
return render_template('editor_changelog.html', editor=editor,
changelog_entries=changelog_entries)
+@app.route('/editor/<ident>/wip', methods=['GET'])
+def editor_wip(ident):
+ raise NotImplementedError
+ try:
+ editor = api.get_editor(ident)
+ entries = api.get_editor_wip(ident)
+ except ApiException as ae:
+ abort(ae.status)
+ return render_template('editor_changelog.html', editor=editor,
+ entries=entries)
+
@app.route('/changelog', methods=['GET'])
def changelog_view():
try:
@@ -369,16 +375,33 @@ def search():
### Auth ####################################################################
-@app.route('/login')
+@app.route('/auth/login')
def login():
# show the user a list of login options
- return render_template('release_search.html', query=query, fulltext_only=fulltext_only)
+ return render_template('auth_login.html')
+
+@app.route('/auth/token_login', methods=['GET', 'POST'])
+def token_login():
+ # show the user a list of login options
+ if 'token' in request.args:
+ return handle_token_login(request.args.get('token'))
+ if 'token' in request.form:
+ return handle_token_login(request.form.get('token'))
+ return render_template('auth_token_login.html')
-@app.route('/login')
+@app.route('/auth/logout')
def logout():
# TODO: clear extra session info
- logout_user()
- return render_template('logout.html')
+ handle_logout()
+ return render_template('auth_logout.html')
+
+@app.route('/auth/account')
+@login_required
+def logout():
+ # TODO: clear extra session info
+ handle_logout()
+ return render_template('auth_logout.html')
+
### Static Routes ###########################################################
diff --git a/python/fatcat_web/templates/auth_login.html b/python/fatcat_web/templates/auth_login.html
new file mode 100644
index 00000000..98b1c7c4
--- /dev/null
+++ b/python/fatcat_web/templates/auth_login.html
@@ -0,0 +1,17 @@
+{% extends "base.html" %}
+{% block body %}
+<h1>Login</h1>
+
+<p>via OAuth / OpenID Connect:
+<ul>
+ <li><a href="/auth/gitlab/login">gitlab.com</a>
+ <li><strike><a href="/auth/google/login">google.com</a></strike>
+ <li><strike><a href="/auth/orcid/login">orcid.org</a></strike>
+</ul>
+
+<p>Other options...
+<ul>
+ <li><a href="/auth/token_login">Using auth token</a> (admin/operator)
+</ul>
+
+{% endblock %}
diff --git a/python/fatcat_web/templates/auth_logout.html b/python/fatcat_web/templates/auth_logout.html
new file mode 100644
index 00000000..819d42fe
--- /dev/null
+++ b/python/fatcat_web/templates/auth_logout.html
@@ -0,0 +1,8 @@
+{% extends "base.html" %}
+{% block body %}
+<h1>Logout</h1>
+
+<p>If you are seeing this page, you are now logged out.
+
+<p>Use the links above to return to the home page or log back in.
+{% endblock %}
diff --git a/python/fatcat_web/templates/auth_token_login.html b/python/fatcat_web/templates/auth_token_login.html
new file mode 100644
index 00000000..4c28f938
--- /dev/null
+++ b/python/fatcat_web/templates/auth_token_login.html
@@ -0,0 +1,29 @@
+{% extends "base.html" %}
+{% block body %}
+<h1>Login with Token</h1>
+
+<p>This page is intended for operators and contingencies, not for general use. It
+allows editors (users) to use an existing token (macaroon) for authentication;
+a new web interface session and cookie are constructed using the token.
+
+<br>
+<br>
+<br>
+
+{% if current_user.is_authenticated %}
+ <div class="ui negative message">
+ <div class="header">You are already logged in!</div>
+ <p>You should logout first. Re-authenticating would be undefined behavior.
+ </div>
+{% else %}
+ <form class="" role="login" action="/auth/token_login" method="post">
+ <div class="ui form">
+ <div class="ui action input huge fluid">
+ <input type="password" placeholder="Your Fatcat API Auth Token..." name="token" value="{% if token %}{{ token }}{% endif %}" aria-label="login using token">
+ <button class="ui button">Login</button>
+ </div>
+ </div>
+ </form>
+{% endif %}
+
+{% endblock %}
diff --git a/python/fatcat_web/templates/base.html b/python/fatcat_web/templates/base.html
index 4b3b7e0b..892ca788 100644
--- a/python/fatcat_web/templates/base.html
+++ b/python/fatcat_web/templates/base.html
@@ -29,17 +29,22 @@
</div>
</form>
</div>
+{% if current_user.is_authenticated %}
<div class="ui simple dropdown item">
- demo-user <i class="dropdown icon"></i>
+ {{ current_user.username }} <i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="/editgroup/current"><i class="edit icon"></i>Edits in Progress</a>
- <a class="item" href="/editor/aaaaaaaaaaaabkvkaaaaaaaaae/changelog"><i class="history icon"></i>History</a>
+ <a class="item" href="/editor/{{ current_user.id }}/changelog"><i class="history icon"></i>History</a>
<div class="divider"></div>
- <a class="item" href="/editor/aaaaaaaaaaaabkvkaaaaaaaaae"><i class="user icon"></i>Account</a>
- <a class="item" href="/logout"><i class="sign out icon"></i>Logout</a>
+ <a class="item" href="/auth/account"><i class="user icon"></i>Account</a>
+ <a class="item" href="/auth/logout"><i class="sign out icon"></i>Logout</a>
</div>
</div>
-
+{% else %}
+ <div class="ui simple item">
+ <a href="/auth/login">Login/Signup</a>
+ </div>
+{% endif %}
</div>
</div>
</header>
diff --git a/python/web_config.py b/python/web_config.py
index 91e43e70..5713738c 100644
--- a/python/web_config.py
+++ b/python/web_config.py
@@ -17,14 +17,19 @@ basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
GIT_REVISION = subprocess.check_output(["git", "describe", "--always"]).strip()
+
# This is, effectively, the QA/PROD flag
FATCAT_DOMAIN = os.environ.get("FATCAT_DOMAIN", default="qa.fatcat.wiki")
+ FATCAT_API_AUTH_TOKEN = os.environ.get("FATCAT_API_AUTH_TOKEN", default=None)
+ FATCAT_API_HOST = os.environ.get("FATCAT_API_HOST", default="https://{}/v0".format(FATCAT_DOMAIN))
+
# can set this to https://search.fatcat.wiki for some experimentation
ELASTICSEARCH_BACKEND = os.environ.get("ELASTICSEARCH_BACKEND", default="http://localhost:9200")
ELASTICSEARCH_INDEX = os.environ.get("ELASTICSEARCH_INDEX", default="fatcat")
- # bogus values for dev/testing
- SECRET_KEY = os.environ.get("SECRET_KEY", default="mQLO6DpyR4t91G1tl/LPMvb/5QFV9vIUDZah5PapTUSmP8jVIrvCRw")
+ # for flask things, like session cookies
+ FLASK_SECRET_KEY = os.environ.get("FLASK_SECRET_KEY", default=None)
+ SECRET_KEY = FLASK_SECRET_KEY
GITLAB_CLIENT_ID = os.environ.get("GITLAB_CLIENT_ID", default="bogus")
GITLAB_CLIENT_SECRET = os.environ.get("GITLAB_CLIENT_SECRET", default="bogus")