Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
8333dd7c2d | |||
913aac26ca | |||
fc4366c028 | |||
d44c21cbd7 | |||
adbdb21e0d | |||
c948615647 | |||
2c15f443f8 | |||
da24cc4f63 | |||
1bb0197b46 | |||
fd6f6bc4b1 | |||
3097092ae5 | |||
993ec142ac | |||
1bc558fc97 | |||
706fd132cd | |||
5e06ffe577 | |||
9c2c0cf108 | |||
2011749836 | |||
cb3623fda7 | |||
86f01abfdd | |||
bb061e18cd | |||
4b74072063 | |||
4bc3fcf30c | |||
05d5650f2f | |||
f0950effca |
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,9 +2,10 @@
|
|||||||
/.python-version
|
/.python-version
|
||||||
/htmlcov
|
/htmlcov
|
||||||
/venv
|
/venv
|
||||||
__javascript__
|
__target__
|
||||||
__pycache__
|
__pycache__
|
||||||
refs
|
refs
|
||||||
|
node_modules
|
||||||
|
|
||||||
\#*
|
\#*
|
||||||
.#*
|
.#*
|
||||||
|
4
HOWTO.md
4
HOWTO.md
@ -12,6 +12,10 @@ Install the Python dependencies. (If doing this in a virtualenv, add the virtual
|
|||||||
cd /path/to/Eos
|
cd /path/to/Eos
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
Install the node dependencies to build the JavaScript code.
|
||||||
|
|
||||||
|
npm install @babel/core @babel/cli @babel/preset-env babelify browserify
|
||||||
|
|
||||||
Build the JavaScript code.
|
Build the JavaScript code.
|
||||||
|
|
||||||
./build_js.sh
|
./build_js.sh
|
||||||
|
27
build_js.sh
27
build_js.sh
@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017 RunasSudo (Yingtong Li)
|
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -20,21 +20,14 @@ FLAGS="-k -mc -o"
|
|||||||
#for f in eos.js eos.js_tests; do
|
#for f in eos.js eos.js_tests; do
|
||||||
for f in eos.js_tests; do
|
for f in eos.js_tests; do
|
||||||
transcrypt -b -n $FLAGS $f.py || exit 1
|
transcrypt -b -n $FLAGS $f.py || exit 1
|
||||||
|
|
||||||
# Javascript identifiers cannot contain dots
|
|
||||||
perl -0777 -pi -e 's/eos.js/eosjs/g' eos/__javascript__/$f.js
|
|
||||||
|
|
||||||
# __pragma__ sometimes stops working???
|
|
||||||
perl -0777 -pi -e "s/__pragma__ \('.*?'\)//gs" eos/__javascript__/$f.js
|
|
||||||
|
|
||||||
# Transcrypt by default suppresses stack traces for some reason??
|
|
||||||
perl -0777 -pi -e 's/__except0__.__cause__ = null;//g' eos/__javascript__/$f.js
|
|
||||||
|
|
||||||
# Fix handling of properties, Transcrypt bug #407
|
|
||||||
perl -0777 -pi -e 's/var __get__ = function \(self, func, quotedFuncName\) \{/var __get__ = function (self, func, quotedFuncName) { if(typeof(func) != "function"){return func;}/g' eos/__javascript__/$f.js
|
|
||||||
perl -0777 -pi -e 's/property.call \((.*?), \g1.\g1.__impl__(.*?)\)/property.call ($1, $1.__impl__$2)/g' eos/__javascript__/$f.js
|
|
||||||
perl -0777 -pi -e 's/property.call \((.*?), \g1.\g1.__implpy_(.*?)\)/property.call ($1, $1.__impl__$2)/g' eos/__javascript__/$f.js
|
|
||||||
done
|
done
|
||||||
|
|
||||||
cp eos/__javascript__/eos.js_tests.js eosweb/core/static/js/eosjs.js
|
# Transcrypt syntax errors
|
||||||
perl -0777 -pi -e 's/eosjs_tests/eosjs/g' eosweb/core/static/js/eosjs.js
|
perl -0777 -pi -e 's/import \{, /import \{/g' __target__/eos*.js
|
||||||
|
|
||||||
|
# Add export
|
||||||
|
echo >> __target__/eos.js_tests.js
|
||||||
|
echo 'export {eos, __kwargtrans__};' >> __target__/eos.js_tests.js
|
||||||
|
|
||||||
|
# Convert to ES5
|
||||||
|
./node_modules/.bin/browserify -t babelify -r ./__target__/eos.js_tests.js:eosjs > eosweb/core/static/js/eosjs.js
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017 RunasSudo (Yingtong Li)
|
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -30,6 +30,9 @@ class NullEncryptedAnswer(EncryptedAnswer):
|
|||||||
def decrypt(self):
|
def decrypt(self):
|
||||||
return None, self.answer
|
return None, self.answer
|
||||||
|
|
||||||
|
def deaudit(self):
|
||||||
|
return self
|
||||||
|
|
||||||
class Ballot(EmbeddedObject):
|
class Ballot(EmbeddedObject):
|
||||||
#_id = UUIDField()
|
#_id = UUIDField()
|
||||||
encrypted_answers = EmbeddedObjectListField()
|
encrypted_answers = EmbeddedObjectListField()
|
||||||
@ -77,6 +80,9 @@ class User(EmbeddedObject):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return {k: v for k, v in self.__dict__.items() if k != '_instance'}
|
||||||
|
|
||||||
def generate_password():
|
def generate_password():
|
||||||
if is_python:
|
if is_python:
|
||||||
#__pragma__('skip')
|
#__pragma__('skip')
|
||||||
@ -104,14 +110,15 @@ class UserVoter(Voter):
|
|||||||
return self.user.name
|
return self.user.name
|
||||||
|
|
||||||
class Question(EmbeddedObject):
|
class Question(EmbeddedObject):
|
||||||
|
_ver = StringField(default='0.7')
|
||||||
|
|
||||||
prompt = StringField()
|
prompt = StringField()
|
||||||
|
description = StringField()
|
||||||
|
|
||||||
class Result(EmbeddedObject):
|
class Result(EmbeddedObject):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class ListChoiceQuestion(Question):
|
class ListChoiceQuestion(Question):
|
||||||
_ver = StringField(default='0.5')
|
|
||||||
|
|
||||||
choices = EmbeddedObjectListField()
|
choices = EmbeddedObjectListField()
|
||||||
min_choices = IntField()
|
min_choices = IntField()
|
||||||
max_choices = IntField()
|
max_choices = IntField()
|
||||||
@ -207,6 +214,8 @@ class STVResult(Result):
|
|||||||
random = BlobField()
|
random = BlobField()
|
||||||
|
|
||||||
class Election(TopLevelObject):
|
class Election(TopLevelObject):
|
||||||
|
_ver = StringField(default='0.9')
|
||||||
|
|
||||||
_id = UUIDField()
|
_id = UUIDField()
|
||||||
workflow = EmbeddedObjectField(Workflow) # Once saved, we don't care what kind of workflow it is
|
workflow = EmbeddedObjectField(Workflow) # Once saved, we don't care what kind of workflow it is
|
||||||
name = StringField()
|
name = StringField()
|
||||||
@ -215,6 +224,13 @@ class Election(TopLevelObject):
|
|||||||
questions = EmbeddedObjectListField()
|
questions = EmbeddedObjectListField()
|
||||||
results = EmbeddedObjectListField(is_hashed=False)
|
results = EmbeddedObjectListField(is_hashed=False)
|
||||||
|
|
||||||
|
is_voters_public = BooleanField(is_hashed=False, default=False)
|
||||||
|
is_votes_public = BooleanField(is_hashed=False, default=False)
|
||||||
|
|
||||||
|
def can_audit(self):
|
||||||
|
"""Can prepared votes be audited?"""
|
||||||
|
return False
|
||||||
|
|
||||||
def verify(self):
|
def verify(self):
|
||||||
#__pragma__('skip')
|
#__pragma__('skip')
|
||||||
from eos.core.hashing import SHA256
|
from eos.core.hashing import SHA256
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -38,7 +38,7 @@ class ElectionTestCase(EosTestCase):
|
|||||||
def test_run_election(self):
|
def test_run_election(self):
|
||||||
# Set up election
|
# Set up election
|
||||||
election = Election()
|
election = Election()
|
||||||
election.workflow = WorkflowBase()
|
election.workflow = BaseWorkflow()
|
||||||
|
|
||||||
# Check _instance
|
# Check _instance
|
||||||
self.assertEqual(election.workflow._instance, (election, 'workflow'))
|
self.assertEqual(election.workflow._instance, (election, 'workflow'))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# pyRCV - Preferential voting counting
|
# pyRCV - Preferential voting counting
|
||||||
# Copyright © 2016–2017 RunasSudo (Yingtong Li)
|
# Copyright © 2016-2021 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -41,10 +41,10 @@ def writeBLT(election, q_num, seats, withdrawn=[]):
|
|||||||
|
|
||||||
for candidate in flat_choices:
|
for candidate in flat_choices:
|
||||||
if candidate.party:
|
if candidate.party:
|
||||||
electionLines.append("'{} – {}'".format(candidate.name, candidate.party))
|
electionLines.append('"{} – {}"'.format(candidate.name, candidate.party))
|
||||||
else:
|
else:
|
||||||
electionLines.append("'{}'".format(candidate.name))
|
electionLines.append('"{}"'.format(candidate.name))
|
||||||
|
|
||||||
electionLines.append("'{} – {}'".format(election.name, question.prompt))
|
electionLines.append('"{} – {}"'.format(election.name, question.prompt))
|
||||||
|
|
||||||
return electionLines
|
return electionLines
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -58,7 +58,12 @@ class WorkflowTask(EmbeddedObject):
|
|||||||
|
|
||||||
def are_dependencies_met(self):
|
def are_dependencies_met(self):
|
||||||
for depends_on_desc in self.depends_on:
|
for depends_on_desc in self.depends_on:
|
||||||
for depends_on_task in self.workflow.get_tasks(depends_on_desc):
|
depends_on_tasks = list(self.workflow.get_tasks(depends_on_desc))
|
||||||
|
|
||||||
|
if len(depends_on_tasks) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for depends_on_task in depends_on_tasks:
|
||||||
if depends_on_task.status is not WorkflowTaskStatus.EXITED:
|
if depends_on_task.status is not WorkflowTaskStatus.EXITED:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@ -184,7 +189,9 @@ class TaskReleaseResults(WorkflowTask):
|
|||||||
# Concrete workflows
|
# Concrete workflows
|
||||||
# ==================
|
# ==================
|
||||||
|
|
||||||
class WorkflowBase(Workflow):
|
class BaseWorkflow(Workflow):
|
||||||
|
"""Base workflow, with no encryption"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017 RunasSudo (Yingtong Li)
|
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -35,16 +35,20 @@ class PostgreSQLDBProvider(eos.core.db.DBProvider):
|
|||||||
return [x[0] for x in self.cur.fetchall()]
|
return [x[0] for x in self.cur.fetchall()]
|
||||||
|
|
||||||
def get_all_by_fields(self, table, fields):
|
def get_all_by_fields(self, table, fields):
|
||||||
|
def does_match(val):
|
||||||
|
if '_id' in fields and val['_id'] != fields.pop('_id'):
|
||||||
|
return False
|
||||||
|
if 'type' in fields and val['type'] != fields.pop('type'):
|
||||||
|
return False
|
||||||
|
for field in fields:
|
||||||
|
if val['value'][field] != fields[field]:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
# TODO: Make this much better
|
# TODO: Make this much better
|
||||||
result = []
|
result = []
|
||||||
for val in self.get_all(table):
|
for val in self.get_all(table):
|
||||||
if '_id' in fields and val['_id'] != fields.pop('_id'):
|
if does_match(val):
|
||||||
continue
|
|
||||||
if 'type' in fields and val['type'] != fields.pop('type'):
|
|
||||||
continue
|
|
||||||
for field in fields:
|
|
||||||
if val['value'][field] != fields[field]:
|
|
||||||
continue
|
|
||||||
result.append(val)
|
result.append(val)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017 RunasSudo (Yingtong Li)
|
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -184,18 +184,21 @@ class RelatedObjectListField(Field):
|
|||||||
return None
|
return None
|
||||||
return EosList([EosObject.deserialise_and_unwrap(x, self.object_type) for x in value])
|
return EosList([EosObject.deserialise_and_unwrap(x, self.object_type) for x in value])
|
||||||
|
|
||||||
if is_python:
|
|
||||||
class UUIDField(Field):
|
class UUIDField(Field):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
if is_python:
|
||||||
super().__init__(default=uuid.uuid4, *args, **kwargs)
|
super().__init__(default=uuid.uuid4, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def serialise(self, value, options=SerialiseOptions.DEFAULT):
|
def serialise(self, value, options=SerialiseOptions.DEFAULT):
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
def deserialise(self, value):
|
def deserialise(self, value):
|
||||||
|
if is_python:
|
||||||
return uuid.UUID(value)
|
return uuid.UUID(value)
|
||||||
else:
|
else:
|
||||||
UUIDField = PrimitiveField
|
return value
|
||||||
|
|
||||||
class DateTimeField(Field):
|
class DateTimeField(Field):
|
||||||
def pad(self, number):
|
def pad(self, number):
|
||||||
@ -359,7 +362,16 @@ class DocumentObjectType(EosObjectType):
|
|||||||
fields = {}
|
fields = {}
|
||||||
if hasattr(cls, '_fields'):
|
if hasattr(cls, '_fields'):
|
||||||
fields = cls._fields.copy() if is_python else Object.create(cls._fields)
|
fields = cls._fields.copy() if is_python else Object.create(cls._fields)
|
||||||
for attr in list(dir(cls)):
|
|
||||||
|
if is_python:
|
||||||
|
attrs = list(dir(cls))
|
||||||
|
else:
|
||||||
|
# We want the raw Javascript name for getOwnPropertyDescriptor
|
||||||
|
__pragma__('jsiter')
|
||||||
|
attrs = [x for x in cls]
|
||||||
|
__pragma__('nojsiter')
|
||||||
|
|
||||||
|
for attr in attrs:
|
||||||
if not is_python:
|
if not is_python:
|
||||||
# We must skip things with getters or else they will be called here (too soon)
|
# We must skip things with getters or else they will be called here (too soon)
|
||||||
if Object.getOwnPropertyDescriptor(cls, attr).js_get:
|
if Object.getOwnPropertyDescriptor(cls, attr).js_get:
|
||||||
@ -407,6 +419,8 @@ class DocumentObject(EosObject, metaclass=DocumentObjectType):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
self._json = None
|
||||||
|
|
||||||
self._field_values = {}
|
self._field_values = {}
|
||||||
|
|
||||||
# Different to Python
|
# Different to Python
|
||||||
@ -444,6 +458,10 @@ class DocumentObject(EosObject, metaclass=DocumentObjectType):
|
|||||||
val.object_init(self, default)
|
val.object_init(self, default)
|
||||||
|
|
||||||
def serialise(self, options=SerialiseOptions.DEFAULT):
|
def serialise(self, options=SerialiseOptions.DEFAULT):
|
||||||
|
if self._ver != self._fields['_ver'].default:
|
||||||
|
# Different version, use stored JSON
|
||||||
|
return self._json
|
||||||
|
|
||||||
return {val.real_name: val.serialise(getattr(self, val.real_name), options) for attr, val in self._fields.items() if ((val.is_hashed or not options.for_hash) and (not options.should_protect or not val.is_protected))}
|
return {val.real_name: val.serialise(getattr(self, val.real_name), options) for attr, val in self._fields.items() if ((val.is_hashed or not options.for_hash) and (not options.should_protect or not val.is_protected))}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -455,7 +473,11 @@ class DocumentObject(EosObject, metaclass=DocumentObjectType):
|
|||||||
for attr, val in cls._fields.items():
|
for attr, val in cls._fields.items():
|
||||||
if attr in value:
|
if attr in value:
|
||||||
attrs[val.internal_name] = val.deserialise(value[val.real_name])
|
attrs[val.internal_name] = val.deserialise(value[val.real_name])
|
||||||
return cls(**attrs)
|
inst = cls(**attrs)
|
||||||
|
|
||||||
|
inst._json = value
|
||||||
|
|
||||||
|
return inst
|
||||||
|
|
||||||
class TopLevelObjectType(DocumentObjectType):
|
class TopLevelObjectType(DocumentObjectType):
|
||||||
def __new__(meta, name, bases, attrs):
|
def __new__(meta, name, bases, attrs):
|
||||||
@ -477,8 +499,10 @@ class TopLevelObjectType(DocumentObjectType):
|
|||||||
|
|
||||||
class TopLevelObject(DocumentObject, metaclass=TopLevelObjectType):
|
class TopLevelObject(DocumentObject, metaclass=TopLevelObjectType):
|
||||||
def save(self):
|
def save(self):
|
||||||
#res = db[self._name].replace_one({'_id': self.serialise()['_id']}, self.serialise(), upsert=True)
|
if self._ver != self._fields['_ver'].default:
|
||||||
#res = dbinfo.db[self._db_name].replace_one({'_id': self._fields['_id'].serialise(self._id)}, EosObject.serialise_and_wrap(self), upsert=True)
|
# Different version, unable to save
|
||||||
|
raise Exception('Attempted to save older vesion object')
|
||||||
|
|
||||||
dbinfo.provider.update_by_id(self._db_name, self._fields['_id'].serialise(self._id), EosObject.serialise_and_wrap(self))
|
dbinfo.provider.update_by_id(self._db_name, self._fields['_id'].serialise(self._id), EosObject.serialise_and_wrap(self))
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -31,12 +31,15 @@ class TaskStatus(EosEnum):
|
|||||||
|
|
||||||
class Task(TopLevelObject):
|
class Task(TopLevelObject):
|
||||||
label = 'Unknown task'
|
label = 'Unknown task'
|
||||||
|
_ver = StringField(default='0.8')
|
||||||
|
|
||||||
_id = UUIDField()
|
_id = UUIDField()
|
||||||
run_strategy = EmbeddedObjectField()
|
run_strategy = EmbeddedObjectField()
|
||||||
|
|
||||||
run_at = DateTimeField()
|
run_at = DateTimeField()
|
||||||
|
|
||||||
|
timeout = IntField(default=3600) # seconds
|
||||||
|
|
||||||
started_at = DateTimeField()
|
started_at = DateTimeField()
|
||||||
completed_at = DateTimeField()
|
completed_at = DateTimeField()
|
||||||
|
|
||||||
@ -113,6 +116,16 @@ class TaskScheduler:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def tick():
|
def tick():
|
||||||
|
now = DateTimeField.now()
|
||||||
|
|
||||||
for task in TaskScheduler.pending_tasks():
|
for task in TaskScheduler.pending_tasks():
|
||||||
if task.run_at and task.run_at < DateTimeField.now():
|
if task.run_at and task.run_at < now:
|
||||||
task.run()
|
task.run()
|
||||||
|
|
||||||
|
for task in TaskScheduler.active_tasks():
|
||||||
|
if task.timeout and (now - task.started_at).total_seconds() > task.timeout:
|
||||||
|
task.status = TaskStatus.TIMEOUT
|
||||||
|
task.completed_at = DateTimeField.now()
|
||||||
|
task.messages.append('Elapsed time exceeded timeout')
|
||||||
|
task.save()
|
||||||
|
task.error()
|
||||||
|
51
eos/core/tasks/threading.py
Normal file
51
eos/core/tasks/threading.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# Eos - Verifiable elections
|
||||||
|
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from eos.core.tasks import *
|
||||||
|
from eos.core.objects import *
|
||||||
|
|
||||||
|
import threading
|
||||||
|
|
||||||
|
class ThreadingRunStrategy(RunStrategy):
|
||||||
|
def run(self, task):
|
||||||
|
def _run():
|
||||||
|
task.status = TaskStatus.PROCESSING
|
||||||
|
task.started_at = DateTimeField.now()
|
||||||
|
task.save()
|
||||||
|
|
||||||
|
try:
|
||||||
|
task._run()
|
||||||
|
task.status = TaskStatus.COMPLETE
|
||||||
|
task.completed_at = DateTimeField.now()
|
||||||
|
task.save()
|
||||||
|
|
||||||
|
task.complete()
|
||||||
|
except Exception as e:
|
||||||
|
task.status = TaskStatus.FAILED
|
||||||
|
task.completed_at = DateTimeField.now()
|
||||||
|
if is_python:
|
||||||
|
#__pragma__('skip')
|
||||||
|
import traceback
|
||||||
|
#__pragma__('noskip')
|
||||||
|
task.messages.append(traceback.format_exc())
|
||||||
|
else:
|
||||||
|
task.messages.append(repr(e))
|
||||||
|
task.save()
|
||||||
|
|
||||||
|
task.error()
|
||||||
|
|
||||||
|
thread = threading.Thread(target=_run, args=())
|
||||||
|
thread.start()
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -133,6 +133,7 @@ class TaskTestCase(EosTestCase):
|
|||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.db_connect_and_reset()
|
cls.db_connect_and_reset()
|
||||||
|
|
||||||
|
@py_only
|
||||||
def test_normal(self):
|
def test_normal(self):
|
||||||
class TaskNormal(Task):
|
class TaskNormal(Task):
|
||||||
result = StringField()
|
result = StringField()
|
||||||
@ -149,6 +150,7 @@ class TaskTestCase(EosTestCase):
|
|||||||
self.assertEqual(task.messages[0], 'Hello World')
|
self.assertEqual(task.messages[0], 'Hello World')
|
||||||
self.assertEqual(task.result, 'Success')
|
self.assertEqual(task.result, 'Success')
|
||||||
|
|
||||||
|
@py_only
|
||||||
def test_error(self):
|
def test_error(self):
|
||||||
class TaskError(Task):
|
class TaskError(Task):
|
||||||
def _run(self):
|
def _run(self):
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017 RunasSudo (Yingtong Li)
|
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -14,8 +14,23 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import eos.js
|
import eos.core.objects
|
||||||
|
import eos.core.bigint
|
||||||
|
import eos.core.hashing
|
||||||
import eos.core.tests
|
import eos.core.tests
|
||||||
|
import eos.core.tasks
|
||||||
|
import eos.core.tasks.direct
|
||||||
|
|
||||||
|
import eos.base.election
|
||||||
|
import eos.base.workflow
|
||||||
|
|
||||||
|
import eos.psr.bitstream
|
||||||
|
import eos.psr.crypto
|
||||||
|
import eos.psr.election
|
||||||
|
import eos.psr.mixnet
|
||||||
|
import eos.psr.workflow
|
||||||
|
|
||||||
|
import eos.redditauth.election
|
||||||
|
|
||||||
import eos.base.tests
|
import eos.base.tests
|
||||||
import eos.psr.tests
|
import eos.psr.tests
|
||||||
|
30
eos/nsauth/election.py
Normal file
30
eos/nsauth/election.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Eos - Verifiable elections
|
||||||
|
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from eos.base.election import *
|
||||||
|
from eos.core.objects import *
|
||||||
|
|
||||||
|
class NationStatesUser(User):
|
||||||
|
username = StringField()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.username
|
||||||
|
|
||||||
|
def matched_by(self, other):
|
||||||
|
if not isinstance(other, NationStatesUser):
|
||||||
|
return False
|
||||||
|
return other.username.lower().strip().replace(' ', '_') == self.username.lower().strip().replace(' ', '_')
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017 RunasSudo (Yingtong Li)
|
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -259,7 +259,8 @@ class PedersenVSSPrivateKey(EmbeddedObject):
|
|||||||
def get_modified_secret(self):
|
def get_modified_secret(self):
|
||||||
mod_s = self.x
|
mod_s = self.x
|
||||||
for j in range(1, threshold + 1): # 1 to threshold
|
for j in range(1, threshold + 1): # 1 to threshold
|
||||||
...
|
# TODO
|
||||||
|
pass
|
||||||
|
|
||||||
def decrypt(self, ciphertext):
|
def decrypt(self, ciphertext):
|
||||||
if (
|
if (
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017 RunasSudo (Yingtong Li)
|
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -225,12 +225,20 @@ class InternalMixingTrustee(MixingTrustee):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
class PSRElection(Election):
|
class PSRElection(Election):
|
||||||
|
is_voters_public = BooleanField(is_hashed=False, default=True)
|
||||||
|
is_votes_public = BooleanField(is_hashed=False, default=True)
|
||||||
|
|
||||||
sk = EmbeddedObjectField(SEGPrivateKey, is_protected=True) # TODO: Threshold
|
sk = EmbeddedObjectField(SEGPrivateKey, is_protected=True) # TODO: Threshold
|
||||||
|
|
||||||
public_key = EmbeddedObjectField(SEGPublicKey)
|
public_key = EmbeddedObjectField(SEGPublicKey)
|
||||||
mixing_trustees = EmbeddedObjectListField()
|
mixing_trustees = EmbeddedObjectListField()
|
||||||
|
|
||||||
|
def can_audit(self):
|
||||||
|
"""Overrides Election.can_audit"""
|
||||||
|
return True
|
||||||
|
|
||||||
def verify(self):
|
def verify(self):
|
||||||
|
"""Overrides Election.verify"""
|
||||||
# Verify ballots
|
# Verify ballots
|
||||||
super().verify()
|
super().verify()
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017 RunasSudo (Yingtong Li)
|
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -43,9 +43,9 @@ class BasePyTestCase(TestCase):
|
|||||||
class BaseJSTestCase(TestCase):
|
class BaseJSTestCase(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
with open('eos/__javascript__/eos.js_tests.js', 'r') as f:
|
with open('eosweb/core/static/js/eosjs.js', 'r') as f:
|
||||||
code = f.read()
|
code = f.read()
|
||||||
cls.ctx = execjs.get().compile('var window={},navigator={};' + code + 'var test=window.eosjs_tests.' + cls.module + '.__all__.' + cls.name + '();test.setUpClass();')
|
cls.ctx = execjs.get().compile('var window={},navigator={};' + code + 'var eosjs=require("eosjs");var test=eosjs.' + cls.module + '.' + cls.name + '();test.setUpClass();')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_method(cls, method):
|
def add_method(cls, method):
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Eos - Verifiable elections
|
# Eos - Verifiable elections
|
||||||
# Copyright © 2017-18 RunasSudo (Yingtong Li)
|
# Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -120,50 +120,6 @@ def run_tests(prefix, lang):
|
|||||||
def sessdb():
|
def sessdb():
|
||||||
app.session_interface.db.create_all()
|
app.session_interface.db.create_all()
|
||||||
|
|
||||||
# TODO: Will remove this once we have a web UI
|
|
||||||
@app.cli.command('drop_db_and_setup')
|
|
||||||
def setup_test_election():
|
|
||||||
# DANGER!
|
|
||||||
dbinfo.provider.reset_db()
|
|
||||||
|
|
||||||
# Set up election
|
|
||||||
election = PSRElection()
|
|
||||||
election.workflow = PSRWorkflow()
|
|
||||||
|
|
||||||
# Set election details
|
|
||||||
election.name = 'Test Election'
|
|
||||||
|
|
||||||
from eos.redditauth.election import RedditUser
|
|
||||||
election.voters.append(UserVoter(user=EmailUser(name='Alice', email='alice@localhost')))
|
|
||||||
election.voters.append(UserVoter(user=EmailUser(name='Bob', email='bob@localhost')))
|
|
||||||
election.voters.append(UserVoter(user=EmailUser(name='Carol', email='carol@localhost')))
|
|
||||||
election.voters.append(UserVoter(user=RedditUser(username='RunasSudo')))
|
|
||||||
|
|
||||||
for voter in election.voters:
|
|
||||||
if isinstance(voter, UserVoter):
|
|
||||||
if isinstance(voter.user, EmailUser):
|
|
||||||
emails.voter_email_password(election, voter)
|
|
||||||
|
|
||||||
election.mixing_trustees.append(InternalMixingTrustee(name='Eos Voting'))
|
|
||||||
election.mixing_trustees.append(InternalMixingTrustee(name='Eos Voting'))
|
|
||||||
|
|
||||||
election.sk = EGPrivateKey.generate()
|
|
||||||
election.public_key = election.sk.public_key
|
|
||||||
|
|
||||||
question = PreferentialQuestion(prompt='President', choices=[
|
|
||||||
Ticket(name='ACME Party', choices=[
|
|
||||||
Choice(name='John Smith'),
|
|
||||||
Choice(name='Joe Bloggs', party='Independent ACME')
|
|
||||||
]),
|
|
||||||
Choice(name='John Q. Public')
|
|
||||||
], min_choices=0, max_choices=3, randomise_choices=True)
|
|
||||||
election.questions.append(question)
|
|
||||||
|
|
||||||
question = ApprovalQuestion(prompt='Chairman', choices=[Choice(name='John Doe'), Choice(name='Andrew Citizen')], min_choices=0, max_choices=1)
|
|
||||||
election.questions.append(question)
|
|
||||||
|
|
||||||
election.save()
|
|
||||||
|
|
||||||
@app.cli.command('verify_election')
|
@app.cli.command('verify_election')
|
||||||
@click.option('--electionid', default=None)
|
@click.option('--electionid', default=None)
|
||||||
def verify_election(electionid):
|
def verify_election(electionid):
|
||||||
@ -196,6 +152,20 @@ def tally_stv_election(electionid, qnum, randfile, numseats):
|
|||||||
task.save()
|
task.save()
|
||||||
task.run()
|
task.run()
|
||||||
|
|
||||||
|
@app.cli.command('run_task')
|
||||||
|
@click.option('--electionid', default=None)
|
||||||
|
@click.option('--task_name', default=None)
|
||||||
|
def tally_stv_election(electionid, task_name):
|
||||||
|
election = Election.get_by_id(electionid)
|
||||||
|
task = WorkflowTaskEntryWebTask(
|
||||||
|
election_id=election._id,
|
||||||
|
workflow_task=task_name,
|
||||||
|
status=TaskStatus.READY,
|
||||||
|
run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])()
|
||||||
|
)
|
||||||
|
task.save()
|
||||||
|
task.run()
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def inject_globals():
|
def inject_globals():
|
||||||
return {'eos': eos, 'eosweb': eosweb, 'SHA256': eos.core.hashing.SHA256}
|
return {'eos': eos, 'eosweb': eosweb, 'SHA256': eos.core.hashing.SHA256}
|
||||||
@ -213,29 +183,6 @@ def tick_scheduler():
|
|||||||
|
|
||||||
# === Views ===
|
# === Views ===
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
elections = Election.get_all()
|
|
||||||
elections.sort(key=lambda e: e.name)
|
|
||||||
|
|
||||||
elections_open = [e for e in elections if e.workflow.get_task('eos.base.workflow.TaskCloseVoting').status == WorkflowTaskStatus.READY]
|
|
||||||
|
|
||||||
elections_soon = [e for e in elections if e.workflow.get_task('eos.base.workflow.TaskOpenVoting').status != WorkflowTaskStatus.EXITED and e.workflow.get_task('eos.base.workflow.TaskOpenVoting').get_entry_task().run_at]
|
|
||||||
elections_soon.sort(key=lambda e: e.workflow.get_task('eos.base.workflow.TaskOpenVoting').get_entry_task().run_at)
|
|
||||||
|
|
||||||
elections_closed = [e for e in elections if e.workflow.get_task('eos.base.workflow.TaskCloseVoting').status == WorkflowTaskStatus.EXITED]
|
|
||||||
elections_closed.sort(key=lambda e: e.workflow.get_task('eos.base.workflow.TaskCloseVoting').exited_at, reverse=True)
|
|
||||||
elections_closed = elections_closed[:5]
|
|
||||||
|
|
||||||
return flask.render_template('index.html', elections_open=elections_open, elections_soon=elections_soon, elections_closed=elections_closed)
|
|
||||||
|
|
||||||
@app.route('/elections')
|
|
||||||
def elections():
|
|
||||||
elections = Election.get_all()
|
|
||||||
elections.sort(key=lambda e: e.name)
|
|
||||||
|
|
||||||
return flask.render_template('elections.html', elections=elections)
|
|
||||||
|
|
||||||
def using_election(func):
|
def using_election(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapped(election_id, **kwargs):
|
def wrapped(election_id, **kwargs):
|
||||||
@ -252,11 +199,76 @@ def election_admin(func):
|
|||||||
return flask.Response('Administrator credentials required', 403)
|
return flask.Response('Administrator credentials required', 403)
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
elections = Election.get_all()
|
||||||
|
elections.sort(key=lambda e: e.name)
|
||||||
|
|
||||||
|
elections_open = [e for e in elections if e.workflow.get_task('eos.base.workflow.TaskCloseVoting').status == WorkflowTaskStatus.READY]
|
||||||
|
|
||||||
|
elections_soon = [e for e in elections if e.workflow.get_task('eos.base.workflow.TaskOpenVoting').status != WorkflowTaskStatus.EXITED and e.workflow.get_task('eos.base.workflow.TaskOpenVoting').get_entry_task()]
|
||||||
|
elections_soon.sort(key=lambda e: e.workflow.get_task('eos.base.workflow.TaskOpenVoting').get_entry_task().run_at)
|
||||||
|
|
||||||
|
elections_closed = [e for e in elections if e.workflow.get_task('eos.base.workflow.TaskCloseVoting').status == WorkflowTaskStatus.EXITED]
|
||||||
|
elections_closed.sort(key=lambda e: e.workflow.get_task('eos.base.workflow.TaskCloseVoting').exited_at, reverse=True)
|
||||||
|
elections_closed = elections_closed[:5]
|
||||||
|
|
||||||
|
return flask.render_template('index.html', elections_open=elections_open, elections_soon=elections_soon, elections_closed=elections_closed)
|
||||||
|
|
||||||
|
@app.route('/elections')
|
||||||
|
def elections():
|
||||||
|
elections = Election.get_all()
|
||||||
|
elections.sort(key=lambda e: e.name)
|
||||||
|
|
||||||
|
return flask.render_template('elections.html', elections=elections)
|
||||||
|
|
||||||
|
@app.route('/elections/batch', methods=['GET', 'POST'])
|
||||||
|
@election_admin
|
||||||
|
def elections_batch():
|
||||||
|
if flask.request.method == 'POST':
|
||||||
|
# Execute
|
||||||
|
for k, v in flask.request.form.items():
|
||||||
|
if k.startswith('election_') and v:
|
||||||
|
election_id = k[9:]
|
||||||
|
election = Election.get_by_id(election_id)
|
||||||
|
for workflow_task in election.workflow.tasks:
|
||||||
|
if workflow_task.status == eos.base.workflow.WorkflowTaskStatus.READY:
|
||||||
|
task = WorkflowTaskEntryWebTask(
|
||||||
|
election_id=election._id,
|
||||||
|
workflow_task=workflow_task._name,
|
||||||
|
status=TaskStatus.READY,
|
||||||
|
run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])()
|
||||||
|
)
|
||||||
|
task.run()
|
||||||
|
break
|
||||||
|
|
||||||
|
elections = []
|
||||||
|
for election in Election.get_all():
|
||||||
|
if any(workflow_task.status == eos.base.workflow.WorkflowTaskStatus.READY for workflow_task in election.workflow.tasks):
|
||||||
|
elections.append(election)
|
||||||
|
|
||||||
|
elections.sort(key=lambda e: e.name)
|
||||||
|
|
||||||
|
return flask.render_template('elections_batch.html', elections=elections)
|
||||||
|
|
||||||
@app.route('/election/<election_id>/')
|
@app.route('/election/<election_id>/')
|
||||||
@using_election
|
@using_election
|
||||||
def election_api_json(election):
|
def election_api_json(election):
|
||||||
is_full = 'full' in flask.request.args
|
is_full = 'full' in flask.request.args
|
||||||
return flask.Response(EosObject.to_json(EosObject.serialise_and_wrap(election, None, SerialiseOptions(should_protect=True, for_hash=(not is_full), combine_related=True))), mimetype='application/json')
|
|
||||||
|
serialised = EosObject.serialise_and_wrap(election, None, SerialiseOptions(should_protect=True, for_hash=(not is_full), combine_related=True))
|
||||||
|
|
||||||
|
# Protect voters, votes if required
|
||||||
|
if not election.is_voters_public:
|
||||||
|
if 'voters' in serialised['value']:
|
||||||
|
del serialised['value']['voters']
|
||||||
|
if not election.is_votes_public:
|
||||||
|
if 'voters' in serialised['value']:
|
||||||
|
for voter in serialised['value']['voters']:
|
||||||
|
if 'votes' in voter['value']:
|
||||||
|
del voter['value']['votes']
|
||||||
|
|
||||||
|
return flask.Response(EosObject.to_json(serialised), mimetype='application/json')
|
||||||
|
|
||||||
@app.route('/election/<election_id>/view')
|
@app.route('/election/<election_id>/view')
|
||||||
@using_election
|
@using_election
|
||||||
@ -279,15 +291,21 @@ def election_view_questions(election):
|
|||||||
@app.route('/election/<election_id>/view/ballots')
|
@app.route('/election/<election_id>/view/ballots')
|
||||||
@using_election
|
@using_election
|
||||||
def election_view_ballots(election):
|
def election_view_ballots(election):
|
||||||
|
if election.is_voters_public or ('user' in flask.session and flask.session['user'].is_admin()):
|
||||||
return flask.render_template('election/view/ballots.html', election=election)
|
return flask.render_template('election/view/ballots.html', election=election)
|
||||||
|
|
||||||
|
return flask.Response('Voters not public', 403)
|
||||||
|
|
||||||
@app.route('/election/<election_id>/voter/<voter_id>')
|
@app.route('/election/<election_id>/voter/<voter_id>')
|
||||||
@using_election
|
@using_election
|
||||||
def election_voter_view(election, voter_id):
|
def election_voter_view(election, voter_id):
|
||||||
|
if (election.is_voters_public and election.is_votes_public) or ('user' in flask.session and flask.session['user'].is_admin()):
|
||||||
voter_id = uuid.UUID(voter_id)
|
voter_id = uuid.UUID(voter_id)
|
||||||
voter = next(voter for voter in election.voters if voter._id == voter_id)
|
voter = next(voter for voter in election.voters if voter._id == voter_id)
|
||||||
return flask.render_template('election/voter/view.html', election=election, voter=voter)
|
return flask.render_template('election/voter/view.html', election=election, voter=voter)
|
||||||
|
|
||||||
|
return flask.Response('Voters not public', 403)
|
||||||
|
|
||||||
@app.route('/election/<election_id>/view/trustees')
|
@app.route('/election/<election_id>/view/trustees')
|
||||||
@using_election
|
@using_election
|
||||||
def election_view_trustees(election):
|
def election_view_trustees(election):
|
||||||
@ -448,6 +466,14 @@ def email_login():
|
|||||||
def email_authenticate():
|
def email_authenticate():
|
||||||
user = None
|
user = None
|
||||||
|
|
||||||
|
for u in app.config['ADMINS']:
|
||||||
|
if isinstance(u, EmailUser):
|
||||||
|
if u.email.lower() == flask.request.form['email'].lower():
|
||||||
|
if u.password == flask.request.form['password']:
|
||||||
|
user = u
|
||||||
|
break
|
||||||
|
|
||||||
|
if user is None:
|
||||||
for election in Election.get_all():
|
for election in Election.get_all():
|
||||||
for voter in election.voters:
|
for voter in election.voters:
|
||||||
if isinstance(voter.user, EmailUser):
|
if isinstance(voter.user, EmailUser):
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017 RunasSudo (Yingtong Li)
|
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -19,18 +19,37 @@
|
|||||||
window = self; // Workaround for libraries
|
window = self; // Workaround for libraries
|
||||||
isLibrariesLoaded = false;
|
isLibrariesLoaded = false;
|
||||||
|
|
||||||
|
eosjs = null;
|
||||||
|
|
||||||
function generateEncryptedVote(election, answers, should_do_fingerprint) {
|
function generateEncryptedVote(election, answers, should_do_fingerprint) {
|
||||||
|
if (election._name === 'eos.psr.election.PSRElection') {
|
||||||
encrypted_answers = [];
|
encrypted_answers = [];
|
||||||
for (var q_num = 0; q_num < answers.length; q_num++) {
|
for (var q_num = 0; q_num < answers.length; q_num++) {
|
||||||
answer_json = answers[q_num];
|
answer_json = answers[q_num];
|
||||||
answer = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(answer_json, null);
|
answer = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(answer_json, null);
|
||||||
encrypted_answer = eosjs.eos.psr.election.__all__.BlockEncryptedAnswer.encrypt(election.public_key, answer, election.questions.__getitem__(q_num).max_bits() + 32); // +32 bits for the length
|
encrypted_answer = eosjs.eos.psr.election.BlockEncryptedAnswer.encrypt(election.public_key, answer, election.questions.__getitem__(q_num).max_bits() + 32); // +32 bits for the length
|
||||||
encrypted_answers.push(eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(encrypted_answer, null));
|
encrypted_answers.push(eosjs.eos.core.objects.EosObject.serialise_and_wrap(encrypted_answer, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
postMessage({
|
postMessage({
|
||||||
encrypted_answers: encrypted_answers
|
encrypted_answers: encrypted_answers
|
||||||
});
|
});
|
||||||
|
} else if (election._name === 'eos.base.election.Election') {
|
||||||
|
encrypted_answers = [];
|
||||||
|
for (var q_num = 0; q_num < answers.length; q_num++) {
|
||||||
|
answer_json = answers[q_num];
|
||||||
|
answer = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(answer_json, null);
|
||||||
|
encrypted_answer = eosjs.eos.base.election.NullEncryptedAnswer();
|
||||||
|
encrypted_answer.answer = answer;
|
||||||
|
encrypted_answers.push(eosjs.eos.core.objects.EosObject.serialise_and_wrap(encrypted_answer, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
postMessage({
|
||||||
|
encrypted_answers: encrypted_answers
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw "Don't know how to encrypt ballots in election of type " + election._name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onmessage = function(msg) {
|
onmessage = function(msg) {
|
||||||
@ -40,10 +59,11 @@ onmessage = function(msg) {
|
|||||||
msg.data.static_base_url + "js/eosjs.js"
|
msg.data.static_base_url + "js/eosjs.js"
|
||||||
);
|
);
|
||||||
isLibrariesLoaded = true;
|
isLibrariesLoaded = true;
|
||||||
|
eosjs = require("eosjs");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.data.action === "generateEncryptedVote") {
|
if (msg.data.action === "generateEncryptedVote") {
|
||||||
msg.data.election = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(msg.data.election, null);
|
msg.data.election = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(msg.data.election, null);
|
||||||
|
|
||||||
generateEncryptedVote(msg.data.election, msg.data.answers);
|
generateEncryptedVote(msg.data.election, msg.data.answers);
|
||||||
} else {
|
} else {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -25,7 +25,7 @@
|
|||||||
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
|
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>The following is your ballot with fingerprint <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64() }}</span>, decrypted and ready for auditing.</p>
|
<p>The following is your ballot with fingerprint <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>, decrypted and ready for auditing.</p>
|
||||||
|
|
||||||
<div class="ui form">
|
<div class="ui form">
|
||||||
{# For some reason nunjucks doesn't like calling this the normal way #}
|
{# For some reason nunjucks doesn't like calling this the normal way #}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<h1>{{ election.name }}</h1>
|
<h1>{{ election.name }}</h1>
|
||||||
|
|
||||||
<p><small><b>{{ election.kind|title }} fingerprint:</b> <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(election).hash_as_b64() }}</span></small></p>
|
<p><small><b>{{ election.kind|title }} fingerprint:</b> <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(election).hash_as_b64() }}</span></small></p>
|
||||||
|
|
||||||
{# Convert the template name to a numerical index for comparison #}
|
{# Convert the template name to a numerical index for comparison #}
|
||||||
{% if template == 'booth/welcome.html' %}
|
{% if template == 'booth/welcome.html' %}
|
||||||
@ -32,9 +32,17 @@
|
|||||||
{% elif template == 'booth/audit.html' %}
|
{% elif template == 'booth/audit.html' %}
|
||||||
{% set menuindex = 4 %}
|
{% set menuindex = 4 %}
|
||||||
{% elif template == 'booth/cast.html' %}
|
{% elif template == 'booth/cast.html' %}
|
||||||
|
{% if election.can_audit() %}
|
||||||
{% set menuindex = 5 %}
|
{% set menuindex = 5 %}
|
||||||
|
{% else %}
|
||||||
|
{% set menuindex = 4 %}
|
||||||
|
{% endif %}
|
||||||
{% elif template == 'booth/complete.html' %}
|
{% elif template == 'booth/complete.html' %}
|
||||||
|
{% if election.can_audit() %}
|
||||||
{% set menuindex = 6 %}
|
{% set menuindex = 6 %}
|
||||||
|
{% else %}
|
||||||
|
{% set menuindex = 5 %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% macro menuitem(index, text) %}
|
{% macro menuitem(index, text) %}
|
||||||
@ -50,9 +58,14 @@
|
|||||||
{{ menuitem(1, "Welcome") }}
|
{{ menuitem(1, "Welcome") }}
|
||||||
{{ menuitem(2, "Select") }}
|
{{ menuitem(2, "Select") }}
|
||||||
{{ menuitem(3, "Review") }}
|
{{ menuitem(3, "Review") }}
|
||||||
|
{% if election.can_audit() %}
|
||||||
{{ menuitem(4, "Audit") }}
|
{{ menuitem(4, "Audit") }}
|
||||||
{{ menuitem(5, "Cast") }}
|
{{ menuitem(5, "Cast") }}
|
||||||
{{ menuitem(6, "Finish") }}
|
{{ menuitem(6, "Finish") }}
|
||||||
|
{% else %}
|
||||||
|
{{ menuitem(4, "Cast") }}
|
||||||
|
{{ menuitem(5, "Finish") }}
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="cast_prompt">
|
<div id="cast_prompt">
|
||||||
<p>Your vote has <span class="superem">not</span> yet been cast. Please make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.</p>
|
<p>Your vote has <span class="superem">not</span> yet been cast.{% if election.can_audit() %} Please make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.{% endif %}</p>
|
||||||
|
|
||||||
<div class="ui negative message">
|
<div class="ui negative message">
|
||||||
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
|
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
|
||||||
@ -69,10 +69,12 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block after %}
|
{% block after %}
|
||||||
|
{% if election.can_audit() %}
|
||||||
<div class="ui tiny message" style="margin-top: 3em;">
|
<div class="ui tiny message" style="margin-top: 3em;">
|
||||||
<div class="header">Information for advanced users</div>
|
<div class="header">Information for advanced users</div>
|
||||||
<p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
<p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(".message .close").on("click", function() {
|
$(".message .close").on("click", function() {
|
||||||
@ -104,8 +106,8 @@
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: "{{ election_base_url }}stage_ballot",
|
url: "{{ election_base_url }}stage_ballot",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
data: eosjs.eos.core.objects.__all__.EosObject.to_json({
|
data: eosjs.eos.core.objects.EosObject.to_json({
|
||||||
"ballot": eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(deauditedBallot, null),
|
"ballot": eosjs.eos.core.objects.EosObject.serialise_and_wrap(deauditedBallot, null),
|
||||||
"fingerprint": booth.fingerprint || null
|
"fingerprint": booth.fingerprint || null
|
||||||
}),
|
}),
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
@ -167,9 +169,9 @@
|
|||||||
dataType: "text"
|
dataType: "text"
|
||||||
})
|
})
|
||||||
.done(function(data) {
|
.done(function(data) {
|
||||||
response = eosjs.eos.core.objects.__all__.EosObject.from_json(data);
|
response = eosjs.eos.core.objects.EosObject.from_json(data);
|
||||||
booth.voter = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(response.voter);
|
booth.voter = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(response.voter);
|
||||||
booth.vote = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(response.vote);
|
booth.vote = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(response.vote);
|
||||||
|
|
||||||
// Clear plaintexts
|
// Clear plaintexts
|
||||||
booth.answers = null;
|
booth.answers = null;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -23,7 +23,9 @@
|
|||||||
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
|
<p>Your vote has <span class="superem">not</span> yet been cast. Please follow the instructions to continue.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>Please make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>. Please retain a copy of your ballot fingerprint – you can use it to verify that your vote has been counted correctly. You may <a href="#" onclick="window.print();return false;">print this page</a> as a receipt if you wish.</p>
|
{% if election.can_audit() %}
|
||||||
|
<p>Please make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>. Please retain a copy of your ballot fingerprint – you can use it to verify that your vote has been counted correctly. You may <a href="#" onclick="window.print();return false;">print this page</a> as a receipt if you wish.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<p>To continue, copy and paste the ballot below and provide it to the election administrator.</p>
|
<p>To continue, copy and paste the ballot below and provide it to the election administrator.</p>
|
||||||
|
|
||||||
@ -39,10 +41,12 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block after %}
|
{% block after %}
|
||||||
|
{% if election.can_audit() %}
|
||||||
<div class="ui tiny message" style="margin-top: 3em;">
|
<div class="ui tiny message" style="margin-top: 3em;">
|
||||||
<div class="header">Information for advanced users</div>
|
<div class="header">Information for advanced users</div>
|
||||||
<p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
<p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block help %}
|
{% block help %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -26,24 +26,33 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="header">Smart ballot tracker</div>
|
<div class="header">Smart ballot tracker</div>
|
||||||
<p>This smart ballot tracker confirms that {{ voter.py_name }} cast a vote in the election {{ election.py_name }} at {{ vote.cast_at }}.</p>
|
<p>This smart ballot tracker confirms that {{ voter.py_name }} cast a vote in the election {{ election.py_name }} at {{ vote.cast_at }}.</p>
|
||||||
<p>Ballot fingerprint: <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(vote.ballot).hash_as_b64(true) }}</span></p>
|
{% if election.can_audit() %}
|
||||||
|
<p>Ballot fingerprint: <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(vote.ballot).hash_as_b64(true) }}</span></p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if election.can_audit() %}
|
||||||
<p>Please check that the ballot fingerprint above matches the ballot fingerprint you recorded earlier.</p>
|
<p>Please check that the ballot fingerprint above matches the ballot fingerprint you recorded earlier.</p>
|
||||||
|
|
||||||
<p>To confirm that your ballot was cast correctly, please go to the <a href="{{ election_base_url }}view/ballots">‘Voters and ballots’ page</a> for the {{ election.kind }} or click ‘Finish’, and confirm that the above ballot fingerprint appears next to your name.</p>
|
<p>To confirm that your ballot was cast correctly, please go to the <a href="{{ election_base_url }}view/ballots">‘Voters and ballots’ page</a> for the {{ election.kind }} or click ‘Finish’, and confirm that the above ballot fingerprint appears next to your name.</p>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
|
{% if election.is_votes_public %}
|
||||||
<a href="{{ election_base_url }}view/ballots" class="ui right floated primary button">Finish</a>
|
<a href="{{ election_base_url }}view/ballots" class="ui right floated primary button">Finish</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ election_base_url }}view" class="ui right floated primary button">Finish</a>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block after %}
|
{% block after %}
|
||||||
|
{% if election.can_audit() %}
|
||||||
<div class="ui tiny message" style="margin-top: 3em;">
|
<div class="ui tiny message" style="margin-top: 3em;">
|
||||||
<div class="header">Information for advanced users</div>
|
<div class="header">Information for advanced users</div>
|
||||||
<p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(vote.ballot).hash_as_b64() }}</span>.</p>
|
<p>Your full ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(vote.ballot).hash_as_b64() }}</span>.</p>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block help %}
|
{% block help %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -30,19 +30,19 @@
|
|||||||
try {
|
try {
|
||||||
rawAnswers = [];
|
rawAnswers = [];
|
||||||
for (var answer_json of booth.answers) {
|
for (var answer_json of booth.answers) {
|
||||||
rawAnswers.push(eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(answer_json, null));
|
rawAnswers.push(eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(answer_json, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedAnswers = [];
|
encryptedAnswers = [];
|
||||||
for (var encrypted_answer_json of msg.data.encrypted_answers) {
|
for (var encrypted_answer_json of msg.data.encrypted_answers) {
|
||||||
encryptedAnswers.push(eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(encrypted_answer_json, null));
|
encryptedAnswers.push(eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(encrypted_answer_json, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
booth.ballot = eosjs.eos.base.election.__all__.Ballot();
|
booth.ballot = eosjs.eos.base.election.Ballot();
|
||||||
booth.ballot.answers = rawAnswers;
|
booth.ballot.answers = rawAnswers;
|
||||||
booth.ballot.encrypted_answers = encryptedAnswers;
|
booth.ballot.encrypted_answers = encryptedAnswers;
|
||||||
booth.ballot.election_id = election._id;
|
booth.ballot.election_id = election._id;
|
||||||
booth.ballot.election_hash = eosjs.eos.core.hashing.__all__.SHA256().update_obj(election).hash_as_b64();
|
booth.ballot.election_hash = eosjs.eos.core.hashing.SHA256().update_obj(election).hash_as_b64();
|
||||||
|
|
||||||
if (should_do_fingerprint) {
|
if (should_do_fingerprint) {
|
||||||
// String.prototype.join confuses fingerprintjs2
|
// String.prototype.join confuses fingerprintjs2
|
||||||
@ -69,7 +69,7 @@
|
|||||||
boothWorker.postMessage({
|
boothWorker.postMessage({
|
||||||
"action": "generateEncryptedVote",
|
"action": "generateEncryptedVote",
|
||||||
"static_base_url": "{{ static_base_url }}",
|
"static_base_url": "{{ static_base_url }}",
|
||||||
"election": eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(election, null),
|
"election": eosjs.eos.core.objects.EosObject.serialise_and_wrap(election, null),
|
||||||
"answers": booth.answers
|
"answers": booth.answers
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -30,8 +30,12 @@
|
|||||||
{% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %}
|
{% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<p>If you are happy with your selections, then make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.</p>
|
{% if election.can_audit() %}
|
||||||
|
<p>If you are happy with your selections, then make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.</p>
|
||||||
<p>Click ‘Continue’, and you will be able to log in to cast your vote.</p>
|
<p>Click ‘Continue’, and you will be able to log in to cast your vote.</p>
|
||||||
|
{% else %}
|
||||||
|
<p>If you are happy with your selections, then click ‘Continue’, and you will be able to log in to cast your vote.</p>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
@ -40,11 +44,13 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block after %}
|
{% block after %}
|
||||||
|
{% if election.can_audit() %}
|
||||||
<div class="ui tiny message" style="margin-top: 3em;">
|
<div class="ui tiny message" style="margin-top: 3em;">
|
||||||
<div class="header">Information for advanced users</div>
|
<div class="header">Information for advanced users</div>
|
||||||
<p>Your ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
<p>Your ballot fingerprint is <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64() }}</span>.</p>
|
||||||
<p>If you would like to audit your ballot, <a href="#" onclick="nextTemplate(1);">click here</a></p>
|
<p>If you would like to audit your ballot, <a href="#" onclick="nextTemplate(1);">click here</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block help %}
|
{% block help %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -30,8 +30,12 @@
|
|||||||
{% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %}
|
{% include templates[selection_model_view_map[election.questions.__getitem__(loop.index0)._name]["selections_review"]] %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<p>If you are happy with your selections, then make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.__all__.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.</p>
|
{% if election.can_audit() %}
|
||||||
|
<p>If you are happy with your selections, then make a note of your ballot fingerprint, <span class="hash">{{ eosjs.eos.core.hashing.SHA256().update_obj(ballot).hash_as_b64(true) }}</span>.</p>
|
||||||
<p>Click ‘Continue’, and you will be able to copy your pre-poll ballot to provide to the election administrator.</p>
|
<p>Click ‘Continue’, and you will be able to copy your pre-poll ballot to provide to the election administrator.</p>
|
||||||
|
{% else %}
|
||||||
|
<p>If you are happy with your selections, then click ‘Continue’, and you will be able to copy your pre-poll ballot to provide to the election administrator.</p>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017 RunasSudo (Yingtong Li)
|
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -18,6 +18,10 @@
|
|||||||
|
|
||||||
<h2>{{ questionNum + 1 }}. {{ election.questions.__getitem__(questionNum).prompt }}</h2>
|
<h2>{{ questionNum + 1 }}. {{ election.questions.__getitem__(questionNum).prompt }}</h2>
|
||||||
|
|
||||||
|
{% if election.questions.__getitem__(questionNum).description %}
|
||||||
|
<p>{{ election.questions.__getitem__(questionNum).description | urlize | replace('<a ', '<a target="_blank" ') | safe }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<p><small>
|
<p><small>
|
||||||
Vote for
|
Vote for
|
||||||
{% if election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices %}
|
{% if election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices %}
|
||||||
@ -107,8 +111,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
answer = eosjs.eos.base.election.__all__.ApprovalAnswer(eosjs.__kwargtrans__({choices: selections}));
|
answer = eosjs.eos.base.election.ApprovalAnswer(eosjs.__kwargtrans__({choices: selections}));
|
||||||
booth.answers[booth.questionNum] = eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(answer);
|
booth.answers[booth.questionNum] = eosjs.eos.core.objects.EosObject.serialise_and_wrap(answer);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -18,6 +18,10 @@
|
|||||||
|
|
||||||
<h2>{{ questionNum + 1 }}. {{ election.questions.__getitem__(questionNum).prompt }}</h2>
|
<h2>{{ questionNum + 1 }}. {{ election.questions.__getitem__(questionNum).prompt }}</h2>
|
||||||
|
|
||||||
|
{% if election.questions.__getitem__(questionNum).description %}
|
||||||
|
<p>{{ election.questions.__getitem__(questionNum).description | urlize | replace('<a ', '<a target="_blank" ') | safe }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<p><small>
|
<p><small>
|
||||||
Vote for
|
Vote for
|
||||||
{% if election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices %}
|
{% if election.questions.__getitem__(questionNum).min_choices == election.questions.__getitem__(questionNum).max_choices %}
|
||||||
@ -249,8 +253,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
answer = eosjs.eos.base.election.__all__.PreferentialAnswer(eosjs.__kwargtrans__({choices: selections}));
|
answer = eosjs.eos.base.election.PreferentialAnswer(eosjs.__kwargtrans__({choices: selections}));
|
||||||
booth.answers[booth.questionNum] = eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(answer);
|
booth.answers[booth.questionNum] = eosjs.eos.core.objects.EosObject.serialise_and_wrap(answer);
|
||||||
|
|
||||||
booth.q_state[booth.questionNum] = [$("#question-choices-selected .dragarea").html(), $("#question-choices-remaining .dragarea").html()]; // wew lad
|
booth.q_state[booth.questionNum] = [$("#question-choices-selected .dragarea").html(), $("#question-choices-remaining .dragarea").html()]; // wew lad
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017 RunasSudo (Yingtong Li)
|
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -19,5 +19,11 @@
|
|||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
{{ tab('Overview', 'election_view') }}
|
{{ tab('Overview', 'election_view') }}
|
||||||
{{ tab('Questions', 'election_view_questions') }}
|
{{ tab('Questions', 'election_view_questions') }}
|
||||||
|
{% if election.is_voters_public or (session.user and session.user.is_admin()) %}
|
||||||
|
{% if election.is_votes_public or (session.user and session.user.is_admin()) %}
|
||||||
{{ tab('Voters and ballots', 'election_view_ballots') }}
|
{{ tab('Voters and ballots', 'election_view_ballots') }}
|
||||||
|
{% else %}
|
||||||
|
{{ tab('Voters', 'election_view_ballots') }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -23,12 +23,15 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Voter</th>
|
<th>Voter</th>
|
||||||
|
{% if election.is_votes_public or (session.user and session.user.is_admin()) %}
|
||||||
<th>Ballot fingerprint</th>
|
<th>Ballot fingerprint</th>
|
||||||
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for voter in election.voters %}
|
{% for voter in election.voters %}
|
||||||
<tr>
|
<tr>
|
||||||
|
{% if election.is_votes_public or (session.user and session.user.is_admin()) %}
|
||||||
<td class="selectable"><a href="{{ url_for('election_voter_view', election_id=election._id, voter_id=voter._id) }}">{{ voter.name }}</a></td>
|
<td class="selectable"><a href="{{ url_for('election_voter_view', election_id=election._id, voter_id=voter._id) }}">{{ voter.name }}</a></td>
|
||||||
{% set votes = voter.votes.get_all() %}
|
{% set votes = voter.votes.get_all() %}
|
||||||
<td class="selectable"><a href="{{ url_for('election_voter_view', election_id=election._id, voter_id=voter._id) }}">
|
<td class="selectable"><a href="{{ url_for('election_voter_view', election_id=election._id, voter_id=voter._id) }}">
|
||||||
@ -38,6 +41,9 @@
|
|||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a></td>
|
</a></td>
|
||||||
|
{% else %}
|
||||||
|
<td>{{ voter.name }}</td>
|
||||||
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2021 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -44,6 +44,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
var eosjs = require("eosjs");
|
||||||
|
|
||||||
var templates = {};
|
var templates = {};
|
||||||
var election = null;
|
var election = null;
|
||||||
var booth = null;
|
var booth = null;
|
||||||
@ -70,7 +72,7 @@
|
|||||||
// Verify booth
|
// Verify booth
|
||||||
if (should_do_fingerprint) {
|
if (should_do_fingerprint) {
|
||||||
if (typeof Fingerprint2 === 'undefined') {
|
if (typeof Fingerprint2 === 'undefined') {
|
||||||
boothError('Your browser did not load fingerprintj2 correctly. Please try again after disabling your ad blockers and similar software. If the issue persists, try using a different browser.');
|
boothError('Your browser did not load fingerprintjs2 correctly. Please try again after disabling your ad blockers and similar software. If the issue persists, try using a different browser.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,7 +80,7 @@
|
|||||||
$.ajax({ url: "{{ url_for('election_api_json', election_id=election._id) }}", dataType: "text" })
|
$.ajax({ url: "{{ url_for('election_api_json', election_id=election._id) }}", dataType: "text" })
|
||||||
.done(function(data) {
|
.done(function(data) {
|
||||||
try {
|
try {
|
||||||
election = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(eosjs.eos.core.objects.__all__.EosObject.from_json(data), null);
|
election = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(eosjs.eos.core.objects.EosObject.from_json(data), null);
|
||||||
|
|
||||||
boothWorker = new Worker("{{ url_for('static', filename='js/booth_worker.js') }}");
|
boothWorker = new Worker("{{ url_for('static', filename='js/booth_worker.js') }}");
|
||||||
|
|
||||||
@ -117,7 +119,7 @@
|
|||||||
})
|
})
|
||||||
.done(function(data) {
|
.done(function(data) {
|
||||||
try {
|
try {
|
||||||
templates[templateUrl] = nunjucks.compile(data);
|
templates[templateUrl] = nunjucks.compile(data, null, templateUrl);
|
||||||
numTemplatesLoaded += 1;
|
numTemplatesLoaded += 1;
|
||||||
if (numTemplatesLoaded == templateUrls.length) {
|
if (numTemplatesLoaded == templateUrls.length) {
|
||||||
// All templates loaded. Show voting booth
|
// All templates loaded. Show voting booth
|
||||||
@ -210,12 +212,14 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
templates['booth/welcome.html'] = null;
|
templates['booth/welcome.html'] = null;
|
||||||
|
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
showTemplate('booth/selections.html');
|
showTemplate('booth/selections.html');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
templates['booth/selections.html'] = null;
|
templates['booth/selections.html'] = null;
|
||||||
|
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
if (fromLeft) {
|
if (fromLeft) {
|
||||||
@ -235,12 +239,14 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
templates['booth/review_prepoll.html'] = null;
|
templates['booth/review_prepoll.html'] = null;
|
||||||
|
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
showTemplate('booth/audit.html', {ballot: booth.ballot});
|
showTemplate('booth/audit.html', {ballot: booth.ballot});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
templates['booth/audit.html'] = null;
|
templates['booth/audit.html'] = null;
|
||||||
|
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
showTemplate('booth/cast_prepoll.html', {ballot: booth.ballot});
|
showTemplate('booth/cast_prepoll.html', {ballot: booth.ballot});
|
||||||
@ -255,18 +261,21 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
templates['booth/review.html'] = null;
|
templates['booth/review.html'] = null;
|
||||||
|
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
showTemplate('booth/audit.html', {ballot: booth.ballot});
|
showTemplate('booth/audit.html', {ballot: booth.ballot});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
templates['booth/audit.html'] = null;
|
templates['booth/audit.html'] = null;
|
||||||
|
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
showTemplate('booth/cast.html', {ballot: booth.ballot, is_cast: false});
|
showTemplate('booth/cast.html', {ballot: booth.ballot, is_cast: false});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
templates['booth/cast.html'] = null;
|
templates['booth/cast.html'] = null;
|
||||||
|
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote});
|
showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote});
|
||||||
@ -277,7 +286,7 @@
|
|||||||
} else {
|
} else {
|
||||||
// Cast immediately
|
// Cast immediately
|
||||||
{% if session.staged_ballot %}
|
{% if session.staged_ballot %}
|
||||||
booth.ballot = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(eosjs.eos.core.objects.__all__.EosObject.from_json('{{ eos.core.objects.EosObject.to_json(session.staged_ballot.ballot)|safe }}'), null);
|
booth.ballot = eosjs.eos.core.objects.EosObject.deserialise_and_unwrap(eosjs.eos.core.objects.EosObject.from_json('{{ eos.core.objects.EosObject.to_json(session.staged_ballot.ballot)|safe }}'), null);
|
||||||
{% endif %}
|
{% endif %}
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
@ -285,6 +294,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
templates['booth/cast.html'] = null;
|
templates['booth/cast.html'] = null;
|
||||||
|
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote});
|
showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote});
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017 RunasSudo (Yingtong Li)
|
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -21,6 +21,9 @@
|
|||||||
{% block electioncontent %}
|
{% block electioncontent %}
|
||||||
{% for question in election.questions %}
|
{% for question in election.questions %}
|
||||||
<h2>{{ loop.index }}. {{ question.prompt }}</h2>
|
<h2>{{ loop.index }}. {{ question.prompt }}</h2>
|
||||||
|
{% if question.description %}
|
||||||
|
<p>{{ question.description | urlize }}</p>
|
||||||
|
{% endif %}
|
||||||
{% include eosweb.core.main.model_view_map[question.__class__]['view'] %}
|
{% include eosweb.core.main.model_view_map[question.__class__]['view'] %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{#
|
{#
|
||||||
Eos - Verifiable elections
|
Eos - Verifiable elections
|
||||||
Copyright © 2017-18 RunasSudo (Yingtong Li)
|
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -23,6 +23,10 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>All elections: Eos Voting for {{ eosweb.app.config['ORG_NAME'] }}</h1>
|
<h1>All elections: Eos Voting for {{ eosweb.app.config['ORG_NAME'] }}</h1>
|
||||||
|
|
||||||
|
{% if session.user and session.user.is_admin() %}
|
||||||
|
<div style="text-align: right; font-size: small;"><a href="{{ url_for('elections_batch') }}">Batch operations</a></div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<p>Please choose an election from the list below:</p>
|
<p>Please choose an election from the list below:</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
56
eosweb/core/templates/elections_batch.html
Normal file
56
eosweb/core/templates/elections_batch.html
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{#
|
||||||
|
Eos - Verifiable elections
|
||||||
|
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% block title %}Perform batch operations{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Perform batch operations</h1>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<table class="ui selectable celled table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Next stage</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for election in elections %}
|
||||||
|
<tr>
|
||||||
|
<td><input type="checkbox" name="election_{{ election._id }}" id="election_{{ election._id }}"></td>
|
||||||
|
<td>{{ election.name }}</td>
|
||||||
|
<td>
|
||||||
|
<ul style="padding-left: 1em; margin: 0;">
|
||||||
|
{% for task in election.workflow.tasks %}
|
||||||
|
{% if task.status == eos.base.workflow.WorkflowTaskStatus.READY %}
|
||||||
|
<li>{{ task.label }}</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<input class="ui primary button" type="submit" value="Execute">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
46
eosweb/nsauth/main.py
Normal file
46
eosweb/nsauth/main.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Eos - Verifiable elections
|
||||||
|
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import flask
|
||||||
|
|
||||||
|
from eos.nsauth.election import *
|
||||||
|
|
||||||
|
import urllib.request, urllib.parse
|
||||||
|
|
||||||
|
blueprint = flask.Blueprint('eosweb.nsauth', __name__, template_folder='templates')
|
||||||
|
|
||||||
|
app = None
|
||||||
|
|
||||||
|
@blueprint.record
|
||||||
|
def reddit_register(setup_state):
|
||||||
|
global app
|
||||||
|
app = setup_state.app
|
||||||
|
|
||||||
|
@blueprint.route('/auth/nationstates/login')
|
||||||
|
def nationstates_login():
|
||||||
|
return flask.render_template('auth/nationstates/login.html')
|
||||||
|
|
||||||
|
@blueprint.route('/auth/nationstates/authenticate', methods=['POST'])
|
||||||
|
def nationstates_authenticate():
|
||||||
|
username = flask.request.form['username'].lower().strip().replace(' ', '_')
|
||||||
|
|
||||||
|
with urllib.request.urlopen(urllib.request.Request('https://www.nationstates.net/cgi-bin/api.cgi?a=verify&' + urllib.parse.urlencode({'nation': username, 'checksum': flask.request.form['checksum']}), headers={'User-Agent': app.config['NATIONSTATES_USER_AGENT']})) as resp:
|
||||||
|
if resp.read().decode('utf-8').strip() != '1':
|
||||||
|
return flask.render_template('auth/nationstates/login.html', error='The nation name or verification code you entered was invalid. Please check your details and try again. If the issue persists, contact the election administrator.')
|
||||||
|
|
||||||
|
flask.session['user'] = NationStatesUser(username=username)
|
||||||
|
|
||||||
|
return flask.redirect(flask.url_for('login_complete'))
|
17
eosweb/nsauth/settings.py
Normal file
17
eosweb/nsauth/settings.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Eos - Verifiable elections
|
||||||
|
# Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
NATIONSTATES_USER_AGENT = 'FIXME'
|
51
eosweb/nsauth/templates/auth/nationstates/login.html
Normal file
51
eosweb/nsauth/templates/auth/nationstates/login.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{% extends 'semantic_base.html' %}
|
||||||
|
|
||||||
|
{#
|
||||||
|
Eos - Verifiable elections
|
||||||
|
Copyright © 2017-2019 RunasSudo (Yingtong Li)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% block title %}Log in{% endblock %}
|
||||||
|
|
||||||
|
{% block basecontent %}
|
||||||
|
<div class="ui middle aligned center aligned grid" style="height: 100%;">
|
||||||
|
<div class="column" style="max-width: 400px;">
|
||||||
|
<p>1. Log in to NationStates below if necessary, and copy your <i>Login Verification Code</i>.</p>
|
||||||
|
<iframe src="https://m.nationstates.net/page=verify_login" style="width: 100%; height: 10em;"></iframe>
|
||||||
|
<p>2. Type your nation name and paste your Login Verification Code into the form below.</p>
|
||||||
|
<form class="ui large form" action="{{ url_for('eosweb.nsauth.nationstates_authenticate') }}" method="post">
|
||||||
|
{% if error %}
|
||||||
|
<div class="ui visible error message">{{ error }}</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="ui stacked segment">
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui left icon input">
|
||||||
|
<i class="user icon"></i>
|
||||||
|
<input type="text" name="username" placeholder="Nation name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui left icon input">
|
||||||
|
<i class="linkify icon"></i>
|
||||||
|
<input type="text" name="checksum" placeholder="Login verification code">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="submit" class="ui fluid large teal submit button" value="Log in">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -14,7 +14,7 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from flask_oauthlib.client import OAuth
|
from authlib.integrations.flask_client import OAuth
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
|
||||||
@ -27,51 +27,48 @@ blueprint = flask.Blueprint('eosweb.redditauth', __name__)
|
|||||||
|
|
||||||
app = None
|
app = None
|
||||||
oauth = None
|
oauth = None
|
||||||
reddit = None
|
|
||||||
|
|
||||||
@blueprint.record
|
@blueprint.record
|
||||||
def reddit_register(setup_state):
|
def reddit_register(setup_state):
|
||||||
global app, oauth, reddit
|
global app, oauth
|
||||||
|
|
||||||
app = setup_state.app
|
app = setup_state.app
|
||||||
|
|
||||||
oauth = OAuth()
|
oauth = OAuth(app)
|
||||||
reddit = oauth.remote_app('Reddit',
|
oauth.register('reddit',
|
||||||
request_token_url=None,
|
#request_token_url=None,
|
||||||
authorize_url='https://www.reddit.com/api/v1/authorize.compact',
|
authorize_url='https://www.reddit.com/api/v1/authorize.compact',
|
||||||
request_token_params={'duration': 'temporary', 'scope': 'identity'},
|
authorize_params={'duration': 'temporary', 'scope': 'identity'},
|
||||||
access_token_url='https://www.reddit.com/api/v1/access_token',
|
access_token_url='https://www.reddit.com/api/v1/access_token',
|
||||||
access_token_method='POST',
|
access_token_method='POST',
|
||||||
access_token_headers={
|
access_token_headers={
|
||||||
'Authorization': 'Basic ' + base64.b64encode('{}:{}'.format(app.config['REDDIT_OAUTH_CLIENT_ID'], app.config['REDDIT_OAUTH_CLIENT_SECRET']).encode('ascii')).decode('ascii'),
|
'Authorization': 'Basic ' + base64.b64encode('{}:{}'.format(app.config['REDDIT_OAUTH_CLIENT_ID'], app.config['REDDIT_OAUTH_CLIENT_SECRET']).encode('ascii')).decode('ascii'),
|
||||||
'User-Agent': app.config['REDDIT_USER_AGENT']
|
'User-Agent': app.config['REDDIT_USER_AGENT']
|
||||||
},
|
},
|
||||||
consumer_key=app.config['REDDIT_OAUTH_CLIENT_ID'],
|
client_id=app.config['REDDIT_OAUTH_CLIENT_ID'],
|
||||||
consumer_secret=app.config['REDDIT_OAUTH_CLIENT_SECRET']
|
client_secret=app.config['REDDIT_OAUTH_CLIENT_SECRET'],
|
||||||
|
fetch_token=lambda: flask.session.get('user').oauth_token
|
||||||
)
|
)
|
||||||
|
|
||||||
@reddit.tokengetter
|
|
||||||
def get_reddit_oauth_token():
|
|
||||||
return (flask.session.get('user').oauth_token, '')
|
|
||||||
|
|
||||||
@blueprint.route('/auth/reddit/login')
|
@blueprint.route('/auth/reddit/login')
|
||||||
def reddit_login():
|
def reddit_login():
|
||||||
return reddit.authorize(callback=app.config['BASE_URI'] + flask.url_for('eosweb.redditauth.reddit_oauth_authorized'), state=uuid.uuid4())
|
return oauth.reddit.authorize_redirect(redirect_uri=app.config['BASE_URI'] + flask.url_for('eosweb.redditauth.reddit_oauth_authorized'), state=str(uuid.uuid4()))
|
||||||
|
|
||||||
@blueprint.route('/auth/reddit/oauth_callback')
|
@blueprint.route('/auth/reddit/oauth_callback')
|
||||||
def reddit_oauth_authorized():
|
def reddit_oauth_authorized():
|
||||||
resp = reddit.authorized_response()
|
try:
|
||||||
if resp is None:
|
token = oauth.reddit.authorize_access_token()
|
||||||
|
except:
|
||||||
# Request denied
|
# Request denied
|
||||||
return flask.redirect(flask.url_for('login_cancelled'))
|
return flask.redirect(flask.url_for('login_cancelled'))
|
||||||
|
|
||||||
user = RedditUser()
|
user = RedditUser()
|
||||||
user.oauth_token = resp['access_token']
|
user.oauth_token = token
|
||||||
flask.session['user'] = user
|
flask.session['user'] = user
|
||||||
|
|
||||||
me = reddit.get('https://oauth.reddit.com/api/v1/me', headers={
|
me = oauth.reddit.get('https://oauth.reddit.com/api/v1/me', headers={
|
||||||
'User-Agent': app.config['REDDIT_USER_AGENT']
|
'User-Agent': app.config['REDDIT_USER_AGENT']
|
||||||
})
|
})
|
||||||
user.username = me.data['name']
|
user.username = me.json()['name']
|
||||||
|
|
||||||
return flask.redirect(flask.url_for('login_complete'))
|
return flask.redirect(flask.url_for('login_complete'))
|
||||||
|
2
js.html
2
js.html
@ -1 +1 @@
|
|||||||
<script src="eos/__javascript__/eos.js_tests.js"></script>
|
<script src="eosweb/core/static/js/eosjs.js"></script>
|
||||||
|
@ -9,9 +9,11 @@ AUTH_METHODS = [
|
|||||||
('reddit', 'Reddit')
|
('reddit', 'Reddit')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
import eos.base.election
|
||||||
import eos.redditauth.election
|
import eos.redditauth.election
|
||||||
ADMINS = [
|
ADMINS = [
|
||||||
#eos.redditauth.election.RedditUser(username='xxxxxxxx')
|
#eos.redditauth.election.RedditUser(username='xxxxxxxx'),
|
||||||
|
#eos.base.election.EmailUser(email='xxxxx@example.com', password='abc123'),
|
||||||
]
|
]
|
||||||
|
|
||||||
TASK_RUN_STRATEGY = 'eos.core.tasks.direct.DirectRunStrategy'
|
TASK_RUN_STRATEGY = 'eos.core.tasks.direct.DirectRunStrategy'
|
||||||
@ -37,6 +39,8 @@ MAIL_USERNAME, MAIL_PASSWORD = None, None
|
|||||||
MAIL_DEFAULT_SENDER = 'eos@localhost'
|
MAIL_DEFAULT_SENDER = 'eos@localhost'
|
||||||
|
|
||||||
# Reddit
|
# Reddit
|
||||||
|
# Register a web app at https://www.reddit.com/prefs/apps
|
||||||
|
# The redirect URI will be http(s)://hostname(:port)/auth/reddit/oauth_callback
|
||||||
|
|
||||||
REDDIT_OAUTH_CLIENT_ID = 'xxxxxxxxxxxxxx'
|
REDDIT_OAUTH_CLIENT_ID = 'xxxxxxxxxxxxxx'
|
||||||
REDDIT_OAUTH_CLIENT_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
REDDIT_OAUTH_CLIENT_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||||
|
6839
package-lock.json
generated
Normal file
6839
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
package.json
Normal file
9
package.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/cli": "^7.15.7",
|
||||||
|
"@babel/core": "^7.15.8",
|
||||||
|
"@babel/preset-env": "^7.15.8",
|
||||||
|
"babelify": "^10.0.0",
|
||||||
|
"browserify": "^17.0.0"
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,17 @@
|
|||||||
|
Authlib==0.14.3
|
||||||
coverage==4.4.1
|
coverage==4.4.1
|
||||||
Flask==0.12.2
|
Flask==0.12.2
|
||||||
Flask-Mail==0.9.1
|
Flask-Mail==0.9.1
|
||||||
Flask-OAuthlib==0.9.4
|
flask-paginate==0.7.0
|
||||||
Flask-Session==0.3.1
|
Flask-Session==0.3.1
|
||||||
Flask-SQLAlchemy==2.3.2
|
Flask-SQLAlchemy==2.3.2
|
||||||
gunicorn==19.7.1
|
gunicorn==19.7.1
|
||||||
libsass==0.13.4
|
libsass==0.13.4
|
||||||
premailer==3.1.1
|
premailer==3.1.1
|
||||||
psycopg2==2.7.3.2
|
psycopg2==2.8.5
|
||||||
PyExecJS==1.4.1
|
PyExecJS==1.4.1
|
||||||
pymongo==3.5.1
|
pymongo[srv]==3.10.1
|
||||||
pyRCV==0.3
|
pyRCV==0.3
|
||||||
pytz==2017.3
|
pytz==2017.3
|
||||||
timeago==1.0.8
|
timeago==1.0.8
|
||||||
Transcrypt==3.6.60
|
Transcrypt==3.9.0
|
||||||
|
@ -1 +1 @@
|
|||||||
python-3.6.3
|
python-3.7.2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user