diff options
-rw-r--r-- | python/fatcat_web/__init__.py | 11 | ||||
-rw-r--r-- | python/fatcat_web/auth.py | 60 | ||||
-rw-r--r-- | python/fatcat_web/routes.py | 45 | ||||
-rw-r--r-- | python/fatcat_web/templates/auth_login.html | 17 | ||||
-rw-r--r-- | python/fatcat_web/templates/auth_logout.html | 8 | ||||
-rw-r--r-- | python/fatcat_web/templates/auth_token_login.html | 29 | ||||
-rw-r--r-- | python/fatcat_web/templates/base.html | 15 | ||||
-rw-r--r-- | python/web_config.py | 9 |
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") |