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:
vince0656 2018-09-03 15:39:42 +01:00
parent 59d3b26e95
commit 25a7f72160
7 changed files with 138 additions and 57 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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"))

View file

@ -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

View file

@ -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">&times;</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>

View file

@ -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/>

View file

@ -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);
} }