Updated various parts of the application to improve usability. Started laying some of the ground work for a bulletin board. Added informative emails that keep voters, trustees and organisers updated with the state of an event.
This commit is contained in:
parent
59d3b26e95
commit
25a7f72160
7 changed files with 138 additions and 57 deletions
|
@ -206,6 +206,7 @@ class Ballot(models.Model):
|
||||||
voter = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="ballots")
|
voter = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="ballots")
|
||||||
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="ballots")
|
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="ballots")
|
||||||
selection = models.CharField(max_length=1)
|
selection = models.CharField(max_length=1)
|
||||||
|
json_str = models.CharField(max_length=10240)
|
||||||
cast = models.BooleanField(default=False)
|
cast = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,26 @@ def get_email_sign_off():
|
||||||
return sign_off
|
return sign_off
|
||||||
|
|
||||||
|
|
||||||
|
def email_e_results_ready(event):
|
||||||
|
event_title = event.title
|
||||||
|
email_subject = "Event Results for '" + event_title + "' Have Been Decrypted"
|
||||||
|
|
||||||
|
# Construct the email body. This can be later replaced by a HTML email.
|
||||||
|
email_body = str("")
|
||||||
|
email_body += "Dear Event Organiser,\n\n"
|
||||||
|
email_body += "This email is to inform you that all of the partial decryptions for the event '" + event_title + \
|
||||||
|
"' have been supplied. These have been used to decrypt the results which are now available.\n\n"
|
||||||
|
email_body += "As an organiser, the event results can be found at the following URL:\n\n"
|
||||||
|
email_body += "http://" + settings.DOMAIN + "/event/" + str(event.pk) + "/results/"
|
||||||
|
email_body += get_email_sign_off()
|
||||||
|
|
||||||
|
# Get all of the organisers for the event and send them an email
|
||||||
|
organisers = event.users_organisers.all()
|
||||||
|
|
||||||
|
for organiser in organisers:
|
||||||
|
organiser.email_user(email_subject, email_body)
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Combines all of the voter ballots for a poll option into a single 'CombinedBallot'
|
Combines all of the voter ballots for a poll option into a single 'CombinedBallot'
|
||||||
'''
|
'''
|
||||||
|
@ -99,6 +119,11 @@ def combine_ballots(polls):
|
||||||
|
|
||||||
combined_cipher = add_ciphers(ciphers)
|
combined_cipher = add_ciphers(ciphers)
|
||||||
|
|
||||||
|
# If a combined ballot already exists, clear it
|
||||||
|
if CombinedBallot.objects.filter(poll=poll, option=option).exists():
|
||||||
|
CombinedBallot.objects.filter(poll=poll, option=option).delete()
|
||||||
|
|
||||||
|
# Create a combined ballot for this option in the poll
|
||||||
CombinedBallot.objects.create(poll=poll,
|
CombinedBallot.objects.create(poll=poll,
|
||||||
option=option,
|
option=option,
|
||||||
cipher_text_c1=combined_cipher['C1'],
|
cipher_text_c1=combined_cipher['C1'],
|
||||||
|
@ -179,6 +204,30 @@ def email_trustees_prep(trustees, event):
|
||||||
trustee.send_email(email_subject, email_body)
|
trustee.send_email(email_subject, email_body)
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
Task triggered when all trustees have supplied their partial public keys and the event has been prepared
|
||||||
|
'''
|
||||||
|
@task()
|
||||||
|
def email_organisers_next_steps(event):
|
||||||
|
event_title = event.title
|
||||||
|
email_subject = "Event '" + event_title + "' Successfully Prepared by All Trustees"
|
||||||
|
|
||||||
|
email_body = str("")
|
||||||
|
email_body += "Dear Event Organiser,\n\n"
|
||||||
|
email_body += "This email is to inform you that all trustees have supplied their public keys for the event '" \
|
||||||
|
+ event_title + "'. The event is therefore prepared and ready to accept votes when voting opens.\n\n"
|
||||||
|
email_body += "Once voting has ended, you need to visit the following URL to begin the decryption process:\n\n"
|
||||||
|
email_body += "http://" + settings.DOMAIN + "/event/" + str(event.pk) + "/\n\n"
|
||||||
|
email_body += "Once you've accessed the page, simply hit the 'End' button to email all trustees to ask for their " \
|
||||||
|
"partial decryptions for all polls for this event."
|
||||||
|
email_body += get_email_sign_off()
|
||||||
|
|
||||||
|
# Get the list of organisers that need emailing and email them
|
||||||
|
organisers = event.users_organisers.all()
|
||||||
|
|
||||||
|
for organiser in organisers:
|
||||||
|
organiser.email_user(email_subject, email_body)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Emails a URL containing an access key for all of the voters for an event
|
Emails a URL containing an access key for all of the voters for an event
|
||||||
'''
|
'''
|
||||||
|
@ -317,3 +366,6 @@ def combine_decryptions_and_tally(event):
|
||||||
poll.result_json = result
|
poll.result_json = result
|
||||||
poll.save()
|
poll.save()
|
||||||
|
|
||||||
|
# Email the list of organisers to inform them that the results for this event are ready
|
||||||
|
email_e_results_ready(event)
|
||||||
|
|
||||||
|
|
|
@ -18,7 +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 .tasks import email_voting_success, email_organisers_next_steps
|
||||||
|
|
||||||
from .utils.EventModelAdaptor import EventModelAdaptor
|
from .utils.EventModelAdaptor import EventModelAdaptor
|
||||||
|
|
||||||
|
@ -187,7 +187,8 @@ def event_vote(request, event_id, poll_id):
|
||||||
cant_vote_reason = "The event either isn't ready for voting or it has expired and therefore you cannot vote."
|
cant_vote_reason = "The event either isn't ready for voting or it has expired and therefore you cannot vote."
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
ballot_json = json.loads(request.POST.get('ballot'))
|
ballot_str = request.POST.get('ballot')
|
||||||
|
ballot_json = json.loads(ballot_str)
|
||||||
selection = request.POST.get('selection')
|
selection = request.POST.get('selection')
|
||||||
encrypted_votes_json = ballot_json['encryptedVotes']
|
encrypted_votes_json = ballot_json['encryptedVotes']
|
||||||
|
|
||||||
|
@ -219,6 +220,7 @@ def event_vote(request, event_id, poll_id):
|
||||||
|
|
||||||
ballot.cast = True
|
ballot.cast = True
|
||||||
ballot.selection = selection
|
ballot.selection = selection
|
||||||
|
ballot.json_str = ballot_str
|
||||||
ballot.save()
|
ballot.save()
|
||||||
|
|
||||||
voter = email_key[0].user
|
voter = email_key[0].user
|
||||||
|
@ -270,6 +272,7 @@ def event_trustee_setup(request, event_id):
|
||||||
create_ballots.delay(event)
|
create_ballots.delay(event)
|
||||||
generate_combpk.delay(event)
|
generate_combpk.delay(event)
|
||||||
email_voters_vote_url.delay(event.voters.all(), event)
|
email_voters_vote_url.delay(event.voters.all(), event)
|
||||||
|
email_organisers_next_steps.delay(event)
|
||||||
|
|
||||||
success_msg = 'You have successfully submitted your public key for this event!'
|
success_msg = 'You have successfully submitted your public key for this event!'
|
||||||
messages.add_message(request, messages.SUCCESS, success_msg)
|
messages.add_message(request, messages.SUCCESS, success_msg)
|
||||||
|
@ -365,8 +368,14 @@ def event_trustee_decrypt(request, event_id):
|
||||||
text=part_dec)
|
text=part_dec)
|
||||||
|
|
||||||
if event.all_part_decs_received():
|
if event.all_part_decs_received():
|
||||||
# TODO: Combine partial decryptions and gen results
|
# Decrypt the result once all partial decryptions have been received
|
||||||
|
# This will email all organisers once the results are ready
|
||||||
combine_decryptions_and_tally.delay(event)
|
combine_decryptions_and_tally.delay(event)
|
||||||
|
else:
|
||||||
|
# TODO: Get how many trustees have submitted a partial decryption
|
||||||
|
# TODO: Then get how many are left to submit their partial decryptions
|
||||||
|
# TODO: Then email the list of organisers to update them with this information
|
||||||
|
str("")
|
||||||
|
|
||||||
messages.add_message(request, messages.SUCCESS, 'Your partial decryptions have been successfully submitted')
|
messages.add_message(request, messages.SUCCESS, 'Your partial decryptions have been successfully submitted')
|
||||||
return HttpResponseRedirect(reverse("user_home"))
|
return HttpResponseRedirect(reverse("user_home"))
|
||||||
|
|
|
@ -76,39 +76,7 @@
|
||||||
|
|
||||||
//new function
|
//new function
|
||||||
demosEncrypt.decryptSubmitCiphers = function() {
|
demosEncrypt.decryptSubmitCiphers = function() {
|
||||||
var skString = $('#secret-key').val();
|
|
||||||
if (!skString) {
|
|
||||||
alert("Please enter your secret key");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//rebuild our secret key
|
|
||||||
var ctx = new CTX("BN254CX");
|
|
||||||
var skBytes = skString.split(",");
|
|
||||||
var sk = new ctx.BIG.fromBytes(skBytes);
|
|
||||||
|
|
||||||
var inputs = $("form input[type=text]");
|
|
||||||
|
|
||||||
inputs.each(function() { //for each ciphertext to decrypt
|
|
||||||
var ciphertext = {
|
|
||||||
C1: null,
|
|
||||||
C2: null
|
|
||||||
};
|
|
||||||
|
|
||||||
var temp = JSON.parse($(this).val());
|
|
||||||
var c1Bytes = getBytes(temp.C1.split(','));
|
|
||||||
ciphertext.C1 = new ctx.ECP.fromBytes(c1Bytes);
|
|
||||||
|
|
||||||
var c2Bytes = getBytes(temp.C2.split(','));
|
|
||||||
ciphertext.C2 = new ctx.ECP.fromBytes(c2Bytes);
|
|
||||||
|
|
||||||
// Perform partial decryption where the method returns an object containing an ECP()
|
|
||||||
var partial = partDec(sk, ciphertext);
|
|
||||||
|
|
||||||
var bytes = [];
|
|
||||||
partial.D.toBytes(bytes);
|
|
||||||
$(this).val(bytes.toString());//submit in byte array form
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//new function
|
//new function
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<h2>Trustee Event Decryption for Event '{{ event.title }}'</h2>
|
<h2>Trustee Event Decryption for Event '{{ event.title }}'</h2>
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><strong>Submit your Secret Key as '{{ user_email }}'</strong></div>
|
<div class="panel-heading"><strong>Upload your Secret Key as '{{ user_email }}'</strong></div>
|
||||||
<div class="panel panel-body">
|
<div class="panel panel-body">
|
||||||
<input id="secret-key" name="secret-key" class="textinput textInput form-control" type="text" disabled/>
|
<input id="secret-key" name="secret-key" class="textinput textInput form-control" type="text" disabled/>
|
||||||
<div class="alert alert-info" role="alert" style="margin-top: 0.75em;">
|
<div class="alert alert-info" role="alert" style="margin-top: 0.75em;">
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
<input type="file" id="files_sk_upload" name="file" class="btn-info">
|
<input type="file" id="files_sk_upload" name="file" class="btn-info">
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<div class="panel-heading"><strong>Ciphers</strong></div>
|
<div class="panel-heading"><strong>Encrypted Event Data</strong></div>
|
||||||
<div class="panel panel-body">
|
<div class="panel panel-body">
|
||||||
<form id="cipher-form" method="POST">
|
<form id="cipher-form" method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
@ -48,25 +48,26 @@
|
||||||
<br/>
|
<br/>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<button id="decrypt-btn"
|
<button id="decrypt-btn"
|
||||||
onclick="demosEncrypt.decryptSubmitCiphers()"
|
|
||||||
|
onclick="decryptSubmitCiphers()"
|
||||||
class="btn btn-success">
|
class="btn btn-success">
|
||||||
Decrypt & Submit</button>
|
Send Partial Decryptions</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Information Dialog called upon request -->
|
<!-- Information Dialog called upon request -->
|
||||||
<div class="modal fade" id="modalDialog" role="dialog">
|
<div class="modal fade" id="modalDialog" role="dialog" tabindex="-1" data-backdrop="static">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog" role="document">
|
||||||
|
|
||||||
<!-- Dialog content-->
|
<!-- Dialog content-->
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
<h4 class="modal-title" style="text-align: center"><strong>Partial Decryption Successfully Received</strong></h4>
|
||||||
<h4 class="modal-title" style="text-align: center"><strong>Thank You</strong></h4>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
<p>Thank you! You can now close down this page.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<h4>Key Generation For: {{ user_email }}</h4>
|
<h4>Key Generation For: {{ user_email }}</h4>
|
||||||
<br/>
|
<br/>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><strong>Step 1: Generate Your Secret Key</strong></div>
|
<div class="panel-heading"><strong>Step 1: Generate and Download Your Secret Key</strong></div>
|
||||||
<div class="panel panel-body">
|
<div class="panel panel-body">
|
||||||
<input id="secret-key" class="textinput textInput form-control" type="text"/>
|
<input id="secret-key" class="textinput textInput form-control" type="text"/>
|
||||||
<input id="event-param" type="text" value="{{event.EID}}" hidden/>
|
<input id="event-param" type="text" value="{{event.EID}}" hidden/>
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// -------------- Global vars --------------------
|
||||||
|
var filesHandleSK = document.getElementById('files_sk_upload');
|
||||||
|
var CSRF = $( "input[name='csrfmiddlewaretoken']" ).val();
|
||||||
|
|
||||||
|
// -------------- Helper fns --------------------
|
||||||
//SK checking algorithm - If PK and SK matches, it returns True; otherwise, it returns false.
|
//SK checking algorithm - If PK and SK matches, it returns True; otherwise, it returns false.
|
||||||
// Written by Bingsheng Zhang
|
// Written by Bingsheng Zhang
|
||||||
function skCheck(ctx, params, SK, PK) {
|
function skCheck(ctx, params, SK, PK) {
|
||||||
|
@ -5,6 +10,29 @@ function skCheck(ctx, params, SK, PK) {
|
||||||
return D.equals(PK)
|
return D.equals(PK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function csrfSafeMethod(method) {
|
||||||
|
// these HTTP methods do not require CSRF protection
|
||||||
|
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDialog(titleTxt, bodyTxt) {
|
||||||
|
var modalDialog = $('#modalDialog');
|
||||||
|
var title = modalDialog.find('.modal-title');
|
||||||
|
var body = modalDialog.find('.modal-body');
|
||||||
|
|
||||||
|
title.text(titleTxt);
|
||||||
|
var bodyText = bodyTxt;
|
||||||
|
|
||||||
|
var p = document.createElement("p");
|
||||||
|
p.innerHTML = bodyText;
|
||||||
|
body.empty();
|
||||||
|
body.append( p );
|
||||||
|
|
||||||
|
modalDialog.modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------
|
||||||
|
|
||||||
function validateSKFromString(SKStr) {
|
function validateSKFromString(SKStr) {
|
||||||
// Re-create the SK from the string byte definition
|
// Re-create the SK from the string byte definition
|
||||||
let ctx = new CTX("BN254CX");
|
let ctx = new CTX("BN254CX");
|
||||||
|
@ -38,20 +66,44 @@ function validateSKFromString(SKStr) {
|
||||||
return skCheck(ctx, params, sk, pk);
|
return skCheck(ctx, params, sk, pk);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showDialog(titleTxt, bodyTxt) {
|
function decryptSubmitCiphers() {
|
||||||
var modalDialog = $('#modalDialog');
|
var skString = $('#secret-key').val();
|
||||||
var title = modalDialog.find('.modal-title');
|
|
||||||
var body = modalDialog.find('.modal-body');
|
|
||||||
|
|
||||||
title.text(titleTxt);
|
if (!skString) {
|
||||||
var bodyText = bodyTxt;
|
showDialog('Error', 'You haven\'t supplied your secret key. Please go back and upload this from file.');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Rebuild the trustee's secret key
|
||||||
|
var ctx = new CTX("BN254CX");
|
||||||
|
var skBytes = skString.split(",");
|
||||||
|
var sk = new ctx.BIG.fromBytes(skBytes);
|
||||||
|
|
||||||
var p = document.createElement("p");
|
var inputs = $("form input[type=text]");
|
||||||
p.innerHTML = bodyText;
|
|
||||||
body.empty();
|
|
||||||
body.append( p );
|
|
||||||
|
|
||||||
modalDialog.modal('show');
|
inputs.each(function() { //for each ciphertext to decrypt
|
||||||
|
let input = $(this);
|
||||||
|
console.log(input.attr('name'));
|
||||||
|
|
||||||
|
var ciphertext = {
|
||||||
|
C1: null,
|
||||||
|
C2: null
|
||||||
|
};
|
||||||
|
|
||||||
|
var temp = JSON.parse(input.val());
|
||||||
|
var c1Bytes = getBytes(temp.C1.split(','));
|
||||||
|
ciphertext.C1 = new ctx.ECP.fromBytes(c1Bytes);
|
||||||
|
|
||||||
|
var c2Bytes = getBytes(temp.C2.split(','));
|
||||||
|
ciphertext.C2 = new ctx.ECP.fromBytes(c2Bytes);
|
||||||
|
|
||||||
|
// Perform partial decryption where the method returns an object containing an ECP()
|
||||||
|
var partial = partDec(sk, ciphertext);
|
||||||
|
|
||||||
|
var bytes = [];
|
||||||
|
partial.D.toBytes(bytes);
|
||||||
|
input.val(bytes.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processFileSKChange(event) {
|
function processFileSKChange(event) {
|
||||||
|
@ -85,8 +137,6 @@ function processFileSKChange(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var filesHandleSK = document.getElementById('files_sk_upload');
|
|
||||||
|
|
||||||
if(filesHandleSK) {
|
if(filesHandleSK) {
|
||||||
filesHandleSK.addEventListener('change', processFileSKChange, false);
|
filesHandleSK.addEventListener('change', processFileSKChange, false);
|
||||||
}
|
}
|
Reference in a new issue