Merge pull request #11 from vincentmdealmeida/PartialDecryption
Full end-to-end voting is working using the new binary encoding schem…
This commit is contained in:
commit
3fd9173666
14 changed files with 631 additions and 555 deletions
293
Node/index.js
293
Node/index.js
|
@ -40,7 +40,7 @@ app.get('/', function(request, response){
|
|||
app.get('/param', function(request, response){
|
||||
var param = gpGen();
|
||||
|
||||
console.log('Generated Param:' + param);
|
||||
console.log('Generated Group Param');
|
||||
response.json(param);
|
||||
|
||||
});
|
||||
|
@ -98,164 +98,84 @@ app.post('/cmpkstring', function(request, response){
|
|||
|
||||
//addition function on homomorphically encrypted variables
|
||||
//this may need some work, different method of serialisation maybe?
|
||||
app.get('/addec', function(request, response){
|
||||
var c1 = request.query['C1'];
|
||||
var c2 = request.query['C2'];
|
||||
var number = request.query['number']; //number of ciphertexts to add
|
||||
//all the list of ciphertext objects to give to the function
|
||||
var parsed = [];
|
||||
app.post('/add_ciphers', function(request, response){
|
||||
console.log("\nEndpoint /add_ciphers called");
|
||||
const C1s = request.body.ciphers.c1s;
|
||||
const C2s = request.body.ciphers.c2s;
|
||||
const CIPHER_COUNT = C1s.length;
|
||||
|
||||
// Will store a list of parsed ciphers from the C1s and C2s arrays passed in
|
||||
var parsedCiphers = [];
|
||||
var ctx = new CTX("BN254CX");
|
||||
console.log('Addec:');
|
||||
|
||||
if(number == c1.length)
|
||||
if(CIPHER_COUNT > 1)
|
||||
{
|
||||
for (var i = 0; i < c1.length; i++) {
|
||||
console.log(i + ".C1: " + c1[i]);
|
||||
var c1Bytes = Buffer.from(c1[i].split(','), 'hex');
|
||||
console.log("Combining " + CIPHER_COUNT + " ciphers");
|
||||
|
||||
for (var i = 0; i < CIPHER_COUNT; i++) {
|
||||
|
||||
var c1Bytes = Buffer.from(C1s[i].split(','), 'hex');
|
||||
var newC1 = new ctx.ECP.fromBytes(c1Bytes);
|
||||
|
||||
var cipher =
|
||||
{
|
||||
C1:newC1,
|
||||
C2:null
|
||||
};
|
||||
parsed.push(cipher);
|
||||
|
||||
}
|
||||
|
||||
for (var j = 0; j < c2.length; j++) {
|
||||
console.log(j + ".C2: " + c2[j]);
|
||||
var c2Bytes = Buffer.from(c2[j].split(','), 'hex');
|
||||
var c2Bytes = Buffer.from(C2s[i].split(','), 'hex');
|
||||
var newC2 = new ctx.ECP.fromBytes(c2Bytes);
|
||||
|
||||
parsed[j].C2 = newC2;
|
||||
}
|
||||
}
|
||||
var cipher = {
|
||||
C1 : newC1,
|
||||
C2 : newC2
|
||||
};
|
||||
|
||||
else if(number == 1)
|
||||
{
|
||||
console.log("only one cipher");
|
||||
var c1Bytes = Buffer.from(c1.split(','), 'hex');
|
||||
parsedCiphers.push(cipher);
|
||||
}
|
||||
|
||||
} else if(CIPHER_COUNT === 1) {
|
||||
console.log("Combining only one cipher");
|
||||
|
||||
var c1Bytes = Buffer.from(C1s[0].split(','), 'hex');
|
||||
var newC1 = new ctx.ECP.fromBytes(c1Bytes);
|
||||
console.log("C1: " + c1);
|
||||
var c2Bytes = Buffer.from(c2.split(','), 'hex');
|
||||
|
||||
|
||||
var c2Bytes = Buffer.from(C2s[0].split(','), 'hex');
|
||||
var newC2 = new ctx.ECP.fromBytes(c2Bytes);
|
||||
console.log("C2: " + c2);
|
||||
|
||||
var cipher =
|
||||
{
|
||||
C1:newC1,
|
||||
C2:newC2
|
||||
};
|
||||
parsed.push(cipher);
|
||||
}
|
||||
|
||||
|
||||
response.json(add(parsed));
|
||||
});
|
||||
|
||||
|
||||
//tally partially decrypted ciphertexts
|
||||
app.get('/tally', function(request, response){
|
||||
console.log("\nEndpoint /tally called");
|
||||
var amount = request.query['number'];//number of decryptions taking in
|
||||
var paramString = request.query['param'];//event group parameter in JSON
|
||||
var partialsStrings = request.query['decs'];//array of partial decryption(s) in bytes
|
||||
var ciphertextString = request.query['cipher'];//ciphertext being decrypted in JSON
|
||||
|
||||
//re-build parameters
|
||||
var tempParams = JSON.parse(JSON.parse(paramString).crypto);
|
||||
var ctx = new CTX("BN254CX"); //new context we can use
|
||||
var n = new ctx.BIG();
|
||||
var g1 = new ctx.ECP();
|
||||
var g2 = new ctx.ECP2();
|
||||
|
||||
//copying the values
|
||||
n.copy(tempParams.n);
|
||||
g1.copy(tempParams.g1);
|
||||
g2.copy(tempParams.g2);
|
||||
|
||||
var params = {
|
||||
n:n,
|
||||
g1:g1,
|
||||
g2:g2
|
||||
};
|
||||
|
||||
//re-build partial decryptions
|
||||
var partials = [];
|
||||
if(amount == partialsStrings.length)
|
||||
{
|
||||
console.log(amount + " partial decryptions");
|
||||
for(var i = 0; i < partialsStrings.length; i++)
|
||||
{
|
||||
var bytes = Buffer.from(partialsStrings[i].split(','), 'hex');
|
||||
|
||||
var dec = {
|
||||
D:new ctx.ECP.fromBytes(bytes)
|
||||
};
|
||||
|
||||
partials.push(dec);
|
||||
}
|
||||
}
|
||||
else if(amount == 1)
|
||||
{
|
||||
console.log("\nOnly one partial decryption received\n");
|
||||
console.log(JSON.parse(paramString).crypto + "\n");
|
||||
|
||||
var bytes = Buffer.from(partialsStrings.split(','), 'hex');
|
||||
var dec = {
|
||||
D : new ctx.ECP.fromBytes(bytes)
|
||||
C1 : newC1,
|
||||
C2 : newC2
|
||||
};
|
||||
|
||||
partials.push(dec);
|
||||
parsedCiphers.push(cipher);
|
||||
}
|
||||
|
||||
//re-build combined ciphertext
|
||||
var tempCipher = JSON.parse(ciphertextString);
|
||||
// Combine the ciphers here
|
||||
var combinedCipher = add(parsedCiphers);
|
||||
|
||||
var cipher = {
|
||||
C1: new ctx.ECP(),
|
||||
C2: new ctx.ECP()
|
||||
// Get the byte string of the C1 and C2 part for transmission
|
||||
var C1Bytes = [];
|
||||
combinedCipher.C1.toBytes(C1Bytes);
|
||||
|
||||
var C2Bytes = [];
|
||||
combinedCipher.C2.toBytes(C2Bytes);
|
||||
|
||||
var responseData = {
|
||||
C1: C1Bytes.toString(),
|
||||
C2: C2Bytes.toString()
|
||||
};
|
||||
|
||||
cipher.C1.copy(tempCipher.C1);
|
||||
cipher.C2.copy(tempCipher.C2);
|
||||
|
||||
response.json(tally(params, partials, cipher))
|
||||
});
|
||||
|
||||
app.post('/comb_sks', function(request, response){
|
||||
console.log("\nEndpoint /comb_sks called");
|
||||
const SKsAsStrings = request.body.SKs;
|
||||
|
||||
// Parse and combine the secret keys
|
||||
var ctx = new CTX("BN254CX");
|
||||
var parsedSKs = [];
|
||||
|
||||
for(var i = 0; i < SKsAsStrings.length; i++) {
|
||||
var skBytes = SKsAsStrings[i].split(",");
|
||||
parsedSKs.push(new ctx.BIG.fromBytes(skBytes));
|
||||
}
|
||||
|
||||
console.log("Combining " + parsedSKs.length + " SKs...");
|
||||
var SKBytes = [];
|
||||
combine_sks(parsedSKs).SK.toBytes(SKBytes);
|
||||
|
||||
response.send(SKBytes.toString());
|
||||
response.json(responseData);
|
||||
});
|
||||
|
||||
app.post('/get_tally', function(request, response){
|
||||
const COUNT = request.body.count;
|
||||
console.log("\nEndpoint /get_tally called");
|
||||
|
||||
// Extract the data from the request
|
||||
const TEMP_PARAMS = JSON.parse(JSON.parse(request.body.param).crypto);
|
||||
const C1s = request.body.ciphers.c1s;
|
||||
const C2s = request.body.ciphers.c2s;
|
||||
const SK = request.body.sk;
|
||||
const BALLOT_CIPHER = request.body.ballot_cipher;
|
||||
const PART_DECS = request.body.part_decs;
|
||||
const VOTERS_COUNT = request.body.voters_count;
|
||||
|
||||
console.log("\nFrom /get_tally - C1 array length (num of voters for the opt): " + C1s.length);
|
||||
|
||||
//re-build parameters
|
||||
var ctx = new CTX("BN254CX"); //new context we can use
|
||||
// Re-build parameters
|
||||
var ctx = new CTX("BN254CX");
|
||||
var n = new ctx.BIG();
|
||||
var g1 = new ctx.ECP();
|
||||
var g2 = new ctx.ECP2();
|
||||
|
@ -265,31 +185,39 @@ app.post('/get_tally', function(request, response){
|
|||
g2.copy(TEMP_PARAMS.g2);
|
||||
|
||||
var params = {
|
||||
n:n,
|
||||
g1:g1,
|
||||
g2:g2
|
||||
n : n,
|
||||
g1 : g1,
|
||||
g2 : g2
|
||||
};
|
||||
|
||||
//rebuild our secret key
|
||||
var skBytes = SK.split(",");
|
||||
var sk = new ctx.BIG.fromBytes(skBytes);
|
||||
// Initialise the ballot cipher
|
||||
var c1Bytes = Buffer.from(BALLOT_CIPHER.C1.split(','), 'hex');
|
||||
var newC1 = new ctx.ECP.fromBytes(c1Bytes);
|
||||
|
||||
var tally = 0;
|
||||
var c2Bytes = Buffer.from(BALLOT_CIPHER.C2.split(','), 'hex');
|
||||
var newC2 = new ctx.ECP.fromBytes(c2Bytes);
|
||||
|
||||
for(var i = 0; i < COUNT; i++) {
|
||||
var c1Bytes = Buffer.from(C1s[i].split(','), 'hex');
|
||||
var newC1 = new ctx.ECP.fromBytes(c1Bytes);
|
||||
var cipher =
|
||||
{
|
||||
C1 : newC1,
|
||||
C2 : newC2
|
||||
};
|
||||
|
||||
var c2Bytes = Buffer.from(C2s[i].split(','), 'hex');
|
||||
var newC2 = new ctx.ECP.fromBytes(c2Bytes);
|
||||
// Initialise all of the partial decryptions
|
||||
var partials = [];
|
||||
for(var i = 0; i < PART_DECS.length; i++)
|
||||
{
|
||||
var bytes = Buffer.from(PART_DECS[i].split(','), 'hex');
|
||||
|
||||
var cipher = {C1: newC1, C2: newC2};
|
||||
tally += decrypt(params, sk, cipher).M;
|
||||
var dec = {
|
||||
D : new ctx.ECP.fromBytes(bytes)
|
||||
};
|
||||
|
||||
partials.push(dec);
|
||||
}
|
||||
|
||||
console.log("Tally: " + tally + "\n");
|
||||
|
||||
response.send("" + tally);
|
||||
// Send the decrypted cipher value (vote tally for an option)
|
||||
response.send("" + getCipherVal(params, partials, cipher, VOTERS_COUNT).M);
|
||||
});
|
||||
|
||||
var server = app.listen(port, function(){
|
||||
|
@ -311,7 +239,7 @@ https://github.com/milagro-crypto/milagro-crypto-js
|
|||
|
||||
|
||||
//Group parameter generator: returns rng object and generators g1,g2 for G1,G2 as well as order
|
||||
gpGen = function(){
|
||||
gpGen = function() {
|
||||
//init, and base generators
|
||||
var ctx = new CTX("BN254CX");
|
||||
|
||||
|
@ -348,7 +276,7 @@ gpGen = function(){
|
|||
|
||||
|
||||
//creates ElGamal public and secret key
|
||||
keyGen=function(params){
|
||||
keyGen = function(params) {
|
||||
var ctx = new CTX("BN254CX");
|
||||
//set rng
|
||||
var RAW = [];
|
||||
|
@ -376,7 +304,7 @@ keyGen=function(params){
|
|||
|
||||
//combine multiple public key together
|
||||
//the input is an array of PKs
|
||||
combine_pks=function(PKs){
|
||||
combine_pks = function(PKs) {
|
||||
var ctx = new CTX("BN254CX");
|
||||
var pk=new ctx.ECP();
|
||||
//copy the first pk
|
||||
|
@ -393,7 +321,7 @@ combine_pks=function(PKs){
|
|||
|
||||
// Written by Vincent de Almeida: Combines multiple secret keys together
|
||||
// The SKs in the SKs array should already have been initialised using 'new ctx.BIG.fromBytes()'
|
||||
combine_sks=function(SKs) {
|
||||
combine_sks = function(SKs) {
|
||||
// 'add' the rest of the sks to the first
|
||||
var sk = SKs[0];
|
||||
|
||||
|
@ -407,7 +335,7 @@ combine_sks=function(SKs) {
|
|||
};
|
||||
|
||||
//ElGamal encryption
|
||||
encrypt=function(params,PK, m){
|
||||
encrypt = function(params,PK, m) {
|
||||
var ctx = new CTX("BN254CX");
|
||||
//set rand
|
||||
var RAW = [];
|
||||
|
@ -441,31 +369,30 @@ encrypt=function(params,PK, m){
|
|||
|
||||
|
||||
//add ciphertexts
|
||||
add=function(Ciphers){
|
||||
add = function(Ciphers) {
|
||||
var ctx = new CTX("BN254CX");
|
||||
var s1=new ctx.ECP();
|
||||
var s2=new ctx.ECP();
|
||||
var s1 = new ctx.ECP();
|
||||
var s2 = new ctx.ECP();
|
||||
|
||||
//copy the first cipher
|
||||
s1.copy(Ciphers[0].C1);
|
||||
s2.copy(Ciphers[0].C2);
|
||||
|
||||
//multiple the rest ciphertexts
|
||||
for(i=1;i<Ciphers.length;i++){
|
||||
for(var i = 1; i < Ciphers.length; i++){
|
||||
s1.add(Ciphers[i].C1);
|
||||
}
|
||||
//no idea why I need two loops
|
||||
for(j=1;j<Ciphers.length;j++){
|
||||
s2.add(Ciphers[j].C2);
|
||||
s2.add(Ciphers[i].C2);
|
||||
}
|
||||
|
||||
return{
|
||||
C1:s1,
|
||||
C2:s2
|
||||
return {
|
||||
C1 : s1,
|
||||
C2 : s2
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//ElGamal decryption
|
||||
decrypt=function(params,SK, C){
|
||||
decrypt = function(params,SK, C, votersCount) {
|
||||
var ctx = new CTX("BN254CX");
|
||||
var D=new ctx.ECP();
|
||||
D = ctx.PAIR.G1mul(C.C1,SK);
|
||||
|
@ -474,9 +401,9 @@ decrypt=function(params,SK, C){
|
|||
gM.copy(C.C2);
|
||||
gM.sub(D);
|
||||
|
||||
//search for message by brute force
|
||||
// Search for value based on the number of voters
|
||||
var B;
|
||||
for (j = 0; j < 1000; j++) {
|
||||
for (var j = 0; j <= votersCount; j++) {
|
||||
//use D as temp var
|
||||
B = new ctx.BIG(j);
|
||||
D = ctx.PAIR.G1mul(params.g1,B);
|
||||
|
@ -485,8 +412,7 @@ decrypt=function(params,SK, C){
|
|||
M:j
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return{
|
||||
M: "Error"
|
||||
|
@ -495,27 +421,28 @@ decrypt=function(params,SK, C){
|
|||
|
||||
|
||||
//ElGamal partial decryption
|
||||
partDec=function(SK, C){
|
||||
partDec = function(SK, C) {
|
||||
var ctx = new CTX("BN254CX");
|
||||
var D=new ctx.ECP();
|
||||
var D = new ctx.ECP();
|
||||
D = ctx.PAIR.G1mul(C.C1,SK);
|
||||
|
||||
return{
|
||||
return {
|
||||
D: D
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
//Tally, combine partial decryption
|
||||
//Ds is the array of partial decryptions; C is the ciphertext.
|
||||
tally=function(params,Ds, C){
|
||||
// Combines partial decryptions to enable the decryption of a cipher text which will be an int val representing
|
||||
// a tally of votes for an option. Ds is the array of partial decryptions; C is the ciphertext.
|
||||
getCipherVal = function(params, Ds, C, votersCount) {
|
||||
// Create a context and initialise the first decryption part
|
||||
var ctx = new CTX("BN254CX");
|
||||
var D=new ctx.ECP();
|
||||
var D = new ctx.ECP();
|
||||
D.copy(Ds[0].D);
|
||||
|
||||
//combine D
|
||||
for(i=1;i<Ds.length;i++){
|
||||
// Combine the decryptions (in Ds array) into a single decryption by adding them to D
|
||||
for(var i = 1; i < Ds.length; i++){
|
||||
D.add(Ds[i].D);
|
||||
}
|
||||
|
||||
|
@ -524,9 +451,9 @@ tally=function(params,Ds, C){
|
|||
gM.copy(C.C2);
|
||||
gM.sub(D);
|
||||
|
||||
//search for message by brute force
|
||||
// Search for the value based on the number of voters
|
||||
var B;
|
||||
for (var j = 0; j < 1000; j++) {
|
||||
for (var j = 0; j <= votersCount; j++) {
|
||||
//use D as temp var
|
||||
B = new ctx.BIG(j);
|
||||
D = ctx.PAIR.G1mul(params.g1,B);
|
||||
|
@ -537,7 +464,7 @@ tally=function(params,Ds, C){
|
|||
|
||||
}
|
||||
|
||||
|
||||
// If the search failed
|
||||
return{
|
||||
M: "Error"
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.contrib import admin
|
|||
# Register your models here.
|
||||
|
||||
from allauthdemo.auth.models import DemoUser
|
||||
from .models import Event, PollOption, Poll, Organiser
|
||||
from .models import Event, PollOption, Poll
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
@ -3,11 +3,12 @@ import urllib2
|
|||
|
||||
'''
|
||||
|
||||
All functions in this file have been re-implemenented by Thomas Smith
|
||||
All functions in this file have been re-implemenented by Vincent de Almeida
|
||||
|
||||
File then updated by Vincent de Almeida. Changes include:
|
||||
Changes include:
|
||||
-Update filename to 'crypto_rpc' to reflect the RPC nature of the methods
|
||||
-Modified RPC calls that send data to POST requests to avoid large query URLs
|
||||
-Modified RPC calls that send data to POST requests to avoid large query URLs (using a helper function)
|
||||
-Added a new cipher combination and tally function
|
||||
|
||||
'''
|
||||
|
||||
|
@ -39,58 +40,24 @@ def combpk(pks):
|
|||
return send_post_req(url, data)
|
||||
|
||||
|
||||
def addec(amount, ciphers):
|
||||
url = 'http://localhost:8080/addec'
|
||||
querystring = '?number='+str(amount)
|
||||
c1s = ciphers['c1s']
|
||||
c2s = ciphers['c2s']
|
||||
for i, value in enumerate(c1s):
|
||||
querystring += "&C1="+str(c1s[i])
|
||||
querystring += "&C2="+str(c2s[i])
|
||||
def add_ciphers(ciphers):
|
||||
url = 'http://localhost:8080/add_ciphers'
|
||||
|
||||
print(url+querystring)
|
||||
jsondict = json.load(urllib2.urlopen(url+querystring))
|
||||
print(json.dumps(jsondict))
|
||||
return json.dumps(jsondict)
|
||||
|
||||
|
||||
# Deprecated functionality and has been superseded by get_tally
|
||||
def tally(amount, group_param, decs, cipher):
|
||||
url = 'http://localhost:8080/tally'
|
||||
querystring = '?number='+str(amount)
|
||||
querystring += '¶m='+urllib2.quote(str(group_param))
|
||||
|
||||
for i, value in enumerate(decs):
|
||||
querystring += "&decs="+str(value)
|
||||
|
||||
querystring += '&cipher=' + urllib2.quote(str(cipher))
|
||||
|
||||
jsondict = json.load(urllib2.urlopen(url+querystring))
|
||||
|
||||
return str(jsondict['M'])
|
||||
|
||||
|
||||
def combine_sks(sks):
|
||||
url = 'http://localhost:8080/comb_sks'
|
||||
|
||||
# Construct POST data
|
||||
data = {}
|
||||
data['SKs'] = sks
|
||||
data['ciphers'] = ciphers
|
||||
|
||||
# Return the new combined SK
|
||||
return send_post_req(url, data)
|
||||
return json.loads(send_post_req(url, data))
|
||||
|
||||
|
||||
def get_tally(count, ciphers, sk, group_param):
|
||||
def get_tally(ballot_cipher, part_decs, group_param, voters_count):
|
||||
url = 'http://localhost:8080/get_tally'
|
||||
|
||||
# Construct POST data
|
||||
data = {}
|
||||
data['count'] = count
|
||||
data['ciphers'] = ciphers
|
||||
data['sk'] = sk
|
||||
data['ballot_cipher'] = ballot_cipher
|
||||
data['part_decs'] = part_decs
|
||||
data['param'] = group_param
|
||||
data['voters_count'] = voters_count
|
||||
|
||||
# Return the tally of votes for the option
|
||||
return send_post_req(url, data)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ from crispy_forms.layout import LayoutObject, Layout, TEMPLATE_PACK, Fieldset, B
|
|||
from crispy_forms.bootstrap import StrictButton, TabHolder, Tab, FormActions, PrependedText, PrependedAppendedText, Accordion, AccordionGroup
|
||||
from captcha.fields import ReCaptchaField
|
||||
from allauthdemo.auth.models import DemoUser
|
||||
from .models import Event, Poll, PollOption, Organiser
|
||||
from .models import Event, Poll, PollOption
|
||||
|
||||
def is_valid_email(email):
|
||||
try:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from django.core.mail import send_mail
|
||||
from django.db import models
|
||||
|
@ -8,6 +9,7 @@ from django.utils import timezone
|
|||
|
||||
from allauthdemo.auth.models import DemoUser
|
||||
|
||||
|
||||
class EmailUser(models.Model):
|
||||
email = models.CharField(max_length=80, unique=True)
|
||||
|
||||
|
@ -35,16 +37,23 @@ class Event(models.Model):
|
|||
creator = models.CharField(max_length=256, blank=True)
|
||||
c_email = models.CharField(max_length=512, blank=True)
|
||||
trustees = models.CharField(max_length=4096)
|
||||
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
|
||||
# Custom helper methods
|
||||
def EID_hr(self):
|
||||
EID_json = json.loads(self.EID)
|
||||
return EID_json['hr']
|
||||
try:
|
||||
EID_json = json.loads(self.EID)
|
||||
return EID_json['hr']
|
||||
except ValueError:
|
||||
return self.EID
|
||||
|
||||
def EID_crypto(self):
|
||||
EID_json = json.loads(self.EID)
|
||||
EID_crypto_str = EID_json['crypto']
|
||||
return json.loads(EID_crypto_str)
|
||||
try:
|
||||
EID_json = json.loads(self.EID)
|
||||
EID_crypto_str = EID_json['crypto']
|
||||
return json.loads(EID_crypto_str)
|
||||
except ValueError:
|
||||
return "None - Event not Initialised"
|
||||
|
||||
def duration(self):
|
||||
duration_str = self.start_time_formatted()
|
||||
|
@ -63,26 +72,55 @@ class Event(models.Model):
|
|||
def end_time_formatted_utc(self):
|
||||
return self.end_time.strftime("%d-%m-%y %H:%M %Z")
|
||||
|
||||
# Total number of options in all polls
|
||||
def total_num_opts(self):
|
||||
polls = self.polls.all()
|
||||
count = 0
|
||||
|
||||
for poll in polls:
|
||||
count += poll.options.all().count()
|
||||
|
||||
return count
|
||||
|
||||
def total_num_partial_decs(self):
|
||||
polls = self.polls.all()
|
||||
count = 0
|
||||
|
||||
for poll in polls:
|
||||
count += PartialBallotDecryption.objects.filter(poll=poll).count()
|
||||
|
||||
return count
|
||||
|
||||
def all_part_decs_received(self):
|
||||
received = False
|
||||
|
||||
if self.total_num_partial_decs() == self.total_num_opts() * self.users_trustees.all().count():
|
||||
received = True
|
||||
|
||||
return received
|
||||
|
||||
def status(self):
|
||||
status_str = ""
|
||||
|
||||
# Get the current date and time to compare against to establish if this is a past, current or
|
||||
# future event
|
||||
# future event. Prepared means the public key has been initialised
|
||||
present = timezone.now()
|
||||
|
||||
if self.ended is False:
|
||||
if present < self.start_time and self.public_key is None:
|
||||
if present < self.start_time and self.prepared is False:
|
||||
status_str = "Future"
|
||||
elif present < self.start_time and self.public_key is not None:
|
||||
elif present < self.start_time and self.prepared is True:
|
||||
status_str = "Prepared"
|
||||
elif present >= self.start_time and present <= self.end_time and self.public_key is not None:
|
||||
elif present >= self.start_time and present <= self.end_time and self.prepared is True:
|
||||
status_str = "Active"
|
||||
elif present > self.end_time and self.public_key is not None:
|
||||
elif present >= self.start_time and present <= self.end_time and self.prepared is False:
|
||||
status_str = "Future"
|
||||
elif present > self.end_time:
|
||||
status_str = "Expired"
|
||||
else:
|
||||
if self.event_sk.all().count() == 1:
|
||||
if self.all_part_decs_received():
|
||||
status_str = "Decrypted"
|
||||
elif self.event_sk.all().count() == 0:
|
||||
else:
|
||||
status_str = "Ended"
|
||||
|
||||
return status_str
|
||||
|
@ -109,13 +147,12 @@ class TrusteeKey(models.Model):
|
|||
user = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="trustee_keys")
|
||||
key = models.CharField(max_length=255, unique=True)
|
||||
|
||||
|
||||
class AccessKey(models.Model):
|
||||
event = models.ForeignKey(Event, 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)
|
||||
|
||||
#total = models.IntegerField(blank=True, null=True, default=0)
|
||||
|
||||
def has_started(self):
|
||||
return timezone.now() >= self.start
|
||||
|
||||
|
@ -125,60 +162,60 @@ class AccessKey(models.Model):
|
|||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class Poll(models.Model):
|
||||
question_text = models.CharField(max_length=200)
|
||||
total_votes = models.IntegerField(default=0)
|
||||
min_num_selections = models.IntegerField(default=0)
|
||||
max_num_selections = models.IntegerField(default=1)
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="polls")
|
||||
enc = models.CharField(max_length=4096, null=True)
|
||||
|
||||
#index = models.IntegerField()
|
||||
combined_ballots = 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)
|
||||
|
||||
def __str__(self):
|
||||
return self.question_text
|
||||
|
||||
|
||||
class PollOption(models.Model):
|
||||
choice_text = models.CharField(max_length=200)
|
||||
votes = models.IntegerField(default=0)
|
||||
question = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="options")
|
||||
#index = models.IntegerField()
|
||||
|
||||
def __str__(self):
|
||||
return self.choice_text
|
||||
|
||||
class Decryption(models.Model):
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="decryptions")
|
||||
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="decryptions")
|
||||
user = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="decryptions")
|
||||
text = models.CharField(max_length=1024)
|
||||
|
||||
class TrusteeSK(models.Model):
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="trustee_sk")
|
||||
trustee = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="trustee_sk")
|
||||
key = models.CharField(max_length=1024)
|
||||
class CombinedBallot(models.Model):
|
||||
poll = models.ForeignKey(Poll, 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_c2 = models.CharField(max_length=4096)
|
||||
|
||||
|
||||
# A partial decryption supplied by a trustee for a combined ballot that relates to a poll option
|
||||
class PartialBallotDecryption(models.Model):
|
||||
event = models.ForeignKey(Event, 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")
|
||||
user = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="decryption")
|
||||
text = models.CharField(max_length=4096)
|
||||
|
||||
class EventSK(models.Model):
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="event_sk")
|
||||
key = models.CharField(max_length=1024)
|
||||
|
||||
class Ballot(models.Model):
|
||||
voter = models.ForeignKey(EmailUser, on_delete=models.CASCADE, related_name="ballots")
|
||||
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="ballots")
|
||||
cast = models.BooleanField(default=False)
|
||||
|
||||
|
||||
# Implements the new binary encoding scheme
|
||||
class EncryptedVote(models.Model):
|
||||
ballot = models.ForeignKey(Ballot, on_delete=models.CASCADE, related_name="encrypted_vote")
|
||||
|
||||
|
||||
class VoteFragment(models.Model):
|
||||
encrypted_vote = models.ForeignKey(EncryptedVote, on_delete=models.CASCADE, related_name="fragment")
|
||||
cipher_text_c1 = models.CharField(max_length=4096)
|
||||
cipher_text_c2 = models.CharField(max_length=4096)
|
||||
|
||||
class Organiser(models.Model):
|
||||
index = models.IntegerField(default=0)
|
||||
email = models.CharField(max_length=100, blank=False, null=False)
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||
|
||||
|
||||
|
|
|
@ -7,9 +7,9 @@ from celery import task
|
|||
|
||||
from django.conf import settings
|
||||
|
||||
from allauthdemo.polls.models import AccessKey, Ballot, Decryption, TrusteeSK, EventSK
|
||||
from allauthdemo.polls.models import AccessKey, Ballot, CombinedBallot, PartialBallotDecryption
|
||||
|
||||
from .crypto_rpc import param, combpk, addec, tally, get_tally, combine_sks
|
||||
from .crypto_rpc import param, combpk, add_ciphers, get_tally
|
||||
|
||||
'''
|
||||
Goal: This py file defines celery tasks that can be initiated
|
||||
|
@ -22,28 +22,85 @@ from .crypto_rpc import param, combpk, addec, tally, get_tally, combine_sks
|
|||
# Will store the result of the initial cal to param() from .cpp_calls
|
||||
group_param = None
|
||||
|
||||
|
||||
'''
|
||||
Helper functions
|
||||
|
||||
gen_access_key - Will generate an a key for accessing either the event preparation page, voting page and decryption page
|
||||
email_trustees_dec - Will email trustees a link to begin decrypting the event
|
||||
|
||||
'''
|
||||
def gen_access_key():
|
||||
return base64.urlsafe_b64encode(urandom(16)).decode('utf-8')
|
||||
|
||||
|
||||
def email_trustees_dec(event):
|
||||
email_subject = "Event Ballot Decryption for '" + 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 submit your trustee secret key to begin event decryption:\n\n"
|
||||
email_body_base = str("")
|
||||
email_body_base += "Dear Trustee,\n\n"
|
||||
email_body_base += "You're now required to decrypt the event: " + event.title + \
|
||||
". This will require uploading your secret key that you have previously backed up.\n\n"
|
||||
email_body_base += "Please visit the following URL to submit your trustee secret key to begin event decryption:\n\n"
|
||||
url_base = "http://" + settings.DOMAIN + "/event/" + str(event.pk) + "/decrypt/?key="
|
||||
email_body = email_body + url_base
|
||||
email_body_base += url_base
|
||||
|
||||
sign_off = get_email_sign_off()
|
||||
|
||||
for trustee in event.users_trustees.all():
|
||||
# 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)
|
||||
email_body = str(email_body_base + key)
|
||||
email_body += sign_off
|
||||
|
||||
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"
|
||||
sign_off += "Kind Regards,\n"
|
||||
sign_off += "DEMOS 2 Admin - Lancaster University"
|
||||
|
||||
return sign_off
|
||||
|
||||
'''
|
||||
Combines all of the voter ballots for a poll option into a single 'CombinedBallot'
|
||||
'''
|
||||
def combine_ballots(polls):
|
||||
for poll in polls:
|
||||
options = poll.options.all()
|
||||
opt_count = len(options)
|
||||
ballots = Ballot.objects.filter(poll=poll)
|
||||
|
||||
for i in range(opt_count):
|
||||
option = options[i]
|
||||
|
||||
# Collect all fragments for this opt
|
||||
frags_c1 = list()
|
||||
frags_c2 = list()
|
||||
|
||||
for ballot in ballots:
|
||||
enc_vote = ballot.encrypted_vote.get()
|
||||
|
||||
if enc_vote is not None:
|
||||
fragments = enc_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)
|
||||
|
||||
CombinedBallot.objects.create(poll=poll,
|
||||
option=option,
|
||||
cipher_text_c1=combined_cipher['C1'],
|
||||
cipher_text_c2=combined_cipher['C2'])
|
||||
|
||||
@task()
|
||||
def create_ballots(event):
|
||||
|
@ -53,6 +110,7 @@ def create_ballots(event):
|
|||
for voter in voters:
|
||||
ballot = poll.ballots.create(voter=voter, poll=poll)
|
||||
|
||||
|
||||
@task()
|
||||
def create_ballots_for_poll(poll):
|
||||
for voter in poll.event.voters.all():
|
||||
|
@ -67,16 +125,29 @@ 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"
|
||||
email_body_base = str("")
|
||||
email_body_base += "Dear Trustee,\n\n"
|
||||
email_body_base += "You have been enrolled as a trustee onto the event: " + event.title + \
|
||||
". You are required to visit the URL below to generate your secret key and associated public" \
|
||||
" key that will be used to encrypt the event.\n\n You will need to ensure that you back up" \
|
||||
" your secret key as this will be needed to decrypt the event - please don't lose this as it" \
|
||||
" cannot be re-generated. DEMOS2 will never and cannot store your secret key.\n\n"
|
||||
email_body_base += "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
|
||||
email_body_base += url_base
|
||||
|
||||
sign_off = get_email_sign_off()
|
||||
|
||||
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)
|
||||
email_body = str(email_body_base + key)
|
||||
email_body += sign_off
|
||||
|
||||
trustee.send_email(email_subject, email_body)
|
||||
|
||||
|
||||
'''
|
||||
Emails a URL containing an access key for all of the voters for an event
|
||||
|
@ -86,13 +157,18 @@ def email_voters_vote_url(voters, event):
|
|||
email_subject = "Voting Access for Event '" + event.title + "'"
|
||||
|
||||
# Plain text email - this could be replaced for a HTML-based email in the future
|
||||
email_body_base = "Please visit the following URL in order to vote on the event '" + event.title + "':\n\n"
|
||||
url_base = "http://" + settings.DOMAIN + "/event/" + str(event.pk) + "/poll/1/vote/?key="
|
||||
email_body_base = email_body_base + url_base
|
||||
# TODO: The URL needs updating and it could be replaced with a single UUID that's unique
|
||||
# TODO: for the voter for an event which would shorten the URL
|
||||
email_body_base = str("")
|
||||
email_body_base += "Dear Voter,\n\n"
|
||||
email_body_base += "You have been enrolled as a voter onto the event: " + event.title + ".\n\nYou can vote between the following dates and times:\n"
|
||||
email_body_base += "Start: " + event.start_time_formatted_utc() + "\n"
|
||||
email_body_base += "End: " + event.end_time_formatted_utc() + "\n\n"
|
||||
email_body_base += "Please visit the following URL in order to vote on the event where further instructions can be found on the page:\n\n"
|
||||
url_base = "http://" + settings.DOMAIN + "/event/" + str(event.pk) + "/poll/" + str(event.polls.all()[0].uuid) + "/vote/?key="
|
||||
email_body_base += url_base
|
||||
|
||||
duration_info = "\n\nYou can vote between the following dates and times:\n"
|
||||
duration_info = duration_info + "Start: " + event.start_time_formatted_utc() + "\n"
|
||||
duration_info = duration_info + "End: " + event.end_time_formatted_utc()
|
||||
sign_off = get_email_sign_off()
|
||||
|
||||
for voter in voters:
|
||||
# Generate a key and create an AccessKey object
|
||||
|
@ -101,10 +177,11 @@ def email_voters_vote_url(voters, event):
|
|||
|
||||
# Update the email body to incl the access key as well as the duration information
|
||||
email_body = str(email_body_base + key)
|
||||
email_body = email_body + duration_info
|
||||
email_body += sign_off
|
||||
|
||||
voter.send_email(email_subject, email_body)
|
||||
|
||||
|
||||
'''
|
||||
Updates the EID of an event to contain 2 event IDs: a human readable one (hr) and a crypto one (GP from param())
|
||||
'''
|
||||
|
@ -120,95 +197,16 @@ def update_EID(event):
|
|||
event.EID = json.dumps(EID)
|
||||
event.save()
|
||||
|
||||
|
||||
@task()
|
||||
def event_ended(event):
|
||||
# Email all trustees to request their secret keys
|
||||
# Combine all the ballots for every option in every poll which will be decrypted by the trustees
|
||||
polls = event.polls.all()
|
||||
combine_ballots(polls)
|
||||
|
||||
# Email all trustees to request their partial decryptions using their secret keys
|
||||
email_trustees_dec(event)
|
||||
|
||||
@task()
|
||||
def gen_event_sk_and_dec(event):
|
||||
trustee_sks = TrusteeSK.objects.filter(event=event)
|
||||
t_sks_count = len(trustee_sks)
|
||||
|
||||
# Combine SKs if there's more than one
|
||||
event_sk = None
|
||||
if t_sks_count == 1:
|
||||
event_sk = trustee_sks.get().key
|
||||
else:
|
||||
t_sks_str_list = list()
|
||||
|
||||
for t_sk in trustee_sks:
|
||||
t_sks_str_list.append(t_sk.key)
|
||||
|
||||
event_sk = combine_sks(t_sks_str_list)
|
||||
|
||||
EventSK.objects.create(event=event, key=event_sk)
|
||||
|
||||
# With the event sk created, we can decrypt the event
|
||||
decrypt_and_tally(event)
|
||||
|
||||
@task()
|
||||
def decrypt_and_tally(event):
|
||||
polls = event.polls.all()
|
||||
sk = EventSK.objects.filter(event=event).get().key
|
||||
|
||||
for i in range(len(polls)):
|
||||
poll = polls[i]
|
||||
result = str("")
|
||||
result += "{\"name\": \"" + poll.question_text + "\","
|
||||
|
||||
# get num of opts and ballots
|
||||
options = poll.options.all()
|
||||
opt_count = len(options)
|
||||
ballots = Ballot.objects.filter(poll=poll, cast=True)
|
||||
|
||||
result += "\"options\": ["
|
||||
for j in range(opt_count):
|
||||
# Collect all fragments for this opt
|
||||
frags_c1 = list()
|
||||
frags_c2 = list()
|
||||
|
||||
for ballot in ballots:
|
||||
enc_vote = ballot.encrypted_vote.get()
|
||||
|
||||
if enc_vote is not None:
|
||||
fragments = enc_vote.fragment.all()
|
||||
frags_c1.append(fragments[j].cipher_text_c1)
|
||||
frags_c2.append(fragments[j].cipher_text_c2)
|
||||
|
||||
ciphers = {
|
||||
'c1s': frags_c1,
|
||||
'c2s': frags_c2
|
||||
}
|
||||
|
||||
count = len(frags_c1)
|
||||
votes = get_tally(count, ciphers, sk, event.EID)
|
||||
|
||||
result += "{\"option\": \"" + str(options[j].choice_text) + "\", \"votes\": " + str(votes) + "}"
|
||||
|
||||
if j != (opt_count-1):
|
||||
result += ","
|
||||
|
||||
result += "]}"
|
||||
|
||||
if i != (len(polls) - 1):
|
||||
result += ","
|
||||
|
||||
poll.enc = result
|
||||
poll.save()
|
||||
|
||||
@task()
|
||||
def tally_results(event):
|
||||
for poll in event.polls.all():
|
||||
decs = list()
|
||||
for dec in poll.decryptions.all():
|
||||
decs.append(dec.text)
|
||||
amount = len(decs)
|
||||
result = tally(amount, event.EID, decs, poll.enc)
|
||||
|
||||
# TODO: Email organisers using email_user method?
|
||||
|
||||
print(poll.question_text + ": " + result)
|
||||
|
||||
@task()
|
||||
def generate_combpk(event):
|
||||
|
@ -222,25 +220,54 @@ def generate_combpk(event):
|
|||
event.prepared = True
|
||||
event.save()
|
||||
|
||||
@task
|
||||
def generate_enc(poll):
|
||||
# c1 and c2 components of ciphertexts
|
||||
c1s = list()
|
||||
c2s = list()
|
||||
|
||||
for ballot in poll.ballots.all():
|
||||
if ballot.cast:
|
||||
c1s.append(str(ballot.cipher_text_c1))
|
||||
c2s.append(str(ballot.cipher_text_c2))
|
||||
@task()
|
||||
def combine_decryptions_and_tally(event):
|
||||
polls = event.polls.all()
|
||||
polls_count = len(polls)
|
||||
|
||||
ciphers = {
|
||||
'c1s': c1s,
|
||||
'c2s': c2s
|
||||
}
|
||||
for i in range(polls_count):
|
||||
poll = polls[i]
|
||||
result = str("")
|
||||
result += "{\"name\": \"" + poll.question_text + "\","
|
||||
|
||||
count = len(c1s)
|
||||
options = poll.options.all()
|
||||
opt_count = len(options)
|
||||
result += "\"options\": ["
|
||||
for j in range(opt_count):
|
||||
option = options[j]
|
||||
|
||||
poll.enc = addec(count, ciphers)
|
||||
poll.save()
|
||||
# Find the combined ballot for the current option of the current poll
|
||||
# and then extract the C1 and C2 components of the cipher that contains the tally
|
||||
combined_ballot = CombinedBallot.objects.filter(poll=poll,
|
||||
option=option)[0]
|
||||
|
||||
ballot_cipher = {}
|
||||
ballot_cipher['C1'] = combined_ballot.cipher_text_c1
|
||||
ballot_cipher['C2'] = combined_ballot.cipher_text_c2
|
||||
|
||||
# Collect all the partial decryptions for the ballot cipher which will decrypt the result
|
||||
part_decs = PartialBallotDecryption.objects.filter(event=event,
|
||||
poll=poll,
|
||||
option=option)
|
||||
|
||||
part_decs_text = list()
|
||||
for part_dec in part_decs:
|
||||
part_decs_text.append(part_dec.text)
|
||||
|
||||
# Get the vote tally for this option and add it to the results
|
||||
voters_count = event.voters.all().count()
|
||||
votes = get_tally(ballot_cipher, part_decs_text, event.EID, voters_count)
|
||||
result += "{\"option\": \"" + str(option.choice_text) + "\", \"votes\": \"" + str(votes) + "\"}"
|
||||
|
||||
if j != (opt_count-1):
|
||||
result += ","
|
||||
|
||||
result += "]}"
|
||||
|
||||
if i != (polls_count - 1):
|
||||
result += ","
|
||||
|
||||
poll.result_json = result
|
||||
poll.save()
|
||||
|
||||
|
|
|
@ -8,17 +8,17 @@ app_name = 'polls'
|
|||
urlpatterns = [
|
||||
url(r'^$', login_required(views.EventListView.as_view()), name='index'),
|
||||
url(r'^create/$', login_required(views.create_event), name='create-event'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', login_required(views.EventDetailView.as_view()), name='view-event'),
|
||||
url(r'^(?P<pk>[0-9]+)/polls/$', login_required(views.EventDetailPollsView.as_view()), name='event-polls'),
|
||||
url(r'^(?P<pk>[0-9]+)/entities/$', login_required(views.EventDetailEntitiesView.as_view()), name='event-entities'),
|
||||
url(r'^(?P<pk>[0-9]+)/advanced/$', login_required(views.EventDetailAdvancedView.as_view()), name='event-advanced'),
|
||||
url(r'^(?P<event_id>[0-9]+)/end/$', login_required(views.event_end), name='end-event'),
|
||||
url(r'^(?P<event_id>[0-9]+)/results/$', login_required(views.results), name='event-results'),
|
||||
url(r'^(?P<event_id>[0-9]+)/edit/$', login_required(views.edit_event), name='edit-event'),
|
||||
url(r'^(?P<event_id>[0-9]+)/delete/$', login_required(views.del_event), name='del-event'),
|
||||
url(r'^(?P<event_id>[0-9]+)/decrypt/$', views.event_trustee_decrypt, name='decrypt-event'),
|
||||
url(r'^(?P<event_id>[0-9]+)/prepare/$', views.event_trustee_setup, name='prepare-event'),
|
||||
url(r'^(?P<event_id>[0-9]+)/poll/(?P<poll_num>[0-9]+)/vote/$', views.event_vote, name='event-vote'),
|
||||
url(r'^(?P<event_id>[0-9]+)/create/poll/$', login_required(views.manage_questions), name='create-poll'),
|
||||
url(r'^(?P<event_id>[0-9]+)/poll/(?P<poll_num>[0-9]+)/edit$', login_required(views.edit_poll), name='edit-poll')
|
||||
url(r'^(?P<pk>[0-9a-f-]+)/$', login_required(views.EventDetailView.as_view()), name='view-event'),
|
||||
url(r'^(?P<pk>[0-9a-f-]+)/polls/$', login_required(views.EventDetailPollsView.as_view()), name='event-polls'),
|
||||
url(r'^(?P<pk>[0-9a-f-]+)/entities/$', login_required(views.EventDetailEntitiesView.as_view()), name='event-entities'),
|
||||
url(r'^(?P<pk>[0-9a-f-]+)/advanced/$', login_required(views.EventDetailAdvancedView.as_view()), name='event-advanced'),
|
||||
url(r'^(?P<event_id>[0-9a-f-]+)/end/$', login_required(views.event_end), name='end-event'),
|
||||
url(r'^(?P<event_id>[0-9a-f-]+)/results/$', login_required(views.results), name='event-results'),
|
||||
url(r'^(?P<event_id>[0-9a-f-]+)/edit/$', login_required(views.edit_event), name='edit-event'),
|
||||
url(r'^(?P<event_id>[0-9a-f-]+)/delete/$', login_required(views.del_event), name='del-event'),
|
||||
url(r'^(?P<event_id>[0-9a-f-]+)/decrypt/$', views.event_trustee_decrypt, name='decrypt-event'),
|
||||
url(r'^(?P<event_id>[0-9a-f-]+)/prepare/$', views.event_trustee_setup, name='prepare-event'),
|
||||
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-]+)/poll/(?P<poll_id>[0-9a-f-]+)/edit$', login_required(views.edit_poll), name='edit-poll')
|
||||
]
|
||||
|
|
|
@ -11,10 +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, TrusteeSK
|
||||
from .models import Event, Poll, Ballot, EncryptedVote, TrusteeKey, PartialBallotDecryption, CombinedBallot
|
||||
from allauthdemo.auth.models import DemoUser
|
||||
|
||||
from .tasks import email_trustees_prep, update_EID, generate_combpk, event_ended, create_ballots, create_ballots_for_poll, email_voters_vote_url, gen_event_sk_and_dec
|
||||
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 .utils.EventModelAdaptor import EventModelAdaptor
|
||||
|
||||
|
@ -25,17 +26,16 @@ class EventListView(generic.ListView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventListView, self).get_context_data(**kwargs)
|
||||
#context['now'] = timezone.now()
|
||||
return context
|
||||
|
||||
|
||||
class EventDetailView(generic.DetailView):
|
||||
template_name="polls/event_detail_details.html"
|
||||
template_name = "polls/event_detail_details.html"
|
||||
model = Event
|
||||
|
||||
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['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"
|
||||
|
||||
return context
|
||||
|
@ -63,20 +63,13 @@ class PollDetailView(generic.View):
|
|||
return context
|
||||
|
||||
|
||||
def util_get_poll_by_event_index(event, poll_num):
|
||||
try:
|
||||
poll_num = int(poll_num)
|
||||
if ((poll_num < 1) or (poll_num > event.polls.all().count())):
|
||||
return None
|
||||
poll = event.polls.filter().order_by('id')[poll_num-1] # index field eventually
|
||||
except ValueError:
|
||||
return None
|
||||
return poll
|
||||
def util_get_poll_by_event_index(event, poll_id):
|
||||
return event.polls.get(uuid=poll_id)
|
||||
|
||||
|
||||
def edit_poll(request, event_id, poll_num):
|
||||
def edit_poll(request, event_id, poll_id):
|
||||
event = get_object_or_404(Event, pk=event_id)
|
||||
poll = util_get_poll_by_event_index(event, poll_num)
|
||||
poll = util_get_poll_by_event_index(event, poll_id)
|
||||
|
||||
if (poll == None):
|
||||
raise Http404("Poll does not exist")
|
||||
|
@ -98,33 +91,50 @@ def edit_poll(request, event_id, poll_num):
|
|||
return HttpResponseRedirect(reverse('polls:event-polls', args=[poll.event_id]))
|
||||
|
||||
|
||||
def event_vote(request, event_id, poll_num):
|
||||
def event_vote(request, event_id, poll_id):
|
||||
event = get_object_or_404(Event, pk=event_id)
|
||||
|
||||
if not event.prepared:
|
||||
messages.add_message(request, messages.WARNING, "This Event isn\'t ready for voting yet.")
|
||||
return HttpResponseRedirect(reverse("user_home"))
|
||||
|
||||
event_poll_count = event.polls.all().count()
|
||||
prev_poll_index, next_poll_index = False, False
|
||||
can_vote, has_voted, voter_email = False, False, ""
|
||||
poll = util_get_poll_by_event_index(event, poll_num)
|
||||
# Lookup the specified poll
|
||||
poll = event.polls.get(uuid=poll_id)
|
||||
|
||||
if poll is None:
|
||||
messages.add_message(request, messages.ERROR, "There was an error loading the voting page.")
|
||||
return HttpResponseRedirect(reverse("user_home"))
|
||||
|
||||
poll_num = int(poll_num) # now known to be safe as it succeeded in the util function
|
||||
polls = event.polls.all()
|
||||
event_poll_count = len(polls)
|
||||
prev_poll_uuid, next_poll_uuid, poll_num = False, False, 0
|
||||
can_vote, cant_vote_reason, has_voted, voter_email = False, "", False, ""
|
||||
|
||||
if poll_num > 1:
|
||||
prev_poll_index = (poll_num - 1)
|
||||
if poll_num < event_poll_count:
|
||||
next_poll_index = (poll_num + 1)
|
||||
for i in range(event_poll_count):
|
||||
poll = polls[i]
|
||||
poll_uuid = str(poll.uuid)
|
||||
req_poll_uuid = str(poll_id)
|
||||
|
||||
if poll_uuid == req_poll_uuid:
|
||||
poll_num = str(i+1)
|
||||
|
||||
# If current voting request isn't for the last poll, then make sure we link to the next
|
||||
if i != event_poll_count - 1:
|
||||
# Only set the previous poll's uuid if we're not looking at the first poll
|
||||
if i != 0:
|
||||
prev_poll_uuid = str(polls[i - 1].uuid)
|
||||
|
||||
next_poll_uuid = str(polls[i + 1].uuid)
|
||||
else:
|
||||
if i != 0:
|
||||
prev_poll_uuid = str(polls[i - 1].uuid)
|
||||
|
||||
break
|
||||
|
||||
access_key = request.GET.get('key', None)
|
||||
email_key = event.keys.filter(key=access_key)
|
||||
email_key_str = email_key[0].key
|
||||
|
||||
ballot = None
|
||||
if email_key.exists() and event.voters.filter(email=email_key[0].user.email).exists():
|
||||
# Passing this test means the user can vote
|
||||
voter_email = email_key[0].user.email
|
||||
|
@ -135,15 +145,18 @@ def event_vote(request, event_id, poll_num):
|
|||
if ballot.exists() and ballot[0].cast:
|
||||
has_voted = True
|
||||
else:
|
||||
messages.add_message(request, messages.ERROR, "You don\'t have permission to vote in this event.")
|
||||
return HttpResponseRedirect(reverse("user_home"))
|
||||
can_vote = False
|
||||
cant_vote_reason = "You don't have permission to access this page."
|
||||
|
||||
if event.status() != "Active":
|
||||
can_vote = False
|
||||
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 ballot is None:
|
||||
ballot = Ballot.objects.get_or_create(voter=email_key[0].user, poll=poll)
|
||||
ballot = Ballot.objects.get_or_create(voter=email_key[0].user, poll=poll)[0]
|
||||
|
||||
# Will store the fragments of the encoding scheme that define the vote
|
||||
encrypted_vote = EncryptedVote.objects.get_or_create(ballot=ballot[0])[0]
|
||||
encrypted_vote = EncryptedVote.objects.get_or_create(ballot=ballot)[0]
|
||||
|
||||
# Clear any existing fragments - a voter changing their vote
|
||||
encrypted_vote.fragment.all().delete()
|
||||
|
@ -160,11 +173,13 @@ def event_vote(request, event_id, poll_num):
|
|||
cipher_text_c1=cipher_c1,
|
||||
cipher_text_c2=cipher_c2)
|
||||
|
||||
ballot[0].cast = True
|
||||
ballot[0].save()
|
||||
ballot.cast = True
|
||||
ballot.save()
|
||||
|
||||
if next_poll_index:
|
||||
return HttpResponseRedirect(reverse('polls:event-vote', kwargs={'event_id': event.id, 'poll_num': next_poll_index }) + "?key=" + email_key[0].key)
|
||||
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)!'
|
||||
|
@ -175,9 +190,9 @@ def event_vote(request, event_id, poll_num):
|
|||
return render(request, "polls/event_vote.html",
|
||||
{
|
||||
"object": poll, "poll_num": poll_num, "event": event, "poll_count": event.polls.all().count(),
|
||||
"prev_index": prev_poll_index, "next_index": next_poll_index, "min_selection": poll.min_num_selections,
|
||||
"max_selection": poll.max_num_selections, "can_vote": can_vote, "voter_email": voter_email,
|
||||
"has_voted": has_voted
|
||||
"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,
|
||||
"voter_email": voter_email, "has_voted": has_voted, "a_key": email_key_str
|
||||
})
|
||||
|
||||
|
||||
|
@ -245,7 +260,7 @@ def results(request, event_id):
|
|||
results = ""
|
||||
results += "{\"polls\":["
|
||||
for poll in polls:
|
||||
results += poll.enc
|
||||
results += poll.result_json
|
||||
|
||||
results += "]}"
|
||||
|
||||
|
@ -260,21 +275,63 @@ def event_trustee_decrypt(request, event_id):
|
|||
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 TrusteeSK.objects.filter(event=event, trustee=email_key[0].user).exists():
|
||||
messages.add_message(request, messages.WARNING, 'You have already provided your decryption key for this event')
|
||||
|
||||
if PartialBallotDecryption.objects.filter(event=event, user=email_key[0].user).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":
|
||||
return render(request, "polls/event_decrypt.html", {"event": event, "user_email": email_key[0].user.email})
|
||||
# Gen a list of ciphers from the combined ballots for every opt of every poll
|
||||
polls = event.polls.all()
|
||||
poll_ciphers = []
|
||||
|
||||
for poll in polls:
|
||||
options = poll.options.all()
|
||||
|
||||
options_ciphers = []
|
||||
for option in options:
|
||||
combined_ballot = CombinedBallot.objects.filter(poll=poll, option=option).get()
|
||||
|
||||
cipher = {}
|
||||
cipher['C1'] = combined_ballot.cipher_text_c1
|
||||
cipher['C2'] = combined_ballot.cipher_text_c2
|
||||
options_ciphers.append(cipher)
|
||||
|
||||
poll_ciphers.append(options_ciphers)
|
||||
|
||||
return render(request,
|
||||
"polls/event_decrypt.html",
|
||||
{
|
||||
"event": event,
|
||||
"user_email": email_key[0].user.email,
|
||||
"poll_ciphers": poll_ciphers
|
||||
})
|
||||
|
||||
elif request.method == "POST":
|
||||
sk = request.POST['secret-key']
|
||||
polls = event.polls.all()
|
||||
polls_count = len(polls)
|
||||
|
||||
TrusteeSK.objects.create(event=event,
|
||||
trustee=email_key[0].user,
|
||||
key=sk)
|
||||
for i in range(polls_count):
|
||||
options = polls[i].options.all()
|
||||
options_count = len(options)
|
||||
|
||||
if event.trustee_sk.count() == event.users_trustees.count():
|
||||
# Generate the event SK and decrypt the event to tally the results
|
||||
gen_event_sk_and_dec.delay(event)
|
||||
for j in range(options_count):
|
||||
input_name = ""
|
||||
input_name = "poll-" + str(i) + "-cipher-" + str(j)
|
||||
|
||||
part_dec = request.POST[input_name]
|
||||
|
||||
PartialBallotDecryption.objects.create(event=event,
|
||||
poll=polls[i],
|
||||
option=options[j],
|
||||
user=email_key[0].user,
|
||||
text=part_dec)
|
||||
|
||||
if event.all_part_decs_received():
|
||||
# TODO: Combine partial decryptions and gen results
|
||||
combine_decryptions_and_tally.delay(event)
|
||||
|
||||
messages.add_message(request, messages.SUCCESS, 'Your secret key has been successfully submitted')
|
||||
return HttpResponseRedirect(reverse("user_home"))
|
||||
|
@ -434,7 +491,7 @@ def edit_event(request, event_id):
|
|||
def del_event(request, event_id):
|
||||
event = get_object_or_404(Event, pk=event_id)
|
||||
if request.method == "GET":
|
||||
return render(request, "polls/del_event.html", {"event_title": event.title, "event_id": event.id})
|
||||
return render(request, "polls/del_event.html", {"event_title": event.title, "event_id": event.uuid})
|
||||
elif request.method == "POST":
|
||||
event.delete()
|
||||
return HttpResponseRedirect(reverse('polls:index'))
|
|
@ -56,7 +56,6 @@
|
|||
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
{% block app_js_vars %}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -73,8 +72,33 @@
|
|||
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
|
@ -147,9 +171,16 @@
|
|||
$('#cipher-form').submit();
|
||||
};
|
||||
|
||||
function getBytes(arr) {
|
||||
for(var i = 0; i < arr.length; i++) {
|
||||
arr[i] = parseInt(arr[i]);
|
||||
}
|
||||
|
||||
return new Uint8Array(arr);
|
||||
}
|
||||
|
||||
//new function
|
||||
demosEncrypt.decryptCipher = function() {
|
||||
demosEncrypt.decryptSubmitCiphers = function() {
|
||||
var skString = $('#secret-key').val();
|
||||
if (!skString) {
|
||||
alert("Please enter your secret key");
|
||||
|
@ -158,28 +189,32 @@
|
|||
//rebuild our secret key
|
||||
var ctx = new CTX("BN254CX");
|
||||
var skBytes = skString.split(",");
|
||||
var sk =new ctx.BIG.fromBytes(skBytes);
|
||||
var sk = new ctx.BIG.fromBytes(skBytes);
|
||||
|
||||
var inputs = $("form input[type=text]");
|
||||
|
||||
inputs.each(function() { //for each ciphertext to decrypt
|
||||
var ciphertext = {
|
||||
C1: new ctx.ECP(),
|
||||
C2: new ctx.ECP()
|
||||
}
|
||||
var temp = JSON.parse($(this).val());
|
||||
ciphertext.C1.copy(temp.C1);
|
||||
ciphertext.C2.copy(temp.C2);
|
||||
C1: null,
|
||||
C2: null
|
||||
};
|
||||
|
||||
var partial = partDec(sk, ciphertext);//returns an object containing an ECP()
|
||||
var temp = JSON.parse($(this).val());
|
||||
var c1Bytes = getBytes(temp.C1.split(','));
|
||||
ciphertext.C1 = new ctx.ECP.fromBytes(c1Bytes);
|
||||
|
||||
var c2Bytes = getBytes(temp.C2.split(','));
|
||||
ciphertext.C2 = new ctx.ECP.fromBytes(c2Bytes);
|
||||
|
||||
// Perform partial decryption where the method returns an object containing an ECP()
|
||||
var partial = partDec(sk, ciphertext);
|
||||
|
||||
var bytes = [];
|
||||
partial.D.toBytes(bytes);
|
||||
$(this).val(bytes.toString());//submit in byte array form
|
||||
})
|
||||
|
||||
$('input[type=submit]').prop("disabled", false);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//new function
|
||||
demosEncrypt.generateKeys = function() {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
{% extends "bases/bootstrap-with-nav.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load bootstrap3 %}
|
||||
{% comment %} is it safe really? {% endcomment %}
|
||||
{% block sk-file-name %}{{ event.title|safe }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -11,21 +10,42 @@
|
|||
<hr/>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Submit your Secret Key as '{{ user_email }}'</strong></div>
|
||||
<div class="panel panel-body">
|
||||
<form id="sk-form" method="POST">
|
||||
{% csrf_token %}
|
||||
<input id="secret-key" name="secret-key" class="textinput textInput form-control" type="text"/>
|
||||
<div class="alert alert-info" role="alert" style="margin-top: 0.75em;">
|
||||
<div class="panel panel-body">
|
||||
<input id="secret-key" name="secret-key" class="textinput textInput form-control" type="text" disabled/>
|
||||
<div class="alert alert-info" role="alert" style="margin-top: 0.75em;">
|
||||
Your secret key will be used to decrypt the event and get a vote tally for every poll.
|
||||
</div>
|
||||
<label for="files_sk_upload" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-cloud-upload"></span>
|
||||
It won't be sent to the server.
|
||||
</div>
|
||||
<label for="files_sk_upload" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-cloud-upload"></span>
|
||||
Upload Key
|
||||
</label>
|
||||
<input type="file" id="files_sk_upload" name="file" class="btn-info">
|
||||
<input type="submit" value="Submit" class="btn btn-success"/>
|
||||
</form>
|
||||
</div>
|
||||
</label>
|
||||
<input type="file" id="files_sk_upload" name="file" class="btn-info">
|
||||
</div>
|
||||
<br/>
|
||||
<div class="panel-heading"><strong>Ciphers</strong></div>
|
||||
<div class="panel panel-body">
|
||||
<form id="cipher-form" method="POST">
|
||||
{% csrf_token %}
|
||||
{% for opts_ciphers in poll_ciphers %}
|
||||
{% for cipher in opts_ciphers %}
|
||||
<input id="cipher"
|
||||
name="poll-{{ forloop.parentloop.counter0 }}-cipher-{{ forloop.counter0 }}"
|
||||
class="textinput textInput form-control"
|
||||
type="text"
|
||||
value="{ "C1": "{{ cipher.C1 }}", "C2": "{{ cipher.C2 }}" }"
|
||||
/>
|
||||
|
||||
<br/>
|
||||
{% endfor %}
|
||||
<br/>
|
||||
{% endfor %}
|
||||
<button id="decrypt-btn"
|
||||
onclick="demosEncrypt.decryptSubmitCiphers()"
|
||||
class="btn btn-success">
|
||||
Decrypt & Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -15,16 +15,16 @@
|
|||
<!-- Edit Button -->
|
||||
<div class="col-xs-5 col-sm-3 col-md-3 marginTopEditButton">
|
||||
{% if object.has_received_votes and object.ended == False %}
|
||||
<a href="{% url 'polls:end-event' event.id %}" class="btn btn-danger" style="float: right;">
|
||||
<a href="{% url 'polls:end-event' event.uuid %}" class="btn btn-danger" style="float: right;">
|
||||
<span class="glyphicon glyphicon-stop"></span> End
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if decrypted == True and object.ended == True %}
|
||||
<a href="{% url 'polls:event-results' event.id %}" class="btn btn-success" style="float: right;">
|
||||
<a href="{% url 'polls:event-results' event.uuid %}" class="btn btn-success" style="float: right;">
|
||||
<span class="glyphicon glyphicon-stats"></span> Results
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'polls:edit-event' event.id %}" class="btn btn-primary" style="float: right; margin-right: 0.4em;">
|
||||
<a href="{% url 'polls:edit-event' event.uuid %}" class="btn btn-primary" style="float: right; margin-right: 0.4em;">
|
||||
<span class="fa fa-pencil"></span> Edit
|
||||
</a>
|
||||
</div>
|
||||
|
@ -44,17 +44,17 @@
|
|||
<br/>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="{% block event_nav_details %}{% endblock %}">
|
||||
<a href="{% url 'polls:view-event' event.id %}"><strong>Summary</strong></a>
|
||||
<a href="{% url 'polls:view-event' event.uuid %}"><strong>Summary</strong></a>
|
||||
</li>
|
||||
<li class="{% block event_nav_polls %}{% endblock %}">
|
||||
<a href="{% url 'polls:event-polls' event.id %}"><strong>Polls ({{ object.polls.count }})</strong></a>
|
||||
<a href="{% url 'polls:event-polls' event.uuid %}"><strong>Polls ({{ object.polls.count }})</strong></a>
|
||||
</li>
|
||||
<li class="{% block event_nav_organisers %}{% endblock %}">
|
||||
<a href="{% url 'polls:event-entities' event.id %}"><strong>Entities</strong></a>
|
||||
<a href="{% url 'polls:event-entities' event.uuid %}"><strong>Entities</strong></a>
|
||||
</li>
|
||||
{% if is_organiser %}
|
||||
<li class="{% block event_nav_launch %}{% endblock %}">
|
||||
<a href="{% url 'polls:event-advanced' event.id %}"><strong>Advanced</strong></a>
|
||||
<a href="{% url 'polls:event-advanced' event.uuid %}"><strong>Advanced</strong></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
{% block event_content %}
|
||||
{% if object.polls.all %}
|
||||
{% for poll in object.polls.all %}
|
||||
<h3>Poll: {{ poll.question_text }} (<a href="{% url 'polls:edit-poll' event_id=event.id poll_num=forloop.counter %}">Edit</a>)</h3>
|
||||
<h3>Poll: {{ poll.question_text }} (<a href="{% url 'polls:edit-poll' event_id=event.uuid poll_id=poll.uuid %}">Edit</a>)</h3>
|
||||
<br/>
|
||||
<h4>Poll Options:</h4>
|
||||
<ul class="list-group">
|
||||
|
@ -21,6 +21,6 @@
|
|||
<p>No polls are available for this Event.</p>
|
||||
{% endif %}
|
||||
{% if is_organiser %}
|
||||
<a href="{% url 'polls:create-poll' event.id %}" class="btn btn-default" role="button">Add Poll</a>
|
||||
<a href="{% url 'polls:create-poll' event.uuid %}" class="btn btn-default" role="button">Add Poll</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</div>
|
||||
{% if object_list %}
|
||||
<div class="form-group">
|
||||
<table id="trustees-input-table" class="table table-hover marginTopEventList">
|
||||
<table id="event-list-table" class="table table-hover marginTopEventList">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">Event</th>
|
||||
|
@ -33,14 +33,14 @@
|
|||
<tbody>
|
||||
{% for event in object_list %}
|
||||
<tr>
|
||||
<td class="text-center"><a href="{% url 'polls:view-event' event.id %}">{{ event.title }}</a></td>
|
||||
<td class="text-center"><a href="{% url 'polls:view-event' event.uuid %}">{{ event.title }}</a></td>
|
||||
<td class="text-center">{{ event.duration }}</td>
|
||||
<td class="text-center">{{ event.polls.count }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'polls:edit-event' event.id %}">
|
||||
<a href="{% url 'polls:edit-event' event.uuid %}">
|
||||
<span class="btn btn-default glyphicon glyphicon-pencil"></span>
|
||||
</a>
|
||||
<a href="{% url 'polls:del-event' event.id %}">
|
||||
<a href="{% url 'polls:del-event' event.uuid %}">
|
||||
<span class="btn btn-default glyphicon glyphicon-trash"></span>
|
||||
</a>
|
||||
</td>
|
||||
|
|
|
@ -3,79 +3,85 @@
|
|||
{% load bootstrap3 %}
|
||||
|
||||
{% block app_js_vars %}
|
||||
var option_count = {{ object.options.count }};
|
||||
var option_count = {{ object.options.count }};
|
||||
var min_selections = {{ min_selection }};
|
||||
var max_selections = {{ max_selection }};
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container">
|
||||
<input id="event-param" type="text" value="{{event.EID}}" hidden/>
|
||||
<input id="comb_pk" type="text" value="{{event.public_key}}" hidden/>
|
||||
{% if can_vote %}
|
||||
<!-- Hidden fields -->
|
||||
<input id="event-param" type="text" value="{{event.EID}}" hidden/>
|
||||
<input id="comb_pk" type="text" value="{{event.public_key}}" hidden/>
|
||||
|
||||
<!-- TODO: Add warning not to share the URL-->
|
||||
<h2>Event Voting Page for the Event '{{ object.event.title }}'</h2>
|
||||
<div class="alert alert-warning" role="alert" style="margin-top: 1em;">
|
||||
You are voting as: <strong>{{ voter_email }}</strong> - Ensure this is correct and don't share this URL!
|
||||
</div>
|
||||
<span><strong>Voting status:</strong>
|
||||
{% if has_voted %}
|
||||
Voted - Re-Submitting will Change your Vote
|
||||
{% else %}
|
||||
Not Voted
|
||||
{% endif %}
|
||||
</span>
|
||||
<br/>
|
||||
<span><strong>Number of polls for this event:</strong> {{ poll_count }}</span>
|
||||
<br/>
|
||||
<br/>
|
||||
<span><strong>Instructions:</strong>
|
||||
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. <strong>For this specific poll</strong> you need to make a <strong>
|
||||
minimum</strong> of {{ min_selection }} option selection(s) and a <strong>maximum</strong> of
|
||||
{{ max_selection }}. Please make your choice below.
|
||||
</span>
|
||||
<div class="panel panel-body">
|
||||
{% if prev_index %}
|
||||
<a href="{% url 'polls:event-vote' event_id=object.event.id poll_num=prev_index %}" class="btn" role="button">
|
||||
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if next_index %}
|
||||
<a href="{% url 'polls:event-vote' event_id=object.event.id poll_num=next_index %}" class="btn" role="button">
|
||||
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if object.options.all %}
|
||||
<h3>Poll {{ poll_num }} of {{ poll_count }}: {{object.question_text}}</h3>
|
||||
{% if can_vote %}
|
||||
{% load crispy_forms_tags %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Options</strong></div>
|
||||
<div class="panel panel-body">
|
||||
<select class="radio-inline select form-control" id="poll-options" name="options">
|
||||
{% load custom_filters_tags %}
|
||||
<option value="{{ -1|get_ballot_value:object.options.all.count }}">Please Select...</option>
|
||||
{% for option in object.options.all %}
|
||||
<option value="{{forloop.counter|get_ballot_value:object.options.all.count}}">{{ option.choice_text }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<hr/>
|
||||
<button id="keygen-btn" onclick="demosEncrypt.encryptAndSubmit()" class="btn btn-primary">Submit</button>
|
||||
<form id="cipher-form" method="post" action="" class="">
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<p>You don't have permission to vote in this event.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p>No options are available.</p>
|
||||
{% endif %}
|
||||
<!-- Event info and instructions -->
|
||||
<h2>Event Voting Page for the Event '{{ object.event.title }}'</h2>
|
||||
<hr/>
|
||||
<div class="alert alert-warning" role="alert" style="margin-top: 1em;">
|
||||
You are voting as: <strong>{{ voter_email }}</strong> - Ensure this is correct and don't share this URL!
|
||||
</div>
|
||||
<span><strong>Voting status:</strong>
|
||||
{% if has_voted %}
|
||||
Voted - Re-Submitting will Change your Vote
|
||||
{% else %}
|
||||
Not Voted
|
||||
{% endif %}
|
||||
</span>
|
||||
<br/>
|
||||
<span><strong>Number of polls for this event:</strong> {{ poll_count }}</span>
|
||||
<br/>
|
||||
<br/>
|
||||
<span><strong>Instructions:</strong>
|
||||
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. <strong>For this specific poll</strong> you need to make a <strong>
|
||||
minimum</strong> of {{ min_selection }} option selection(s) and a <strong>maximum</strong> of
|
||||
{{ max_selection }}. <br/><br/>Please make your choice below.
|
||||
</span>
|
||||
|
||||
<!-- Poll Voting Section -->
|
||||
<h3>Poll {{ poll_num }} of {{ poll_count }}: {{object.question_text}}</h3>
|
||||
<hr/>
|
||||
|
||||
{% load crispy_forms_tags %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Options</strong></div>
|
||||
<div class="panel panel-body">
|
||||
<select class="radio-inline select form-control" id="poll-options" name="options">
|
||||
{% load custom_filters_tags %}
|
||||
<option value="{{ -1|get_ballot_value:object.options.all.count }}">Please Select...</option>
|
||||
{% for option in object.options.all %}
|
||||
<option value="{{forloop.counter|get_ballot_value:object.options.all.count}}">{{ option.choice_text }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<hr/>
|
||||
<button id="keygen-btn" onclick="demosEncrypt.encryptAndSubmit()" class="btn btn-primary">Submit</button>
|
||||
<form id="cipher-form" method="post" action="" class="">
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Poll Navigation -->
|
||||
<div class="panel panel-body">
|
||||
{% if prev_uuid %}
|
||||
<a href="{% url 'polls:event-vote' event_id=object.event.uuid poll_id=prev_uuid %}?key={{ a_key }}" class="btn btn-danger"
|
||||
role="button">
|
||||
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span> Previous Poll
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if next_uuid %}
|
||||
<a href="{% url 'polls:event-vote' event_id=object.event.uuid poll_id=next_uuid %}?key={{ a_key }}" class="btn btn-primary"
|
||||
role="button" style="float: right;">
|
||||
Next Poll <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %} <!-- for: { if can_vote %} -->
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<p>{{ cant_vote_reason }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
{% endblock %}
|
Reference in a new issue