diff options
| author | Bryan Newbold <bnewbold@robocracy.org> | 2019-01-03 20:45:29 -0800 | 
|---|---|---|
| committer | Bryan Newbold <bnewbold@robocracy.org> | 2019-01-03 20:45:29 -0800 | 
| commit | 422a8cc47489aa44b852ff0add1ef6ea63cfc1ff (patch) | |
| tree | 6640c13f10271cffe8e442e1fc75202d032121ca /python | |
| parent | 1cb3b1afa8df555d890cc556751222cf22c22696 (diff) | |
| download | fatcat-422a8cc47489aa44b852ff0add1ef6ea63cfc1ff.tar.gz fatcat-422a8cc47489aa44b852ff0add1ef6ea63cfc1ff.zip | |
several auth improvements
Diffstat (limited to 'python')
| -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") | 
