From 745fdf06b12df721ed0657b5646853dcffaf6f53 Mon Sep 17 00:00:00 2001 From: vince0656 Date: Fri, 13 Jul 2018 17:19:35 +0100 Subject: [PATCH] Updated the voting and event decryption pages. On the voting page, added the ability to vote by selecting checkboxes instead of a drop down select menu. This adds flexibility at event creation to specify a wide range of min and max option selections for polls. On the event decryption page, SK validation was added based on the stored trustee PK as well as a dialog to display any validation errors. Most of the updates took place behind the scenes at the backend. --- allauthdemo/polls/models.py | 9 +- allauthdemo/polls/tasks.py | 34 ++- allauthdemo/polls/views.py | 57 ++-- .../templates/bases/bootstrap-jquery.html | 108 +------ allauthdemo/templates/polls/create_event.html | 3 +- .../templates/polls/event_decrypt.html | 27 ++ allauthdemo/templates/polls/event_vote.html | 63 +++- static/css/main.css | 7 +- static/js/decrypt_event.js | 74 ++++- static/js/event_vote.js | 273 ++++++++++++++++++ 10 files changed, 503 insertions(+), 152 deletions(-) create mode 100644 static/js/event_vote.js diff --git a/allauthdemo/polls/models.py b/allauthdemo/polls/models.py index 0cc0d7f..581e88e 100755 --- a/allauthdemo/polls/models.py +++ b/allauthdemo/polls/models.py @@ -51,7 +51,7 @@ class Event(models.Model): try: EID_json = json.loads(self.EID) EID_crypto_str = EID_json['crypto'] - return json.loads(EID_crypto_str) + return json.dumps(json.loads(EID_crypto_str)) except ValueError: return "None - Event not Initialised" @@ -213,8 +213,13 @@ class EncryptedVote(models.Model): ballot = models.ForeignKey(Ballot, on_delete=models.CASCADE, related_name="encrypted_vote") +class CombinedEncryptedVote(models.Model): + ballot = models.ForeignKey(Ballot, on_delete=models.CASCADE, related_name="comb_encrypted_vote") + + class VoteFragment(models.Model): - encrypted_vote = models.ForeignKey(EncryptedVote, on_delete=models.CASCADE, related_name="fragment") + encrypted_vote = models.ForeignKey(EncryptedVote, on_delete=models.CASCADE, related_name="fragment", null=True) + comb_encrypted_vote = models.ForeignKey(CombinedEncryptedVote, on_delete=models.CASCADE, related_name="fragment", null=True) cipher_text_c1 = models.CharField(max_length=4096) cipher_text_c2 = models.CharField(max_length=4096) diff --git a/allauthdemo/polls/tasks.py b/allauthdemo/polls/tasks.py index db65344..ebe53ad 100755 --- a/allauthdemo/polls/tasks.py +++ b/allauthdemo/polls/tasks.py @@ -7,7 +7,7 @@ from celery import task from django.conf import settings -from allauthdemo.polls.models import AccessKey, Ballot, CombinedBallot, PartialBallotDecryption +from allauthdemo.polls.models import AccessKey, Ballot, CombinedBallot, PartialBallotDecryption, EncryptedVote, CombinedEncryptedVote, VoteFragment from .crypto_rpc import param, combpk, add_ciphers, get_tally @@ -58,6 +58,7 @@ def email_trustees_dec(event): trustee.send_email(email_subject, email_body) + def get_email_sign_off(): sign_off = str("") sign_off += "\n\nPlease note: This email address is not monitored so please don't reply to this email.\n\n" @@ -66,6 +67,7 @@ def get_email_sign_off(): return sign_off + ''' Combines all of the voter ballots for a poll option into a single 'CombinedBallot' ''' @@ -83,7 +85,7 @@ def combine_ballots(polls): frags_c2 = list() for ballot in ballots: - enc_vote = ballot.encrypted_vote.get() + enc_vote = ballot.comb_encrypted_vote.get() if enc_vote is not None: fragments = enc_vote.fragment.all() @@ -102,6 +104,34 @@ def combine_ballots(polls): cipher_text_c1=combined_cipher['C1'], cipher_text_c2=combined_cipher['C2']) +@task() +def combine_encrypted_votes(voter, poll): + poll_options_count = poll.options.all().count() + ballot = Ballot.objects.get_or_create(voter=voter, poll=poll)[0] + e_votes = EncryptedVote.objects.filter(ballot=ballot) + + CombinedEncryptedVote.objects.filter(ballot=ballot).delete() + comb_e_vote = CombinedEncryptedVote.objects.create(ballot=ballot) + + for i in range(poll_options_count): + frags_c1 = list() + frags_c2 = list() + + for e_vote in e_votes: + fragments = e_vote.fragment.all() + frags_c1.append(fragments[i].cipher_text_c1) + frags_c2.append(fragments[i].cipher_text_c2) + + ciphers = { + 'c1s': frags_c1, + 'c2s': frags_c2 + } + + combined_cipher = add_ciphers(ciphers) + VoteFragment.objects.create(comb_encrypted_vote=comb_e_vote, + cipher_text_c1=combined_cipher['C1'], + cipher_text_c2=combined_cipher['C2']) + @task() def create_ballots(event): voters = event.voters.all() diff --git a/allauthdemo/polls/views.py b/allauthdemo/polls/views.py index a528616..a6c8235 100755 --- a/allauthdemo/polls/views.py +++ b/allauthdemo/polls/views.py @@ -11,11 +11,11 @@ from django.views import generic from django.conf import settings from .forms import PollForm, OptionFormset, VoteForm, EventSetupForm, EventEditForm -from .models import Event, Poll, Ballot, EncryptedVote, TrusteeKey, PartialBallotDecryption, CombinedBallot +from .models import Event, Poll, Ballot, EncryptedVote, TrusteeKey, PartialBallotDecryption, CombinedBallot, VoteFragment from allauthdemo.auth.models import DemoUser 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 +from .tasks import create_ballots_for_poll, email_voters_vote_url, combine_decryptions_and_tally, combine_encrypted_votes from .utils.EventModelAdaptor import EventModelAdaptor @@ -153,39 +153,35 @@ 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." if request.method == "POST": - ballot = Ballot.objects.get_or_create(voter=email_key[0].user, poll=poll)[0] + data = json.loads(request.POST.lists()[0][0]) + ballot_json = data['ballot'] + encrypted_votes_json = ballot_json['encryptedVotes'] - # Will store the fragments of the encoding scheme that define the vote - encrypted_vote = EncryptedVote.objects.get_or_create(ballot=ballot)[0] + # Before storing the encrypted votes, we need the voter's ballot + ballot, created = Ballot.objects.get_or_create(voter=email_key[0].user, poll=poll) + EncryptedVote.objects.filter(ballot=ballot).delete() - # Clear any existing fragments - a voter changing their vote - encrypted_vote.fragment.all().delete() + for e_vote in encrypted_votes_json: + # Will store the fragments of the encoding scheme that define the vote + encrypted_vote = EncryptedVote.objects.create(ballot=ballot) + fragments_json = e_vote['fragments'] - # Add in the new ciphers - fragment_count = int(request.POST['vote_frag_count']) - for i in range(fragment_count): - i_str = str(i) - - cipher_c1 = request.POST['cipher_c1_frag_' + i_str] - cipher_c2 = request.POST['cipher_c2_frag_' + i_str] - - encrypted_vote.fragment.create(encrypted_vote=encrypted_vote, - cipher_text_c1=cipher_c1, - cipher_text_c2=cipher_c2) + for fragment in fragments_json: + VoteFragment.objects.create(encrypted_vote=encrypted_vote, + cipher_text_c1=fragment['C1'], + cipher_text_c2=fragment['C2']) ballot.cast = True ballot.save() + combine_encrypted_votes.delay(email_key[0].user, poll) + if next_poll_uuid: return HttpResponseRedirect(reverse('polls:event-vote', kwargs={'event_id': event.uuid, 'poll_id': next_poll_uuid}) + "?key=" + email_key_str) - else: - # The user has finished voting in the event - success_msg = 'You have successfully cast your vote(s)!' - messages.add_message(request, messages.SUCCESS, success_msg) - return HttpResponseRedirect(reverse("user_home")) + return HttpResponse('Voted Successfully!') return render(request, "polls/event_vote.html", { @@ -273,16 +269,20 @@ def event_trustee_decrypt(request, event_id): if access_key: email_key = event.keys.filter(key=access_key) + trustee = email_key[0].user - if email_key.exists() and event.users_trustees.filter(email=email_key[0].user.email).exists(): + if email_key.exists() and event.users_trustees.filter(email=trustee.email).exists(): - if PartialBallotDecryption.objects.filter(event=event, user=email_key[0].user).count() == event.total_num_opts(): + if PartialBallotDecryption.objects.filter(event=event, user=trustee).count() == event.total_num_opts(): warning_msg = 'You have already provided your decryption key for this event - Thank You' messages.add_message(request, messages.WARNING, warning_msg) return HttpResponseRedirect(reverse("user_home")) elif request.method == "GET": + # Get the Trustee's original PK - used in the template for SK validation + trustee_pk = TrusteeKey.objects.get(event=event, user=trustee).key + # Gen a list of ciphers from the combined ballots for every opt of every poll polls = event.polls.all() poll_ciphers = [] @@ -305,7 +305,8 @@ def event_trustee_decrypt(request, event_id): "polls/event_decrypt.html", { "event": event, - "user_email": email_key[0].user.email, + "user_email": trustee.email, + "trustee_pk": trustee_pk, "poll_ciphers": poll_ciphers }) @@ -318,7 +319,7 @@ def event_trustee_decrypt(request, event_id): options_count = len(options) for j in range(options_count): - input_name = "" + input_name = str("") input_name = "poll-" + str(i) + "-cipher-" + str(j) part_dec = request.POST[input_name] @@ -326,7 +327,7 @@ def event_trustee_decrypt(request, event_id): PartialBallotDecryption.objects.create(event=event, poll=polls[i], option=options[j], - user=email_key[0].user, + user=trustee, text=part_dec) if event.all_part_decs_received(): diff --git a/allauthdemo/templates/bases/bootstrap-jquery.html b/allauthdemo/templates/bases/bootstrap-jquery.html index d49a34e..e19f8b0 100755 --- a/allauthdemo/templates/bases/bootstrap-jquery.html +++ b/allauthdemo/templates/bases/bootstrap-jquery.html @@ -19,6 +19,7 @@ + @@ -64,113 +65,6 @@ } - /* - - Code written with "New function" comments have - been totally or mostly re-implemented by Thomas Smith - - - */ - - dropDownFragsNotZero = function(frags) { - var valid = false; - - for(var i = 0; i < frags.length; i++) { - var frag = frags[i]; - - if(frag !== "0") { - valid = true; - break; - } - } - - return valid; - }; - - //new function - demosEncrypt.encryptAndSubmit = function() { - // Drop down option selection validation - if(min_selections === 1 && max_selections === 1) { - var fragments = $('#poll-options').val().split(","); - - if(!dropDownFragsNotZero(fragments)) { - alert("You have to select an option in order to vote."); - return; - } - } // TODO: Checkbox validation goes here - - // Disable the enc and submit button to prevent fn from being called twice - $('#keygen-btn').prop("disabled", true); - - // Elliptic curve cryptography params used for encryption of encrypted vote - // fragments - var ctx = new CTX("BN254CX"); - var n = new ctx.BIG(); - var g1 = new ctx.ECP(); - var g2 = new ctx.ECP2(); - - var parameter = $('#event-param').val(); - var tempParams = JSON.parse(JSON.parse(parameter).crypto); - - //copying the values - n.copy(tempParams.n); - g1.copy(tempParams.g1); - g2.copy(tempParams.g2); - - var params = { - n:n, - g1:g1, - g2:g2 - }; - - var tempPK = JSON.parse($('#comb_pk').val()); - var pk = new ctx.ECP(0); - pk.copy(tempPK.PK); - - // Obtain the user's selection (their vote) and encrypt the fragments of the binary encoding - const selection = $('#poll-options').val(); - const selectionFragments = selection.split(','); - - var cipherForm = document.getElementById("cipher-form"); - - for(var i = 0; i < selectionFragments.length; i++) { - // Encrypt this fragment for the selection - var cipher = encrypt(params, pk, parseInt(selectionFragments[i])); - - // Store C1 and C2 from the cipher in 2 arrays - var c1Bytes = []; - cipher.C1.toBytes(c1Bytes); - - var c2Bytes = []; - cipher.C2.toBytes(c2Bytes); - - // Inject hidden input controls into the form that represents a single ballot - var c1Input = document.createElement("input"); - c1Input.setAttribute("type", "hidden"); - c1Input.setAttribute("name", "cipher_c1_frag_" + i); - c1Input.setAttribute("value", c1Bytes.toString()); - - var c2Input = document.createElement("input"); - c2Input.setAttribute("type", "hidden"); - c2Input.setAttribute("name", "cipher_c2_frag_" + i); - c2Input.setAttribute("value", c2Bytes.toString()); - - cipherForm.appendChild(c1Input); - cipherForm.appendChild(c2Input); - } - - // Inject a final input control into the form which specifies the number of fragments - // That make up an encrypted vote - var fragCountInput = document.createElement("input"); - fragCountInput.setAttribute("type", "hidden"); - fragCountInput.setAttribute("name", "vote_frag_count"); - fragCountInput.setAttribute("value", "" + selectionFragments.length); - cipherForm.appendChild(fragCountInput); - - // Submit the encrypted vote to the server - $('#cipher-form').submit(); - }; - function getBytes(arr) { for(var i = 0; i < arr.length; i++) { arr[i] = parseInt(arr[i]); diff --git a/allauthdemo/templates/polls/create_event.html b/allauthdemo/templates/polls/create_event.html index 3728649..b20b051 100755 --- a/allauthdemo/templates/polls/create_event.html +++ b/allauthdemo/templates/polls/create_event.html @@ -12,7 +12,7 @@ {% if not forloop.first %},{% endif %} { title: "{{ event.title }}", - slug: "{{ event.EID }}" + slug: "{{ event.EID_hr }}" } {% endfor %} ]; @@ -34,6 +34,7 @@
+ diff --git a/allauthdemo/templates/polls/event_decrypt.html b/allauthdemo/templates/polls/event_decrypt.html index 8ca2808..ca38235 100755 --- a/allauthdemo/templates/polls/event_decrypt.html +++ b/allauthdemo/templates/polls/event_decrypt.html @@ -4,6 +4,13 @@ {% block sk-file-name %}{{ event.title|safe }}{% endblock %} {% block content %} +

