Integrate authentication into voting booth
Also fix a number (several too many) bugs that found their way into the system following the previous few commits
This commit is contained in:
parent
f92c7640d5
commit
20abbdae78
@ -149,7 +149,7 @@ class DateTimeField(Field):
|
||||
if is_python:
|
||||
return datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
|
||||
else:
|
||||
return Date.parse(value)
|
||||
return __pragma__('js', '{}', 'new Date(value)')
|
||||
|
||||
@staticmethod
|
||||
def now():
|
||||
|
@ -96,6 +96,7 @@ def setup_test_election():
|
||||
election.voters.append(Voter(name='Alice'))
|
||||
election.voters.append(Voter(name='Bob'))
|
||||
election.voters.append(Voter(name='Charlie'))
|
||||
election.voters.append(Voter(name='RunasSudo'))
|
||||
|
||||
election.mixing_trustees.append(InternalMixingTrustee(name='Eos Voting'))
|
||||
election.mixing_trustees.append(InternalMixingTrustee(name='Eos Voting'))
|
||||
@ -172,8 +173,9 @@ def election_view(election):
|
||||
@using_election
|
||||
def election_booth(election):
|
||||
selection_model_view_map = EosObject.to_json({key._name: val for key, val in model_view_map.items()}) # ewww
|
||||
auth_methods = EosObject.to_json(app.config['AUTH_METHODS'])
|
||||
|
||||
return flask.render_template('election/booth.html', election=election, selection_model_view_map=selection_model_view_map)
|
||||
return flask.render_template('election/booth.html', election=election, selection_model_view_map=selection_model_view_map, auth_methods=auth_methods)
|
||||
|
||||
@app.route('/election/<election_id>/view/questions')
|
||||
@using_election
|
||||
@ -193,20 +195,24 @@ def election_view_trustees(election):
|
||||
@app.route('/election/<election_id>/cast_ballot', methods=['POST'])
|
||||
@using_election
|
||||
def election_api_cast_vote(election):
|
||||
if election.workflow.get_task('eos.base.workflow.TaskOpenVoting').status >= WorkflowTask.Status.EXITED or election.workflow.get_task('eos.base.workflow.TaskCloseVoting').status <= WorkflowTask.Status.READY:
|
||||
if election.workflow.get_task('eos.base.workflow.TaskOpenVoting').status < WorkflowTask.Status.EXITED or election.workflow.get_task('eos.base.workflow.TaskCloseVoting').status > WorkflowTask.Status.READY:
|
||||
# Voting is not yet open or has closed
|
||||
return flask.Response('Voting is not yet open or has closed', 405)
|
||||
return flask.Response('Voting is not yet open or has closed', 409)
|
||||
|
||||
data = json.loads(flask.request.data)
|
||||
|
||||
if 'user' not in flask.session:
|
||||
# User is not authenticated
|
||||
return flask.Response('Not authenticated', 403)
|
||||
|
||||
voter = None
|
||||
for election_voter in election.voters:
|
||||
if election_voter.name == data['auth']['username']:
|
||||
if election_voter.name == flask.session['user'].username:
|
||||
voter = election_voter
|
||||
break
|
||||
|
||||
if voter is None:
|
||||
# User is not authenticated
|
||||
# Invalid user
|
||||
return flask.Response('Invalid credentials', 403)
|
||||
|
||||
# Cast the vote
|
||||
|
@ -21,18 +21,25 @@
|
||||
{% block content %}
|
||||
<div id="cast_prompt">
|
||||
<p>Your vote has <b>not yet been cast</b>. If you have not already done so, please make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
||||
<p>This election requires you to log in to vote. Please enter your name below, then click ‘Cast ballot’ to cast your ballot.</p>
|
||||
<div class="ui form">
|
||||
<div class="inline field">
|
||||
<label for="booth_login_name">Name</label>
|
||||
<input type="text" id="booth_login_name">
|
||||
</div>
|
||||
<div class="ui error message" id="error_invalid_id">
|
||||
<p>This election requires you to log in to vote. If you disconnected your internet connection earlier, you must now reconnect it before proceeding.</p>
|
||||
|
||||
{% if username %}
|
||||
<p>You are currently logged in as {{ username }}. Please select an option from the list below if you would like to switch accounts. Otherwise, click ‘Cast ballot’ to continue.</p>
|
||||
{% else %}
|
||||
<p>You are not currently logged in. Please select an option from the list below to log in. Your ballot will be automatically cast once you have logged in.</p>
|
||||
{% endif %}
|
||||
|
||||
<ul class="ui list">
|
||||
{% for auth_method in auth_methods %}
|
||||
<li><a href="/auth/{{ auth_method[0] }}/login" target="_blank" onclick="login(this);return false;">{{ auth_method[1] }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div class="ui hidden error message" id="error_invalid_id">
|
||||
<i class="close icon"></i>
|
||||
<div class="header">Error</div>
|
||||
<p>The log in details you entered are not valid for this election. Please check your username and password and try again. If the issue persists, contact your election administrator.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui hidden error message" id="error_unknown">
|
||||
<i class="close icon"></i>
|
||||
@ -48,7 +55,7 @@
|
||||
|
||||
{% block buttons %}
|
||||
<button class="ui left floated button" onclick="prevTemplate(2);">Back</a>
|
||||
<button class="ui right floated primary button" onclick="castBallot();">Cast ballot</button>
|
||||
<button class="ui right floated primary{% if not username %} hidden{% endif %} button" id="cast_button" onclick="castBallot();">Cast ballot</button>
|
||||
{% endblock %}
|
||||
|
||||
{% block after %}
|
||||
@ -57,6 +64,14 @@
|
||||
$(this).closest(".message").addClass("hidden");
|
||||
});
|
||||
|
||||
function login(el) {
|
||||
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
||||
}
|
||||
function callback_complete() {
|
||||
$("#cast_button").removeClass("hidden");
|
||||
castBallot();
|
||||
}
|
||||
|
||||
function castBallot() {
|
||||
$("#cast_prompt").hide();
|
||||
$("#casting").show();
|
||||
@ -65,8 +80,7 @@
|
||||
url: "{{ election_base_url }}cast_ballot",
|
||||
type: "POST",
|
||||
data: eosjs.eos.core.objects.__all__.EosObject.to_json({
|
||||
"ballot": eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(booth.ballot, null),
|
||||
"auth": { "username": $("#booth_login_name").val() }
|
||||
"ballot": eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(booth.ballot, null)
|
||||
}),
|
||||
contentType: "application/json",
|
||||
dataType: "text"
|
||||
@ -83,14 +97,14 @@
|
||||
})
|
||||
.fail(function(xhr, status, err) {
|
||||
if (xhr.status === 403) { // Forbidden
|
||||
$("#error_invalid_id").addClass("visible");
|
||||
$("#error_invalid_id").removeClass("hidden");
|
||||
|
||||
$("#error_unknown").addClass("hidden");
|
||||
} else {
|
||||
$("#error_unknown_tech").text("Technical details: " + err);
|
||||
$("#error_unknown_tech").text("Technical details: " + err + " – " + xhr.responseText);
|
||||
$("#error_unknown").removeClass("hidden");
|
||||
|
||||
$("#error_invalid_id").removeClass("visible");
|
||||
$("#error_invalid_id").addClass("hidden");
|
||||
}
|
||||
|
||||
$("#casting").hide();
|
||||
|
@ -29,9 +29,9 @@
|
||||
|
||||
<p>Please check that the ballot fingerprint above matches the ballot fingerprint you recorded earlier.</p>
|
||||
|
||||
<p>To confirm that your ballot was cast correctly, please go to the <a href="{{ election_base_url }}ballots">‘Voters and ballots’ page</a> for the election or click ‘Finish’, and confirm that the above ballot fingerprint appears next to your name.</p>
|
||||
<p>To confirm that your ballot was cast correctly, please go to the <a href="{{ election_base_url }}view/ballots">‘Voters and ballots’ page</a> for the election or click ‘Finish’, and confirm that the above ballot fingerprint appears next to your name.</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block buttons %}
|
||||
<a href="{{ election_base_url }}ballots" class="ui right floated primary button">Finish</a>
|
||||
<a href="{{ election_base_url }}view/ballots" class="ui right floated primary button">Finish</a>
|
||||
{% endblock %}
|
||||
|
@ -27,6 +27,8 @@
|
||||
}
|
||||
booth.ballot = eosjs.eos.base.election.__all__.Ballot();
|
||||
booth.ballot.encrypted_answers = encryptedAnswers;
|
||||
booth.ballot.election_id = election._id;
|
||||
booth.ballot.election_hash = eosjs.eos.core.hashing.__all__.SHA256().update_obj(election).hash_as_b64();
|
||||
|
||||
nextTemplate();
|
||||
} catch (err) {
|
||||
|
@ -21,9 +21,9 @@
|
||||
{% block content %}
|
||||
<p>Your vote has <b>not yet been cast</b>. Your selections are shown below. Please review your selections and ensure you are happy with them before continuing.</p>
|
||||
|
||||
{% for question in election.questions %}
|
||||
{% for question in election.questions.impl %}
|
||||
<h2>{{ loop.index }}. {{ question.prompt }}</h2>
|
||||
{% include templates[selection_model_view_map[election.questions[loop.index0]._name]["selections_review"]] %}
|
||||
{% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %}
|
||||
{% endfor %}
|
||||
|
||||
<p>If you are happy with your selections, then make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
||||
|
@ -33,7 +33,7 @@
|
||||
boothError("Question template unable to save selections");
|
||||
}
|
||||
|
||||
showTemplate(selection_model_view_map[election.questions[booth.questionNum]._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
||||
showTemplate(selection_model_view_map[election.questions.__getitem__(booth.questionNum)._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
||||
|
||||
function previousQuestion() {
|
||||
saveSelections();
|
||||
@ -41,17 +41,17 @@
|
||||
prevTemplate();
|
||||
} else {
|
||||
booth.questionNum--;
|
||||
showTemplate(selection_model_view_map[election.questions[booth.questionNum]._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
||||
showTemplate(selection_model_view_map[election.questions.__getitem__(booth.questionNum)._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
||||
}
|
||||
}
|
||||
|
||||
function nextQuestion() {
|
||||
saveSelections();
|
||||
if (booth.questionNum == election.questions.length - 1) {
|
||||
if (booth.questionNum == election.questions.__len__() - 1) {
|
||||
nextTemplate();
|
||||
} else {
|
||||
booth.questionNum++;
|
||||
showTemplate(selection_model_view_map[election.questions[booth.questionNum]._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
||||
showTemplate(selection_model_view_map[election.questions.__getitem__(booth.questionNum)._name]["selections_make"], { "questionNum": booth.questionNum }, "#question-box");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -16,13 +16,13 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
|
||||
<h2>{{ questionNum + 1 }}. {{ election.questions[questionNum].prompt }}</h2>
|
||||
<h2>{{ questionNum + 1 }}. {{ election.questions.__getitem__(questionNum).prompt }}</h2>
|
||||
|
||||
<p><small>Vote for between {{ election.questions[questionNum].min_choices }} and {{ election.questions[questionNum].max_choices }} candidates. Click the check-boxes to the left of the candidates' names to make your selection, then click the ‘Continue’ button. If you make a mistake, click the check-boxes again to clear your selection.</small></p>
|
||||
<p><small>Vote for between {{ election.questions.__getitem__(questionNum).min_choices }} and {{ election.questions.__getitem__(questionNum).max_choices }} candidates. Click the check-boxes to the left of the candidates' names to make your selection, then click the ‘Continue’ button. If you make a mistake, click the check-boxes again to clear your selection.</small></p>
|
||||
|
||||
<div id="question-choices" class="ui form" style="margin-bottom: 1em;">
|
||||
<div class="grouped fields">
|
||||
{% for choice in election.questions[questionNum].choices %}
|
||||
{% for choice in election.questions.__getitem__(questionNum).choices.impl %}
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="question-choice-{{ loop.index0 }}" onchange="choicesChanged();">
|
||||
@ -40,7 +40,7 @@
|
||||
<script>
|
||||
function choicesChanged() {
|
||||
var numChoices = $("#question-choices input:checked").length;
|
||||
if (numChoices >= election.questions[booth.questionNum].max_choices) {
|
||||
if (numChoices >= election.questions.__getitem__(booth.questionNum).max_choices) {
|
||||
// Prevent making any more selections
|
||||
$("#question-choices input:not(:checked)").prop("disabled", true);
|
||||
$("#message-max-choices").removeClass("hidden");
|
||||
@ -52,7 +52,7 @@
|
||||
|
||||
// Fill in ballot with previous selections
|
||||
if (booth.answers[booth.questionNum]) {
|
||||
for (var selection of booth.answers[booth.questionNum].value.choices) { // Answer already serialised
|
||||
for (var selection of booth.answers.__getitem__(booth.questionNum).value.choices) { // Answer already serialised
|
||||
$("#question-choice-" + selection).prop("checked", true);
|
||||
}
|
||||
choicesChanged();
|
||||
|
@ -20,7 +20,7 @@
|
||||
{% for choice in booth.answers[loop.index0].value.choices %}
|
||||
<div class="item">
|
||||
<i class="checkmark icon"></i>
|
||||
<div class="content">{{ question.choices[choice] }}</div>
|
||||
<div class="content">{{ question.choices.__getitem__(choice) }}</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="item">
|
||||
|
@ -26,7 +26,7 @@
|
||||
{% else %}
|
||||
<p>You are not currently logged in. Please select an option from the list below to log in.</p>
|
||||
{% endif %}
|
||||
<ul>
|
||||
<ul class="ui list">
|
||||
{% 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 %}
|
||||
|
@ -39,6 +39,9 @@
|
||||
var currentBoothTask = 0;
|
||||
var selection_model_view_map = {{ selection_model_view_map|safe }}; {# :rooWut: #}
|
||||
|
||||
var username = {% if session.user %}"{{ session.user.username }}"{% else %}null{% endif %};
|
||||
var auth_methods = {{ auth_methods|safe }};
|
||||
|
||||
function resetBooth() {
|
||||
booth = {
|
||||
"questionNum": 0,
|
||||
@ -143,7 +146,9 @@
|
||||
"election": election,
|
||||
"booth": booth,
|
||||
"eosjs": eosjs,
|
||||
"selection_model_view_map": selection_model_view_map
|
||||
"selection_model_view_map": selection_model_view_map,
|
||||
"username": username,
|
||||
"auth_methods": auth_methods
|
||||
}, opts);
|
||||
$(destination).html(templates[template].render(opts));
|
||||
} catch (err) {
|
||||
|
@ -62,7 +62,9 @@ def main(app):
|
||||
user.oauth_token = resp['access_token']
|
||||
flask.session['user'] = user
|
||||
|
||||
me = reddit.get('https://oauth.reddit.com/api/v1/me')
|
||||
me = reddit.get('https://oauth.reddit.com/api/v1/me', headers={
|
||||
'User-Agent': app.config['REDDIT_USER_AGENT']
|
||||
})
|
||||
user.username = me.data['name']
|
||||
|
||||
return flask.redirect(flask.url_for('login_complete'))
|
||||
|
Loading…
Reference in New Issue
Block a user