Implemented full end-to-end encryption and decryption of an event using trustee public and secret keys. This required modification of the NodeJS crypto server to receive post data from the crypto_rpc py methods and for combining SKs together. Additionally, the new binary voting encoding scheme has been implemented for encryption and decryption of the event. General UI improvements have been made and as well as some other bug fixes
This commit is contained in:
parent
0c354cd542
commit
e33b91f852
23 changed files with 809 additions and 446 deletions
208
Node/index.js
208
Node/index.js
|
@ -1,30 +1,33 @@
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
|
||||||
Code by Thomas Smith
|
Code by Bingsheng Zhang, Thomas Smith, Vincent de Almeida
|
||||||
|
|
||||||
|
Dependencies can be found in 'package.json' and installed using 'npm install'
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var port = 8080;
|
var port = 8080;
|
||||||
|
|
||||||
var express = require('express');
|
|
||||||
var Buffer = require('buffer').Buffer;
|
var Buffer = require('buffer').Buffer;
|
||||||
var CTX = require('milagro-crypto-js')
|
var CTX = require('milagro-crypto-js');
|
||||||
var app = express();
|
|
||||||
/*
|
|
||||||
var cors = require('cors')
|
|
||||||
app.use(cors());
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
var express = require('express');
|
||||||
|
var bodyParser = require("body-parser");
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
// Express server configuration
|
||||||
app.use(express.static('test'));
|
app.use(express.static('test'));
|
||||||
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
//default test
|
//default test
|
||||||
app.get('/', function(request, response){
|
app.get('/', function(request, response){
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
message: 'hello world',
|
message: 'hello world',
|
||||||
value: 5
|
value: 5
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
//response.send('Hey there'+request.ip);
|
//response.send('Hey there'+request.ip);
|
||||||
|
@ -40,13 +43,13 @@ app.get('/param', function(request, response){
|
||||||
console.log('Generated Param:' + param);
|
console.log('Generated Param:' + param);
|
||||||
response.json(param);
|
response.json(param);
|
||||||
|
|
||||||
})
|
});
|
||||||
|
|
||||||
//combine public keys and return the full combined one - JSON Version
|
//combine public keys and return the full combined one - JSON Version
|
||||||
app.get('/combpk', function(request, response){
|
app.get('/combpk', function(request, response){
|
||||||
|
console.log('\nEndpoint /combpk called');
|
||||||
|
|
||||||
|
var partials = request.query['PK'];
|
||||||
var partials = request.query['PK']
|
|
||||||
|
|
||||||
var parsed = [];
|
var parsed = [];
|
||||||
|
|
||||||
|
@ -57,45 +60,40 @@ app.get('/combpk', function(request, response){
|
||||||
parsed.push(JSON.parse(partials[i]));
|
parsed.push(JSON.parse(partials[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
var PK = combine(parsed);
|
var PK = combine_pks(parsed);
|
||||||
response.json(PK);
|
response.json(PK);
|
||||||
|
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
//byte array version
|
//byte array version
|
||||||
app.get('/cmpkstring', function(request, response){
|
app.post('/cmpkstring', function(request, response){
|
||||||
|
console.log('\nEndpoint /cmpkstring called');
|
||||||
var ctx = new CTX("BN254CX");
|
var ctx = new CTX("BN254CX");
|
||||||
|
|
||||||
var partials = request.query['PK']
|
var partials = request.body.PKs;
|
||||||
//if there is only one key, partials will be an array of the individual bytes
|
|
||||||
//if more than one, it will be an array of arrays
|
|
||||||
//we need to factor for this in code
|
|
||||||
var noOfKeys = request.query['number'];
|
|
||||||
var parsed = [];
|
var parsed = [];
|
||||||
|
|
||||||
if(noOfKeys == partials.length)//if we're submitting more than one key
|
if(partials.length > 1)//if we're submitting more than one key
|
||||||
{
|
{
|
||||||
console.log('Combining' + noOfKeys + " keys...");
|
console.log('Combining ' + partials.length + " public keys into one...");
|
||||||
for (var i = partials.length - 1; i >= 0; i--) {
|
for (var i = partials.length - 1; i >= 0; i--) {
|
||||||
console.log('PK' +i+ ': '+partials[i]);
|
console.log('PK' + i + ': ' + partials[i]);
|
||||||
var bytes = Buffer.from(partials[i].split(','), 'hex');
|
var bytes = Buffer.from(partials[i].split(','), 'hex');
|
||||||
console.log(bytes)
|
var pk = new ctx.ECP.fromBytes(bytes);
|
||||||
var pk = new ctx.ECP.fromBytes(bytes);
|
parsed.push(pk);
|
||||||
parsed.push(pk);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(noOfKeys == 1)
|
else if(partials.length === 1)
|
||||||
{
|
{
|
||||||
console.log("Combining just one key");
|
console.log("Combining just one public key...");
|
||||||
var bytes = Buffer.from(partials.split(','), 'hex');
|
var bytes = Buffer.from(partials[0].split(','), 'hex');
|
||||||
console.log(bytes);
|
|
||||||
var pk = new ctx.ECP.fromBytes(bytes);
|
var pk = new ctx.ECP.fromBytes(bytes);
|
||||||
parsed.push(pk);
|
parsed.push(pk);
|
||||||
}
|
}
|
||||||
|
|
||||||
response.json(combine(parsed));
|
response.json(combine_pks(parsed));
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
//addition function on homomorphically encrypted variables
|
//addition function on homomorphically encrypted variables
|
||||||
|
@ -155,20 +153,19 @@ app.get('/addec', function(request, response){
|
||||||
|
|
||||||
|
|
||||||
response.json(add(parsed));
|
response.json(add(parsed));
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//tally partially decrypted ciphertexts
|
//tally partially decrypted ciphertexts
|
||||||
app.get('/tally', function(request, response){
|
app.get('/tally', function(request, response){
|
||||||
console.log("called tally");
|
console.log("\nEndpoint /tally called");
|
||||||
var amount = request.query['number'];//number of decryptions taking in
|
var amount = request.query['number'];//number of decryptions taking in
|
||||||
var paramString = request.query['param'];//event group parameter in JSON
|
var paramString = request.query['param'];//event group parameter in JSON
|
||||||
var partialsStrings = request.query['decs'];//array of partial decryption(s) in bytes
|
var partialsStrings = request.query['decs'];//array of partial decryption(s) in bytes
|
||||||
var ciphertextString = request.query['cipher'];//ciphertext being decrypted in JSON
|
var ciphertextString = request.query['cipher'];//ciphertext being decrypted in JSON
|
||||||
|
|
||||||
//re-build parameters
|
//re-build parameters
|
||||||
var tempParams = JSON.parse(paramString);
|
var tempParams = JSON.parse(JSON.parse(paramString).crypto);
|
||||||
var ctx = new CTX("BN254CX"); //new context we can use
|
var ctx = new CTX("BN254CX"); //new context we can use
|
||||||
var n = new ctx.BIG();
|
var n = new ctx.BIG();
|
||||||
var g1 = new ctx.ECP();
|
var g1 = new ctx.ECP();
|
||||||
|
@ -183,47 +180,117 @@ app.get('/tally', function(request, response){
|
||||||
n:n,
|
n:n,
|
||||||
g1:g1,
|
g1:g1,
|
||||||
g2:g2
|
g2:g2
|
||||||
}
|
};
|
||||||
|
|
||||||
//re-build partial decryptions
|
//re-build partial decryptions
|
||||||
var partials = []
|
var partials = [];
|
||||||
if(amount == partialsStrings.length)
|
if(amount == partialsStrings.length)
|
||||||
{
|
{
|
||||||
console.log(amount + " partial decryptions");
|
console.log(amount + " partial decryptions");
|
||||||
for(var i = 0; i < partialsStrings.length; i++)
|
for(var i = 0; i < partialsStrings.length; i++)
|
||||||
{
|
{
|
||||||
var bytes = Buffer.from(partialsStrings[i].split(','), 'hex');
|
var bytes = Buffer.from(partialsStrings[i].split(','), 'hex');
|
||||||
|
|
||||||
var dec = {
|
var dec = {
|
||||||
D:new ctx.ECP.fromBytes(bytes)
|
D:new ctx.ECP.fromBytes(bytes)
|
||||||
}
|
};
|
||||||
|
|
||||||
partials.push(dec);
|
partials.push(dec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(amount == 1)
|
else if(amount == 1)
|
||||||
{
|
{
|
||||||
console.log("Only one partial decryption received")
|
console.log("\nOnly one partial decryption received\n");
|
||||||
console.log(paramString)
|
console.log(JSON.parse(paramString).crypto + "\n");
|
||||||
|
|
||||||
var bytes = Buffer.from(partialsStrings.split(','), 'hex');
|
var bytes = Buffer.from(partialsStrings.split(','), 'hex');
|
||||||
var dec = {
|
var dec = {
|
||||||
D:new ctx.ECP.fromBytes(bytes)
|
D : new ctx.ECP.fromBytes(bytes)
|
||||||
}
|
};
|
||||||
|
|
||||||
partials.push(dec);
|
partials.push(dec);
|
||||||
}
|
}
|
||||||
|
|
||||||
//re-build combined ciphertext
|
//re-build combined ciphertext
|
||||||
var tempCipher = JSON.parse(ciphertextString);
|
var tempCipher = JSON.parse(ciphertextString);
|
||||||
|
|
||||||
cipher = {
|
var cipher = {
|
||||||
C1: new ctx.ECP(),
|
C1: new ctx.ECP(),
|
||||||
C2: new ctx.ECP()
|
C2: new ctx.ECP()
|
||||||
}
|
};
|
||||||
|
|
||||||
cipher.C1.copy(tempCipher.C1);
|
cipher.C1.copy(tempCipher.C1);
|
||||||
cipher.C2.copy(tempCipher.C2);
|
cipher.C2.copy(tempCipher.C2);
|
||||||
|
|
||||||
response.json(tally(params, partials, cipher))
|
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());
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/get_tally', function(request, response){
|
||||||
|
const COUNT = request.body.count;
|
||||||
|
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;
|
||||||
|
|
||||||
|
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
|
||||||
|
var n = new ctx.BIG();
|
||||||
|
var g1 = new ctx.ECP();
|
||||||
|
var g2 = new ctx.ECP2();
|
||||||
|
|
||||||
|
n.copy(TEMP_PARAMS.n);
|
||||||
|
g1.copy(TEMP_PARAMS.g1);
|
||||||
|
g2.copy(TEMP_PARAMS.g2);
|
||||||
|
|
||||||
|
var params = {
|
||||||
|
n:n,
|
||||||
|
g1:g1,
|
||||||
|
g2:g2
|
||||||
|
};
|
||||||
|
|
||||||
|
//rebuild our secret key
|
||||||
|
var skBytes = SK.split(",");
|
||||||
|
var sk = new ctx.BIG.fromBytes(skBytes);
|
||||||
|
|
||||||
|
var tally = 0;
|
||||||
|
|
||||||
|
for(var i = 0; i < COUNT; i++) {
|
||||||
|
var c1Bytes = Buffer.from(C1s[i].split(','), 'hex');
|
||||||
|
var newC1 = new ctx.ECP.fromBytes(c1Bytes);
|
||||||
|
|
||||||
|
var c2Bytes = Buffer.from(C2s[i].split(','), 'hex');
|
||||||
|
var newC2 = new ctx.ECP.fromBytes(c2Bytes);
|
||||||
|
|
||||||
|
var cipher = {C1: newC1, C2: newC2};
|
||||||
|
tally += decrypt(params, sk, cipher).M;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Tally: " + tally + "\n");
|
||||||
|
|
||||||
|
response.send("" + tally);
|
||||||
|
});
|
||||||
|
|
||||||
var server = app.listen(port, function(){
|
var server = app.listen(port, function(){
|
||||||
var host = server.address().address;
|
var host = server.address().address;
|
||||||
|
@ -277,7 +344,7 @@ gpGen = function(){
|
||||||
g1:P,
|
g1:P,
|
||||||
g2:Q
|
g2:Q
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
//creates ElGamal public and secret key
|
//creates ElGamal public and secret key
|
||||||
|
@ -304,12 +371,12 @@ keyGen=function(params){
|
||||||
PK:pk,
|
PK:pk,
|
||||||
SK:sk
|
SK:sk
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
//combine multiple public key together
|
//combine multiple public key together
|
||||||
//the input is an array of PKs
|
//the input is an array of PKs
|
||||||
combine=function(PKs){
|
combine_pks=function(PKs){
|
||||||
var ctx = new CTX("BN254CX");
|
var ctx = new CTX("BN254CX");
|
||||||
var pk=new ctx.ECP();
|
var pk=new ctx.ECP();
|
||||||
//copy the first pk
|
//copy the first pk
|
||||||
|
@ -319,11 +386,25 @@ combine=function(PKs){
|
||||||
pk.add(PKs[i]);
|
pk.add(PKs[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return{
|
return {
|
||||||
PK:pk
|
PK : pk
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// 'add' the rest of the sks to the first
|
||||||
|
var sk = SKs[0];
|
||||||
|
|
||||||
|
for(var i = 1; i < SKs.length; i++) {
|
||||||
|
sk.add(SKs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
SK: sk
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//ElGamal encryption
|
//ElGamal encryption
|
||||||
encrypt=function(params,PK, m){
|
encrypt=function(params,PK, m){
|
||||||
|
@ -356,7 +437,7 @@ encrypt=function(params,PK, m){
|
||||||
C1:C1,
|
C1:C1,
|
||||||
C2:C2
|
C2:C2
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
//add ciphertexts
|
//add ciphertexts
|
||||||
|
@ -380,7 +461,7 @@ add=function(Ciphers){
|
||||||
C1:s1,
|
C1:s1,
|
||||||
C2:s2
|
C2:s2
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
//ElGamal decryption
|
//ElGamal decryption
|
||||||
|
@ -410,9 +491,7 @@ decrypt=function(params,SK, C){
|
||||||
return{
|
return{
|
||||||
M: "Error"
|
M: "Error"
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//ElGamal partial decryption
|
//ElGamal partial decryption
|
||||||
|
@ -424,8 +503,7 @@ partDec=function(SK, C){
|
||||||
return{
|
return{
|
||||||
D: D
|
D: D
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -446,24 +524,24 @@ tally=function(params,Ds, C){
|
||||||
gM.copy(C.C2);
|
gM.copy(C.C2);
|
||||||
gM.sub(D);
|
gM.sub(D);
|
||||||
|
|
||||||
//search for message by brute force
|
//search for message by brute force
|
||||||
var B;
|
var B;
|
||||||
for (j = 0; j < 1000; j++) {
|
for (var j = 0; j < 1000; j++) {
|
||||||
//use D as temp var
|
//use D as temp var
|
||||||
B = new ctx.BIG(j);
|
B = new ctx.BIG(j);
|
||||||
D = ctx.PAIR.G1mul(params.g1,B);
|
D = ctx.PAIR.G1mul(params.g1,B);
|
||||||
if (D.equals(gM))
|
if (D.equals(gM))
|
||||||
return{
|
return{
|
||||||
M:j
|
M: j
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
return{
|
return{
|
||||||
M: "Error"
|
M: "Error"
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,10 @@
|
||||||
"url": "https://github.com/vincentmdealmeida/DEMOS2"
|
"url": "https://github.com/vincentmdealmeida/DEMOS2"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Bingsheng Zang, Thomas Smith",
|
"author": "Bingsheng Zang, Thomas Smith, Vincent de Almeida",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"body-parser": "^1.18.3",
|
||||||
"express": "^4.16.3",
|
"express": "^4.16.3",
|
||||||
"milagro-crypto-js": "git+https://github.com/milagro-crypto/milagro-crypto-js.git"
|
"milagro-crypto-js": "git+https://github.com/milagro-crypto/milagro-crypto-js.git"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
import os
|
|
||||||
import shlex
|
|
||||||
import subprocess
|
|
||||||
import json
|
import json
|
||||||
import urllib2
|
import urllib2
|
||||||
|
|
||||||
|
@ -10,26 +7,40 @@ All functions in this file have been re-implemenented by Thomas Smith
|
||||||
|
|
||||||
File then updated by Vincent de Almeida. Changes include:
|
File then updated by Vincent de Almeida. Changes include:
|
||||||
-Update filename to 'crypto_rpc' to reflect the RPC nature of the methods
|
-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
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def send_post_req(url, data):
|
||||||
|
data = json.dumps(data)
|
||||||
|
|
||||||
|
# Create a request specifying the Content-Type
|
||||||
|
req = urllib2.Request(url, data, {'Content-Type': 'application/json'})
|
||||||
|
f = urllib2.urlopen(req)
|
||||||
|
response = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def param():
|
def param():
|
||||||
url = 'http://localhost:8080/param' # RPC URL
|
url = 'http://localhost:8080/param'
|
||||||
jsondict = json.load(urllib2.urlopen(url))
|
jsondict = json.load(urllib2.urlopen(url))
|
||||||
return json.dumps(jsondict)
|
return json.dumps(jsondict)
|
||||||
|
|
||||||
def combpk(amount, pks):
|
|
||||||
url = 'http://localhost:8080/cmpkstring' # RPC URL
|
|
||||||
querystring = '?number='+str(amount)
|
|
||||||
for pk in pks:
|
|
||||||
querystring += '&PK='+pk
|
|
||||||
|
|
||||||
print(url+querystring)
|
def combpk(pks):
|
||||||
jsondict = json.load(urllib2.urlopen(url+querystring))
|
url = 'http://localhost:8080/cmpkstring'
|
||||||
print(json.dumps(jsondict))
|
|
||||||
return json.dumps(jsondict)
|
data = {}
|
||||||
|
data['PKs'] = pks
|
||||||
|
|
||||||
|
return send_post_req(url, data)
|
||||||
|
|
||||||
|
|
||||||
def addec(amount, ciphers):
|
def addec(amount, ciphers):
|
||||||
url = 'http://localhost:8080/addec' # RPC URL
|
url = 'http://localhost:8080/addec'
|
||||||
querystring = '?number='+str(amount)
|
querystring = '?number='+str(amount)
|
||||||
c1s = ciphers['c1s']
|
c1s = ciphers['c1s']
|
||||||
c2s = ciphers['c2s']
|
c2s = ciphers['c2s']
|
||||||
|
@ -42,23 +53,44 @@ def addec(amount, ciphers):
|
||||||
print(json.dumps(jsondict))
|
print(json.dumps(jsondict))
|
||||||
return json.dumps(jsondict)
|
return json.dumps(jsondict)
|
||||||
|
|
||||||
def tally(amount, param, decs, cipher):
|
|
||||||
url = 'http://localhost:8080/tally' # RPC URL
|
|
||||||
querystring = '?number='+str(amount)
|
|
||||||
querystring += '¶m='+urllib2.quote(str(param))
|
|
||||||
|
|
||||||
testquerystring = '?number='+str(amount)
|
# Deprecated functionality and has been superseded by get_tally
|
||||||
testquerystring += '¶m='+str(param)
|
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):
|
for i, value in enumerate(decs):
|
||||||
querystring += "&decs="+str(value)
|
querystring += "&decs="+str(value)
|
||||||
testquerystring += "&decs="+str(value)
|
|
||||||
|
|
||||||
querystring += '&cipher=' + urllib2.quote(str(cipher))
|
querystring += '&cipher=' + urllib2.quote(str(cipher))
|
||||||
testquerystring += '&cipher=' + str(cipher)
|
|
||||||
|
|
||||||
print(url+querystring)
|
|
||||||
print(url+testquerystring)
|
|
||||||
jsondict = json.load(urllib2.urlopen(url+querystring))
|
jsondict = json.load(urllib2.urlopen(url+querystring))
|
||||||
print('tally: ' + str(jsondict['M']))
|
|
||||||
return str(jsondict['M'])
|
return str(jsondict['M'])
|
||||||
|
|
||||||
|
|
||||||
|
def combine_sks(sks):
|
||||||
|
url = 'http://localhost:8080/comb_sks'
|
||||||
|
|
||||||
|
# Construct POST data
|
||||||
|
data = {}
|
||||||
|
data['SKs'] = sks
|
||||||
|
|
||||||
|
# Return the new combined SK
|
||||||
|
return send_post_req(url, data)
|
||||||
|
|
||||||
|
|
||||||
|
def get_tally(count, ciphers, sk, group_param):
|
||||||
|
url = 'http://localhost:8080/get_tally'
|
||||||
|
|
||||||
|
# Construct POST data
|
||||||
|
data = {}
|
||||||
|
data['count'] = count
|
||||||
|
data['ciphers'] = ciphers
|
||||||
|
data['sk'] = sk
|
||||||
|
data['param'] = group_param
|
||||||
|
|
||||||
|
# Return the tally of votes for the option
|
||||||
|
return send_post_req(url, data)
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ class Event(models.Model):
|
||||||
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)
|
||||||
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)
|
||||||
|
@ -35,6 +36,7 @@ class Event(models.Model):
|
||||||
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)
|
||||||
|
|
||||||
|
# Custom helper methods
|
||||||
def EID_hr(self):
|
def EID_hr(self):
|
||||||
EID_json = json.loads(self.EID)
|
EID_json = json.loads(self.EID)
|
||||||
return EID_json['hr']
|
return EID_json['hr']
|
||||||
|
@ -68,15 +70,36 @@ class Event(models.Model):
|
||||||
# future event
|
# future event
|
||||||
present = timezone.now()
|
present = timezone.now()
|
||||||
|
|
||||||
if present >= self.start_time and present <= self.end_time:
|
if self.ended is False:
|
||||||
status_str = "Active"
|
if present < self.start_time and self.public_key is None:
|
||||||
elif present > self.end_time:
|
status_str = "Future"
|
||||||
status_str = "Expired"
|
elif present < self.start_time and self.public_key is not None:
|
||||||
elif present < self.start_time:
|
status_str = "Prepared"
|
||||||
status_str = "Future"
|
elif present >= self.start_time and present <= self.end_time and self.public_key is not None:
|
||||||
|
status_str = "Active"
|
||||||
|
elif present > self.end_time and self.public_key is not None:
|
||||||
|
status_str = "Expired"
|
||||||
|
else:
|
||||||
|
if self.event_sk.all().count() == 1:
|
||||||
|
status_str = "Decrypted"
|
||||||
|
elif self.event_sk.all().count() == 0:
|
||||||
|
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
|
||||||
|
been received for every poll.
|
||||||
|
'''
|
||||||
|
def has_received_votes(self):
|
||||||
|
received_votes = True
|
||||||
|
|
||||||
|
for poll in self.polls.all():
|
||||||
|
if Ballot.objects.filter(poll=poll, cast=True).count() == 0:
|
||||||
|
received_votes = False
|
||||||
|
|
||||||
|
return received_votes
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
@ -84,12 +107,12 @@ class Event(models.Model):
|
||||||
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=1024, unique=True) # ideally composite key here, but django doesn't really support yet
|
key = models.CharField(max_length=255, unique=True)
|
||||||
|
|
||||||
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=1024, unique=True) # ideally composite key here, but django doesn't really support yet
|
key = models.CharField(max_length=255, unique=True)
|
||||||
|
|
||||||
#total = models.IntegerField(blank=True, null=True, default=0)
|
#total = models.IntegerField(blank=True, null=True, default=0)
|
||||||
|
|
||||||
|
@ -115,20 +138,6 @@ class Poll(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.question_text
|
return self.question_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)
|
|
||||||
|
|
||||||
#some modification to this class
|
|
||||||
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")
|
|
||||||
cipher_text_c1 = models.CharField(max_length=4096)#the encryption system uses two byte strings
|
|
||||||
cipher_text_c2 = models.CharField(max_length=4096)
|
|
||||||
cast = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -138,6 +147,35 @@ class PollOption(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.choice_text
|
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 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):
|
class Organiser(models.Model):
|
||||||
index = models.IntegerField(default=0)
|
index = models.IntegerField(default=0)
|
||||||
email = models.CharField(max_length=100, blank=False, null=False)
|
email = models.CharField(max_length=100, blank=False, null=False)
|
||||||
|
|
|
@ -5,14 +5,11 @@ import json
|
||||||
from os import urandom
|
from os import urandom
|
||||||
from celery import task
|
from celery import task
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.core.validators import EmailValidator
|
|
||||||
from django.core.mail import send_mail
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from allauthdemo.polls.models import AccessKey
|
from allauthdemo.polls.models import AccessKey, Ballot, Decryption, TrusteeSK, EventSK
|
||||||
|
|
||||||
from .crypto_rpc import param, combpk, addec, tally
|
from .crypto_rpc import param, combpk, addec, tally, get_tally, combine_sks
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Goal: This py file defines celery tasks that can be initiated
|
Goal: This py file defines celery tasks that can be initiated
|
||||||
|
@ -25,25 +22,43 @@ from .crypto_rpc import param, combpk, addec, tally
|
||||||
# Will store the result of the initial cal to param() from .cpp_calls
|
# Will store the result of the initial cal to param() from .cpp_calls
|
||||||
group_param = None
|
group_param = None
|
||||||
|
|
||||||
def is_valid_email(email):
|
|
||||||
try:
|
|
||||||
valid_email = EmailValidator(whitelist=None)
|
|
||||||
valid_email(email)
|
|
||||||
return True
|
|
||||||
except ValidationError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@task()
|
|
||||||
def create_ballots(poll):
|
|
||||||
for voter in poll.event.voters.all():
|
|
||||||
ballot = poll.ballots.create(voter=voter, poll=poll)
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Will generate a key for accessing either the event preparation page or the voting page
|
Helper functions
|
||||||
|
|
||||||
|
gen_access_key - Will generate an a key for accessing either the event preparation page, voting page and decryption page
|
||||||
'''
|
'''
|
||||||
def gen_access_key():
|
def gen_access_key():
|
||||||
return base64.urlsafe_b64encode(urandom(16)).decode('utf-8')
|
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"
|
||||||
|
url_base = "http://" + settings.DOMAIN + "/event/" + str(event.pk) + "/decrypt/?key="
|
||||||
|
email_body = email_body + url_base
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
@task()
|
||||||
|
def create_ballots(event):
|
||||||
|
voters = event.voters.all()
|
||||||
|
|
||||||
|
for poll in event.polls.all():
|
||||||
|
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():
|
||||||
|
ballot = poll.ballots.create(voter=voter, poll=poll)
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Emails an event preparation URL containing an access key for all of the trustees for an event
|
Emails an event preparation URL containing an access key for all of the trustees for an event
|
||||||
'''
|
'''
|
||||||
|
@ -64,19 +79,31 @@ def email_trustees_prep(trustees, event):
|
||||||
trustee.send_email(email_subject, email_body + key)
|
trustee.send_email(email_subject, email_body + key)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Emails the access keys for all of the voters for an event
|
Emails a URL containing an access key for all of the voters for an event
|
||||||
'''
|
'''
|
||||||
@task()
|
@task()
|
||||||
def email_voters_a_key(voters, event):
|
def email_voters_vote_url(voters, event):
|
||||||
email_subject = "Voting Access for Event '" + event.title + "'"
|
email_subject = "Voting Access for Event '" + event.title + "'"
|
||||||
email_body = 'Key: '
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
for voter in voters:
|
for voter in voters:
|
||||||
# Generate a key and create an AccessKey object
|
# Generate a key and create an AccessKey object
|
||||||
key = gen_access_key()
|
key = gen_access_key()
|
||||||
AccessKey.objects.create(user=voter, event=event, key=key)
|
AccessKey.objects.create(user=voter, event=event, key=key)
|
||||||
|
|
||||||
voter.send_email(email_subject, email_body + key)
|
# 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
|
||||||
|
|
||||||
|
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())
|
Updates the EID of an event to contain 2 event IDs: a human readable one (hr) and a crypto one (GP from param())
|
||||||
|
@ -93,6 +120,83 @@ def update_EID(event):
|
||||||
event.EID = json.dumps(EID)
|
event.EID = json.dumps(EID)
|
||||||
event.save()
|
event.save()
|
||||||
|
|
||||||
|
@task()
|
||||||
|
def event_ended(event):
|
||||||
|
# Email all trustees to request 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()
|
@task()
|
||||||
def tally_results(event):
|
def tally_results(event):
|
||||||
for poll in event.polls.all():
|
for poll in event.polls.all():
|
||||||
|
@ -101,39 +205,42 @@ def tally_results(event):
|
||||||
decs.append(dec.text)
|
decs.append(dec.text)
|
||||||
amount = len(decs)
|
amount = len(decs)
|
||||||
result = tally(amount, event.EID, decs, poll.enc)
|
result = tally(amount, event.EID, decs, poll.enc)
|
||||||
send_mail(
|
|
||||||
'Your Results:',
|
# TODO: Email organisers using email_user method?
|
||||||
poll.question_text + ": " + result,
|
|
||||||
'from@example.com',
|
|
||||||
["fake@fake.com"],
|
|
||||||
fail_silently=False,
|
|
||||||
)
|
|
||||||
print(poll.question_text + ": " + result)
|
print(poll.question_text + ": " + result)
|
||||||
|
|
||||||
@task()
|
@task()
|
||||||
def generate_combpk(event):
|
def generate_combpk(event):
|
||||||
pks = list()
|
pks = list()
|
||||||
|
|
||||||
for tkey in event.trustee_keys.all():
|
for tkey in event.trustee_keys.all():
|
||||||
pks.append(str(tkey.key))
|
pks.append(str(tkey.key))
|
||||||
amount = len(pks)
|
|
||||||
event.public_key = combpk(amount, pks)
|
event.public_key = combpk(pks)
|
||||||
|
|
||||||
event.prepared = True
|
event.prepared = True
|
||||||
event.save()
|
event.save()
|
||||||
|
|
||||||
@task
|
@task
|
||||||
def generate_enc(poll):
|
def generate_enc(poll):
|
||||||
c1s = list()#c1 components of ciphertexts
|
# c1 and c2 components of ciphertexts
|
||||||
c2s = list()#c1 components of ciphertexts
|
c1s = list()
|
||||||
|
c2s = list()
|
||||||
|
|
||||||
for ballot in poll.ballots.all():
|
for ballot in poll.ballots.all():
|
||||||
if (ballot.cast):
|
if ballot.cast:
|
||||||
c1s.append(str(ballot.cipher_text_c1))
|
c1s.append(str(ballot.cipher_text_c1))
|
||||||
c2s.append(str(ballot.cipher_text_c2))
|
c2s.append(str(ballot.cipher_text_c2))
|
||||||
|
|
||||||
ciphers = {
|
ciphers = {
|
||||||
'c1s':c1s,
|
'c1s': c1s,
|
||||||
'c2s':c2s
|
'c2s': c2s
|
||||||
}
|
}
|
||||||
amount = len(c1s)
|
|
||||||
poll.enc = addec(amount, ciphers)
|
count = len(c1s)
|
||||||
|
|
||||||
|
poll.enc = addec(count, ciphers)
|
||||||
poll.save()
|
poll.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,17 @@ register = template.Library()
|
||||||
#get a value for additively homomorphic encryption ballots
|
#get a value for additively homomorphic encryption ballots
|
||||||
#we can't do maths in the template normally so a filter is a way around it
|
#we can't do maths in the template normally so a filter is a way around it
|
||||||
@register.filter
|
@register.filter
|
||||||
def get_ballot_value(value):
|
def get_ballot_value(option_no, options_count):
|
||||||
return pow(10, value-1)
|
ballot_value = ""
|
||||||
|
|
||||||
|
for i in range(options_count):
|
||||||
|
|
||||||
|
if (i+1) == option_no:
|
||||||
|
ballot_value = ballot_value + "1"
|
||||||
|
else:
|
||||||
|
ballot_value = ballot_value + "0"
|
||||||
|
|
||||||
|
if not i == (options_count-1):
|
||||||
|
ballot_value = ballot_value + ","
|
||||||
|
|
||||||
|
return ballot_value
|
||||||
|
|
|
@ -1,37 +1,24 @@
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.contrib.auth.decorators import login_required, permission_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
app_name = 'polls'
|
app_name = 'polls'
|
||||||
|
|
||||||
#urlpatterns = [
|
|
||||||
# url(r'^$', views.index, name='index'),
|
|
||||||
# ex: /polls/5/
|
|
||||||
# url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
|
|
||||||
# ex: /polls/5/results/
|
|
||||||
# url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
|
|
||||||
# ex: /polls/5/vote/
|
|
||||||
# url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
|
|
||||||
#]
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^vote/(?P<poll_id>[0-9]+)/$', views.test_poll_vote, name='vote-poll'),
|
url(r'^$', login_required(views.EventListView.as_view()), name='index'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', views.EventDetailView.as_view(), name='view-event'),
|
|
||||||
url(r'^(?P<pk>[0-9]+)/polls$', views.EventDetailPollsView.as_view(), name='event-polls'),
|
|
||||||
url(r'^(?P<pk>[0-9]+)/organisers$', views.EventDetailOrganisersView.as_view(), name='event-organisers'),
|
|
||||||
url(r'^$', views.EventListView.as_view(), name='index'),
|
|
||||||
url(r'^create/$', login_required(views.create_event), name='create-event'),
|
url(r'^create/$', login_required(views.create_event), name='create-event'),
|
||||||
url(r'^(?P<event_id>[0-9]+)/decrypt/$', login_required(views.event_trustee_decrypt), name='decrypt-event'),
|
url(r'^(?P<pk>[0-9]+)/$', login_required(views.EventDetailView.as_view()), name='view-event'),
|
||||||
url(r'^(?P<event_id>[0-9]+)/prepare/$', login_required(views.event_trustee_setup), name='prepare-event'),
|
url(r'^(?P<pk>[0-9]+)/polls/$', login_required(views.EventDetailPollsView.as_view()), name='event-polls'),
|
||||||
url(r'^(?P<event_id>[0-9]+)/encrypt/$', login_required(views.event_addec), name='enc-event'),
|
url(r'^(?P<pk>[0-9]+)/entities/$', login_required(views.EventDetailEntitiesView.as_view()), name='event-entities'),
|
||||||
url(r'^(?P<pk>[0-9]+)/launch/$', views.EventDetailLaunchView.as_view(), name='launch-event'),
|
url(r'^(?P<pk>[0-9]+)/advanced/$', login_required(views.EventDetailAdvancedView.as_view()), name='event-advanced'),
|
||||||
url(r'^edit/(?P<event_id>[0-9]+)/$', login_required(views.edit_event), name='edit-event'),
|
url(r'^(?P<event_id>[0-9]+)/end/$', login_required(views.event_end), name='end-event'),
|
||||||
url(r'^delete/(?P<event_id>[0-9]+)/$', login_required(views.del_event), name='del-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]+)/create/poll/$', login_required(views.manage_questions), name='create-poll'),
|
||||||
url(r'^(?P<event_id>[0-9]+)/poll/(?P<poll_num>[0-9]+)/$', login_required(views.view_poll), name='view-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<event_id>[0-9]+)/poll/(?P<poll_num>[0-9]+)/edit$', login_required(views.edit_poll), name='edit-poll'),
|
|
||||||
#url(r'^(?P<pk>[0-9]+)/$', login_required(views.DetailView.as_view()), name='detail'),
|
|
||||||
#url(r'^(?P<pk>[0-9]+)/results/$', login_required(views.ResultsView.as_view()), name='results'),
|
|
||||||
#url(r'^(?P<question_id>[0-9]+)/vote/$', login_required(views.vote), name='vote'),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -539,12 +539,9 @@ class EventModelAdaptor:
|
||||||
# Extract the list of trustees
|
# Extract the list of trustees
|
||||||
trustees_list = self.form_data.pop('trustee-email-input')
|
trustees_list = self.form_data.pop('trustee-email-input')
|
||||||
|
|
||||||
for trustee in trustees_list:
|
for trustee_email in trustees_list:
|
||||||
if trustee != '':
|
if trustee_email != '':
|
||||||
if EmailUser.objects.filter(email=trustee).exists():
|
self.trustees.append(trustee_email)
|
||||||
self.trustees.append(EmailUser.objects.filter(email=trustee).get())
|
|
||||||
else:
|
|
||||||
self.trustees.append(EmailUser(email=trustee))
|
|
||||||
|
|
||||||
# Extract the email list of voters
|
# Extract the email list of voters
|
||||||
voters_csv_string = self.form_data.pop('voters-list-input')[0].replace(' ', '')
|
voters_csv_string = self.form_data.pop('voters-list-input')[0].replace(' ', '')
|
||||||
|
@ -552,17 +549,21 @@ class EventModelAdaptor:
|
||||||
|
|
||||||
for voter_email in voters_email_list:
|
for voter_email in voters_email_list:
|
||||||
if voter_email != '':
|
if voter_email != '':
|
||||||
if EmailUser.objects.filter(email=voter_email).exists():
|
self.voters.append(voter_email)
|
||||||
self.voters.append(EmailUser.objects.filter(email=voter_email).get())
|
|
||||||
else:
|
|
||||||
self.voters.append(EmailUser(email=voter_email))
|
|
||||||
|
|
||||||
# Create the Event model object - this does not persist it to the DB
|
# Create the Event model object - this does not persist it to the DB
|
||||||
|
creator = ""
|
||||||
|
if self.user.first_name is not None:
|
||||||
|
creator += self.user.first_name + " "
|
||||||
|
|
||||||
|
if self.user.last_name is not None:
|
||||||
|
creator += self.user.last_name
|
||||||
|
|
||||||
self.event = Event(start_time=self.starts_at,
|
self.event = Event(start_time=self.starts_at,
|
||||||
end_time=self.ends_at,
|
end_time=self.ends_at,
|
||||||
title=self.event_name,
|
title=self.event_name,
|
||||||
EID=self.identifier,
|
EID=self.identifier,
|
||||||
creator=self.user.first_name + ' ' + self.user.last_name,
|
creator=creator,
|
||||||
c_email=self.user.email,
|
c_email=self.user.email,
|
||||||
trustees=voters_csv_string)
|
trustees=voters_csv_string)
|
||||||
|
|
||||||
|
@ -629,20 +630,21 @@ class EventModelAdaptor:
|
||||||
# so it can just be added
|
# so it can just be added
|
||||||
self.event.users_organisers = self.organisers
|
self.event.users_organisers = self.organisers
|
||||||
|
|
||||||
# Add the list of trustees to the event, making sure they're instantiated
|
# Add the list of trustees to the event
|
||||||
|
db_trustees = list()
|
||||||
for trustee in self.trustees:
|
for trustee in self.trustees:
|
||||||
if not EmailUser.objects.filter(email=trustee.email).exists():
|
user, created = EmailUser.objects.get_or_create(email=trustee)
|
||||||
trustee.save()
|
db_trustees.append(user)
|
||||||
|
|
||||||
self.event.users_trustees = self.trustees
|
self.event.users_trustees = db_trustees
|
||||||
|
|
||||||
# Add the list of voters to the event, making sure they're instantiated
|
# Add the list of voters to the event
|
||||||
# Additionally, generating the AccessKey for voters
|
db_voters = list()
|
||||||
for voter in self.voters:
|
for voter in self.voters:
|
||||||
if not EmailUser.objects.filter(email=voter.email).exists():
|
user, created = EmailUser.objects.get_or_create(email=voter)
|
||||||
voter.save()
|
db_voters.append(user)
|
||||||
|
|
||||||
self.event.voters = self.voters
|
self.event.voters = db_voters
|
||||||
|
|
||||||
# Extract all the poll data for the event and associated poll option data
|
# Extract all the poll data for the event and associated poll option data
|
||||||
# This can only be done at this point as the event has been persisted
|
# This can only be done at this point as the event has been persisted
|
||||||
|
|
|
@ -2,25 +2,23 @@ import urllib
|
||||||
import urllib2
|
import urllib2
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from io import StringIO
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.http import HttpResponseRedirect, HttpResponse, Http404
|
from django.http import HttpResponseRedirect, HttpResponse, Http404
|
||||||
from django.http.response import HttpResponseNotAllowed
|
from django.http.response import HttpResponseNotAllowed
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.shortcuts import get_object_or_404, render, render_to_response
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.utils import timezone
|
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import serializers
|
|
||||||
|
|
||||||
from .forms import EventForm, PollForm, OptionFormset, QuestionFormset, OrganiserFormSet, TrusteeFormSet, VoteForm, EventSetupForm, EventEditForm, DecryptionFormset, DecryptionFormSetHelper
|
from .forms import PollForm, OptionFormset, VoteForm, EventSetupForm, EventEditForm
|
||||||
from .models import Event, Poll, PollOption, EmailUser, Ballot, TrusteeKey, Decryption
|
from .models import Event, Poll, Ballot, EncryptedVote, TrusteeKey, TrusteeSK
|
||||||
from allauthdemo.auth.models import DemoUser
|
from allauthdemo.auth.models import DemoUser
|
||||||
|
|
||||||
from .tasks import email_trustees_prep, update_EID, generate_combpk, generate_enc, tally_results
|
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 .utils.EventModelAdaptor import EventModelAdaptor
|
from .utils.EventModelAdaptor import EventModelAdaptor
|
||||||
|
|
||||||
|
|
||||||
class EventListView(generic.ListView):
|
class EventListView(generic.ListView):
|
||||||
|
|
||||||
model = Event
|
model = Event
|
||||||
|
@ -30,6 +28,7 @@ class EventListView(generic.ListView):
|
||||||
#context['now'] = timezone.now()
|
#context['now'] = timezone.now()
|
||||||
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
|
||||||
|
@ -37,38 +36,32 @@ class EventDetailView(generic.DetailView):
|
||||||
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['now'] = timezone.now()
|
|
||||||
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 EventDetailOrganisersView(EventDetailView):
|
|
||||||
template_name="polls/event_detail_organisers.html"
|
|
||||||
|
|
||||||
class EventDetailLaunchView(EventDetailView):
|
class EventDetailEntitiesView(EventDetailView):
|
||||||
template_name="polls/event_detail_launch.html"
|
template_name = "polls/event_detail_entities.html"
|
||||||
|
|
||||||
|
|
||||||
|
class EventDetailAdvancedView(EventDetailView):
|
||||||
|
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['now'] = timezone.now()
|
|
||||||
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
|
||||||
|
|
||||||
#my_value = self.kwargs.get('key', 'default_value')
|
|
||||||
|
|
||||||
def test_poll_detail(request, event_id, poll_num, key=None):
|
|
||||||
context = {}
|
|
||||||
context['form'] = VoteForm(instance=self.object)
|
|
||||||
context['poll_count'] = self.object.event.polls.all().count()
|
|
||||||
return render(request, "polls/event_setup.html", context)
|
|
||||||
|
|
||||||
def util_get_poll_by_event_index(event, poll_num):
|
def util_get_poll_by_event_index(event, poll_num):
|
||||||
try:
|
try:
|
||||||
|
@ -80,78 +73,114 @@ def util_get_poll_by_event_index(event, poll_num):
|
||||||
return None
|
return None
|
||||||
return poll
|
return poll
|
||||||
|
|
||||||
|
|
||||||
def edit_poll(request, event_id, poll_num):
|
def edit_poll(request, event_id, poll_num):
|
||||||
event = get_object_or_404(Event, pk=event_id)
|
event = get_object_or_404(Event, pk=event_id)
|
||||||
event_poll_count = event.polls.all().count()
|
|
||||||
poll = util_get_poll_by_event_index(event, poll_num)
|
poll = util_get_poll_by_event_index(event, poll_num)
|
||||||
|
|
||||||
if (poll == None):
|
if (poll == None):
|
||||||
raise Http404("Poll does not exist")
|
raise Http404("Poll does not exist")
|
||||||
|
|
||||||
form = PollForm(instance=poll, prefix="main")
|
if request.method == 'GET':
|
||||||
formset = OptionFormset(instance=poll, prefix="formset_options")
|
form = PollForm(instance=poll, prefix="main")
|
||||||
return render(request, "polls/generic_form.html", {'form_title': "Edit Poll: " + poll.question_text, 'form': form, 'option_formset': formset})
|
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})
|
||||||
|
elif request.method == 'POST':
|
||||||
|
form = PollForm(request.POST, instance=poll, prefix="main")
|
||||||
|
|
||||||
def view_poll(request, event_id, poll_num):
|
if form.is_valid():
|
||||||
#return HttpResponse(param("012345"))
|
form.save()
|
||||||
#return HttpResponse(combpk(param("012345"), "ABzqvL+pqTi+DNLLRcM62RwCoaZTaXVbOs3sk4fc0+Dc 0 AAaQd6S1x+bcgnkDp2ev5mTt34ICQdZIzP9GaqG4x5sy 0" "ABhQay9jI4pZvkAETNwfo8iwJ8eBMkjqplqAiu/FZxMy 0 ABPxj0jVj3rt0VW54iv4tV02gYtujnR41t5gf97asrPs 0 ABfoiW03bsYIUgfAThmjurmOViKy9L89vfkIavhQIblm 1 ABhQay9jI4pZvkAETNwfo8iwJ8eBMkjqplqAiu/FZxMy 0 ABPxj0jVj3rt0VW54iv4tV02gYtujnR41t5gf97asrPs 0 ABfoiW03bsYIUgfAThmjurmOViKy9L89vfkIavhQIblm 1 ABhQay9jI4pZvkAETNwfo8iwJ8eBMkjqplqAiu/FZxMy 0 ABPxj0jVj3rt0VW54iv4tV02gYtujnR41t5gf97asrPs 0 ABfoiW03bsYIUgfAThmjurmOViKy9L89vfkIavhQIblm 1"))
|
|
||||||
#return HttpResponse(addec("ACMW70Yj3+mJ/FO+6VOSDGYPYHf7NoTXdpInbfzUqYpH 0 ABV4Mo496B0FW3AW/7gY6Fs+oz6BwfwilonMYeriUyV/ 0 AAg+bdGhs3sxSxAc/wcKdBNUy+el8A2b4yVYShNOb8uX 0 AAspJbn5V2AaY4CgLkzCkHwUWbC5nyxrBzw+o4Az8HVM 1 ABKI7o5Yhgi44XwpFnPpLnH0/czbXA8y5vM4ucV8vojo 1 AAwVrT9+dcQsqRZYoI7+QsJvWOgd7JaJpfI6envmC2jU 1 ABIZO0DK4OrdROD805of6iRk2RenonGYmo2qG2IB1sj/ 1 ACMUHQdjGN0wyCd2AgDHMk9u0TpnywNVtamHWopGho8L 0 ABNT5lbE4siC3QklQXRvTwSQPwtme91+UrIr9iXT3y84 1 ABib0mmQ9ZVCrErqFwDgoRp3jHPpjHGQR2vsMVlwM+vI 0 ABvf3cg1NSS8fn6EKJNnTomeoflcEY1WBxkPPKrBBFl+ 0 ACBUZAtolN4HNh+mw4jLZuHzD+/rYHKR5av16PUc6BJF 0", "2"))
|
formset = OptionFormset(request.POST, instance=poll, prefix="formset_options")
|
||||||
#return HttpResponse(tally("ACNQLLQlh+lNm1Dc+X+dEI0ECVLTkxRHjRnzX1OA+HtW 0 AAWOsUZK/G/cjhUee/gPAXop3Bc0CTVG3iDdQxD6+XqV 0", "ACNQLLQlh+lNm1Dc+X+dEI0ECVLTkxRHjRnzX1OA+HtW 0 0 2", "2"))
|
|
||||||
|
if formset.is_valid():
|
||||||
|
formset.save()
|
||||||
|
return HttpResponseRedirect(reverse('polls:event-polls', args=[poll.event_id]))
|
||||||
|
|
||||||
|
|
||||||
|
def event_vote(request, event_id, poll_num):
|
||||||
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"))
|
||||||
|
|
||||||
event_poll_count = event.polls.all().count()
|
event_poll_count = event.polls.all().count()
|
||||||
prev_poll_index, next_poll_index = False, False
|
prev_poll_index, next_poll_index = False, False
|
||||||
can_vote, has_voted, voter_email, vote_count = False, False, "", 0
|
can_vote, has_voted, voter_email = False, False, ""
|
||||||
poll = util_get_poll_by_event_index(event, poll_num)
|
poll = util_get_poll_by_event_index(event, poll_num)
|
||||||
|
|
||||||
if (poll == None):
|
if poll is None:
|
||||||
raise Http404("Poll does not exist")
|
messages.add_message(request, messages.ERROR, "There was an error loading the voting page.")
|
||||||
|
return HttpResponseRedirect(reverse("user_home"))
|
||||||
|
|
||||||
form = VoteForm(instance=poll)
|
poll_num = int(poll_num) # now known to be safe as it succeeded in the util function
|
||||||
poll_num = int(poll_num) # now known to be safe as it suceeded in the util function
|
|
||||||
|
|
||||||
if (poll_num > 1):
|
if poll_num > 1:
|
||||||
prev_poll_index = (poll_num - 1)
|
prev_poll_index = (poll_num - 1)
|
||||||
if (poll_num < event_poll_count):
|
if poll_num < event_poll_count:
|
||||||
next_poll_index = (poll_num + 1)
|
next_poll_index = (poll_num + 1)
|
||||||
|
|
||||||
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)
|
||||||
vote_count = Ballot.objects.filter(poll=poll, cast=True).count()
|
|
||||||
|
|
||||||
if (email_key.exists() and event.voters.filter(email=email_key[0].user.email).exists()):
|
ballot = None
|
||||||
ballot = Ballot.objects.filter(voter=email_key[0].user, poll=poll)
|
if email_key.exists() and event.voters.filter(email=email_key[0].user.email).exists():
|
||||||
if (ballot.exists() and ballot[0].cast):
|
# Passing this test means the user can vote
|
||||||
has_voted = True
|
|
||||||
|
|
||||||
if (access_key and email_key.exists()): #or (can_vote(request.user, event))
|
|
||||||
voter_email = email_key[0].user.email
|
voter_email = email_key[0].user.email
|
||||||
can_vote = True
|
can_vote = True
|
||||||
|
|
||||||
if (request.method == "POST"):
|
# Check whether this is the first time a user is voting
|
||||||
form = VoteForm(request.POST, instance=poll)
|
ballot = Ballot.objects.filter(voter=email_key[0].user, poll=poll)
|
||||||
if (email_key.exists()):
|
if ballot.exists() and ballot[0].cast:
|
||||||
#return HttpResponse(email_key[0].key)
|
has_voted = True
|
||||||
ballot = Ballot.objects.get_or_create(voter=email_key[0].user, poll=poll)[0]
|
else:
|
||||||
|
messages.add_message(request, messages.ERROR, "You don\'t have permission to vote in this event.")
|
||||||
|
return HttpResponseRedirect(reverse("user_home"))
|
||||||
|
|
||||||
if (form.is_valid()):
|
if request.method == "POST":
|
||||||
ballot.cipher_text_c1 = request.POST["cipher_text_c1"]
|
if ballot is None:
|
||||||
ballot.cipher_text_c2 = request.POST["cipher_text_c2"]
|
ballot = Ballot.objects.get_or_create(voter=email_key[0].user, poll=poll)
|
||||||
ballot.cast = True
|
|
||||||
ballot.save()
|
|
||||||
if (next_poll_index):
|
|
||||||
return HttpResponseRedirect(reverse('polls:view-poll', kwargs={'event_id': event.id, 'poll_num': next_poll_index }) + "?key=" + email_key[0].key)
|
|
||||||
else:
|
|
||||||
return HttpResponse("Voted successfully!") # finished all polls in event
|
|
||||||
|
|
||||||
return render(request, "polls/poll_detail.html",
|
# Will store the fragments of the encoding scheme that define the vote
|
||||||
{"object": poll, "poll_num": poll_num , "event": event, "form": form, "poll_count": event.polls.all().count(),
|
encrypted_vote = EncryptedVote.objects.get_or_create(ballot=ballot[0])[0]
|
||||||
"prev_index": prev_poll_index , "next_index": next_poll_index,
|
|
||||||
"can_vote": can_vote, "voter_email": voter_email, "has_voted": has_voted, "vote_count": vote_count
|
# Clear any existing fragments - a voter changing their vote
|
||||||
|
encrypted_vote.fragment.all().delete()
|
||||||
|
|
||||||
|
# Add in the new ciphers
|
||||||
|
fragment_count = int(request.POST['vote_frag_count'])
|
||||||
|
for i in range(fragment_count):
|
||||||
|
i_str = str(i)
|
||||||
|
|
||||||
|
cipher_c1 = request.POST['cipher_c1_frag_' + i_str]
|
||||||
|
cipher_c2 = request.POST['cipher_c2_frag_' + i_str]
|
||||||
|
|
||||||
|
encrypted_vote.fragment.create(encrypted_vote=encrypted_vote,
|
||||||
|
cipher_text_c1=cipher_c1,
|
||||||
|
cipher_text_c2=cipher_c2)
|
||||||
|
|
||||||
|
ballot[0].cast = True
|
||||||
|
ballot[0].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)
|
||||||
|
else:
|
||||||
|
# The user has finished voting in the event
|
||||||
|
success_msg = 'You have successfully cast your vote(s)!'
|
||||||
|
messages.add_message(request, messages.SUCCESS, success_msg)
|
||||||
|
|
||||||
|
return HttpResponseRedirect(reverse("user_home"))
|
||||||
|
|
||||||
|
return 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
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -178,68 +207,82 @@ def event_trustee_setup(request, event_id):
|
||||||
# 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)
|
||||||
generate_combpk.delay(event)
|
generate_combpk.delay(event)
|
||||||
# TODO: Create Celery task that generates voting URLs for voters as well as creates the ballots
|
email_voters_vote_url.delay(event.voters.all(), 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)
|
||||||
|
|
||||||
# This re-direct may not be appropriate for trustees that don't have logins
|
|
||||||
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, "form": form})
|
return render(request, "polls/event_setup.html", {"event": event, "form": form, "user_email": email_key[0].user.email})
|
||||||
|
|
||||||
#if no key or is invalid?
|
#if no key or is invalid?
|
||||||
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"))
|
||||||
|
|
||||||
def event_addec(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)
|
||||||
for poll in event.polls.all():
|
|
||||||
generate_enc.delay(poll)
|
if not event.ended:
|
||||||
return HttpResponse("Generating enc.")
|
event_ended.delay(event)
|
||||||
|
|
||||||
|
# Mark the event as ended
|
||||||
|
event.ended = True
|
||||||
|
event.save()
|
||||||
|
|
||||||
|
return HttpResponseRedirect(reverse('polls:view-event', args=[event_id]))
|
||||||
|
|
||||||
|
|
||||||
|
# Returns a JSONed version of the results
|
||||||
|
def results(request, event_id):
|
||||||
|
event = get_object_or_404(Event, pk=event_id)
|
||||||
|
polls = event.polls.all()
|
||||||
|
|
||||||
|
results = ""
|
||||||
|
results += "{\"polls\":["
|
||||||
|
for poll in polls:
|
||||||
|
results += poll.enc
|
||||||
|
|
||||||
|
results += "]}"
|
||||||
|
|
||||||
|
return HttpResponse(results)
|
||||||
|
|
||||||
|
|
||||||
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):
|
|
||||||
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 (Decryption.objects.filter(event=event, user=email_key[0].user).exists()):
|
|
||||||
messages.add_message(request, messages.WARNING, 'You have already provided your decryptions for this event')
|
|
||||||
#if (event.decryptions.count() == (event.polls.count() * event.users_trustees.count())):
|
|
||||||
# tally_results.delay(event) # all keys are in
|
|
||||||
return HttpResponseRedirect(reverse("user_home"))
|
|
||||||
elif (request.method == "GET"):
|
|
||||||
initial = []
|
|
||||||
for poll in event.polls.all():
|
|
||||||
initial.append({'text': poll.enc })
|
|
||||||
formset = DecryptionFormset(initial=initial)
|
|
||||||
else:
|
|
||||||
formset = DecryptionFormset(request.POST)
|
|
||||||
data = []
|
|
||||||
for form in formset:
|
|
||||||
if form.is_valid():
|
|
||||||
data.append(form.cleaned_data.get('text'))
|
|
||||||
if (len(data) == event.polls.count()):
|
|
||||||
for dec, poll in zip(data, event.polls.all()):
|
|
||||||
Decryption.objects.get_or_create(user=email_key[0].user, event=event, poll=poll, text=dec)
|
|
||||||
messages.add_message(request, messages.SUCCESS, 'Decryption complete.')
|
|
||||||
if (event.decryptions.count() == (event.polls.count() * event.users_trustees.count())):
|
|
||||||
tally_results.delay(event) # all keys are in
|
|
||||||
else:
|
|
||||||
messages.add_message(request, messages.ERROR, 'You didn\'t provide decryptions for every poll. Please try again.')
|
|
||||||
return HttpResponseRedirect(reverse("user_home"))
|
|
||||||
return render(request, "polls/event_decrypt.html", {"event": event, "formset": formset, "helper": DecryptionFormSetHelper() })
|
|
||||||
|
|
||||||
|
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 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')
|
||||||
|
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})
|
||||||
|
elif request.method == "POST":
|
||||||
|
sk = request.POST['secret-key']
|
||||||
|
|
||||||
|
TrusteeSK.objects.create(event=event,
|
||||||
|
trustee=email_key[0].user,
|
||||||
|
key=sk)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
messages.add_message(request, messages.SUCCESS, 'Your secret key has been successfully submitted')
|
||||||
|
return HttpResponseRedirect(reverse("user_home"))
|
||||||
|
|
||||||
|
# Without an access key, the client does not have permission to access this page
|
||||||
messages.add_message(request, messages.WARNING, 'You do not have permission to decrypt this Event.')
|
messages.add_message(request, messages.WARNING, 'You do not have permission to decrypt this Event.')
|
||||||
return HttpResponseRedirect(reverse("user_home"))
|
return HttpResponseRedirect(reverse("user_home"))
|
||||||
|
|
||||||
def test_poll_vote(request, poll_id):
|
|
||||||
poll = get_object_or_404(Poll, pk=poll_id)
|
|
||||||
form = VoteForm(instance=poll)
|
|
||||||
return render(request, "polls/vote_poll.html", {"vote_form": form, "poll": poll})
|
|
||||||
|
|
||||||
def manage_questions(request, event_id):
|
def manage_questions(request, event_id):
|
||||||
|
|
||||||
|
@ -262,7 +305,7 @@ def manage_questions(request, event_id):
|
||||||
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.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]))
|
||||||
|
|
||||||
|
@ -274,6 +317,7 @@ def manage_questions(request, event_id):
|
||||||
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",
|
||||||
|
@ -285,6 +329,7 @@ def render_invalid(request, events, demo_users, invalid_fields):
|
||||||
"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()
|
||||||
|
@ -347,6 +392,7 @@ def create_event(request):
|
||||||
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":
|
||||||
|
@ -384,7 +430,6 @@ def edit_event(request, 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)
|
||||||
|
|
||||||
#class CreatePoll(generic.View):
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -393,8 +438,3 @@ def del_event(request, event_id):
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
event.delete()
|
event.delete()
|
||||||
return HttpResponseRedirect(reverse('polls:index'))
|
return HttpResponseRedirect(reverse('polls:index'))
|
||||||
|
|
||||||
def can_vote(user, event):
|
|
||||||
if event.voters.filter(email=user.email).exists():
|
|
||||||
return True
|
|
||||||
return False
|
|
|
@ -14,7 +14,7 @@
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
{% bootstrap_messages %}
|
||||||
{% if socialaccount_providers %}
|
{% if socialaccount_providers %}
|
||||||
<div class="col-md-5 col-lg-5">
|
<div class="col-md-5 col-lg-5">
|
||||||
{% include "allauth/account/provider_panel.html" with process="login" %}
|
{% include "allauth/account/provider_panel.html" with process="login" %}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
<script src="{% static 'js/papaparse.min.js' %}" type="text/javascript"></script>
|
<script src="{% static 'js/papaparse.min.js' %}" type="text/javascript"></script>
|
||||||
<script src="{% static 'js/create-event-poll.js' %}" type="text/javascript"></script>
|
<script src="{% static 'js/create-event-poll.js' %}" type="text/javascript"></script>
|
||||||
|
<script src="{% static 'js/decrypt_event.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 type="text/javascript" src="{% static 'js/core/rand.js' %}"></script>
|
<script type="text/javascript" src="{% static 'js/core/rand.js' %}"></script>
|
||||||
|
@ -74,15 +75,19 @@
|
||||||
|
|
||||||
//new function
|
//new function
|
||||||
demosEncrypt.encryptAndSubmit = function() {
|
demosEncrypt.encryptAndSubmit = function() {
|
||||||
var ctx = new CTX("BN254CX"); //new context we can use
|
// Disable the enc and submit button to prevent fn from being called twice
|
||||||
|
$('#keygen-btn').prop("disabled", true);
|
||||||
|
|
||||||
|
// Elliptic curve cryptography params used for encryption of encrypted vote
|
||||||
|
// fragments
|
||||||
|
var ctx = new CTX("BN254CX");
|
||||||
var n = new ctx.BIG();
|
var n = new ctx.BIG();
|
||||||
var g1 = new ctx.ECP();
|
var g1 = new ctx.ECP();
|
||||||
var g2 = new ctx.ECP2();
|
var g2 = new ctx.ECP2();
|
||||||
|
|
||||||
var param = $('#event-param').val();
|
var parameter = $('#event-param').val();
|
||||||
//console.log(param);
|
var tempParams = JSON.parse(JSON.parse(parameter).crypto);
|
||||||
|
|
||||||
var tempParams = JSON.parse(param);
|
|
||||||
//copying the values
|
//copying the values
|
||||||
n.copy(tempParams.n);
|
n.copy(tempParams.n);
|
||||||
g1.copy(tempParams.g1);
|
g1.copy(tempParams.g1);
|
||||||
|
@ -92,26 +97,55 @@
|
||||||
n:n,
|
n:n,
|
||||||
g1:g1,
|
g1:g1,
|
||||||
g2:g2
|
g2:g2
|
||||||
}
|
};
|
||||||
|
|
||||||
var tempPK = JSON.parse($('#comb_pk').val());
|
var tempPK = JSON.parse($('#comb_pk').val());
|
||||||
|
|
||||||
var pk = new ctx.ECP(0);
|
var pk = new ctx.ECP(0);
|
||||||
pk.copy(tempPK.PK);
|
pk.copy(tempPK.PK);
|
||||||
var answer = $('#poll-options').val();
|
|
||||||
console.log(answer);
|
|
||||||
var cipher = encrypt(params, pk, answer);
|
|
||||||
|
|
||||||
var c1Bytes = [];
|
// Obtain the user's selection (their vote) and encrypt the fragments of the binary encoding
|
||||||
cipher.C1.toBytes(c1Bytes);
|
const selection = $('#poll-options').val();
|
||||||
var c2Bytes = [];
|
const selectionFragments = selection.split(',');
|
||||||
cipher.C2.toBytes(c2Bytes);
|
|
||||||
|
|
||||||
$('#id_cipher_text_c1').val(c1Bytes.toString());
|
var cipherForm = document.getElementById("cipher-form");
|
||||||
$('#id_cipher_text_c2').val(c2Bytes.toString());
|
|
||||||
|
|
||||||
|
for(var i = 0; i < selectionFragments.length; i++) {
|
||||||
|
// Encrypt this fragment for the selection
|
||||||
|
var cipher = encrypt(params, pk, parseInt(selectionFragments[i]));
|
||||||
|
|
||||||
|
// Store C1 and C2 from the cipher in 2 arrays
|
||||||
|
var c1Bytes = [];
|
||||||
|
cipher.C1.toBytes(c1Bytes);
|
||||||
|
|
||||||
|
var c2Bytes = [];
|
||||||
|
cipher.C2.toBytes(c2Bytes);
|
||||||
|
|
||||||
|
// Inject hidden input controls into the form that represents a single ballot
|
||||||
|
var c1Input = document.createElement("input");
|
||||||
|
c1Input.setAttribute("type", "hidden");
|
||||||
|
c1Input.setAttribute("name", "cipher_c1_frag_" + i);
|
||||||
|
c1Input.setAttribute("value", c1Bytes.toString());
|
||||||
|
|
||||||
|
var c2Input = document.createElement("input");
|
||||||
|
c2Input.setAttribute("type", "hidden");
|
||||||
|
c2Input.setAttribute("name", "cipher_c2_frag_" + i);
|
||||||
|
c2Input.setAttribute("value", c2Bytes.toString());
|
||||||
|
|
||||||
|
cipherForm.appendChild(c1Input);
|
||||||
|
cipherForm.appendChild(c2Input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject a final input control into the form which specifies the number of fragments
|
||||||
|
// That make up an encrypted vote
|
||||||
|
var fragCountInput = document.createElement("input");
|
||||||
|
fragCountInput.setAttribute("type", "hidden");
|
||||||
|
fragCountInput.setAttribute("name", "vote_frag_count");
|
||||||
|
fragCountInput.setAttribute("value", "" + selectionFragments.length);
|
||||||
|
cipherForm.appendChild(fragCountInput);
|
||||||
|
|
||||||
|
// Submit the encrypted vote to the server
|
||||||
$('#cipher-form').submit();
|
$('#cipher-form').submit();
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
//new function
|
//new function
|
||||||
|
@ -149,7 +183,7 @@
|
||||||
|
|
||||||
//new function
|
//new function
|
||||||
demosEncrypt.generateKeys = function() {
|
demosEncrypt.generateKeys = function() {
|
||||||
parameter = $("#event-param").val();
|
var parameter = $("#event-param").val();
|
||||||
var tempParams = JSON.parse(JSON.parse(parameter).crypto);
|
var tempParams = JSON.parse(JSON.parse(parameter).crypto);
|
||||||
//the full objects need to be initalised as per the library, then copy the values we need into it
|
//the full objects need to be initalised as per the library, then copy the values we need into it
|
||||||
//I follow Bingsheng's code as to what objects are used in the parameter object
|
//I follow Bingsheng's code as to what objects are used in the parameter object
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<title>{% block title %}dẽmos 2{% endblock %}</title>
|
<title>{% block title %}DĒMOS 2{% endblock %}</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
|
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
|
||||||
|
|
|
@ -7,24 +7,24 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Event: {{event.title}}</h1>
|
<h2>Trustee Event Decryption for Event '{{ event.title }}'</h2>
|
||||||
<h2>Trustee Decrypt</h2>
|
<hr/>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">Secret Key</div>
|
<div class="panel-heading"><strong>Submit your Secret Key as '{{ user_email }}'</strong></div>
|
||||||
<div class="panel panel-body">
|
<div class="panel panel-body">
|
||||||
<input id="secret-key" class="textinput textInput form-control" type="text"></input>
|
<form id="sk-form" method="POST">
|
||||||
<p>Use your secret key to generate a decrypted cipher</p>
|
{% csrf_token %}
|
||||||
<button id="keygen-btn" onclick="demosEncrypt.decryptCipher()" class="btn btn-default">Decrypt</button>
|
<input id="secret-key" name="secret-key" class="textinput textInput form-control" type="text"/>
|
||||||
</div>
|
<div class="alert alert-info" role="alert" style="margin-top: 0.75em;">
|
||||||
</div>
|
Your secret key will be used to decrypt the event and get a vote tally for every poll.
|
||||||
<div class="panel panel-default">
|
</div>
|
||||||
<div class="panel-heading">Encrypted Ciphers</div>
|
<label for="files_sk_upload" class="btn btn-primary">
|
||||||
<div class="panel panel-body">
|
<span class="glyphicon glyphicon-cloud-upload"></span>
|
||||||
{% load crispy_forms_tags %}
|
Upload Key
|
||||||
<form method="post" action="" class="">
|
</label>
|
||||||
{% crispy formset helper %}
|
<input type="file" id="files_sk_upload" name="file" class="btn-info">
|
||||||
<input class="btn btn-default" type="submit" value="Submit" disabled>
|
<input type="submit" value="Submit" class="btn btn-success"/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,12 +9,22 @@
|
||||||
{% if is_organiser %}
|
{% if is_organiser %}
|
||||||
<div>
|
<div>
|
||||||
<!-- Heading -->
|
<!-- Heading -->
|
||||||
<div class="col-xs-7 col-sm-9 col-md-10">
|
<div class="col-xs-7 col-sm-9 col-md-9">
|
||||||
<h2>Event: {{object.title}}</h2>
|
<h2>Event: {{object.title}}</h2>
|
||||||
</div>
|
</div>
|
||||||
<!-- Edit Button -->
|
<!-- Edit Button -->
|
||||||
<div class="col-xs-5 col-sm-3 col-md-2 marginTopEditButton">
|
<div class="col-xs-5 col-sm-3 col-md-3 marginTopEditButton">
|
||||||
<a href="{% url 'polls:edit-event' event.id %}" class="btn btn-primary" style="float: right;">
|
{% if object.has_received_votes and object.ended == False %}
|
||||||
|
<a href="{% url 'polls:end-event' event.id %}" 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;">
|
||||||
|
<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;">
|
||||||
<span class="fa fa-pencil"></span> Edit
|
<span class="fa fa-pencil"></span> Edit
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,11 +50,11 @@
|
||||||
<a href="{% url 'polls:event-polls' event.id %}"><strong>Polls ({{ object.polls.count }})</strong></a>
|
<a href="{% url 'polls:event-polls' event.id %}"><strong>Polls ({{ object.polls.count }})</strong></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="{% block event_nav_organisers %}{% endblock %}">
|
<li class="{% block event_nav_organisers %}{% endblock %}">
|
||||||
<a href="{% url 'polls:event-organisers' event.id %}"><strong>Entities</strong></a>
|
<a href="{% url 'polls:event-entities' event.id %}"><strong>Entities</strong></a>
|
||||||
</li>
|
</li>
|
||||||
{% if is_organiser %}
|
{% if is_organiser %}
|
||||||
<li class="{% block event_nav_launch %}{% endblock %}">
|
<li class="{% block event_nav_launch %}{% endblock %}">
|
||||||
<a href="{% url 'polls:launch-event' event.id %}"><strong>Advanced</strong></a>
|
<a href="{% url 'polls:event-advanced' event.id %}"><strong>Advanced</strong></a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
{% block event_content %}
|
{% block event_content %}
|
||||||
{% if object.polls.all %}
|
{% if object.polls.all %}
|
||||||
{% for poll in object.polls.all %}
|
{% for poll in object.polls.all %}
|
||||||
<h3>Poll: {{ poll.question_text }} (<a href="{% url 'polls:view-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.id poll_num=forloop.counter %}">Edit</a>)</h3>
|
||||||
<br/>
|
<br/>
|
||||||
<h4>Poll Options:</h4>
|
<h4>Poll Options:</h4>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
|
|
|
@ -46,9 +46,13 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="btn statusBtn
|
<div class="btn statusBtn
|
||||||
{% if event.status == 'Expired' %}btn-danger{% endif %}
|
{% if event.status == 'Future' %}btn-info{% endif %}
|
||||||
|
{% if event.status == 'Prepared' %}btn-info{% endif %}
|
||||||
{% if event.status == 'Active' %}btn-success{% endif %}
|
{% if event.status == 'Active' %}btn-success{% endif %}
|
||||||
{% if event.status == 'Future' %}btn-info{% endif %}">
|
{% if event.status == 'Expired' %}btn-danger{% endif %}
|
||||||
|
{% if event.status == 'Ended' %}btn-danger{% endif %}
|
||||||
|
{% if event.status == 'Decrypted' %}btn-primary{% endif %}
|
||||||
|
">
|
||||||
{{ event.status }}
|
{{ event.status }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Trustee Event Setup for Event '{{ event.title }}'</h2>
|
<h2>Trustee Event Setup for Event '{{ event.title }}'</h2>
|
||||||
<hr/>
|
<hr/>
|
||||||
|
<h4>Key Generation For: {{ user_email }}</h4>
|
||||||
|
<br/>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><strong>Step 1: Generate Your Secret Key</strong></div>
|
<div class="panel-heading"><strong>Step 1: Generate Your Secret Key</strong></div>
|
||||||
<div class="panel panel-body">
|
<div class="panel panel-body">
|
||||||
|
|
81
allauthdemo/templates/polls/event_vote.html
Executable file
81
allauthdemo/templates/polls/event_vote.html
Executable file
|
@ -0,0 +1,81 @@
|
||||||
|
{% extends "bases/bootstrap-with-nav.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
{% block app_js_vars %}
|
||||||
|
var option_count = {{ object.options.count }};
|
||||||
|
{% 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/>
|
||||||
|
|
||||||
|
<!-- 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 %}
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% endblock %}
|
|
@ -1,85 +0,0 @@
|
||||||
{% extends "bases/bootstrap-with-nav.html" %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
{% load bootstrap3 %}
|
|
||||||
|
|
||||||
{% block app_js_vars %}
|
|
||||||
|
|
||||||
|
|
||||||
var option_count = {{ object.options.count }};
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<input id="event-param" type="text" value="{{event.EID}}" hidden></input>
|
|
||||||
<input id="comb_pk" type="text" value="{{event.public_key}}" hidden></input>
|
|
||||||
|
|
||||||
<h1>Poll: {{object.question_text}}</h1>
|
|
||||||
<span>Poll {{ poll_num }} of {{ poll_count }} in Event: <a href="{% url 'polls:view-event' object.event.id %}">{{ object.event.title }}</a></span>
|
|
||||||
<div class="panel panel-body">
|
|
||||||
{% if prev_index %}
|
|
||||||
<a href="{% url 'polls:view-poll' 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:view-poll' 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>
|
|
||||||
<a href="{% url 'polls:edit-poll' event_id=object.event.id poll_num=poll_num %}"><span class="fa fa-pencil"></span> Edit Poll</a>
|
|
||||||
{% if object.options.all %}
|
|
||||||
<h3>Options</h3>
|
|
||||||
<p> {{ vote_count }} vote(s) have been cast</p>
|
|
||||||
{% if can_vote %}
|
|
||||||
{% if has_voted %}
|
|
||||||
<p>You have already voted in this poll. Resubmitting the form will change your vote.</p>
|
|
||||||
{% endif %}
|
|
||||||
<p>Voting as {{ voter_email }} -- Do NOT share this url</p>
|
|
||||||
{% load crispy_forms_tags %}
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">Options</div>
|
|
||||||
<div class="panel panel-body">
|
|
||||||
<select class="radio-inline select form-control" id="poll-options" name="options">
|
|
||||||
{% load custom_filters_tags %}
|
|
||||||
{% for option in object.options.all %}
|
|
||||||
<option value="{{forloop.counter|get_ballot_value}}">{{ option.choice_text }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<button id="keygen-btn" onclick="demosEncrypt.encryptAndSubmit()" class="btn btn-default">Encrypt & Submit</button>
|
|
||||||
<form id="cipher-form" method="post" action="" class="">
|
|
||||||
{% crispy form %}
|
|
||||||
{% csrf_token %}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="alert alert-warning" role="alert">
|
|
||||||
<p>You do not have permission to vote in this Event.</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<p>No options are available.</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
POLL ENC {{ object.enc }}
|
|
||||||
|
|
||||||
{% if form.errors %}
|
|
||||||
{% for field in form %}
|
|
||||||
{% for error in field.errors %}
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
<strong>{{ error|escape }}</strong>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
{% for error in form.non_field_errors %}
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
<strong>{{ error|escape }}</strong>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
|
@ -157,7 +157,7 @@ input[type="file"] {
|
||||||
/* Events List page / Events Detail */
|
/* Events List page / Events Detail */
|
||||||
|
|
||||||
.statusBtn {
|
.statusBtn {
|
||||||
width: 74px;
|
width: 89px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.marginTopEventList {
|
.marginTopEventList {
|
||||||
|
|
20
static/js/decrypt_event.js
Normal file
20
static/js/decrypt_event.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
function processFileSKChange(event) {
|
||||||
|
var files = event.target.files;
|
||||||
|
|
||||||
|
if(files !== undefined
|
||||||
|
&& files[0] !== undefined) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = function(e) {
|
||||||
|
$('input#secret-key').val(reader.result);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(files[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var filesHandleSK = document.getElementById('files_sk_upload');
|
||||||
|
|
||||||
|
if(filesHandleSK) {
|
||||||
|
filesHandleSK.addEventListener('change', processFileSKChange, false);
|
||||||
|
}
|
Reference in a new issue