🎉 We did it, Reddit! Voting booth now functional at a basic level
This commit is contained in:
parent
ca8c0ce614
commit
453443d22a
@ -30,13 +30,17 @@ class NullEncryptedAnswer(EncryptedAnswer):
|
|||||||
return self.answer
|
return self.answer
|
||||||
|
|
||||||
class Ballot(EmbeddedObject):
|
class Ballot(EmbeddedObject):
|
||||||
_id = UUIDField()
|
#_id = UUIDField()
|
||||||
encrypted_answers = EmbeddedObjectListField()
|
encrypted_answers = EmbeddedObjectListField()
|
||||||
|
|
||||||
|
class Vote(EmbeddedObject):
|
||||||
|
ballot = EmbeddedObjectField()
|
||||||
|
cast_at = StringField()
|
||||||
|
|
||||||
class Voter(EmbeddedObject):
|
class Voter(EmbeddedObject):
|
||||||
_id = UUIDField()
|
_id = UUIDField()
|
||||||
name = StringField()
|
name = StringField()
|
||||||
ballots = EmbeddedObjectListField()
|
votes = EmbeddedObjectListField()
|
||||||
|
|
||||||
class EmailVoter(Voter):
|
class EmailVoter(Voter):
|
||||||
email = StringField()
|
email = StringField()
|
||||||
|
@ -94,7 +94,8 @@ class ElectionTestCase(EosTestCase):
|
|||||||
answer = ApprovalAnswer(choices=VOTES[i][j])
|
answer = ApprovalAnswer(choices=VOTES[i][j])
|
||||||
encrypted_answer = NullEncryptedAnswer(answer=answer)
|
encrypted_answer = NullEncryptedAnswer(answer=answer)
|
||||||
ballot.encrypted_answers.append(encrypted_answer)
|
ballot.encrypted_answers.append(encrypted_answer)
|
||||||
election.voters[i].ballots.append(ballot)
|
vote = Vote(ballot=ballot)
|
||||||
|
election.voters[i].votes.append(vote)
|
||||||
|
|
||||||
election.save()
|
election.save()
|
||||||
|
|
||||||
|
@ -135,7 +135,9 @@ class TaskDecryptVotes(WorkflowTask):
|
|||||||
election.results.append(EosObject.objects['eos.base.election.RawResult']())
|
election.results.append(EosObject.objects['eos.base.election.RawResult']())
|
||||||
|
|
||||||
for voter in election.voters:
|
for voter in election.voters:
|
||||||
for ballot in voter.ballots:
|
if len(voter.votes) > 0:
|
||||||
|
vote = voter.votes[-1]
|
||||||
|
ballot = vote.ballot
|
||||||
for i in range(len(ballot.encrypted_answers)):
|
for i in range(len(ballot.encrypted_answers)):
|
||||||
answer = ballot.encrypted_answers[i].decrypt()
|
answer = ballot.encrypted_answers[i].decrypt()
|
||||||
election.results[i].answers.append(answer)
|
election.results[i].answers.append(answer)
|
||||||
|
@ -302,6 +302,9 @@ class DocumentObject(EosObject, metaclass=DocumentObjectType):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def deserialise(cls, value):
|
def deserialise(cls, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
attrs = {}
|
attrs = {}
|
||||||
for attr, val in cls._fields.items():
|
for attr, val in cls._fields.items():
|
||||||
json_attr = attr[3:] if attr.startswith('py_') else attr
|
json_attr = attr[3:] if attr.startswith('py_') else attr
|
||||||
|
@ -84,7 +84,9 @@ class MixingTrustee(Trustee):
|
|||||||
# Use the raw ballots from voters
|
# Use the raw ballots from voters
|
||||||
orig_answers = []
|
orig_answers = []
|
||||||
for voter in self.recurse_parents(Election).voters:
|
for voter in self.recurse_parents(Election).voters:
|
||||||
for ballot in voter.ballots:
|
if len(voter.votes) > 0:
|
||||||
|
vote = voter.votes[-1]
|
||||||
|
ballot = vote.ballot
|
||||||
orig_answers.append(ballot.encrypted_answers[question_num])
|
orig_answers.append(ballot.encrypted_answers[question_num])
|
||||||
return orig_answers
|
return orig_answers
|
||||||
|
|
||||||
|
@ -265,7 +265,8 @@ class ElectionTestCase(EosTestCase):
|
|||||||
answer = ApprovalAnswer(choices=VOTES[i][j])
|
answer = ApprovalAnswer(choices=VOTES[i][j])
|
||||||
encrypted_answer = BlockEncryptedAnswer.encrypt(election.sk.public_key, answer)
|
encrypted_answer = BlockEncryptedAnswer.encrypt(election.sk.public_key, answer)
|
||||||
ballot.encrypted_answers.append(encrypted_answer)
|
ballot.encrypted_answers.append(encrypted_answer)
|
||||||
election.voters[i].ballots.append(ballot)
|
vote = Vote(ballot=ballot)
|
||||||
|
election.voters[i].votes.append(vote)
|
||||||
|
|
||||||
election.save()
|
election.save()
|
||||||
|
|
||||||
@ -287,8 +288,8 @@ class ElectionTestCase(EosTestCase):
|
|||||||
else:
|
else:
|
||||||
orig_answers = []
|
orig_answers = []
|
||||||
for voter in election.voters:
|
for voter in election.voters:
|
||||||
for ballot in voter.ballots:
|
ballot = voter.votes[-1].ballot
|
||||||
orig_answers.append(ballot.encrypted_answers[i])
|
orig_answers.append(ballot.encrypted_answers[i])
|
||||||
shuffled_answers, commitments = election.mixing_trustees[j].mixnets[i].shuffle(orig_answers)
|
shuffled_answers, commitments = election.mixing_trustees[j].mixnets[i].shuffle(orig_answers)
|
||||||
election.mixing_trustees[j].mixed_questions.append(EosList(shuffled_answers))
|
election.mixing_trustees[j].mixed_questions.append(EosList(shuffled_answers))
|
||||||
election.mixing_trustees[j].commitments.append(EosList(commitments))
|
election.mixing_trustees[j].commitments.append(EosList(commitments))
|
||||||
|
@ -26,7 +26,10 @@ from eos.psr.workflow import *
|
|||||||
import eos.core.hashing
|
import eos.core.hashing
|
||||||
import eosweb
|
import eosweb
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
import json
|
||||||
|
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
@ -123,7 +126,32 @@ def election_view_ballots(election):
|
|||||||
def election_view_trustees(election):
|
def election_view_trustees(election):
|
||||||
return flask.render_template('election/trustees.html', election=election)
|
return flask.render_template('election/trustees.html', election=election)
|
||||||
|
|
||||||
|
@app.route('/election/<election_id>/cast_ballot', methods=['POST'])
|
||||||
|
@using_election
|
||||||
|
def election_api_cast_vote(election):
|
||||||
|
data = json.loads(flask.request.data)
|
||||||
|
|
||||||
|
voter = None
|
||||||
|
for election_voter in election.voters:
|
||||||
|
if election_voter.name == data['auth']['username']:
|
||||||
|
voter = election_voter
|
||||||
|
break
|
||||||
|
|
||||||
|
if voter is None:
|
||||||
|
# User is not authenticated
|
||||||
|
return flask.Response('Invalid credentials', 403)
|
||||||
|
|
||||||
|
# Cast the vote
|
||||||
|
ballot = EosObject.deserialise_and_unwrap(data['ballot'])
|
||||||
|
vote = Vote(ballot=ballot, cast_at=datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'))
|
||||||
|
voter.votes.append(vote)
|
||||||
|
|
||||||
|
election.save()
|
||||||
|
|
||||||
|
return flask.Response(json.dumps({
|
||||||
|
'voter': EosObject.serialise_and_wrap(voter),
|
||||||
|
'vote': EosObject.serialise_and_wrap(vote)
|
||||||
|
}), mimetype='application/json')
|
||||||
|
|
||||||
# === Model-Views ===
|
# === Model-Views ===
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ model_view_map = {
|
|||||||
'tabs': 'election/psr/tabs.html'
|
'tabs': 'election/psr/tabs.html'
|
||||||
},
|
},
|
||||||
ApprovalQuestion: {
|
ApprovalQuestion: {
|
||||||
'selections_make': 'question/approval/selections_make.html'
|
'selections_make': 'question/approval/selections_make.html',
|
||||||
|
'selections_review': 'question/approval/selections_review.html'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,15 @@
|
|||||||
window = self; // Workaround for libraries
|
window = self; // Workaround for libraries
|
||||||
isLibrariesLoaded = false;
|
isLibrariesLoaded = false;
|
||||||
|
|
||||||
function generateEncryptedVote(election, selections) {
|
function generateEncryptedVote(election, answers) {
|
||||||
encryptedVote = eos_js.eos_core.objects.__all__.PlaintextVote({ "choices": selections, "election_uuid": election.id, "election_hash": election.hash() });
|
encrypted_answers = [];
|
||||||
|
for (var answer_json of answers) {
|
||||||
|
answer = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(answer_json, null);
|
||||||
|
encrypted_answer = eosjs.eos.psr.election.__all__.BlockEncryptedAnswer.encrypt(election.public_key, answer);
|
||||||
|
encrypted_answers.push(eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(encrypted_answer, null));
|
||||||
|
}
|
||||||
|
|
||||||
postMessage(eos_js.eos_core.libobjects.__all__.EosObject.serialise_and_wrap(encryptedVote, null));
|
postMessage(encrypted_answers);
|
||||||
}
|
}
|
||||||
|
|
||||||
onmessage = function(msg) {
|
onmessage = function(msg) {
|
||||||
@ -34,9 +39,9 @@ onmessage = function(msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (msg.data.action === "generateEncryptedVote") {
|
if (msg.data.action === "generateEncryptedVote") {
|
||||||
msg.data.election = eosjs.eos.core.libobjects.__all__.EosObject.deserialise_and_unwrap(msg.data.election, null);
|
msg.data.election = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(msg.data.election, null);
|
||||||
|
|
||||||
generateEncryptedVote(msg.data.election, msg.data.selections);
|
generateEncryptedVote(msg.data.election, msg.data.answers);
|
||||||
} else {
|
} else {
|
||||||
throw "Unknown action: " + msg.data.action;
|
throw "Unknown action: " + msg.data.action;
|
||||||
}
|
}
|
||||||
|
28
eosweb/core/static/nunjucks/booth/audit.html
Normal file
28
eosweb/core/static/nunjucks/booth/audit.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{% extends templates['booth/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 content %}
|
||||||
|
TODO
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block buttons %}
|
||||||
|
<button class="ui left floated button" onclick="prevTemplate();">Back</a>
|
||||||
|
<button class="ui right floated primary button" onclick="nextTemplate();">Continue</button>
|
||||||
|
{% endblock %}
|
@ -29,17 +29,19 @@
|
|||||||
{% set menuindex = 3 %}
|
{% set menuindex = 3 %}
|
||||||
{% elif template == 'booth/audit.html' %}
|
{% elif template == 'booth/audit.html' %}
|
||||||
{% set menuindex = 4 %}
|
{% set menuindex = 4 %}
|
||||||
{% elif template == 'booth/complete.html' %}
|
{% elif template == 'booth/cast.html' %}
|
||||||
{% set menuindex = 5 %}
|
{% set menuindex = 5 %}
|
||||||
|
{% elif template == 'booth/complete.html' %}
|
||||||
|
{% set menuindex = 6 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="ui secondary pointing menu" id="election-tab-menu">
|
<div class="ui secondary pointing menu" id="election-tab-menu">
|
||||||
{# oh dear god #}
|
{# oh dear god #}
|
||||||
<a href="#" class="ui{% if menuindex >= 1 %} active{% endif %} item"{% if menuindex != 1 %} style="color: #767676;{% if menuindex > 1 %} font-weight: 400;{% endif %}"{% endif %}>1. Welcome</a>
|
<span class="ui{% if menuindex >= 1 %} active{% endif %} item"{% if menuindex != 1 %} style="color: #767676;{% if menuindex > 1 %} font-weight: 400;{% endif %}"{% endif %}>1. Welcome</span>
|
||||||
<a href="#" class="ui{% if menuindex >= 2 %} active{% endif %} item"{% if menuindex != 2 %} style="color: #767676;{% if menuindex > 2 %} font-weight: 400;{% endif %}"{% endif %}>2. Make selections</a>
|
<span class="ui{% if menuindex >= 2 %} active{% endif %} item"{% if menuindex != 2 %} style="color: #767676;{% if menuindex > 2 %} font-weight: 400;{% endif %}"{% endif %}>2. Make selections</span>
|
||||||
<a href="#" class="ui{% if menuindex >= 3 %} active{% endif %} item"{% if menuindex != 3 %} style="color: #767676;{% if menuindex > 3 %} font-weight: 400;{% endif %}"{% endif %}>3. Review selections</a>
|
<span class="ui{% if menuindex >= 3 %} active{% endif %} item"{% if menuindex != 3 %} style="color: #767676;{% if menuindex > 3 %} font-weight: 400;{% endif %}"{% endif %}>3. Review selections</span>
|
||||||
<a href="#" class="ui{% if menuindex >= 4 %} active{% endif %} item"{% if menuindex != 4 %} style="color: #767676;{% if menuindex > 4 %} font-weight: 400;{% endif %}"{% endif %}>4. Audit ballot</a>
|
<span class="ui{% if menuindex >= 4 %} active{% endif %} item"{% if menuindex != 4 %} style="color: #767676;{% if menuindex > 4 %} font-weight: 400;{% endif %}"{% endif %}>4. Audit ballot</span>
|
||||||
<a href="#" class="ui{% if menuindex >= 5 %} active{% endif %} item"{% if menuindex != 5 %} style="color: #767676;{% if menuindex > 5 %} font-weight: 400;{% endif %}"{% endif %}>5. Cast ballot</a>
|
<span class="ui{% if menuindex >= 5 %} active{% endif %} item"{% if menuindex != 5 %} style="color: #767676;{% if menuindex > 5 %} font-weight: 400;{% endif %}"{% endif %}>5. Cast ballot</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
|
103
eosweb/core/static/nunjucks/booth/cast.html
Normal file
103
eosweb/core/static/nunjucks/booth/cast.html
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
{% extends templates['booth/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 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">
|
||||||
|
<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>
|
||||||
|
<div class="header">Error</div>
|
||||||
|
<p>An unknown error occurred while attempting to cast your ballot. You may click the ‘Cast ballot’ button below to try again. If the issue persists, contact your election administrator.</p>
|
||||||
|
<p id="error_unknown_tech"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="casting" style="display: none;">
|
||||||
|
<div class="ui active text loader">Casting your ballot. Please wait.</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% 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>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block after %}
|
||||||
|
<script>
|
||||||
|
$(".message .close").on("click", function() {
|
||||||
|
$(this).closest(".message").addClass("hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
function castBallot() {
|
||||||
|
$("#cast_prompt").hide();
|
||||||
|
$("#casting").show();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
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() }
|
||||||
|
}),
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "text"
|
||||||
|
})
|
||||||
|
.done(function(data) {
|
||||||
|
response = eosjs.eos.core.objects.__all__.EosObject.from_json(data);
|
||||||
|
booth.voter = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(response.voter);
|
||||||
|
booth.vote = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(response.vote);
|
||||||
|
|
||||||
|
// Clear plaintexts
|
||||||
|
booth.answers = null;
|
||||||
|
|
||||||
|
nextTemplate();
|
||||||
|
})
|
||||||
|
.fail(function(xhr, status, err) {
|
||||||
|
if (xhr.status === 403) { // Forbidden
|
||||||
|
$("#error_invalid_id").addClass("visible");
|
||||||
|
|
||||||
|
$("#error_unknown").addClass("hidden");
|
||||||
|
} else {
|
||||||
|
$("#error_unknown_tech").text("Technical details: " + err);
|
||||||
|
$("#error_unknown").removeClass("hidden");
|
||||||
|
|
||||||
|
$("#error_invalid_id").removeClass("visible");
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#casting").hide();
|
||||||
|
$("#cast_prompt").show();
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
37
eosweb/core/static/nunjucks/booth/complete.html
Normal file
37
eosweb/core/static/nunjucks/booth/complete.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{% extends templates['booth/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 content %}
|
||||||
|
<p>Your vote has been successfully cast. The following is your ‘smart ballot tracker’. Please retain a copy of your smart ballot tracker – you can use it to verify that your vote has been counted correctly. You may <a href="#" onclick="window.print();return false;">print this page</a> as a receipt if you wish.</p>
|
||||||
|
|
||||||
|
<div class="ui success message">
|
||||||
|
<div class="header">Smart ballot tracker</div>
|
||||||
|
<p>This smart ballot tracker confirms that {{ voter.py_name }} cast a vote in the election {{ election.py_name }} at {{ vote.cast_at }}.</p>
|
||||||
|
<p>Ballot fingerprint: <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(vote.ballot).hash_as_b64() }}</span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block buttons %}
|
||||||
|
<a href="{{ election_base_url }}ballots" class="ui right floated primary button">Finish</a>
|
||||||
|
{% endblock %}
|
52
eosweb/core/static/nunjucks/booth/encrypt.html
Normal file
52
eosweb/core/static/nunjucks/booth/encrypt.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{#
|
||||||
|
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/>.
|
||||||
|
#}
|
||||||
|
|
||||||
|
<div class="ui active text loader">Preparing your ballot. Please wait.</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
boothWorker.onmessage = function(msg) {
|
||||||
|
try {
|
||||||
|
encryptedAnswers = [];
|
||||||
|
for (var encrypted_answer_json of msg.data) {
|
||||||
|
encryptedAnswers.push(eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(encrypted_answer_json, null));
|
||||||
|
}
|
||||||
|
booth.ballot = eosjs.eos.base.election.__all__.Ballot();
|
||||||
|
booth.ballot.encrypted_answers = encryptedAnswers;
|
||||||
|
|
||||||
|
nextTemplate();
|
||||||
|
} catch (err) {
|
||||||
|
boothError(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boothWorker.onerror = function(err) {
|
||||||
|
boothError(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
boothWorker.postMessage({
|
||||||
|
"action": "generateEncryptedVote",
|
||||||
|
"static_base_url": "{{ static_base_url }}",
|
||||||
|
"election": eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(election, null),
|
||||||
|
"answers": booth.answers,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
boothError(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
</script>
|
41
eosweb/core/static/nunjucks/booth/review.html
Normal file
41
eosweb/core/static/nunjucks/booth/review.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{% extends templates['booth/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 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 %}
|
||||||
|
<h2>{{ loop.index }}. {{ question.prompt }}</h2>
|
||||||
|
{% include templates[selection_model_view_map[election.questions[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>
|
||||||
|
<p>Click ‘Continue’, and you will be able to log into cast your vote.</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block buttons %}
|
||||||
|
<button class="ui left floated button" onclick="prevTemplate();">Back</a>
|
||||||
|
<button class="ui right floated primary button" onclick="nextTemplate(2);">Continue</button>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block after %}
|
||||||
|
<div style="clear: both; margin-bottom: 1em;"></div>
|
||||||
|
<p><small>If you would like to audit your ballot, <a href="#" onclick="nextTemplate(1);">click here</a>. Auditing your ballot is an <b>optional</b> step you can take to check that your vote has been prepared correctly. You do not need to audit your ballot in order to cast a vote.</small></p>
|
||||||
|
{% endblock %}
|
@ -51,8 +51,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fill in ballot with previous selections
|
// Fill in ballot with previous selections
|
||||||
if (booth.selections[booth.questionNum]) {
|
if (booth.answers[booth.questionNum]) {
|
||||||
for (var selection of booth.selections[booth.questionNum]) {
|
for (var selection of booth.answers[booth.questionNum].value.choices) { // Answer already serialised
|
||||||
$("#question-choice-" + selection).prop("checked", true);
|
$("#question-choice-" + selection).prop("checked", true);
|
||||||
}
|
}
|
||||||
choicesChanged();
|
choicesChanged();
|
||||||
@ -63,6 +63,7 @@
|
|||||||
$("#question-choices input:checked").each(function(i, el) {
|
$("#question-choices input:checked").each(function(i, el) {
|
||||||
selections.push(parseInt(el.id.substring("question-choice-".length)))
|
selections.push(parseInt(el.id.substring("question-choice-".length)))
|
||||||
});
|
});
|
||||||
booth.selections[booth.questionNum] = selections;
|
answer = eosjs.eos.base.election.__all__.ApprovalAnswer(eosjs.__kwargtrans__({choices: selections}));
|
||||||
|
booth.answers[booth.questionNum] = eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(answer);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
{#
|
||||||
|
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/>.
|
||||||
|
#}
|
||||||
|
|
||||||
|
<div class="ui list">
|
||||||
|
{% 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>
|
||||||
|
{% else %}
|
||||||
|
<div class="item">
|
||||||
|
<i class="warning circle icon"></i>
|
||||||
|
<div class="content">No candidates selected</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if booth.answers[loop.index0].value.choices.length < question.max_choices %}
|
||||||
|
<div class="ui warning message">
|
||||||
|
<p>You have selected fewer than the maximum allowed number of candidates. If this was not your intention, please click the ‘Back’ button below now, and correct your selections.</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
@ -30,8 +30,8 @@
|
|||||||
{% for voter in election.voters %}
|
{% for voter in election.voters %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ voter.name }}</td>
|
<td>{{ voter.name }}</td>
|
||||||
{% if voter.ballot|length > 0 %}
|
{% if voter.votes|length > 0 %}
|
||||||
<td class="hash">{{ SHA256().update_obj(voter.ballot[-1]).hash_as_b64() }}</td>
|
<td class="hash">{{ SHA256().update_obj(voter.votes[-1].ballot).hash_as_b64() }}</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td class="hash"></td>
|
<td class="hash"></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
function resetBooth() {
|
function resetBooth() {
|
||||||
booth = {
|
booth = {
|
||||||
"questionNum": 0,
|
"questionNum": 0,
|
||||||
"selections": [],
|
"answers": [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
resetBooth();
|
resetBooth();
|
||||||
@ -71,6 +71,7 @@
|
|||||||
// Load templates for the question types
|
// Load templates for the question types
|
||||||
for (var question of election.questions) {
|
for (var question of election.questions) {
|
||||||
templates[selection_model_view_map[question._name]['selections_make']] = null;
|
templates[selection_model_view_map[question._name]['selections_make']] = null;
|
||||||
|
templates[selection_model_view_map[question._name]['selections_review']] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadTemplates();
|
loadTemplates();
|
||||||
@ -137,10 +138,11 @@
|
|||||||
"templates": templates,
|
"templates": templates,
|
||||||
"template": template,
|
"template": template,
|
||||||
"election_base_url": "{{ url_for('election_api_json', election_id=election._id) }}",
|
"election_base_url": "{{ url_for('election_api_json', election_id=election._id) }}",
|
||||||
"static_base_url": "{{ url_for('static', filename='nunjucks') }}",
|
"static_base_url": "{{ url_for('static', filename='') }}",
|
||||||
"election": election,
|
"election": election,
|
||||||
"booth": booth,
|
"booth": booth,
|
||||||
"eosjs": eosjs,
|
"eosjs": eosjs,
|
||||||
|
"selection_model_view_map": selection_model_view_map
|
||||||
}, opts);
|
}, opts);
|
||||||
$(destination).html(templates[template].render(opts));
|
$(destination).html(templates[template].render(opts));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -149,13 +151,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextTemplate() {
|
function nextTemplate(num) {
|
||||||
currentBoothTask++;
|
if (!num) {
|
||||||
|
num = 1;
|
||||||
|
}
|
||||||
|
currentBoothTask += num;
|
||||||
boothTasks[currentBoothTask].activate(true);
|
boothTasks[currentBoothTask].activate(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function prevTemplate() {
|
function prevTemplate(num) {
|
||||||
currentBoothTask--;
|
if (!num) {
|
||||||
|
num = 1;
|
||||||
|
}
|
||||||
|
currentBoothTask -= num;
|
||||||
boothTasks[currentBoothTask].activate(false);
|
boothTasks[currentBoothTask].activate(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,18 +171,52 @@
|
|||||||
// TODO: Make modular
|
// TODO: Make modular
|
||||||
|
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(shouldInit) {
|
activate: function(fromLeft) {
|
||||||
showTemplate('booth/welcome.html');
|
showTemplate('booth/welcome.html');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
templates['booth/base.html'] = null;
|
templates['booth/base.html'] = null;
|
||||||
templates['booth/welcome.html'] = null;
|
templates['booth/welcome.html'] = null;
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(shouldInit) {
|
activate: function(fromLeft) {
|
||||||
showTemplate('booth/selections.html');
|
showTemplate('booth/selections.html');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
templates['booth/selections.html'] = null;
|
templates['booth/selections.html'] = null;
|
||||||
|
boothTasks.append({
|
||||||
|
activate: function(fromLeft) {
|
||||||
|
if (fromLeft) {
|
||||||
|
showTemplate('booth/encrypt.html');
|
||||||
|
} else {
|
||||||
|
prevTemplate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
templates['booth/encrypt.html'] = null;
|
||||||
|
boothTasks.append({
|
||||||
|
activate: function(fromLeft) {
|
||||||
|
showTemplate('booth/review.html', {ballot: booth.ballot});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
templates['booth/review.html'] = null;
|
||||||
|
boothTasks.append({
|
||||||
|
activate: function(fromLeft) {
|
||||||
|
showTemplate('booth/audit.html', {ballot: booth.ballot});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
templates['booth/audit.html'] = null;
|
||||||
|
boothTasks.append({
|
||||||
|
activate: function(fromLeft) {
|
||||||
|
showTemplate('booth/cast.html', {ballot: booth.ballot});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
templates['booth/cast.html'] = null;
|
||||||
|
boothTasks.append({
|
||||||
|
activate: function(fromLeft) {
|
||||||
|
showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
templates['booth/complete.html'] = null;
|
||||||
|
|
||||||
// === END BOOTH TASKS ===
|
// === END BOOTH TASKS ===
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user