Trustee Event Decryption for Event '{{ event.title }}'

@@ -49,4 +56,24 @@
+ + + {% endblock %} diff --git a/allauthdemo/templates/polls/event_vote.html b/allauthdemo/templates/polls/event_vote.html index 4325859..315bc85 100755 --- a/allauthdemo/templates/polls/event_vote.html +++ b/allauthdemo/templates/polls/event_vote.html @@ -3,9 +3,10 @@ {% load bootstrap3 %} {% block app_js_vars %} - var option_count = {{ object.options.count }}; - var min_selections = {{ min_selection }}; - var max_selections = {{ max_selection }}; + const OPTION_COUNT = {{ object.options.count }}; + const MIN_SELECTIONS = {{ min_selection }}; + const MAX_SELECTIONS = {{ max_selection }}; + var selectedCount = 0; {% endblock %} {% block content %} @@ -13,6 +14,7 @@
{% if can_vote %} + {% csrf_token %} @@ -37,7 +39,15 @@ You will be shown each poll for this event one by one where you will need to make a selection for the current poll before moving onto the next poll. For this specific poll you need to make a minimum of {{ min_selection }} option selection(s) and a maximum of - {{ max_selection }}.

Please make your choice below. + {{ max_selection }}. + + {% if min_selection == 0 %} +

Due to the fact that you are permitted to select nothing, simply hitting submit will submit a + 'blank' vote so please be aware of this. You can always re-visit this page before the event ends if you + change your mind. + {% endif %} + +

Please make your choice below. @@ -48,18 +58,30 @@
Options
- {% load custom_filters_tags %} {% for option in object.options.all %} {% endfor %} - + {% endcomment %} + {% for option in object.options.all %} +
+ {% load custom_filters_tags %} + +
+ {% endfor %}
- -
- {% csrf_token %} -
+
+ + + + +
@@ -78,6 +100,27 @@ {% endif %}
+ + + + {% else %}