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