This commit is contained in:
Rumperuu 2018-09-19 08:38:50 +01:00
parent dff885437e
commit 08b5985396
9 changed files with 704 additions and 562 deletions

View File

@ -11,223 +11,223 @@ from allauthdemo.auth.models import DemoUser
class EmailUser(models.Model): class EmailUser(models.Model):
email = models.CharField(max_length=80, unique=True) email = models.CharField(max_length=80, unique=True)
def send_email(self, subject, message, from_email=None): def send_email(self, subject, message, from_email=None):
""" """
Sends an email to this User. Sends an email to this User.
""" """
send_mail(subject, message, from_email, [self.email]) send_mail(subject, message, from_email, [self.email])
def __unicode__(self): def __unicode__(self):
return self.email return self.email
class Event(models.Model): class Event(models.Model):
users_organisers = models.ManyToManyField(DemoUser, blank=True, related_name="organisers") users_organisers = models.ManyToManyField(DemoUser, blank=True, related_name="organisers")
users_trustees = models.ManyToManyField(EmailUser, blank=True, related_name="trustees") users_trustees = models.ManyToManyField(EmailUser, blank=True, related_name="trustees")
voters = models.ManyToManyField(EmailUser, blank=True, related_name="voters") voters = models.ManyToManyField(EmailUser, blank=True, related_name="voters")
start_time = models.DateTimeField() start_time = models.DateTimeField()
end_time = models.DateTimeField() end_time = models.DateTimeField()
prepared = models.BooleanField(default=False) prepared = models.BooleanField(default=False)
ended = models.BooleanField(default=False) ended = models.BooleanField(default=False)
public_key = models.CharField(null=True, blank=False, max_length=1024) public_key = models.CharField(null=True, blank=False, max_length=1024)
title = models.CharField(max_length=1024) title = models.CharField(max_length=1024)
EID = models.CharField(max_length=2048, blank=True) EID = models.CharField(max_length=2048, blank=True)
creator = models.CharField(max_length=256, blank=True) creator = models.CharField(max_length=256, blank=True)
c_email = models.CharField(max_length=512, blank=True) c_email = models.CharField(max_length=512, blank=True)
trustees = models.CharField(max_length=4096) trustees = models.CharField(max_length=4096)
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# Custom helper methods # Custom helper methods
def EID_hr(self): def EID_hr(self):
try: try:
EID_json = json.loads(self.EID) EID_json = json.loads(self.EID)
return EID_json['hr'] return EID_json['hr']
except ValueError: except ValueError:
return self.EID return self.EID
def EID_crypto(self): def EID_crypto(self):
try: try:
EID_json = json.loads(self.EID) EID_json = json.loads(self.EID)
EID_crypto_str = EID_json['crypto'] EID_crypto_str = EID_json['crypto']
return json.dumps(json.loads(EID_crypto_str)) return json.dumps(json.loads(EID_crypto_str))
except ValueError: except ValueError:
return "None - Event not Initialised" return "None - Event not Initialised"
def duration(self): def duration(self):
duration_str = self.start_time_formatted() duration_str = self.start_time_formatted()
duration_str = duration_str + " - " + self.end_time_formatted_utc() duration_str = duration_str + " - " + self.end_time_formatted_utc()
return duration_str return duration_str
def start_time_formatted(self): def start_time_formatted(self):
return self.start_time.strftime("%d-%m-%y %H:%M") return self.start_time.strftime("%d-%m-%y %H:%M")
def start_time_formatted_utc(self): def start_time_formatted_utc(self):
return self.start_time.strftime("%d-%m-%y %H:%M %Z") return self.start_time.strftime("%d-%m-%y %H:%M %Z")
def end_time_formatted(self): def end_time_formatted(self):
return self.end_time.strftime("%d-%m-%y %H:%M") return self.end_time.strftime("%d-%m-%y %H:%M")
def end_time_formatted_utc(self): def end_time_formatted_utc(self):
return self.end_time.strftime("%d-%m-%y %H:%M %Z") return self.end_time.strftime("%d-%m-%y %H:%M %Z")
# Total number of options in all polls # Total number of options in all polls
def total_num_opts(self): def total_num_opts(self):
polls = self.polls.all() polls = self.polls.all()
count = 0 count = 0
for poll in polls: for poll in polls:
count += poll.options.all().count() count += poll.options.all().count()
return count return count
def total_num_partial_decs(self): def total_num_partial_decs(self):
polls = self.polls.all() polls = self.polls.all()
count = 0 count = 0
for poll in polls: for poll in polls:
count += PartialBallotDecryption.objects.filter(poll=poll).count() count += PartialBallotDecryption.objects.filter(poll=poll).count()
return count return count
def all_part_decs_received(self): def all_part_decs_received(self):
received = False received = False
if self.total_num_partial_decs() == self.total_num_opts() * self.users_trustees.all().count(): if self.total_num_partial_decs() == self.total_num_opts() * self.users_trustees.all().count():
received = True received = True
return received return received
def status(self): def status(self):
status_str = "" status_str = ""
# Get the current date and time to compare against to establish if this is a past, current or # Get the current date and time to compare against to establish if this is a past, current or
# future event. Prepared means the public key has been initialised # future event. Prepared means the public key has been initialised
present = timezone.now() present = timezone.now()
if self.ended is False: if self.ended is False:
if present < self.start_time and self.prepared is False: if present < self.start_time and self.prepared is False:
status_str = "Future" status_str = "Future"
elif present < self.start_time and self.prepared is True: elif present < self.start_time and self.prepared is True:
status_str = "Prepared" status_str = "Prepared"
elif present >= self.start_time and present <= self.end_time and self.prepared is True: elif present >= self.start_time and present <= self.end_time and self.prepared is True:
status_str = "Active" status_str = "Active"
elif present >= self.start_time and present <= self.end_time and self.prepared is False: elif present >= self.start_time and present <= self.end_time and self.prepared is False:
status_str = "Future" status_str = "Future"
elif present > self.end_time: elif present > self.end_time:
status_str = "Expired" status_str = "Expired"
else: else:
if self.all_part_decs_received(): if self.all_part_decs_received():
status_str = "Decrypted" status_str = "Decrypted"
else: else:
status_str = "Ended" status_str = "Ended"
return status_str return status_str
''' '''
The result applies to all polls for an event so True will only be returned when votes have The result applies to all polls for an event so True will only be returned when votes have
been received for every poll. been received for every poll.
''' '''
def has_received_votes(self): def has_received_votes(self):
received_votes = True received_votes = True
for poll in self.polls.all(): for poll in self.polls.all():
if Ballot.objects.filter(poll=poll, cast=True).count() == 0: if Ballot.objects.filter(poll=poll, cast=True).count() == 0:
received_votes = False received_votes = False
return received_votes return received_votes
def __str__(self): def __str__(self):
return self.title return self.title
class TrusteeKey(models.Model): class TrusteeKey(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="trustee_keys") event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="trustee_keys")
user = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="trustee_keys") user = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="trustee_keys")
key = models.CharField(max_length=260) key = models.CharField(max_length=260)
class AccessKey(models.Model): class AccessKey(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="keys") event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="keys")
user = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="keys") user = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="keys")
key = models.CharField(max_length=255, unique=True) key = models.CharField(max_length=255, unique=True)
def has_started(self): def has_started(self):
return timezone.now() >= self.start return timezone.now() >= self.start
def has_ended(self): def has_ended(self):
return timezone.now() >= self.end return timezone.now() >= self.end
def __unicode__(self): def __unicode__(self):
return self.title return self.title
class Poll(models.Model): class Poll(models.Model):
question_text = models.CharField(max_length=200) question_text = models.CharField(max_length=200)
total_votes = models.IntegerField(default=0) total_votes = models.IntegerField(default=0)
min_num_selections = models.IntegerField(default=0) min_num_selections = models.IntegerField(default=0)
max_num_selections = models.IntegerField(default=1) max_num_selections = models.IntegerField(default=1)
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="polls") event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="polls")
combined_ballots = models.CharField(max_length=4096, null=True) combined_ballots = models.CharField(max_length=4096, null=True)
result_json = models.CharField(max_length=4096, null=True) result_json = models.CharField(max_length=4096, null=True)
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
def __str__(self): def __str__(self):
return self.question_text return self.question_text
class PollOption(models.Model): class PollOption(models.Model):
choice_text = models.CharField(max_length=200) choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0) votes = models.IntegerField(default=0)
question = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="options") question = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="options")
def __str__(self): def __str__(self):
return self.choice_text return self.choice_text
class CombinedBallot(models.Model): class CombinedBallot(models.Model):
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="combined_ballot") poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="combined_ballot")
option = models.ForeignKey(PollOption, on_delete=models.CASCADE, related_name="combined_ballot") option = models.ForeignKey(PollOption, on_delete=models.CASCADE, related_name="combined_ballot")
cipher_text_c1 = models.CharField(max_length=4096) cipher_text_c1 = models.CharField(max_length=4096)
cipher_text_c2 = models.CharField(max_length=4096) cipher_text_c2 = models.CharField(max_length=4096)
# A partial decryption supplied by a trustee for a combined ballot that relates to a poll option # A partial decryption supplied by a trustee for a combined ballot that relates to a poll option
class PartialBallotDecryption(models.Model): class PartialBallotDecryption(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="decryption") event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="decryption")
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="decryption") poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="decryption")
option = models.ForeignKey(PollOption, on_delete=models.CASCADE, related_name="decryption") option = models.ForeignKey(PollOption, on_delete=models.CASCADE, related_name="decryption")
user = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="decryption") user = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="decryption")
text = models.CharField(max_length=4096) text = models.CharField(max_length=4096)
class Ballot(models.Model): 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) json_str = models.CharField(max_length=10240)
cast = models.BooleanField(default=False) cast = models.BooleanField(default=False)
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=10240) ballot = models.CharField(max_length=10240)
# Implements the new binary encoding scheme # Implements the new binary encoding scheme
class EncryptedVote(models.Model): class EncryptedVote(models.Model):
ballot = models.ForeignKey(Ballot, on_delete=models.CASCADE, related_name="encrypted_vote") ballot = models.ForeignKey(Ballot, on_delete=models.CASCADE, related_name="encrypted_vote")
class CombinedEncryptedVote(models.Model): class CombinedEncryptedVote(models.Model):
ballot = models.ForeignKey(Ballot, on_delete=models.CASCADE, related_name="comb_encrypted_vote") ballot = models.ForeignKey(Ballot, on_delete=models.CASCADE, related_name="comb_encrypted_vote")
class VoteFragment(models.Model): class VoteFragment(models.Model):
encrypted_vote = models.ForeignKey(EncryptedVote, on_delete=models.CASCADE, related_name="fragment", null=True) 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) 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_c1 = models.CharField(max_length=4096)
cipher_text_c2 = models.CharField(max_length=4096) cipher_text_c2 = models.CharField(max_length=4096)

