Basic Reddit authentication flow

This commit is contained in:
RunasSudo 2017-11-25 22:37:54 +11:00
parent bf1b8cee09
commit c70dabcbe1
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
7 changed files with 238 additions and 1 deletions

View File

@ -50,6 +50,28 @@ if 'EOSWEB_SETTINGS' in os.environ:
# Connect to database # Connect to database
db_connect(app.config['DB_NAME'], app.config['MONGO_URI']) db_connect(app.config['DB_NAME'], app.config['MONGO_URI'])
# Make Flask's serialisation, e.g. for sessions, EosObject aware
class EosObjectJSONEncoder(flask.json.JSONEncoder):
def default(self, obj):
if isinstance(obj, EosObject):
return EosObject.serialise_and_wrap(obj)
return super().default(obj)
class EosObjectJSONDecoder(flask.json.JSONDecoder):
def __init__(self, *args, **kwargs):
self.super_object_hook = kwargs.get('object_hook', None)
kwargs['object_hook'] = self.my_object_hook
super().__init__(*args, **kwargs)
def my_object_hook(self, val):
if 'type' in val:
if val['type'] in EosObject.objects:
return EosObject.deserialise_and_unwrap(val)
if self.super_object_hook:
return self.super_object_hook(val)
return val
app.json_encoder = EosObjectJSONEncoder
app.json_decoder = EosObjectJSONDecoder
@app.cli.command('test') @app.cli.command('test')
@click.option('--prefix', default=None) @click.option('--prefix', default=None)
@click.option('--lang', default=None) @click.option('--lang', default=None)
@ -123,6 +145,8 @@ def count_test_election():
def inject_globals(): def inject_globals():
return {'eos': eos, 'eosweb': eosweb, 'SHA256': eos.core.hashing.SHA256} return {'eos': eos, 'eosweb': eosweb, 'SHA256': eos.core.hashing.SHA256}
# === Views ===
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('index.html') return flask.render_template('index.html')
@ -197,6 +221,35 @@ def election_api_cast_vote(election):
'vote': EosObject.serialise_and_wrap(vote) 'vote': EosObject.serialise_and_wrap(vote)
}), mimetype='application/json') }), mimetype='application/json')
@app.route('/debug')
def debug():
assert False
@app.route('/auth/login')
def login():
return flask.render_template('auth/login.html')
@app.route('/auth/logout')
def logout():
flask.session['user'] = None
#return flask.redirect(flask.request.args['next'] if 'next' in flask.request.args else '/')
# I feel like there's some kind of exploit here, so we'll leave this for now
return flask.redirect('/')
@app.route('/auth/login_complete')
def login_complete():
return flask.render_template('auth/login_complete.html')
@app.route('/auth/login_cancelled')
def login_cancelled():
return flask.render_template('auth/login_cancelled.html')
# === Apps ===
for app_name in app.config['APPS']:
app_main = importlib.import_module(app_name + '.main')
app_main.main(app)
# === Model-Views === # === Model-Views ===
model_view_map = {} model_view_map = {}

View File

