Merge pull request #21 from vincentmdealmeida/Fixes
Fixed a number of bugs that arose from merging the code from Bens for…
This commit is contained in:
commit
59d3b26e95
6 changed files with 281 additions and 75 deletions
|
@ -211,7 +211,7 @@ class Ballot(models.Model):
|
||||||
|
|
||||||
class EncBallot(models.Model):
|
class EncBallot(models.Model):
|
||||||
handle = models.CharField(primary_key=True, default=uuid.uuid4, editable=False, max_length=255)
|
handle = models.CharField(primary_key=True, default=uuid.uuid4, editable=False, max_length=255)
|
||||||
ballot = models.CharField(max_length=4096)
|
ballot = models.CharField(max_length=10240)
|
||||||
|
|
||||||
|
|
||||||
# Implements the new binary encoding scheme
|
# Implements the new binary encoding scheme
|
||||||
|
|
|
@ -212,6 +212,22 @@ def email_voters_vote_url(voters, event):
|
||||||
voter.send_email(email_subject, email_body)
|
voter.send_email(email_subject, email_body)
|
||||||
|
|
||||||
|
|
||||||
|
@task()
|
||||||
|
def email_voting_success(voter, ballotHandle, eventTitle):
|
||||||
|
email_subject = "Vote(s) received for Event '" + eventTitle + "'"
|
||||||
|
|
||||||
|
# Plain text email - this could be replaced for a HTML-based email in the future
|
||||||
|
email_body_base = str("")
|
||||||
|
email_body_base += "Dear Voter,\n\n"
|
||||||
|
email_body_base += "Thank you for your vote(s) for the event: " + eventTitle + ". This has been securely encrypted "
|
||||||
|
email_body_base += "and anonymously stored in our system.\n\n"
|
||||||
|
email_body_base += "For your reference, the identifier for your selected ballot is:\n"
|
||||||
|
email_body_base += ballotHandle
|
||||||
|
email_body_base += get_email_sign_off()
|
||||||
|
|
||||||
|
voter.send_email(email_subject, email_body_base)
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Updates the EID of an event to contain 2 event IDs: a human readable one (hr) and a crypto one (GP from param())
|
Updates the EID of an event to contain 2 event IDs: a human readable one (hr) and a crypto one (GP from param())
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -18,6 +18,7 @@ from allauthdemo.auth.models import DemoUser
|
||||||
|
|
||||||
from .tasks import email_trustees_prep, update_EID, generate_combpk, event_ended, create_ballots
|
from .tasks import email_trustees_prep, update_EID, generate_combpk, event_ended, create_ballots
|
||||||
from .tasks import create_ballots_for_poll, email_voters_vote_url, combine_decryptions_and_tally, combine_encrypted_votes
|
from .tasks import create_ballots_for_poll, email_voters_vote_url, combine_decryptions_and_tally, combine_encrypted_votes
|
||||||
|
from .tasks import email_voting_success
|
||||||
|
|
||||||
from .utils.EventModelAdaptor import EventModelAdaptor
|
from .utils.EventModelAdaptor import EventModelAdaptor
|
||||||
|
|
||||||
|
@ -220,7 +221,9 @@ def event_vote(request, event_id, poll_id):
|
||||||
ballot.selection = selection
|
ballot.selection = selection
|
||||||
ballot.save()
|
ballot.save()
|
||||||
|
|
||||||
combine_encrypted_votes.delay(email_key[0].user, poll)
|
voter = email_key[0].user
|
||||||
|
combine_encrypted_votes.delay(voter, poll)
|
||||||
|
email_voting_success.delay(voter, handle_json, event.title)
|
||||||
|
|
||||||
if next_poll_uuid:
|
if next_poll_uuid:
|
||||||
return HttpResponseRedirect(reverse('polls:event-vote', kwargs={'event_id': event.uuid,
|
return HttpResponseRedirect(reverse('polls:event-vote', kwargs={'event_id': event.uuid,
|
||||||
|
|
|
@ -34,8 +34,6 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
<br/>
|
<br/>
|
||||||
<span><strong>Number of polls for this event:</strong> {{ poll_count }}</span>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
<br/>
|
||||||
<span><strong>Instructions:</strong>
|
<span><strong>Instructions:</strong>
|
||||||
You will be shown each poll for this event one by one where you will need to make a selection for the current
|
You will be shown each poll for this event one by one where you will need to make a selection for the current
|
||||||
|
@ -61,26 +59,21 @@
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><strong>Options</strong></div>
|
<div class="panel-heading"><strong>Options</strong></div>
|
||||||
<div class="panel panel-body">
|
<div class="panel panel-body">
|
||||||
{% comment %}<select class="radio-inline select form-control" id="poll-options" name="options">
|
|
||||||
{% load custom_filters_tags %}
|
|
||||||
<option value="{{ -1|get_ballot_value:object.options.all.count }}">Please Select...</option>
|
|
||||||
{% for option in object.options.all %}
|
|
||||||
<option value="{{forloop.counter|get_ballot_value:object.options.all.count}}">{{ option.choice_text }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>{% endcomment %}
|
|
||||||
{% for option in object.options.all %}
|
{% for option in object.options.all %}
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
{% load custom_filters_tags %}
|
{% load custom_filters_tags %}
|
||||||
<label><input type="checkbox" value="{{forloop.counter|get_ballot_value:object.options.all.count}}">{{ option.choice_text }}</label>
|
<label id="{{forloop.counter|get_ballot_value:object.options.all.count}}">
|
||||||
|
<input type="checkbox" value="{{forloop.counter|get_ballot_value:object.options.all.count}}">{{ option.choice_text }}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr/>
|
<hr/>
|
||||||
<div id="ballot-gen-progress-area">
|
<div id="ballot-gen-progress-area">
|
||||||
<button id="gen-ballots-btn" class="btn btn-primary">Generate Ballots</button>
|
<button id="gen-ballots-btn" class="btn btn-primary">Begin Voting</button>
|
||||||
<!-- Progress bar which is used during encryption -->
|
<!-- Progress bar which is used during encryption -->
|
||||||
<h4 id="progress-bar-description" class="hidden">Generating Ballots...</h4>
|
<h4 id="progress-bar-description" class="hidden">Generating 2 Digital Ballots. Please wait...</h4>
|
||||||
<div id="progress-bar-container" class="progress hidden">
|
<div id="progress-bar-container" class="progress hidden">
|
||||||
<div id="progress-bar" class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
|
<div id="progress-bar" class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
|
||||||
<span class="sr-only">70% Complete</span>
|
<span class="sr-only">70% Complete</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -124,8 +117,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button id="cancelVoteBtn" type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
|
<button id="nextDialogBtn" type="button" class="btn btn-primary">Next</button>
|
||||||
|
<button id="cancelDialogBtn" type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
|
||||||
<button id="closeDialogBtn" type="button" class="btn btn-primary hidden" data-dismiss="modal">Close</button>
|
<button id="closeDialogBtn" type="button" class="btn btn-primary hidden" data-dismiss="modal">Close</button>
|
||||||
|
<button id="startOverDialogBtn" type="button" class="btn btn-danger hidden" data-dismiss="modal">Start Over</button>
|
||||||
|
<button id="submitDialogBtn" type="button" class="btn btn-success hidden">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -208,4 +208,30 @@ input[type="file"] {
|
||||||
.choice-group {
|
.choice-group {
|
||||||
width: 54%;
|
width: 54%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
img[src^="data"] {
|
||||||
|
width: 45%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.containerMarginTop {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skDIV {
|
||||||
|
width: 75%;
|
||||||
|
margin: 0 auto;
|
||||||
|
height: 3em;
|
||||||
|
background-color: darkslategrey;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skDIV > p {
|
||||||
|
text-align: center;
|
||||||
|
height: 3em;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 2.9em;
|
||||||
}
|
}
|
|
@ -1,4 +1,11 @@
|
||||||
var dialogOpen = false;
|
var dialogOpen = false;
|
||||||
|
var DIALOG_BTN_STATES = {
|
||||||
|
STEP_1: 1,
|
||||||
|
STEP_2: 2,
|
||||||
|
STEP_3: 3,
|
||||||
|
VOTE_SUCCESS: 4,
|
||||||
|
VOTE_ERROR: 5
|
||||||
|
};
|
||||||
|
|
||||||
function showDialogWithText(titleTxt, bodyTxt) {
|
function showDialogWithText(titleTxt, bodyTxt) {
|
||||||
var modalDialog = $('#modalDialog');
|
var modalDialog = $('#modalDialog');
|
||||||
|
@ -10,14 +17,55 @@ function showDialogWithText(titleTxt, bodyTxt) {
|
||||||
var p = document.createElement("p");
|
var p = document.createElement("p");
|
||||||
p.innerHTML = bodyTxt;
|
p.innerHTML = bodyTxt;
|
||||||
body.empty();
|
body.empty();
|
||||||
body.append( p );
|
body.append(p);
|
||||||
|
|
||||||
if(!dialogOpen) {
|
if (!dialogOpen) {
|
||||||
modalDialog.modal('toggle');
|
modalDialog.modal('toggle');
|
||||||
dialogOpen = true;
|
dialogOpen = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateDialogButtons(state) {
|
||||||
|
// Trigger the btn selectors once here
|
||||||
|
let nextDialogBtn = $('#nextDialogBtn');
|
||||||
|
let cancelDialogBtn = $('#cancelDialogBtn');
|
||||||
|
let closeDialogBtn = $('#closeDialogBtn');
|
||||||
|
let startOverDialogBtn = $('#startOverDialogBtn');
|
||||||
|
let submitDialogBtn = $('#submitDialogBtn');
|
||||||
|
|
||||||
|
switch(state) {
|
||||||
|
case DIALOG_BTN_STATES.STEP_1:
|
||||||
|
nextDialogBtn.removeClass("hidden");
|
||||||
|
cancelDialogBtn.removeClass("hidden");
|
||||||
|
closeDialogBtn.addClass("hidden");
|
||||||
|
startOverDialogBtn.addClass("hidden");
|
||||||
|
submitDialogBtn.addClass("hidden");
|
||||||
|
break;
|
||||||
|
case DIALOG_BTN_STATES.STEP_2:
|
||||||
|
nextDialogBtn.addClass("hidden");
|
||||||
|
cancelDialogBtn.removeClass("hidden");
|
||||||
|
closeDialogBtn.addClass("hidden");
|
||||||
|
startOverDialogBtn.addClass("hidden");
|
||||||
|
submitDialogBtn.addClass("hidden");
|
||||||
|
break;
|
||||||
|
case DIALOG_BTN_STATES.STEP_3:
|
||||||
|
nextDialogBtn.addClass("hidden");
|
||||||
|
cancelDialogBtn.addClass("hidden");
|
||||||
|
closeDialogBtn.addClass("hidden");
|
||||||
|
startOverDialogBtn.removeClass("hidden");
|
||||||
|
submitDialogBtn.removeClass("hidden");
|
||||||
|
break;
|
||||||
|
case DIALOG_BTN_STATES.VOTE_SUCCESS:
|
||||||
|
case DIALOG_BTN_STATES.VOTE_ERROR:
|
||||||
|
nextDialogBtn.addClass("hidden");
|
||||||
|
cancelDialogBtn.addClass("hidden");
|
||||||
|
closeDialogBtn.removeClass("hidden");
|
||||||
|
startOverDialogBtn.addClass("hidden");
|
||||||
|
submitDialogBtn.addClass("hidden");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This should stop people ticking more than the maximum permitted
|
// This should stop people ticking more than the maximum permitted
|
||||||
function updateCheckboxInteractivity() {
|
function updateCheckboxInteractivity() {
|
||||||
var inputs = $("label input[type=checkbox]");
|
var inputs = $("label input[type=checkbox]");
|
||||||
|
@ -65,10 +113,6 @@ function isVotingInputValid() {
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(selectedCount < MAX_SELECTIONS) {
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will highlight when people haven't selected enough options
|
// This will highlight when people haven't selected enough options
|
||||||
|
|
||||||
if(!valid) {
|
if(!valid) {
|
||||||
|
@ -86,6 +130,7 @@ function isVotingInputValid() {
|
||||||
let titleTxt = 'Voting Error';
|
let titleTxt = 'Voting Error';
|
||||||
|
|
||||||
showDialogWithText(titleTxt, errText);
|
showDialogWithText(titleTxt, errText);
|
||||||
|
updateDialogButtons(DIALOG_BTN_STATES.VOTE_ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,6 +304,18 @@ function SHA256Hash(bytes, toStr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateBallots() {
|
function generateBallots() {
|
||||||
|
// Get the user's selected option
|
||||||
|
let inputs = $("label input[type=checkbox]");
|
||||||
|
let selectedOption = "";
|
||||||
|
inputs.each(function() {
|
||||||
|
let input = $(this);
|
||||||
|
|
||||||
|
if(input.prop('checked')) {
|
||||||
|
selectedOption = input.val();
|
||||||
|
selectedOption = document.getElementById(selectedOption).innerText;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Generate Ballot A and Ballot B to be displayed to the user
|
// Generate Ballot A and Ballot B to be displayed to the user
|
||||||
// This fn starts the process
|
// This fn starts the process
|
||||||
var ballotA = generateBallot();
|
var ballotA = generateBallot();
|
||||||
|
@ -271,11 +328,12 @@ function generateBallots() {
|
||||||
var ballotB = generateBallot();
|
var ballotB = generateBallot();
|
||||||
progressBar.setAttribute("style", "width: 100%;");
|
progressBar.setAttribute("style", "width: 100%;");
|
||||||
|
|
||||||
showFirstQRCode(ballotA, ballotB);
|
showFirstQRCode(ballotA, ballotB, selectedOption);
|
||||||
}, 150);
|
}, 150);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showFirstQRCode(ballotA, ballotB) {
|
// Called in stage 1 of 3 in the voting process
|
||||||
|
function showFirstQRCode(ballotA, ballotB, selectedOption) {
|
||||||
var ballots = new Array(ballotA, ballotB);
|
var ballots = new Array(ballotA, ballotB);
|
||||||
var ballotHashes = new Array(2);
|
var ballotHashes = new Array(2);
|
||||||
|
|
||||||
|
@ -287,14 +345,19 @@ function showFirstQRCode(ballotA, ballotB) {
|
||||||
var modalDialog = $('#modalDialog');
|
var modalDialog = $('#modalDialog');
|
||||||
var title = modalDialog.find('.modal-title');
|
var title = modalDialog.find('.modal-title');
|
||||||
var body = modalDialog.find('.modal-body');
|
var body = modalDialog.find('.modal-body');
|
||||||
var footer = modalDialog.find('.modal-footer');
|
|
||||||
|
|
||||||
body.empty();
|
body.empty();
|
||||||
title.text('Please Scan this QR Code');
|
title.text('Step 1 of 3: Link Your Vote');
|
||||||
|
|
||||||
|
let pleaseScanP = document.createElement('p');
|
||||||
|
pleaseScanP.innerHTML = "Please scan the following QR code from your DEMOS 2 mobile application:";
|
||||||
|
|
||||||
|
let QRDiv = document.createElement('div');
|
||||||
var QRCodeImg = document.createElement('img');
|
var QRCodeImg = document.createElement('img');
|
||||||
QRCodeImg.setAttribute('class', 'QR-code');
|
QRCodeImg.setAttribute('class', 'QR-code');
|
||||||
|
QRCodeImg.setAttribute('id', "qr-img");
|
||||||
new QRCode(QRCodeImg, ballotHashes[0] + ';' + ballotHashes[1]);
|
new QRCode(QRCodeImg, ballotHashes[0] + ';' + ballotHashes[1]);
|
||||||
|
QRDiv.append(QRCodeImg);
|
||||||
|
|
||||||
// ----------------------------------------------
|
// ----------------------------------------------
|
||||||
|
|
||||||
|
@ -315,37 +378,31 @@ function showFirstQRCode(ballotA, ballotB) {
|
||||||
|
|
||||||
// -----------------------------------------------
|
// -----------------------------------------------
|
||||||
|
|
||||||
body.append(QRCodeImg);
|
body.append(pleaseScanP);
|
||||||
|
body.append(QRDiv);
|
||||||
body.append(hashGroupDiv);
|
body.append(hashGroupDiv);
|
||||||
|
|
||||||
var closeButton = $('close-button');
|
// Prepare the appropriate dialog buttons
|
||||||
closeButton.removeClass('btn-success');
|
updateDialogButtons(DIALOG_BTN_STATES.STEP_1);
|
||||||
closeButton.addClass('btn-danger');
|
|
||||||
closeButton.text("Close without submitting vote");
|
|
||||||
|
|
||||||
var nextButton = document.createElement('button');
|
if(!dialogOpen) {
|
||||||
nextButton.setAttribute('type', 'button');
|
modalDialog.modal('toggle');
|
||||||
nextButton.setAttribute('id', 'next-button');
|
dialogOpen = true;
|
||||||
nextButton.setAttribute('class', 'btn btn-default');
|
}
|
||||||
nextButton.innerHTML = "Next";
|
|
||||||
|
|
||||||
footer.prepend(nextButton);
|
$('#nextDialogBtn').click(function(e) {
|
||||||
|
showBallotChoiceDialog(ballots, ballotHashes, selectedOption, modalDialog);
|
||||||
modalDialog.modal('show');
|
|
||||||
|
|
||||||
$('#next-button').click(function(e) {
|
|
||||||
showBallotChoiceDialog(ballots);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showBallotChoiceDialog(ballots) {
|
// Called in stage 2 of 3 in the voting process
|
||||||
|
function showBallotChoiceDialog(ballots, ballotHashes, selectedOption, dialog) {
|
||||||
// Display the ballot choice dialog
|
// Display the ballot choice dialog
|
||||||
var modalDialog = $('#modalDialog');
|
var title = dialog.find('.modal-title');
|
||||||
var title = modalDialog.find('.modal-title');
|
var body = dialog.find('.modal-body');
|
||||||
var body = modalDialog.find('.modal-body');
|
|
||||||
|
|
||||||
body.empty();
|
body.empty();
|
||||||
title.text('Please Select a Ballot');
|
title.text('Step 2 of 3: Select a Ballot');
|
||||||
|
|
||||||
// Generate the body of the dialog which consists of a button for A and for B
|
// Generate the body of the dialog which consists of a button for A and for B
|
||||||
var choiceGroupDiv = document.createElement('div');
|
var choiceGroupDiv = document.createElement('div');
|
||||||
|
@ -363,21 +420,104 @@ function showBallotChoiceDialog(ballots) {
|
||||||
btnChoiceB.innerHTML = 'B';
|
btnChoiceB.innerHTML = 'B';
|
||||||
choiceGroupDiv.append(btnChoiceB);
|
choiceGroupDiv.append(btnChoiceB);
|
||||||
|
|
||||||
body.append(choiceGroupDiv);
|
// ----------------------------------------------
|
||||||
|
|
||||||
modalDialog.modal('show');
|
var hashGroupDiv = document.createElement('div');
|
||||||
|
var br = document.createElement('br');
|
||||||
|
hashGroupDiv.append( br );
|
||||||
|
|
||||||
|
var hashA = document.createElement("span");
|
||||||
|
hashA.innerHTML = "Hash A: " + ballotHashes[0];
|
||||||
|
hashGroupDiv.append( hashA );
|
||||||
|
|
||||||
|
var br2 = document.createElement('br');
|
||||||
|
hashGroupDiv.append( br2 );
|
||||||
|
|
||||||
|
var hashB = document.createElement("span");
|
||||||
|
hashB.innerHTML = "Hash B: " + ballotHashes[1];
|
||||||
|
hashGroupDiv.append( hashB );
|
||||||
|
|
||||||
|
// -----------------------------------------------
|
||||||
|
|
||||||
|
body.append(choiceGroupDiv);
|
||||||
|
body.append(hashGroupDiv);
|
||||||
|
|
||||||
// Register callback functions for the selection of either A or B
|
// Register callback functions for the selection of either A or B
|
||||||
$('#choice-A').click(function(e) {
|
$('#choice-A').click(function(e) {
|
||||||
sendBallotsToServer(ballots[0], ballots[1]);
|
showSelectionConfirmationDialog("A", ballots[0], ballotHashes[0], ballots[1], selectedOption, dialog);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#choice-B').click(function(e) {
|
$('#choice-B').click(function(e) {
|
||||||
sendBallotsToServer(ballots[1], ballots[0]);
|
showSelectionConfirmationDialog("B", ballots[1], ballotHashes[1], ballots[0], selectedOption, dialog);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateDialogButtons(DIALOG_BTN_STATES.STEP_2);
|
||||||
|
|
||||||
|
if(!dialogOpen) {
|
||||||
|
modalDialog.modal('toggle');
|
||||||
|
dialogOpen = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendBallotsToServer(selection, alt) {
|
// Called in stage 3 of 3 in the voting process
|
||||||
|
function showSelectionConfirmationDialog(selection, selectedBallot, selectedBallotHash,
|
||||||
|
otherBallot, selectedOption, dialog) {
|
||||||
|
let title = dialog.find('.modal-title');
|
||||||
|
let body = dialog.find('.modal-body');
|
||||||
|
body.empty();
|
||||||
|
|
||||||
|
title.text("Step 3 of 3: Confirm Ballot Selection");
|
||||||
|
|
||||||
|
// Ballot detail section
|
||||||
|
let selectedInfoSecDiv = document.createElement('div');
|
||||||
|
|
||||||
|
let detailsP = document.createElement('p');
|
||||||
|
detailsP.innerHTML = "Please check the following details are correct: ";
|
||||||
|
selectedInfoSecDiv.append(detailsP);
|
||||||
|
|
||||||
|
let ul = document.createElement('ul');
|
||||||
|
|
||||||
|
let selectedOptionLi = document.createElement('li');
|
||||||
|
selectedOptionLi.innerHTML = "Selected Option: " + selectedOption;
|
||||||
|
|
||||||
|
let ballotSelectionLi = document.createElement('li');
|
||||||
|
ballotSelectionLi.innerHTML = "Selected Ballot: " + selection;
|
||||||
|
|
||||||
|
let ballotHashLi = document.createElement('li');
|
||||||
|
ballotHashLi.innerHTML = "SHA256 Ballot Fingerprint: " + selectedBallotHash;
|
||||||
|
|
||||||
|
ul.append(selectedOptionLi);
|
||||||
|
ul.append(ballotSelectionLi);
|
||||||
|
ul.append(ballotHashLi);
|
||||||
|
selectedInfoSecDiv.append(ul);
|
||||||
|
|
||||||
|
// Instruction section
|
||||||
|
let instructionsP = document.createElement('p');
|
||||||
|
instructionsP.innerHTML = "If you are happy with your selection you can click on the 'Submit' button below to store"
|
||||||
|
+ " your vote. Otherwise you can select 'Start Over' to go through the voting process again.";
|
||||||
|
selectedInfoSecDiv.append(instructionsP);
|
||||||
|
|
||||||
|
let additionalInstructionsP = document.createElement('p');
|
||||||
|
additionalInstructionsP.innerHTML = "You can overwrite your vote later by re-visiting this page and voting again.";
|
||||||
|
selectedInfoSecDiv.append(additionalInstructionsP);
|
||||||
|
|
||||||
|
body.append(selectedInfoSecDiv);
|
||||||
|
|
||||||
|
// Update the dialog buttons accordingly
|
||||||
|
updateDialogButtons(DIALOG_BTN_STATES.STEP_3);
|
||||||
|
|
||||||
|
$('#submitDialogBtn').click(function() {
|
||||||
|
// Dispatch the ballot to the server
|
||||||
|
sendBallotsToServer(selection, selectedBallot, otherBallot);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!dialogOpen) {
|
||||||
|
modalDialog.modal('toggle');
|
||||||
|
dialogOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendBallotsToServer(selection, selectedBallot, otherBallot) {
|
||||||
$.ajaxSetup({
|
$.ajaxSetup({
|
||||||
beforeSend: function(xhr, settings) {
|
beforeSend: function(xhr, settings) {
|
||||||
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
|
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
|
||||||
|
@ -416,14 +556,15 @@ function sendBallotsToServer(selection, alt) {
|
||||||
var pollNum = $('#poll-num').text();
|
var pollNum = $('#poll-num').text();
|
||||||
var ballotID = encodeURIComponent(btoa(JSON.stringify({voterID: voterID, eventID: eventID, pollNum: pollNum})));
|
var ballotID = encodeURIComponent(btoa(JSON.stringify({voterID: voterID, eventID: eventID, pollNum: pollNum})));
|
||||||
|
|
||||||
|
// TODO: Generate a SK rather than using a static one. UUID generated server side and then injected JS side?
|
||||||
var SK = "temporary";
|
var SK = "temporary";
|
||||||
var encAlt = sjcl.encrypt(SK, JSON.stringify(alt));
|
var encAlt = sjcl.encrypt(SK, JSON.stringify(otherBallot));
|
||||||
selection = JSON.stringify(selection);
|
let selectedBallotAsStr = JSON.stringify(selectedBallot);
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type : "POST",
|
type : "POST",
|
||||||
url : window.location,
|
url : window.location,
|
||||||
data : { handle: ballotID, encBallot: encAlt, ballot: selection },
|
data : { handle: ballotID, encBallot: encAlt, ballot: selectedBallotAsStr, selection: selection },
|
||||||
success : function(){
|
success : function(){
|
||||||
onAfterBallotSend(ballotID, SK);
|
onAfterBallotSend(ballotID, SK);
|
||||||
}
|
}
|
||||||
|
@ -432,40 +573,64 @@ function sendBallotsToServer(selection, alt) {
|
||||||
|
|
||||||
// Called once the ballot has been sent to the back-end and dialog has closed
|
// Called once the ballot has been sent to the back-end and dialog has closed
|
||||||
function onAfterBallotSend(ballotID, SK) {
|
function onAfterBallotSend(ballotID, SK) {
|
||||||
let titleText = 'Vote Successfully Received';
|
|
||||||
let bodyText = "Thank you for voting! Your secret key is '"+SK+"'. Make sure to scan this QR code with your phone before closing this window.";
|
|
||||||
|
|
||||||
if(POLL_NUM !== POLL_COUNT) {
|
|
||||||
bodyText += " You can vote on the next poll by closing down this dialog and clicking 'Next Poll'.";
|
|
||||||
}
|
|
||||||
|
|
||||||
// With one ballot selected, we can display a QR code of the ballot ID
|
// With one ballot selected, we can display a QR code of the ballot ID
|
||||||
var modalDialog = $('#modalDialog');
|
var modalDialog = $('#modalDialog');
|
||||||
var title = modalDialog.find('.modal-title');
|
var title = modalDialog.find('.modal-title');
|
||||||
var body = modalDialog.find('.modal-body');
|
var body = modalDialog.find('.modal-body');
|
||||||
title.text(titleText);
|
|
||||||
body.empty();
|
body.empty();
|
||||||
|
|
||||||
var p = document.createElement("p");
|
let titleText = 'Vote Successfully Received';
|
||||||
p.innerHTML = bodyText;
|
title.text(titleText);
|
||||||
body.append(p);
|
|
||||||
|
|
||||||
// Generate the body of the dialog which displays the unselected ballot QR code
|
// Add the first section: Instructions on next steps
|
||||||
|
let instructions1Txt = "Thank you for voting! Please note down the ballot identifier by scanning " +
|
||||||
|
"this QR code using the DEMOS2 mobile application: ";
|
||||||
|
|
||||||
|
var instructions1P = document.createElement("p");
|
||||||
|
instructions1P.innerHTML = instructions1Txt;
|
||||||
|
body.append(instructions1P);
|
||||||
|
|
||||||
|
// Add the second section: QR code that contains the ballot identifier
|
||||||
var QRCodeImg = document.createElement('img');
|
var QRCodeImg = document.createElement('img');
|
||||||
QRCodeImg.setAttribute('class', 'QR-code');
|
QRCodeImg.setAttribute('class', 'QR-code');
|
||||||
new QRCode(QRCodeImg, ballotID);
|
new QRCode(QRCodeImg, ballotID);
|
||||||
|
|
||||||
body.append(QRCodeImg);
|
body.append(QRCodeImg);
|
||||||
|
|
||||||
var closeButton = $('#close-button');
|
// Add the third section: instructions on Ballot ID and SK
|
||||||
closeButton.removeClass('btn-danger');
|
let instructions2Div = document.createElement('div');
|
||||||
closeButton.addClass('btn-success');
|
instructions2Div.setAttribute('class', 'containerMarginTop');
|
||||||
closeButton.text("Close");
|
|
||||||
if(POLL_NUM == POLL_COUNT) {
|
let instructions2Txt = "You will also be emailed the ballot identifier. However, you will need to note down the following " +
|
||||||
$('#next-button').hide();
|
"secret in order to later verify your ballot was recorded as cast: ";
|
||||||
|
let instructions2P = document.createElement('p');
|
||||||
|
instructions2P.innerHTML = instructions2Txt;
|
||||||
|
instructions2Div.append(instructions2P);
|
||||||
|
body.append(instructions2Div);
|
||||||
|
|
||||||
|
// Add the fourth section: SK plain text
|
||||||
|
let SKContainerDiv = document.createElement('div');
|
||||||
|
SKContainerDiv.setAttribute("class", "containerMarginTop");
|
||||||
|
|
||||||
|
let SKDiv = document.createElement('div');
|
||||||
|
SKDiv.setAttribute("class", "skDIV");
|
||||||
|
|
||||||
|
let SKP = document.createElement('p');
|
||||||
|
SKP.innerHTML = SK;
|
||||||
|
SKDiv.append(SKP);
|
||||||
|
|
||||||
|
SKContainerDiv.append(SKDiv);
|
||||||
|
body.append(SKContainerDiv);
|
||||||
|
|
||||||
|
// Conditional fifth section: Instructions on how to vote on the next poll for the event
|
||||||
|
if(POLL_NUM !== POLL_COUNT) {
|
||||||
|
let instructions3Txt = "You can vote on the next poll by closing down this dialog and clicking 'Next Poll'.";
|
||||||
|
let instructions3P = document.createElement('p');
|
||||||
|
instructions3P.innerHTML = instructions3Txt;
|
||||||
|
body.append(instructions3P);
|
||||||
}
|
}
|
||||||
|
|
||||||
modalDialog.modal('show');
|
updateDialogButtons(DIALOG_BTN_STATES.VOTE_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#modalDialog').on('hide.bs.modal', function (e) {
|
$('#modalDialog').on('hide.bs.modal', function (e) {
|
||||||
|
@ -474,7 +639,7 @@ $('#modalDialog').on('hide.bs.modal', function (e) {
|
||||||
if(titleText.indexOf("Received") > -1) {
|
if(titleText.indexOf("Received") > -1) {
|
||||||
// Update page to reflect the fact that a vote has taken place
|
// Update page to reflect the fact that a vote has taken place
|
||||||
location.reload();
|
location.reload();
|
||||||
} else {
|
} else if (titleText.indexOf("Error") === -1) {
|
||||||
// Reset poll voting to allow user to vote again
|
// Reset poll voting to allow user to vote again
|
||||||
progressBar.setAttribute("style", "width: 0%;");
|
progressBar.setAttribute("style", "width: 0%;");
|
||||||
$('#gen-ballots-btn').toggleClass("hidden");
|
$('#gen-ballots-btn').toggleClass("hidden");
|
||||||
|
|
Reference in a new issue