View File

@ -21,5 +21,6 @@ urlpatterns = [
url(r'^(?P<event_id>[0-9a-f-]+)/poll/(?P<poll_id>[0-9a-f-]+)/vote/$', views.event_vote, name='event-vote'), url(r'^(?P<event_id>[0-9a-f-]+)/poll/(?P<poll_id>[0-9a-f-]+)/vote/$', views.event_vote, name='event-vote'),
url(r'^(?P<event_id>[0-9a-f-]+)/create/poll/$', login_required(views.manage_questions), name='create-poll'), url(r'^(?P<event_id>[0-9a-f-]+)/create/poll/$', login_required(views.manage_questions), name='create-poll'),
url(r'^(?P<event_id>[0-9a-f-]+)/poll/(?P<poll_id>[0-9a-f-]+)/edit$', login_required(views.edit_poll), name='edit-poll'), url(r'^(?P<event_id>[0-9a-f-]+)/poll/(?P<poll_id>[0-9a-f-]+)/edit$', login_required(views.edit_poll), name='edit-poll'),
url(r'^audit/$', views.vote_audit, name='vote_audit') url(r'^audit/$', views.vote_audit, name='vote_audit'),
url(r'^find_ballot/$', views.find_ballot, name='find_ballot')
] ]

View File

@ -25,529 +25,559 @@ from .utils.EventModelAdaptor import EventModelAdaptor
class EventListView(generic.ListView): class EventListView(generic.ListView):
model = Event model = Event
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(EventListView, self).get_context_data(**kwargs) context = super(EventListView, self).get_context_data(**kwargs)
return context return context
class EventDetailView(generic.DetailView): class EventDetailView(generic.DetailView):
template_name = "polls/event_detail_details.html" template_name = "polls/event_detail_details.html"
model = Event model = Event
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(EventDetailView, self).get_context_data(**kwargs) context = super(EventDetailView, self).get_context_data(**kwargs)
context['is_organiser'] = (not self.request.user.is_anonymous()) and (self.object.users_organisers.filter(email=self.request.user.email).exists()) context['is_organiser'] = (not self.request.user.is_anonymous()) and (self.object.users_organisers.filter(email=self.request.user.email).exists())
context['decrypted'] = self.object.status() == "Decrypted" context['decrypted'] = self.object.status() == "Decrypted"
# Get the results for all polls # Get the results for all polls
polls = self.object.polls.all() polls = self.object.polls.all()
results = list() results = list()
for poll in polls: for poll in polls:
result_json = poll.result_json result_json = poll.result_json
if result_json is None: if result_json is None:
continue continue
if result_json[len(result_json)-1] == ',': if result_json[len(result_json)-1] == ',':
result_json = result_json[0:len(result_json)-1] result_json = result_json[0:len(result_json)-1]
result_json = json.loads(result_json) result_json = json.loads(result_json)
results.append(result_json) results.append(result_json)
context['event_results'] = results context['event_results'] = results
return context return context
class EventDetailPollsView(EventDetailView): class EventDetailPollsView(EventDetailView):
template_name = "polls/event_detail_polls.html" template_name = "polls/event_detail_polls.html"
class EventDetailEntitiesView(EventDetailView): class EventDetailEntitiesView(EventDetailView):
template_name = "polls/event_detail_entities.html" template_name = "polls/event_detail_entities.html"
class EventDetailAdvancedView(EventDetailView): class EventDetailAdvancedView(EventDetailView):
template_name = "polls/event_detail_advanced.html" template_name = "polls/event_detail_advanced.html"
class PollDetailView(generic.View): class PollDetailView(generic.View):
model = Poll model = Poll
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(PollDetailView, self).get_context_data(**kwargs) context = super(PollDetailView, self).get_context_data(**kwargs)
context['form'] = VoteForm(instance=self.object) context['form'] = VoteForm(instance=self.object)
context['poll_count'] = self.object.event.polls.all().count() context['poll_count'] = self.object.event.polls.all().count()
return context return context
class EventDetailResultsView(EventDetailView): class EventDetailResultsView(EventDetailView):
template_name = "polls/event_results.html" template_name = "polls/event_results.html"
def util_get_poll_by_event_index(event, poll_id): def util_get_poll_by_event_index(event, poll_id):
return event.polls.get(uuid=poll_id) return event.polls.get(uuid=poll_id)
def edit_poll(request, event_id, poll_id): def edit_poll(request, event_id, poll_id):
event = get_object_or_404(Event, pk=event_id) event = get_object_or_404(Event, pk=event_id)
poll = util_get_poll_by_event_index(event, poll_id) poll = util_get_poll_by_event_index(event, poll_id)
if (poll == None): if (poll == None):
raise Http404("Poll does not exist") raise Http404("Poll does not exist")
if request.method == 'GET': if request.method == 'GET':
form = PollForm(instance=poll, prefix="main") form = PollForm(instance=poll, prefix="main")
formset = OptionFormset(instance=poll, prefix="formset_options") formset = OptionFormset(instance=poll, prefix="formset_options")
return render(request, "polls/generic_form.html", {'form_title': "Edit Poll: " + poll.question_text, 'form': form, 'option_formset': formset}) return render(request, "polls/generic_form.html", {'form_title': "Edit Poll: " + poll.question_text, 'form': form, 'option_formset': formset})
elif request.method == 'POST': elif request.method == 'POST':
form = PollForm(request.POST, instance=poll, prefix="main") form = PollForm(request.POST, instance=poll, prefix="main")
if form.is_valid(): if form.is_valid():
form.save() form.save()
formset = OptionFormset(request.POST, instance=poll, prefix="formset_options") formset = OptionFormset(request.POST, instance=poll, prefix="formset_options")
if formset.is_valid(): if formset.is_valid():
formset.save() formset.save()
return HttpResponseRedirect(reverse('polls:event-polls', args=[poll.event_id])) return HttpResponseRedirect(reverse('polls:event-polls', args=[poll.event_id]))
def vote_audit(request): def vote_audit(request):
encrypted_ballot = get_object_or_404(EncBallot, handle=''+urllib.quote_plus(request.GET.get('handle', None))) handle1 = request.GET.get('handle1', None)
handle2 = request.GET.get('handle2', None)
vars = {}
if handle:
encrypted_ballot1 = get_object_or_404(EncBallot, handle=''+urllib.quote_plus(handle1))
if handle2:
encrypted_ballot2 = get_object_or_404(EncBallot, handle=''+urllib.quote_plus(handle2))
return render(request, "polls/vote_audit.html", return render(request, "polls/vote_audit.html",
{ {
"ballot": encrypted_ballot.ballot "handle1": handle1,
}) "ballot1": encrypted_ballot1.ballot,
"handle2": handle2,
"ballot2": encrypted_ballot2.ballot
})
def find_ballot(request):
voter_id = request.GET.get('vid', None)
poll_id = request.GET.get('pid', None)
ballot = get_object_or_404(Ballot, voter=voter_id, poll=poll_id)
hash1 = request.GET.get('hash1', None)
hash2 = request.GET.get('hash2', None)
if not ballot.cast:
messages.add_message(request, messages.WARNING, "This ballot has not been cast.")
return HttpResponseRedirect(reverse("user_home"))
return render(request, "polls/find_ballot.html",
{
"ballot": ballot.json_str,
"hash1": hash1,
"hash2": hash2
})
def event_vote(request, event_id, poll_id): def event_vote(request, event_id, poll_id):
event = get_object_or_404(Event, pk=event_id) event = get_object_or_404(Event, pk=event_id)
if not event.prepared: if not event.prepared:
messages.add_message(request, messages.WARNING, "This Event isn\'t ready for voting yet.") messages.add_message(request, messages.WARNING, "This Event isn\'t ready for voting yet.")
return HttpResponseRedirect(reverse("user_home")) return HttpResponseRedirect(reverse("user_home"))
# Lookup the specified poll # Lookup the specified poll
poll = event.polls.get(uuid=poll_id) poll = event.polls.get(uuid=poll_id)
if poll is None: if poll is None:
messages.add_message(request, messages.ERROR, "There was an error loading the voting page.") messages.add_message(request, messages.ERROR, "There was an error loading the voting page.")
return HttpResponseRedirect(reverse("user_home")) return HttpResponseRedirect(reverse("user_home"))
polls = event.polls.all() polls = event.polls.all()
event_poll_count = len(polls) event_poll_count = len(polls)
prev_poll_uuid, next_poll_uuid, poll_num = False, False, 0 prev_poll_uuid, next_poll_uuid, poll_num = False, False, 0
can_vote, cant_vote_reason, has_voted, voter_email = False, "", False, "" can_vote, cant_vote_reason, has_voted, voter_email = False, "", False, ""
for i in range(event_poll_count): for i in range(event_poll_count):
poll = polls[i] poll = polls[i]
poll_uuid = str(poll.uuid) poll_uuid = str(poll.uuid)
req_poll_uuid = str(poll_id) req_poll_uuid = str(poll_id)
if poll_uuid == req_poll_uuid: if poll_uuid == req_poll_uuid:
poll_num = str(i+1) poll_num = str(i+1)
# If current voting request isn't for the last poll, then make sure we link to the next # If current voting request isn't for the last poll, then make sure we link to the next
if i != event_poll_count - 1: if i != event_poll_count - 1:
# Only set the previous poll's uuid if we're not looking at the first poll # Only set the previous poll's uuid if we're not looking at the first poll
if i != 0: if i != 0:
prev_poll_uuid = str(polls[i - 1].uuid) prev_poll_uuid = str(polls[i - 1].uuid)
next_poll_uuid = str(polls[i + 1].uuid) next_poll_uuid = str(polls[i + 1].uuid)
else: else:
if i != 0: if i != 0:
prev_poll_uuid = str(polls[i - 1].uuid) prev_poll_uuid = str(polls[i - 1].uuid)
break break
access_key = request.GET.get('key', None) access_key = request.GET.get('key', None)
email_key = event.keys.filter(key=access_key) email_key = event.keys.filter(key=access_key)
email_key_str = email_key[0].key email_key_str = email_key[0].key
if email_key.exists() and event.voters.filter(email=email_key[0].user.email).exists(): if email_key.exists() and event.voters.filter(email=email_key[0].user.email).exists():
# Passing this test means the user can vote # Passing this test means the user can vote
voter_email = email_key[0].user.email voter_email = email_key[0].user.email
can_vote = True can_vote = True
# Check whether this is the first time a user is voting # Check whether this is the first time a user is voting
ballot = Ballot.objects.filter(voter=email_key[0].user, poll=poll) ballot = Ballot.objects.filter(voter=email_key[0].user, poll=poll)
if ballot.exists() and ballot[0].cast: if ballot.exists() and ballot[0].cast:
has_voted = True has_voted = True
else: else:
can_vote = False can_vote = False
cant_vote_reason = "You don't have permission to access this page." cant_vote_reason = "You don't have permission to access this page."
if event.status() != "Active": if event.status() != "Active":
can_vote = False can_vote = False
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_str = request.POST.get('ballot') ballot_str = request.POST.get('ballot')
ballot_json = json.loads(ballot_str) 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']
enc_ballot_json = request.POST.get('encBallot') enc_ballot_json = request.POST.get('encBallot')
handle_json = request.POST.get('handle') handle_json = request.POST.get('handle')
# Adds or replaces the encrypted un-submitted ballot to the database for the auditor app to pick up later # Adds or replaces the encrypted un-submitted ballot to the database for the auditor app to pick up later
if EncBallot.objects.filter(handle=handle_json).exists(): if EncBallot.objects.filter(handle=handle_json).exists():
b = EncBallot.objects.get(handle=handle_json) b = EncBallot.objects.get(handle=handle_json)
b.ballot = enc_ballot_json b.ballot = enc_ballot_json
b.save() b.save()
else: else:
b = EncBallot(handle=handle_json, ballot=enc_ballot_json) b = EncBallot(handle=handle_json, ballot=enc_ballot_json)
b.save() b.save()
# Before storing the encrypted votes, we need the voter's ballot # 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) ballot, created = Ballot.objects.get_or_create(voter=email_key[0].user, poll=poll)
EncryptedVote.objects.filter(ballot=ballot).delete() EncryptedVote.objects.filter(ballot=ballot).delete()
for e_vote in encrypted_votes_json: for e_vote in encrypted_votes_json:
# Will store the fragments of the encoding scheme that define the vote # Will store the fragments of the encoding scheme that define the vote
encrypted_vote = EncryptedVote.objects.create(ballot=ballot) encrypted_vote = EncryptedVote.objects.create(ballot=ballot)
fragments_json = e_vote['fragments'] fragments_json = e_vote['fragments']
for fragment in fragments_json: for fragment in fragments_json:
VoteFragment.objects.create(encrypted_vote=encrypted_vote, VoteFragment.objects.create(encrypted_vote=encrypted_vote,
cipher_text_c1=fragment['C1'], cipher_text_c1=fragment['C1'],
cipher_text_c2=fragment['C2']) cipher_text_c2=fragment['C2'])
ballot.cast = True ballot.cast = True
ballot.selection = selection ballot.selection = selection
ballot.json_str = ballot_str ballot.json_str = ballot_str
ballot.save() ballot.save()
voter = email_key[0].user voter = email_key[0].user
combine_encrypted_votes.delay(voter, poll) combine_encrypted_votes.delay(voter, poll)
email_voting_success.delay(voter, handle_json, event.title) 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,
'poll_id': next_poll_uuid}) 'poll_id': next_poll_uuid})
+ "?key=" + email_key_str) + "?key=" + email_key_str)
return HttpResponse('Voted Successfully!') return HttpResponse('Voted Successfully!')
return render(request, "polls/event_vote.html", return render(request, "polls/event_vote.html",
{ {
"object": poll, "poll_num": poll_num, "event": event, "poll_count": event.polls.all().count(), "object": poll, "poll_num": poll_num, "event": event, "poll_count": event.polls.all().count(),
"prev_uuid": prev_poll_uuid, "next_uuid": next_poll_uuid, "min_selection": poll.min_num_selections, "prev_uuid": prev_poll_uuid, "next_uuid": next_poll_uuid, "min_selection": poll.min_num_selections,
"max_selection": poll.max_num_selections, "can_vote": can_vote, "cant_vote_reason": cant_vote_reason, "max_selection": poll.max_num_selections, "can_vote": can_vote, "cant_vote_reason": cant_vote_reason,
"voter_email": voter_email, "has_voted": has_voted, "a_key": email_key_str "voter_email": voter_email, "has_voted": has_voted, "a_key": email_key_str
}) })
def event_trustee_setup(request, event_id): def event_trustee_setup(request, event_id):
# Obtain the event and the event preparation access key that's been supplied # Obtain the event and the event preparation access key that's been supplied
event = get_object_or_404(Event, pk=event_id) event = get_object_or_404(Event, pk=event_id)
access_key = request.GET.get('key', None) access_key = request.GET.get('key', None)
# If the a_key is present, check it's valid and related to a trustee EmailUser instance for this event # If the a_key is present, check it's valid and related to a trustee EmailUser instance for this event
if access_key: if access_key:
email_key = event.keys.filter(key=access_key) email_key = event.keys.filter(key=access_key)
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=email_key[0].user.email).exists():
if TrusteeKey.objects.filter(event=event, user=email_key[0].user).exists(): if TrusteeKey.objects.filter(event=event, user=email_key[0].user).exists():
return render(request, "polls/event_setup.html", {"is_trustee": True, return render(request, "polls/event_setup.html", {"is_trustee": True,
"can_submit": False, "can_submit": False,
"access_denied_reason": "You have already submitted your public key for this event. Thank you!" "access_denied_reason": "You have already submitted your public key for this event. Thank you!"
}) })
if request.method == "POST": if request.method == "POST":
form = EventSetupForm(request.POST) form = EventSetupForm(request.POST)
# If form data is valid, create a TrusteeKey object with the supplied public key # If form data is valid, create a TrusteeKey object with the supplied public key
if form.is_valid(): if form.is_valid():
public_key = request.POST.get("public_key") public_key = request.POST.get("public_key")
key = TrusteeKey.objects.get_or_create(event=event, user=email_key[0].user)[0] key = TrusteeKey.objects.get_or_create(event=event, user=email_key[0].user)[0]
key.key = public_key key.key = public_key
key.save() key.save()
# When all trustees have supplied their public key, we can combine them to create a master key # When all trustees have supplied their public key, we can combine them to create a master key
# The event will now be ready to receive votes on the various polls that have been defined - # The event will now be ready to receive votes on the various polls that have been defined -
# voters therefore need to be informed # voters therefore need to be informed
if event.trustee_keys.count() == event.users_trustees.count(): if event.trustee_keys.count() == event.users_trustees.count():
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) 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)
return HttpResponseRedirect(reverse("user_home")) return HttpResponseRedirect(reverse("user_home"))
else: else:
form = EventSetupForm() form = EventSetupForm()
return render(request, "polls/event_setup.html", {"event": event, return render(request, "polls/event_setup.html", {"event": event,
"form": form, "form": form,
"user_email": email_key[0].user.email, "user_email": email_key[0].user.email,
"is_trustee": True, "is_trustee": True,
"can_submit": True "can_submit": True
}) })
else: else:
return render(request, "polls/event_setup.html", {"is_trustee": False, return render(request, "polls/event_setup.html", {"is_trustee": False,
"can_submit": False, "can_submit": False,
"access_denied_reason": "You do not have permission to access this page." "access_denied_reason": "You do not have permission to access this page."
}) })
def event_end(request, event_id): def event_end(request, event_id):
event = get_object_or_404(Event, pk=event_id) event = get_object_or_404(Event, pk=event_id)
if not event.ended: if not event.ended:
event_ended.delay(event) event_ended.delay(event)
# Mark the event as ended # Mark the event as ended
event.ended = True event.ended = True
event.save() event.save()
return HttpResponseRedirect(reverse('polls:view-event', args=[event_id])) return HttpResponseRedirect(reverse('polls:view-event', args=[event_id]))
def event_trustee_decrypt(request, event_id): def event_trustee_decrypt(request, event_id):
event = get_object_or_404(Event, pk=event_id) event = get_object_or_404(Event, pk=event_id)
access_key = request.GET.get('key', None) access_key = request.GET.get('key', None)
if access_key: if access_key:
email_key = event.keys.filter(key=access_key) email_key = event.keys.filter(key=access_key)
trustee = email_key[0].user trustee = email_key[0].user
if email_key.exists() and event.users_trustees.filter(email=trustee.email).exists(): if email_key.exists() and event.users_trustees.filter(email=trustee.email).exists():
if PartialBallotDecryption.objects.filter(event=event, user=trustee).count() == event.total_num_opts(): if PartialBallotDecryption.objects.filter(event=event, user=trustee).count() == event.total_num_opts():
return render(request, "polls/event_decrypt.html", {"is_trustee": True, return render(request, "polls/event_decrypt.html", {"is_trustee": True,
"can_submit": False, "can_submit": False,
"access_denied_reason": "You have already submitted your partial decryptions for this event. Thank you!" "access_denied_reason": "You have already submitted your partial decryptions for this event. Thank you!"
}) })
elif request.method == "GET": elif request.method == "GET":
# Get the Trustee's original PK - used in the template for SK validation # Get the Trustee's original PK - used in the template for SK validation
trustee_pk = TrusteeKey.objects.get(event=event, user=trustee).key 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 # Gen a list of ciphers from the combined ballots for every opt of every poll
polls = event.polls.all() polls = event.polls.all()
poll_ciphers = [] poll_ciphers = []
for poll in polls: for poll in polls:
options = poll.options.all() options = poll.options.all()
options_ciphers = [] options_ciphers = []
for option in options: for option in options:
combined_ballot = CombinedBallot.objects.filter(poll=poll, option=option).get() combined_ballot = CombinedBallot.objects.filter(poll=poll, option=option).get()
cipher = {} cipher = {}
cipher['C1'] = combined_ballot.cipher_text_c1 cipher['C1'] = combined_ballot.cipher_text_c1
cipher['C2'] = combined_ballot.cipher_text_c2 cipher['C2'] = combined_ballot.cipher_text_c2
options_ciphers.append(cipher) options_ciphers.append(cipher)
poll_ciphers.append(options_ciphers) poll_ciphers.append(options_ciphers)
return render(request, return render(request,
"polls/event_decrypt.html", "polls/event_decrypt.html",
{ {
"event": event, "event": event,
"user_email": trustee.email, "user_email": trustee.email,
"trustee_pk": trustee_pk, "trustee_pk": trustee_pk,
"poll_ciphers": poll_ciphers, "poll_ciphers": poll_ciphers,
"is_trustee": True, "is_trustee": True,
"can_submit": True "can_submit": True
}) })
elif request.method == "POST": elif request.method == "POST":
polls = event.polls.all() polls = event.polls.all()
polls_count = len(polls) polls_count = len(polls)
for i in range(polls_count): for i in range(polls_count):
options = polls[i].options.all() options = polls[i].options.all()
options_count = len(options) options_count = len(options)
for j in range(options_count): for j in range(options_count):
input_name = str("") input_name = str("")
input_name = "poll-" + str(i) + "-cipher-" + str(j) input_name = "poll-" + str(i) + "-cipher-" + str(j)
part_dec = request.POST.get(input_name) part_dec = request.POST.get(input_name)
PartialBallotDecryption.objects.create(event=event, PartialBallotDecryption.objects.create(event=event,
poll=polls[i], poll=polls[i],
option=options[j], option=options[j],
user=trustee, user=trustee,
text=part_dec) text=part_dec)
if event.all_part_decs_received(): if event.all_part_decs_received():
# Decrypt the result once all partial decryptions have been received # Decrypt the result once all partial decryptions have been received
# This will email all organisers once the results are ready # This will email all organisers once the results are ready
combine_decryptions_and_tally.delay(event) combine_decryptions_and_tally.delay(event)
else: else:
# TODO: Get how many trustees have submitted a partial decryption # TODO: Get how many trustees have submitted a partial decryption
# TODO: Then get how many are left to submit their partial decryptions # 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 # TODO: Then email the list of organisers to update them with this information
str("") 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"))
# Without an access key, the client does not have permission to access this page # Without an access key, the client does not have permission to access this page
return render(request, "polls/event_decrypt.html", {"is_trustee": False, return render(request, "polls/event_decrypt.html", {"is_trustee": False,
"can_submit": False, "can_submit": False,
"access_denied_reason": "You don't have permission to access this page." "access_denied_reason": "You don't have permission to access this page."
}) })
def manage_questions(request, event_id): def manage_questions(request, event_id):
event = get_object_or_404(Event, pk=event_id) event = get_object_or_404(Event, pk=event_id)
if (request.user.is_anonymous()) or (not event.users_organisers.filter(email=request.user.email).exists()): if (request.user.is_anonymous()) or (not event.users_organisers.filter(email=request.user.email).exists()):
messages.add_message(request, messages.WARNING, 'You do not have permission to access: ' + request.path) messages.add_message(request, messages.WARNING, 'You do not have permission to access: ' + request.path)
return HttpResponseRedirect(reverse("user_home")) return HttpResponseRedirect(reverse("user_home"))
poll = Poll() poll = Poll()
formset = OptionFormset(instance=poll, prefix="formset_organiser") formset = OptionFormset(instance=poll, prefix="formset_organiser")
if request.method == "POST": if request.method == "POST":
form = PollForm(request.POST, prefix="main") form = PollForm(request.POST, prefix="main")
formset = OptionFormset(request.POST, prefix="formset_organiser") # incase form fails, we still want to retain formset data formset = OptionFormset(request.POST, prefix="formset_organiser") # incase form fails, we still want to retain formset data
if form.is_valid(): if form.is_valid():
poll = form.save(commit=False) poll = form.save(commit=False)
poll.event_id = event_id poll.event_id = event_id
poll.save() poll.save()
formset = OptionFormset(request.POST, prefix="formset_organiser", instance=poll) formset = OptionFormset(request.POST, prefix="formset_organiser", instance=poll)
if formset.is_valid(): if formset.is_valid():
formset.save() formset.save()
create_ballots_for_poll.delay(poll) create_ballots_for_poll.delay(poll)
messages.add_message(request, messages.SUCCESS, 'Poll created successfully') messages.add_message(request, messages.SUCCESS, 'Poll created successfully')
return HttpResponseRedirect(reverse('polls:event-polls', args=[poll.event_id])) return HttpResponseRedirect(reverse('polls:event-polls', args=[poll.event_id]))
return render(request, "polls/create_poll.html", {"event": event, "question_form": form, "option_formset": formset}) return render(request, "polls/create_poll.html", {"event": event, "question_form": form, "option_formset": formset})
elif request.method == "GET": elif request.method == "GET":
form = PollForm(prefix="main") #, instance=poll form = PollForm(prefix="main") #, instance=poll
return render(request, "polls/create_poll.html", {"event": event, "question_form": form, "option_formset": formset}) return render(request, "polls/create_poll.html", {"event": event, "question_form": form, "option_formset": formset})
else: else:
return HttpResponseNotAllowed() return HttpResponseNotAllowed()
def render_invalid(request, events, demo_users, invalid_fields): def render_invalid(request, events, demo_users, invalid_fields):
return render(request, return render(request,
"polls/create_event.html", "polls/create_event.html",
{ {
"G_R_SITE_KEY": settings.RECAPTCHA_PUBLIC_KEY, "G_R_SITE_KEY": settings.RECAPTCHA_PUBLIC_KEY,
"user_email": request.user.email, "user_email": request.user.email,
"events": events, "events": events,
"demo_users": demo_users, "demo_users": demo_users,
"invalid_fields": invalid_fields "invalid_fields": invalid_fields
}) })
def create_event(request): def create_event(request):
# Obtain context data for the rendering of the html template and validation # Obtain context data for the rendering of the html template and validation
events = Event.objects.all() events = Event.objects.all()
demo_users = DemoUser.objects.all() demo_users = DemoUser.objects.all()
if request.method == "POST": if request.method == "POST":
'''Perform Google reCAPTCHA validation''' '''Perform Google reCAPTCHA validation'''
recaptcha_response = request.POST.get('g-recaptcha-response') recaptcha_response = request.POST.get('g-recaptcha-response')
url = 'https://www.google.com/recaptcha/api/siteverify' url = 'https://www.google.com/recaptcha/api/siteverify'
values = { values = {
'secret': settings.RECAPTCHA_PRIVATE_KEY, 'secret': settings.RECAPTCHA_PRIVATE_KEY,
'response': recaptcha_response 'response': recaptcha_response
} }
data = urllib.urlencode(values) data = urllib.urlencode(values)
req = urllib2.Request(url, data) req = urllib2.Request(url, data)
response = urllib2.urlopen(req) response = urllib2.urlopen(req)
result = json.load(response) result = json.load(response)
'''Perform form data validation''' '''Perform form data validation'''
adaptor = EventModelAdaptor(request.POST, request.user) adaptor = EventModelAdaptor(request.POST, request.user)
form_data_valid = adaptor.isFormDataValid(events, demo_users) form_data_valid = adaptor.isFormDataValid(events, demo_users)
'''Process form data based on above results''' '''Process form data based on above results'''
if result['success']: if result['success']:
if form_data_valid: if form_data_valid:
# Create the new event using the form data # Create the new event using the form data
adaptor.extractData() adaptor.extractData()
new_event = adaptor.updateModel() new_event = adaptor.updateModel()
# Update the EID to include the GP in its EID # Update the EID to include the GP in its EID
update_EID.delay(new_event) update_EID.delay(new_event)
# Send an email to all trustees for event preparation # Send an email to all trustees for event preparation
trustees = new_event.users_trustees.all() trustees = new_event.users_trustees.all()
email_trustees_prep.delay(trustees, new_event) email_trustees_prep.delay(trustees, new_event)
adaptor.clear_data() adaptor.clear_data()
return HttpResponseRedirect(reverse('polls:index')) return HttpResponseRedirect(reverse('polls:index'))
else: else:
invalid_fields = adaptor.getInvalidFormFields() invalid_fields = adaptor.getInvalidFormFields()
adaptor.clear_data() adaptor.clear_data()
return render_invalid(request, events, demo_users, invalid_fields) return render_invalid(request, events, demo_users, invalid_fields)
else: else:
invalid_fields = adaptor.getInvalidFormFields() invalid_fields = adaptor.getInvalidFormFields()
invalid_fields['recaptcha'] = {'error': 'The reCAPTCHA server validation failed, please try again.'} invalid_fields['recaptcha'] = {'error': 'The reCAPTCHA server validation failed, please try again.'}
adaptor.clear_data() adaptor.clear_data()
return render_invalid(request, events, demo_users, invalid_fields) return render_invalid(request, events, demo_users, invalid_fields)
elif request.method == "GET": elif request.method == "GET":
# Render the template # Render the template
return render(request, return render(request,
"polls/create_event.html", "polls/create_event.html",
{ {
"G_R_SITE_KEY": settings.RECAPTCHA_PUBLIC_KEY, "G_R_SITE_KEY": settings.RECAPTCHA_PUBLIC_KEY,
"user_email": request.user.email, "user_email": request.user.email,
"events": events, "events": events,
"demo_users": demo_users "demo_users": demo_users
}) })
else: else:
return HttpResponseNotAllowed() return HttpResponseNotAllowed()
def edit_event(request, event_id): def edit_event(request, event_id):
event = get_object_or_404(Event, pk=event_id) event = get_object_or_404(Event, pk=event_id)
if request.method == "GET": if request.method == "GET":
form = EventEditForm(instance=event, prefix="main") form = EventEditForm(instance=event, prefix="main")
''' '''
organiser_initial_data = [{'email': request.user.email}] organiser_initial_data = [{'email': request.user.email}]
trustee_initial_data = [] trustee_initial_data = []
for user in event.users_organisers.exclude(email=request.user.email): for user in event.users_organisers.exclude(email=request.user.email):
organiser_initial_data.append({'email': user.email}) organiser_initial_data.append({'email': user.email})
organiser_formset = OrganiserFormSet(prefix="formset_organiser", initial=organiser_initial_data) organiser_formset = OrganiserFormSet(prefix="formset_organiser", initial=organiser_initial_data)
for trustee in event.users_trustees.all(): for trustee in event.users_trustees.all():
trustee_initial_data.append({'email': trustee.email}) trustee_initial_data.append({'email': trustee.email})
trustee_formset = TrusteeFormSet(prefix="formset_trustee", initial=trustee_initial_data) trustee_formset = TrusteeFormSet(prefix="formset_trustee", initial=trustee_initial_data)
''' '''
elif request.method == "POST": elif request.method == "POST":
form = EventEditForm(request.POST, instance=event, prefix="main") form = EventEditForm(request.POST, instance=event, prefix="main")
#trustee_formset = TrusteeFormSet(request.POST, prefix="formset_trustee") #trustee_formset = TrusteeFormSet(request.POST, prefix="formset_trustee")
#organiser_formset = OrganiserFormSet(request.POST, prefix="formset_organiser") # incase form fails, we still want to retain formset data #organiser_formset = OrganiserFormSet(request.POST, prefix="formset_organiser") # incase form fails, we still want to retain formset data
if form.is_valid(): if form.is_valid():
form.save() form.save()
''' '''
if organiser_formset.is_valid(): if organiser_formset.is_valid():
event.users_organisers.clear() event.users_organisers.clear()
for oform in organiser_formset: for oform in organiser_formset:
if (oform.cleaned_data.get('email')): if (oform.cleaned_data.get('email')):
event.users_organisers.add(DemoUser.objects.get(email=oform.cleaned_data['email'])) event.users_organisers.add(DemoUser.objects.get(email=oform.cleaned_data['email']))
event.users_organisers.add(request.user) # always add editor/creator event.users_organisers.add(request.user) # always add editor/creator
if trustee_formset.is_valid(): if trustee_formset.is_valid():
event.users_trustees.clear() event.users_trustees.clear()
for tform in trustee_formset: for tform in trustee_formset:
if (tform.cleaned_data.get('email')): if (tform.cleaned_data.get('email')):
event.users_trustees.add(EmailUser.objects.get_or_create(email=tform.cleaned_data['email'])[0]) event.users_trustees.add(EmailUser.objects.get_or_create(email=tform.cleaned_data['email'])[0])
''' '''
return HttpResponseRedirect(reverse('polls:view-event', kwargs={'pk': event.id})) return HttpResponseRedirect(reverse('polls:view-event', kwargs={'pk': event.id}))
return render(request, "polls/generic_form.html", {"form_title": "Edit Event: " + event.title, "form": form}) #"organiser_formset": organiser_formset, "trustee_formset": trustee_formset}) return render(request, "polls/generic_form.html", {"form_title": "Edit Event: " + event.title, "form": form}) #"organiser_formset": organiser_formset, "trustee_formset": trustee_formset})
#trustee_formset = TrusteeFormSet(request.POST, prefix="formset_trustee", instance=event) #trustee_formset = TrusteeFormSet(request.POST, prefix="formset_trustee", instance=event)
def del_event(request, event_id): def del_event(request, event_id):
event = get_object_or_404(Event, pk=event_id) event = get_object_or_404(Event, pk=event_id)
if request.method == "GET": if request.method == "GET":
return render(request, "polls/del_event.html", {"event_title": event.title, "event_id": event.uuid}) return render(request, "polls/del_event.html", {"event_title": event.title, "event_id": event.uuid})
elif request.method == "POST": elif request.method == "POST":
event.delete() event.delete()
return HttpResponseRedirect(reverse('polls:index')) return HttpResponseRedirect(reverse('polls:index'))