@ -0,0 +1,47 @@
{% extends 'base.html' %}
{#
Eos - Verifiable elections
Copyright © 2017 RunasSudo (Yingtong Li)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{% block title %}Login{% endblock %}
{% block content %}
{% if session.user %}
<p>You are currently logged in as {{ session.user.username }}. Please select an option from the list below to switch accounts.</p>
{% else %}
<p>You are not currently logged in. Please select an option from the list below to log in.</p>
{% endif %}
<ul>
{% for auth_method in eosweb.app.config['AUTH_METHODS'] %}
<li><a href="/auth/{{ auth_method[0] }}/login" target="_blank" onclick="login(this);return false;">{{ auth_method[1] }}</a></li>
{% endfor %}
</ul>
{% endblock %}
{% block basecontent %}
{{ super() }}
<script>
function login(el) {
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
}
function callback_complete() {
window.location = "/";
}
</script>
{% endblock %}

View File

@ -0,0 +1,40 @@
{% extends 'semantic_base.html' %}
{#
Eos - Verifiable elections
Copyright © 2017 RunasSudo (Yingtong Li)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{% block title %}Login{% endblock %}
{% block basecontent %}
<div class="ui middle aligned center aligned grid" style="height: 100%;">
<div class="column" style="max-width: 400px;">
<div class="ui success message">
<div class="header">Log in successful</div>
<p>You have successfully logged in to your account.</p>
<p>You may now close this window and return to your previous page.</p>
</div>
</div>
</div>
<script>
window.setTimeout(function() {
window.close();
window.opener.callback_complete();
}, 1000);
</script>
{% endblock %}

View File

@ -26,7 +26,16 @@
<div class="ui fixed inverted menu" style="margin-right: 1.5em;"> <div class="ui fixed inverted menu" style="margin-right: 1.5em;">
<div class="ui container"> <div class="ui container">
<a href="/" class="header item">Eos Voting</a> <a href="/" class="header item">Eos Voting</a>
<a href="#" class="item right">Log in</a> {% if session.user %}
<div class="ui simple dropdown item right">
{{ session.user.username }} <i class="dropdown icon"></i>
<div class="menu">
<a href="{{ url_for('logout') }}?next={{ request.full_path|urlencode }}" class="item">Log out</a>
</div>
</div>
{% else %}
<a href="{{ url_for('login') }}" class="item right">Log in</a>
{% endif %}
</div> </div>
</div> </div>
<div class="ui main text container" id="main_container"> <div class="ui main text container" id="main_container">

View File

68
eosweb/redditauth/main.py Normal file
View File

@ -0,0 +1,68 @@
# Eos - Verifiable elections
# Copyright © 2017 RunasSudo (Yingtong Li)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from flask_oauthlib.client import OAuth
import flask
from eos.core.objects import *
import base64
import uuid
class RedditUser(DocumentObject):
oauth_token = StringField(is_protected=True)
username = StringField()
def main(app):
oauth = OAuth()
reddit = oauth.remote_app('Reddit',
request_token_url=None,
authorize_url='https://www.reddit.com/api/v1/authorize.compact',
request_token_params={'duration': 'temporary', 'scope': 'identity'},
access_token_url='https://www.reddit.com/api/v1/access_token',
access_token_method='POST',
access_token_headers={
'Authorization': 'Basic ' + base64.b64encode('{}:{}'.format(app.config['REDDIT_OAUTH_CLIENT_ID'], app.config['REDDIT_OAUTH_CLIENT_SECRET']).encode('ascii')).decode('ascii'),
'User-Agent': app.config['REDDIT_USER_AGENT']
},
consumer_key=app.config['REDDIT_OAUTH_CLIENT_ID'],
consumer_secret=app.config['REDDIT_OAUTH_CLIENT_SECRET']
)
@app.route('/auth/reddit/login')
def reddit_login():
return reddit.authorize(callback=app.config['BASE_URI'] + flask.url_for('reddit_oauth_authorized'), state=uuid.uuid4())
@reddit.tokengetter
def get_reddit_oauth_token():
return (flask.session.get('user').oauth_token, '')
@app.route('/auth/reddit/oauth_callback')
def reddit_oauth_authorized():
resp = reddit.authorized_response()
if resp is None:
# Request denied
return flask.redirect(flask.url_for('login_cancelled'))
user = RedditUser()
user.oauth_token = resp['access_token']
flask.session['user'] = user
me = reddit.get('https://oauth.reddit.com/api/v1/me')
user.username = me.data['name']
return flask.redirect(flask.url_for('login_complete'))

View File

@ -0,0 +1,20 @@
# Eos - Verifiable elections
# Copyright © 2017 RunasSudo (Yingtong Li)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
REDDIT_OAUTH_CLIENT_ID = 'xxxxxxxxxxxxxx'
REDDIT_OAUTH_CLIENT_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx'
REDDIT_USER_AGENT = 'FIXME'