2017-09-22 15:59:15 +10:00
# Eos - Verifiable elections
# Copyright © 2017 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 . objects import *
from eos . base . workflow import *
2017-09-24 01:23:43 +10:00
class Answer ( EmbeddedObject ) :
2017-09-22 15:59:15 +10:00
pass
2017-09-24 01:23:43 +10:00
class EncryptedAnswer ( EmbeddedObject ) :
pass
class NullEncryptedAnswer ( EncryptedAnswer ) :
answer = EmbeddedObjectField ( )
2017-09-24 13:32:46 +10:00
def decrypt ( self ) :
2017-11-29 18:29:42 +11:00
return None , self . answer
2017-09-22 15:59:15 +10:00
class Ballot ( EmbeddedObject ) :
2017-11-23 23:10:57 +11:00
#_id = UUIDField()
2017-09-24 01:23:43 +10:00
encrypted_answers = EmbeddedObjectListField ( )
2017-11-24 20:26:18 +11:00
election_id = UUIDField ( )
election_hash = StringField ( )
2017-11-28 22:43:32 +11:00
answers = EmbeddedObjectListField ( is_hashed = False )
def deaudit ( self ) :
encrypted_answers_deaudit = EosList ( )
2017-12-04 13:51:45 +11:00
for encrypted_answer in self . encrypted_answers :
encrypted_answers_deaudit . append ( encrypted_answer . deaudit ( ) )
2017-11-28 22:43:32 +11:00
return Ballot ( encrypted_answers = encrypted_answers_deaudit , election_id = self . election_id , election_hash = self . election_hash )
2017-09-22 15:59:15 +10:00
2017-11-23 23:10:57 +11:00
class Vote ( EmbeddedObject ) :
ballot = EmbeddedObjectField ( )
2017-11-24 20:26:18 +11:00
cast_at = DateTimeField ( )
2017-11-23 23:10:57 +11:00
2017-09-22 15:59:15 +10:00
class Voter ( EmbeddedObject ) :
_id = UUIDField ( )
2017-11-23 23:10:57 +11:00
votes = EmbeddedObjectListField ( )
2017-09-22 15:59:15 +10:00
2017-11-26 11:17:28 +11:00
class User ( EmbeddedObject ) :
2017-12-07 15:33:11 +11:00
admins = [ ]
def matched_by ( self , other ) :
return self == other
def is_admin ( self ) :
for admin in User . admins :
if admin . matched_by ( self ) :
return True
return False
2017-11-26 11:17:28 +11:00
2017-11-26 20:48:15 +11:00
def generate_password ( ) :
if is_python :
#__pragma__('skip')
import random
return ' ' . join ( random . SystemRandom ( ) . choices ( ' 23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz ' , k = 12 ) )
#__pragma__('noskip')
else :
return None
class EmailUser ( User ) :
name = StringField ( )
email = StringField ( is_protected = True )
password = StringField ( is_protected = True , default = generate_password )
def matched_by ( self , other ) :
if not isinstance ( other , EmailUser ) :
return False
2017-11-28 00:26:13 +11:00
return self . email . lower ( ) == other . email . lower ( ) and self . password == other . password
2017-11-26 20:48:15 +11:00
def send_email ( self , host , port , username , password , from_email , content ) :
#__pragma__('skip')
import smtplib
#__pragma__('noskip')
with smtplib . SMTP ( host , port ) as smtp :
if username is not None :
smtp . login ( username , password )
smtp . sendmail ( from_email , [ self . email ] , content )
def email_password ( self , host , port , username , password , from_email ) :
self . send_email ( host , port , username , password , from_email , ' Subject: Registered to vote in {1} \n From: {4} \n To: {2} \n \n Dear {0} , \n \n You are registered to vote in the election {1} . Your log in details are as follows: \n \n Email: {2} \n Password: {3} ' . format ( self . name , self . recurse_parents ( Election ) . name , self . email , self . password , from_email ) )
2017-11-26 11:17:28 +11:00
class UserVoter ( Voter ) :
user = EmbeddedObjectField ( )
@property
def name ( self ) :
return self . user . name
2017-11-23 18:18:01 +11:00
2017-09-22 15:59:15 +10:00
class Question ( EmbeddedObject ) :
prompt = StringField ( )
2017-09-24 13:25:16 +10:00
class Result ( EmbeddedObject ) :
pass
2017-09-22 15:59:15 +10:00
2017-12-07 16:04:24 +11:00
class ListChoiceQuestion ( Question ) :
2017-09-22 15:59:15 +10:00
choices = ListField ( StringField ( ) )
2017-11-23 21:07:16 +11:00
min_choices = IntField ( )
max_choices = IntField ( )
2017-11-24 19:51:10 +11:00
def pretty_answer ( self , answer ) :
2017-12-07 16:04:24 +11:00
if len ( answer . choices ) == 0 :
return ' (blank votes) '
2017-12-04 13:51:45 +11:00
return ' , ' . join ( [ self . choices [ choice ] for choice in answer . choices ] )
2017-09-22 15:59:15 +10:00
2017-12-07 16:04:24 +11:00
class ApprovalQuestion ( ListChoiceQuestion ) :
pass
2017-09-24 01:23:43 +10:00
class ApprovalAnswer ( Answer ) :
choices = ListField ( IntField ( ) )
2017-12-07 16:04:24 +11:00
class PreferentialQuestion ( ListChoiceQuestion ) :
pass
2017-11-27 22:56:43 +11:00
class PreferentialAnswer ( Answer ) :
choices = ListField ( IntField ( ) )
2017-09-28 16:46:30 +10:00
class RawResult ( Result ) :
2017-11-29 18:29:42 +11:00
_ver = StringField ( default = ' 0.2 ' )
plaintexts = ListField ( EmbeddedObjectListField ( ) )
2017-09-28 16:46:30 +10:00
answers = EmbeddedObjectListField ( )
2017-11-24 19:51:10 +11:00
def count ( self ) :
combined = [ ]
for answer in self . answers :
index = next ( ( i for i , val in enumerate ( combined ) if val [ 0 ] == answer ) , None )
if index is None :
combined . append ( [ answer , 1 ] )
else :
combined [ index ] [ 1 ] + = 1
combined . sort ( key = lambda x : x [ 1 ] , reverse = True )
return combined
2017-09-24 13:25:16 +10:00
2017-09-22 15:59:15 +10:00
class Election ( TopLevelObject ) :
2017-12-04 14:30:41 +11:00
_ver = StringField ( default = ' 0.2 ' )
2017-09-22 15:59:15 +10:00
_id = UUIDField ( )
2017-09-22 16:57:40 +10:00
workflow = EmbeddedObjectField ( Workflow ) # Once saved, we don't care what kind of workflow it is
2017-09-22 15:59:15 +10:00
name = StringField ( )
2017-12-04 14:30:41 +11:00
kind = StringField ( default = ' election ' )
2017-11-23 20:42:22 +11:00
voters = EmbeddedObjectListField ( is_hashed = False )
2017-09-22 16:57:40 +10:00
questions = EmbeddedObjectListField ( )
2017-11-23 20:42:22 +11:00
results = EmbeddedObjectListField ( is_hashed = False )
2017-11-29 18:29:42 +11:00
def verify ( self ) :
#__pragma__('skip')
from eos . core . hashing import SHA256
#__pragma__('noskip')
election_hash = SHA256 ( ) . update_obj ( self ) . hash_as_b64 ( )
for voter in self . voters :
for vote in voter . votes :
if vote . ballot . election_id != self . _id :
raise Exception ( ' Invalid election ID on ballot ' )
if vote . ballot . election_hash != election_hash :
raise Exception ( ' Invalid election hash on ballot ' )