View File

@ -21,6 +21,7 @@
<script src="{% static 'js/decrypt_event.js' %}" type="text/javascript"></script> <script src="{% static 'js/decrypt_event.js' %}" type="text/javascript"></script>
<script src="{% static 'js/event_vote.js' %}" type="text/javascript"></script> <script src="{% static 'js/event_vote.js' %}" type="text/javascript"></script>
<script src="{% static 'js/vote_audit.js' %}" type="text/javascript"></script> <script src="{% static 'js/vote_audit.js' %}" type="text/javascript"></script>
<script src="{% static 'js/find_ballot.js' %}" type="text/javascript"></script>
<script src="{% static 'js/encrypt.js' %}" type="text/javascript"></script> <script src="{% static 'js/encrypt.js' %}" type="text/javascript"></script>
<script src="{% static 'js/event_setup.js' %}" type="text/javascript"></script> <script src="{% static 'js/event_setup.js' %}" type="text/javascript"></script>

View File

@ -0,0 +1,11 @@
{% extends "bases/bootstrap-with-nav.html" %}
{% load staticfiles %}
{% load bootstrap3 %}
{% block content %}
<pre hidden id="ballot_found">{{ ballot }}</pre>
<pre hidden id="ballot_hashes">{{ hash1 }};{{ hash2 }}</pre>
<h1 id="ballot_result"></h1>
{% endblock %}

