General housekeeping such as adding package.json for Node server, bash script for launching a celery worker and updating the readme to assist with launching the DEMOS2 app. Updated some models to include UI helper functions. Main work done is around event preparation - with a Celery worker running and the Node server, trustees are now emailed a link to prepare events. The event detail page has also had a bit of an overhaul to include additional information and to make it easier to use
This commit is contained in:
parent
de9eaa7881
commit
f82a380fa4
21 changed files with 337 additions and 696 deletions
|
@ -4,18 +4,21 @@ import subprocess
|
|||
import json
|
||||
import urllib2
|
||||
|
||||
#change this file name etc., temporary change to get it working for the meantime
|
||||
'''
|
||||
|
||||
All functions in this file have been re-implemenented by Thomas Smith
|
||||
|
||||
File then updated by Vincent de Almeida. Changes include:
|
||||
-Update filename to 'crypto_rpc' to reflect the RPC nature of the methods
|
||||
|
||||
'''
|
||||
def param():
|
||||
jsondict = json.load(urllib2.urlopen('http://localhost:8080/param'))
|
||||
url = 'http://localhost:8080/param' # RPC URL
|
||||
jsondict = json.load(urllib2.urlopen(url))
|
||||
return json.dumps(jsondict)
|
||||
|
||||
def combpk(amount, pks):
|
||||
url = 'http://localhost:8080/cmpkstring'
|
||||
url = 'http://localhost:8080/cmpkstring' # RPC URL
|
||||
querystring = '?number='+str(amount)
|
||||
for pk in pks:
|
||||
querystring += '&PK='+pk
|
||||
|
@ -26,7 +29,7 @@ def combpk(amount, pks):
|
|||
return json.dumps(jsondict)
|
||||
|
||||
def addec(amount, ciphers):
|
||||
url = 'http://localhost:8080/addec'
|
||||
url = 'http://localhost:8080/addec' # RPC URL
|
||||
querystring = '?number='+str(amount)
|
||||
c1s = ciphers['c1s']
|
||||
c2s = ciphers['c2s']
|
||||
|
@ -40,7 +43,7 @@ def addec(amount, ciphers):
|
|||
return json.dumps(jsondict)
|
||||
|
||||
def tally(amount, param, decs, cipher):
|
||||
url = 'http://localhost:8080/tally'
|
||||
url = 'http://localhost:8080/tally' # RPC URL
|
||||
querystring = '?number='+str(amount)
|
||||
querystring += '¶m='+urllib2.quote(str(param))
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
from django.core.mail import send_mail
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
@ -9,9 +10,17 @@ from allauthdemo.auth.models import DemoUser
|
|||
|
||||
class EmailUser(models.Model):
|
||||
email = models.CharField(max_length=80, unique=True)
|
||||
|
||||
def send_email(self, subject, message, from_email=None):
|
||||
"""
|
||||
Sends an email to this User.
|
||||
"""
|
||||
send_mail(subject, message, from_email, [self.email])
|
||||
|
||||
def __unicode__(self):
|
||||
return self.email
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
users_organisers = models.ManyToManyField(DemoUser, blank=True, related_name="organisers")
|
||||
users_trustees = models.ManyToManyField(EmailUser, blank=True, related_name="trustees")
|
||||
|
@ -26,11 +35,32 @@ class Event(models.Model):
|
|||
c_email = models.CharField(max_length=512, blank=True)
|
||||
trustees = models.CharField(max_length=4096)
|
||||
|
||||
def EID_hr(self):
|
||||
EID_json = json.loads(self.EID)
|
||||
return EID_json['hr']
|
||||
|
||||
def EID_crypto(self):
|
||||
EID_json = json.loads(self.EID)
|
||||
EID_crypto_str = EID_json['crypto']
|
||||
return json.loads(EID_crypto_str)
|
||||
|
||||
def duration(self):
|
||||
duration_str = self.start_time.strftime("%d-%m-%y %H:%M")
|
||||
duration_str = duration_str + " - " + self.end_time.strftime("%d-%m-%y %H:%M %Z")
|
||||
duration_str = self.start_time_formatted()
|
||||
duration_str = duration_str + " - " + self.end_time_formatted_utc()
|
||||
return duration_str
|
||||
|
||||
def start_time_formatted(self):
|
||||
return self.start_time.strftime("%d-%m-%y %H:%M")
|
||||
|
||||
def start_time_formatted_utc(self):
|
||||
return self.start_time.strftime("%d-%m-%y %H:%M %Z")
|
||||
|
||||
def end_time_formatted(self):
|
||||
return self.end_time.strftime("%d-%m-%y %H:%M")
|
||||
|
||||
def end_time_formatted_utc(self):
|
||||
return self.end_time.strftime("%d-%m-%y %H:%M %Z")
|
||||
|
||||
def status(self):
|
||||
status_str = ""
|
||||
|
||||
|
@ -113,14 +143,4 @@ class Organiser(models.Model):
|
|||
email = models.CharField(max_length=100, blank=False, null=False)
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||
|
||||
'''
|
||||
class Organiser(models.Model):
|
||||
user = models.ForeignKey(DemoUser, on_delete=models.CASCADE)
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||
|
||||
class Trustee(models.Model):
|
||||
user = models.ForeignKey(DemoUser, on_delete=models.CASCADE)
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||
'''
|
||||
#class EventOrganisers():
|
||||
#event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||
|
|
|
@ -1,14 +1,29 @@
|
|||
from __future__ import absolute_import
|
||||
import csv
|
||||
from os import urandom
|
||||
|
||||
import base64
|
||||
from io import StringIO
|
||||
import json
|
||||
from os import urandom
|
||||
from celery import task
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import EmailValidator
|
||||
from django.core.mail import send_mail
|
||||
from allauthdemo.polls.models import Ballot, Event, EmailUser, AccessKey
|
||||
from .cpp_calls import param, combpk, addec, tally
|
||||
from django.conf import settings
|
||||
|
||||
from allauthdemo.polls.models import AccessKey
|
||||
|
||||
from .crypto_rpc import param, combpk, addec, tally
|
||||
|
||||
'''
|
||||
Goal: This py file defines celery tasks that can be initiated
|
||||
|
||||
The following tasks were re-implemented by Thomas Smith: generate_event_param, tally_results, generate_combpk, generate_enc
|
||||
|
||||
This file was also updated by Vincent de Almeida
|
||||
'''
|
||||
|
||||
# Will store the result of the initial cal to param() from .cpp_calls
|
||||
group_param = None
|
||||
|
||||
def is_valid_email(email):
|
||||
try:
|
||||
|
@ -23,36 +38,59 @@ def create_ballots(poll):
|
|||
for voter in poll.event.voters.all():
|
||||
ballot = poll.ballots.create(voter=voter, poll=poll)
|
||||
|
||||
@task()
|
||||
def create_voters(csvfile, event):
|
||||
print("Creating voters for event " + event.title)
|
||||
reader = csv.reader(csvfile, delimiter=',')
|
||||
string = ""
|
||||
for row in reader:
|
||||
email = string.join(row)
|
||||
print(email)
|
||||
#testvoter = EmailUser.objects.get_or_create(email='notarealemail@live.com')[0]
|
||||
#event.voters.add(testvoter)
|
||||
if (is_valid_email(email)):
|
||||
voter = EmailUser.objects.get_or_create(email=email)[0]
|
||||
event.voters.add(voter)
|
||||
key = base64.urlsafe_b64encode(urandom(16)).decode('utf-8')
|
||||
AccessKey.objects.create(user=voter, event=event, key=key)
|
||||
send_mail(
|
||||
'Your Voting Key',
|
||||
'Key: ' + key,
|
||||
'from@example.com',
|
||||
[string.join(row)],
|
||||
fail_silently=False,
|
||||
)
|
||||
'''
|
||||
|
||||
Starting here: functions re-implemented by Thomas Smith
|
||||
Will generate a key for accessing either the event preparation page or the voting page
|
||||
'''
|
||||
def gen_access_key():
|
||||
return base64.urlsafe_b64encode(urandom(16)).decode('utf-8')
|
||||
|
||||
'''
|
||||
Emails an event preparation URL containing an access key for all of the trustees for an event
|
||||
'''
|
||||
@task()
|
||||
def generate_event_param(event):
|
||||
event.EID = param()
|
||||
def email_trustees_prep(trustees, event):
|
||||
email_subject = "Key Generation and Preparation for Event '" + event.title + "'"
|
||||
|
||||
# Plain text email - this could be replaced for a HTML-based email in the future
|
||||
email_body = "Please visit the following URL to prepare the event and generate your trustee secret key:\n\n"
|
||||
url_base = "http://" + settings.DOMAIN + "/event/" + str(event.pk) + "/prepare/?key="
|
||||
email_body = email_body + url_base
|
||||
|
||||
for trustee in trustees:
|
||||
# Generate a key and create an AccessKey object
|
||||
key = gen_access_key()
|
||||
AccessKey.objects.create(user=trustee, event=event, key=key)
|
||||
|
||||
trustee.send_email(email_subject, email_body + key)
|
||||
|
||||
'''
|
||||
Emails the access keys for all of the voters for an event
|
||||
'''
|
||||
@task()
|
||||
def email_voters_a_key(voters, event):
|
||||
email_subject = "Voting Access for Event '" + event.title + "'"
|
||||
email_body = 'Key: '
|
||||
|
||||
for voter in voters:
|
||||
# Generate a key and create an AccessKey object
|
||||
key = gen_access_key()
|
||||
AccessKey.objects.create(user=voter, event=event, key=key)
|
||||
|
||||
voter.send_email(email_subject, email_body + key)
|
||||
|
||||
'''
|
||||
Updates the EID of an event to contain 2 event IDs: a human readable one (hr) and a crypto one (GP from param())
|
||||
'''
|
||||
@task()
|
||||
def update_EID(event):
|
||||
global group_param
|
||||
if group_param is None:
|
||||
group_param = param()
|
||||
|
||||
EID = {}
|
||||
EID['hr'] = event.EID
|
||||
EID['crypto'] = group_param
|
||||
event.EID = json.dumps(EID)
|
||||
event.save()
|
||||
|
||||
@task()
|
||||
|
@ -98,20 +136,4 @@ def generate_enc(poll):
|
|||
poll.enc = addec(amount, ciphers)
|
||||
poll.save()
|
||||
|
||||
'''
|
||||
|
||||
End of re-implemented code
|
||||
|
||||
'''
|
||||
|
||||
@task()
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
@task()
|
||||
def mul(x, y):
|
||||
return x * y
|
||||
|
||||
@task()
|
||||
def xsum(numbers):
|
||||
return sum(numbers)
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import re
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from django.utils.dateparse import parse_datetime
|
||||
|
||||
from allauthdemo.polls.models import Event
|
||||
from allauthdemo.polls.models import Poll
|
||||
from allauthdemo.polls.models import PollOption
|
||||
from allauthdemo.polls.models import EmailUser
|
||||
from allauthdemo.polls.models import Event, Poll, PollOption, EmailUser
|
||||
from allauthdemo.auth.models import DemoUser
|
||||
|
||||
'''
|
||||
|
@ -382,6 +378,7 @@ class EventModelAdaptor:
|
|||
errors_summary = errors_summary + str(i + 1) + " "
|
||||
|
||||
self.invalid_form_fields['polls_data'] = {'val': polls_json}
|
||||
self.invalid_form_fields['poll_count'] = {'val': poll_count}
|
||||
|
||||
if not valid and len(errors_summary) > 34:
|
||||
errors_summary = errors_summary + "and can be corrected by editing them."
|
||||
|
@ -536,7 +533,7 @@ class EventModelAdaptor:
|
|||
organisers_list = self.form_data.pop('organiser-email-input')
|
||||
|
||||
for organiser in organisers_list:
|
||||
if organiser != '' and DemoUser.objects.filter(email=organiser).count() == 1:
|
||||
if organiser != '' and DemoUser.objects.filter(email=organiser).exists():
|
||||
self.organisers.append(DemoUser.objects.filter(email=organiser).get())
|
||||
|
||||
# Extract the list of trustees
|
||||
|
@ -544,7 +541,7 @@ class EventModelAdaptor:
|
|||
|
||||
for trustee in trustees_list:
|
||||
if trustee != '':
|
||||
if EmailUser.objects.filter(email=trustee).count() == 1:
|
||||
if EmailUser.objects.filter(email=trustee).exists():
|
||||
self.trustees.append(EmailUser.objects.filter(email=trustee).get())
|
||||
else:
|
||||
self.trustees.append(EmailUser(email=trustee))
|
||||
|
@ -555,7 +552,7 @@ class EventModelAdaptor:
|
|||
|
||||
for voter_email in voters_email_list:
|
||||
if voter_email != '':
|
||||
if EmailUser.objects.filter(email=voter_email).count() == 1:
|
||||
if EmailUser.objects.filter(email=voter_email).exists():
|
||||
self.voters.append(EmailUser.objects.filter(email=voter_email).get())
|
||||
else:
|
||||
self.voters.append(EmailUser(email=voter_email))
|
||||
|
@ -634,14 +631,15 @@ class EventModelAdaptor:
|
|||
|
||||
# Add the list of trustees to the event, making sure they're instantiated
|
||||
for trustee in self.trustees:
|
||||
if EmailUser.objects.filter(email=trustee.email).count() == 0:
|
||||
if not EmailUser.objects.filter(email=trustee.email).exists():
|
||||
trustee.save()
|
||||
|
||||
self.event.users_trustees = self.trustees
|
||||
|
||||
# Add the list of voters to the event, making sure they're instantiated
|
||||
# Additionally, generating the AccessKey for voters
|
||||
for voter in self.voters:
|
||||
if EmailUser.objects.filter(email=voter.email).count() == 0:
|
||||
if not EmailUser.objects.filter(email=voter.email).exists():
|
||||
voter.save()
|
||||
|
||||
self.event.voters = self.voters
|
||||
|
@ -655,10 +653,10 @@ class EventModelAdaptor:
|
|||
|
||||
self.event.save()
|
||||
|
||||
# Finally perform a data clean up
|
||||
self.__clear_data()
|
||||
# Finally return a reference to the event
|
||||
return self.event
|
||||
|
||||
def __clear_data(self):
|
||||
def clear_data(self):
|
||||
self.form_data = None
|
||||
self.form_data_validation = None
|
||||
self.invalid_form_fields = {}
|
||||
|
|
|
@ -17,8 +17,7 @@ from .forms import EventForm, PollForm, OptionFormset, QuestionFormset, Organise
|
|||
from .models import Event, Poll, PollOption, EmailUser, Ballot, TrusteeKey, Decryption
|
||||
from allauthdemo.auth.models import DemoUser
|
||||
|
||||
from .tasks import create_voters, create_ballots, generate_event_param, generate_combpk, generate_enc, tally_results
|
||||
from .cpp_calls import param, addec, combpk, tally
|
||||
from .tasks import email_trustees_prep, update_EID, generate_combpk, generate_enc, tally_results
|
||||
|
||||
from .utils.EventModelAdaptor import EventModelAdaptor
|
||||
|
||||
|
@ -38,6 +37,7 @@ class EventDetailView(generic.DetailView):
|
|||
def get_context_data(self, **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['now'] = timezone.now()
|
||||
return context
|
||||
|
||||
|
@ -153,28 +153,42 @@ def view_poll(request, event_id, poll_num):
|
|||
})
|
||||
|
||||
def event_trustee_setup(request, event_id):
|
||||
# Obtain the event and the event preparation access key that's been supplied
|
||||
event = get_object_or_404(Event, pk=event_id)
|
||||
access_key = request.GET.get('key', None)
|
||||
if (access_key):
|
||||
|
||||
# If the a_key is present, check it's valid and related to a trustee EmailUser instance for this event
|
||||
if 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 (TrusteeKey.objects.filter(event=event, user=email_key[0].user).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():
|
||||
messages.add_message(request, messages.WARNING, 'You have already submitted your key for this event')
|
||||
return HttpResponseRedirect(reverse("user_home"))
|
||||
if (request.method == "POST"):
|
||||
if request.method == "POST":
|
||||
form = EventSetupForm(request.POST)
|
||||
if (form.is_valid()):
|
||||
|
||||
# If form data is valid, create a TrusteeKey object with the supplied public key
|
||||
if form.is_valid():
|
||||
public_key = request.POST["public_key"]
|
||||
key = TrusteeKey.objects.get_or_create(event=event, user=email_key[0].user)[0]
|
||||
key.key = public_key
|
||||
key.save()
|
||||
if (event.trustee_keys.count() == event.users_trustees.count()): # ready for combpk
|
||||
|
||||
# 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 -
|
||||
# voters therefore need to be informed
|
||||
if event.trustee_keys.count() == event.users_trustees.count():
|
||||
generate_combpk.delay(event)
|
||||
messages.add_message(request, messages.SUCCESS, 'You have successfully submitted your public key for this event')
|
||||
# TODO: Create Celery task that generates voting URLs for voters as well as creates the ballots
|
||||
|
||||
success_msg = 'You have successfully submitted your public key for this event'
|
||||
messages.add_message(request, messages.SUCCESS, success_msg)
|
||||
|
||||
# This re-direct may not be appropriate for trustees that don't have logins
|
||||
return HttpResponseRedirect(reverse("user_home"))
|
||||
else:
|
||||
form = EventSetupForm()
|
||||
return render(request, "polls/event_setup.html", {"event": event, "form": form })
|
||||
return render(request, "polls/event_setup.html", {"event": event, "form": form})
|
||||
|
||||
#if no key or is invalid?
|
||||
messages.add_message(request, messages.WARNING, 'You do not have permission to access: ' + request.path)
|
||||
|
@ -247,11 +261,11 @@ def manage_questions(request, event_id):
|
|||
poll.save()
|
||||
formset = OptionFormset(request.POST, prefix="formset_organiser", instance=poll)
|
||||
if formset.is_valid():
|
||||
for form in formset:
|
||||
formset.save()
|
||||
create_ballots.delay(poll)
|
||||
messages.add_message(request, messages.SUCCESS, 'Poll created successfully')
|
||||
return HttpResponseRedirect(reverse('polls:view-poll', kwargs={'event_id': poll.event_id, 'poll_num': event.polls.count() }))
|
||||
formset.save()
|
||||
#create_ballots.delay(poll)
|
||||
messages.add_message(request, messages.SUCCESS, 'Poll created successfully')
|
||||
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})
|
||||
|
||||
elif request.method == "GET":
|
||||
|
@ -296,17 +310,28 @@ def create_event(request):
|
|||
'''Process form data based on above results'''
|
||||
if result['success']:
|
||||
if form_data_valid:
|
||||
# Create the new event using the form data
|
||||
adaptor.extractData()
|
||||
adaptor.updateModel()
|
||||
new_event = adaptor.updateModel()
|
||||
|
||||
# Update the EID to include the GP in its EID
|
||||
update_EID.delay(new_event)
|
||||
|
||||
# Send an email to all trustees for event preparation
|
||||
trustees = new_event.users_trustees.all()
|
||||
email_trustees_prep.delay(trustees, new_event)
|
||||
|
||||
adaptor.clear_data()
|
||||
|
||||
return HttpResponseRedirect(reverse('polls:index'))
|
||||
else:
|
||||
invalid_fields = adaptor.getInvalidFormFields()
|
||||
adaptor.clear_data()
|
||||
return render_invalid(request, events, demo_users, invalid_fields)
|
||||
|
||||
else:
|
||||
invalid_fields = adaptor.getInvalidFormFields()
|
||||
invalid_fields['recaptcha'] = {'error': 'The reCAPTCHA server validation failed, please try again.'}
|
||||
adaptor.clear_data()
|
||||
return render_invalid(request, events, demo_users, invalid_fields)
|
||||
|
||||
elif request.method == "GET":
|
||||
|
|
Reference in a new issue