View File

@ -4,15 +4,31 @@
{% block content %} {% block content %}
<input id="SK" value="temporary" type="text"/> <label class="gp-1" for="handle1">Ballot #1 handle:</label>
<button id="begin-test">Verify Ballot</button> <input class="gp-1" id="handle1" value="{{ handle1 }}" type="text"/>
<br> <label class="gp-1" for="handle2">Ballot handle:</label>
<br> <input class="gp-1" id="handle2" value="{{ handle2 }}" type="text"/>
<label for="ballot">AES-encrypted ballot from server</label> <button class="gp-1" id="retrieve-ballots">Retrieve Ballots</button>
<pre id="ballot">{{ ballot }}</pre>
<label for="ballot-content">Decrypted ballot</label> <hr>
<pre id="ballot-content"></pre>
<label for="ballot">Ballot encoding</label> <label class="gp-2" for="ballot1">AES-encrypted ballot #1 from server</label>
<pre id="ballot-result"></pre> <pre class="gp-2" id="ballot1">{{ ballot }}</pre>
<input class="gp-2" id="SK1" value="temporary" type="text"/>
<button class="gp-2" id="decrypt-ballot1">Decrypt Ballot</button>
<label class="gp-3" for="ballot-content1">Decrypted ballot</label>
<pre class="gp-3" id="ballot-content1"></pre>
<label class="gp-3" for="ballot-result1">Ballot encoding</label>
<pre class="gp-3" id="ballot-result1"></pre>
<hr>
<label class="gp-2" for="ballot2">AES-encrypted ballot #2 from server</label>
<pre class="gp-2" id="ballot2">{{ ballot2 }}</pre>
<input class="gp-2" id="SK2" value="temporary" type="text"/>
<button class="gp-2" id="decrypt-ballot2">Decrypt Ballot</button>
<label class="gp-3" for="ballot-content2">Decrypted ballot</label>
<pre class="gp-3" id="ballot-content2"></pre>
<label class="gp-3" for="ballot-result2">Ballot encoding</label>
<pre class="gp-3" id="ballot-result2"></pre>
{% endblock %} {% endblock %}

View File

@ -328,12 +328,51 @@ function generateBallots() {
var ballotB = generateBallot(); var ballotB = generateBallot();
progressBar.setAttribute("style", "width: 100%;"); progressBar.setAttribute("style", "width: 100%;");
showFirstQRCode(ballotA, ballotB, selectedOption); showIDsQRCode(ballotA, ballotB, selectedOption);
}, 150); }, 150);
} }
function showIDsQRCode(ballotA, ballotB, selectedOption) {
var voterID = window.location.search.slice(1).split(/=(.+)/)[1];
var eventID = window.location.href.split('/')[4];
var pollID = $('#poll-num').text();
// Display a QR code with ID details
var modalDialog = $('#modalDialog');
var title = modalDialog.find('.modal-title');
var body = modalDialog.find('.modal-body');
body.empty();
title.text('Step 0 of 3: Scan this');
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');
QRCodeImg.setAttribute('class', 'QR-code');
QRCodeImg.setAttribute('id', "qr-img");
new QRCode(QRCodeImg, voterID+";"+eventID+";"+pollID);
QRDiv.append(QRCodeImg);
body.append(pleaseScanP);
body.append(QRDiv);
// Prepare the appropriate dialog buttons
updateDialogButtons(DIALOG_BTN_STATES.STEP_1);
if(!dialogOpen) {
modalDialog.modal('toggle');
dialogOpen = true;
}
$('#nextDialogBtn').click(function(e) {
showHashQRCode(ballotA, ballotB, selectedOption);
});
};
// Called in stage 1 of 3 in the voting process // Called in stage 1 of 3 in the voting process
function showFirstQRCode(ballotA, ballotB, selectedOption) { function showHashQRCode(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);
@ -350,7 +389,7 @@ function showFirstQRCode(ballotA, ballotB, selectedOption) {
title.text('Step 1 of 3: Link Your Vote'); title.text('Step 1 of 3: Link Your Vote');
let pleaseScanP = document.createElement('p'); let pleaseScanP = document.createElement('p');
pleaseScanP.innerHTML = "Please scan the following QR code from your DEMOS 2 mobile application:"; pleaseScanP.innerHTML = "Please scan the following QR code from your DEMOS 2 auditor:";
let QRDiv = document.createElement('div'); let QRDiv = document.createElement('div');
var QRCodeImg = document.createElement('img'); var QRCodeImg = document.createElement('img');
@ -551,19 +590,22 @@ function sendBallotsToServer(selection, selectedBallot, otherBallot) {
var pk = new ctx.ECP(0); var pk = new ctx.ECP(0);
pk.copy(tempPK.PK); pk.copy(tempPK.PK);
var voterID = window.location.search.slice(1).split(/=(.+)/)[1];//.slice(0, -2); // Prepares the unselected ballot to send, encrypted, to the server.
// Creates the unique ballot ID for the unselected ballot
var voterID = window.location.search.slice(1).split(/=(.+)/)[1];
var eventID = window.location.href.split('/')[4]; var eventID = window.location.href.split('/')[4];
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? // TODO: Generate a SK rather than using a static one. UUID generated server side and then injected JS side?
JSON.stringify(otherBallot)
var SK = "temporary"; var SK = "temporary";
var encAlt = sjcl.encrypt(SK, JSON.stringify(otherBallot)); var encAlt = sjcl.encrypt(SK, JSON.stringify({ballotID: ballotID, ballot: otherBallot}));
// Generates an HMAC to protect the encrypted ballot from tampering.
var out = (new sjcl.misc.hmac(key, sjcl.hash.sha256)).mac(encAlt); var out = (new sjcl.misc.hmac(key, sjcl.hash.sha256)).mac(encAlt);
var hmac = sjcl.codec.hex.fromBits(out); var hmac = sjcl.codec.hex.fromBits(out);
// Prepares the selected ballot to submit to the server.
let selectedBallotAsStr = JSON.stringify(selectedBallot); let selectedBallotAsStr = JSON.stringify(selectedBallot);
$.ajax({ $.ajax({
type : "POST", type : "POST",
url : window.location, url : window.location,
@ -593,7 +635,7 @@ function onAfterBallotSend(ballotID, SK, hmac) {
instructions1P.innerHTML = instructions1Txt; instructions1P.innerHTML = instructions1Txt;
body.append(instructions1P); body.append(instructions1P);
// Add the second section: QR code that contains the ballot identifier // Add the second section: QR code that contains the ballot identifier and HMAC
var QRCodeImg = document.createElement('img'); var QRCodeImg = document.createElement('img');
QRCodeImg.setAttribute('class', 'QR-code'); QRCodeImg.setAttribute('class', 'QR-code');
new QRCode(QRCodeImg, ballotID+";"+btoa(hmac)); new QRCode(QRCodeImg, ballotID+";"+btoa(hmac));

10
static/js/find_ballot.js Normal file
View File

@ -0,0 +1,10 @@
$( document ).ready(function() {
ballotHash = SHA256Hash(stringtobytes(JSON.stringify($('#ballot-found').text())), true);
var hashes = $('#ballot_hashes').text().split(';');
$('#ballot_result').text("Ballot not found!");
for (var i = 0; i <= 1; i++) {
if (hashes[i] == ballotHash) {
$('#ballot_result').text("Ballot found!");
}
}
});

View File

@ -6,6 +6,25 @@ function getBytes(arr) {
return new Uint8Array(arr); return new Uint8Array(arr);
} }
$( document ).ready(function() {
$('.gp-2, .gp-3').hide();
var n = 0;
for (var i = 0; i <= 1; i++) {
if ($('#handle'+i).val() != "") {
n++;
}
}
if (n == 2) {
$('ballot-group'+i+' .gp-1').css('opacity','0.6');
$('#retrieve-ballots').prop('disabled', true);
$('ballot-group'+i+' .gp-2').show();
}
});
$('#retrieve-ballots').click(function() {
window.location = "/audit?handle="+$('#handle1').val()+'&handle2='+$('#handle2').val();
});
$('#begin-test').click(function() { $('#begin-test').click(function() {
var ctx = new CTX("BN254CX"); var ctx = new CTX("BN254CX");
var ciphertext = { var ciphertext = {
@ -44,7 +63,19 @@ $('#begin-test').click(function() {
// test whether C2/(C1)^r = g^0 or g^1, and record g's exponent. // test whether C2/(C1)^r = g^0 or g^1, and record g's exponent.
//var c1 = ctx.PAIR.GTpow(ciphertext.C1, ciphertext.r); //var c1 = ctx.PAIR.GTpow(ciphertext.C1, ciphertext.r);
var m = ciphertext.C2.div(Math.pow(ciphertext.C1, ciphertext.r)); var B;
var j;
for (j = 0; j <= 1; j++) {
//use D as temp var
B = new ctx.BIG(j);
D = ctx.PAIR.G1mul(params.g1,B);
if (D.equals(gM)) {
return {
M:j
}
}
};
console.log("m = "+m); console.log("m = "+m);
encoding += (m) ? "1" : "0"; encoding += (m) ? "1" : "0";