diff --git a/.gitignore b/.gitignore index 681947f7ff38ef3bb2c398d79c496ecc6172d9cb..8db764f4eadb6e11a9fc766cea5aa1a491b66aa2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,8 @@ deploy-latest.sh .DS_Store *~ media/* -venv +venv* celerybeat-* env.sh -.cache \ No newline at end of file +.cache +.idea/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index be482e4e44bdb9307de52e9541d65cef31c05445..476acaf46b8d58147f43cfecad671310e550741f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,46 @@ -sudo: false language: python -python: - - "2.7" -# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors +os: linux + +jobs: + include: + - python: "3.7" + dist: xenial + addons: + postgresql: "9.5" + - python: "3.7" + dist: xenial + addons: + postgresql: "9.6" + - python: "3.7" + dist: xenial + addons: + postgresql: "10" + - python: "3.8" + dist: bionic + addons: + postgresql: "9.5" + - python: "3.8" + dist: bionic + addons: + postgresql: "9.6" + - python: "3.8" + dist: bionic + addons: + postgresql: "10" + - python: "3.8" + dist: bionic + addons: + postgresql: "11" + +before_install: + - export BOTO_CONFIG=/dev/null + install: - - pip install setuptools==24.3.1 - - pip install -r requirements.txt -# command to run tests, e.g. python setup.py test -script: "python manage.py test" -addons: - postgresql: "9.3" + - pip3 install --upgrade pip + - pip3 install -r requirements.txt + - pip3 freeze + before_script: - psql -c 'create database helios;' -U postgres + +script: "python3 -Wall manage.py test -v 2" diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 61ac19608d9d2f4d2a54bf94b25ef65330682a43..e37f6e4a175408d6edea7843ccfea90942767fc6 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -7,3 +7,5 @@ Significant contributors: - Olivier de Marneffe - Emily Stark, Mike Hamburg, Tom Wu, and Dan Boneh for SJCL and integration of javascript crypto. - Nicholas Chang-Fong and Aleksander Essex for security reports and fixes. +- Shirley Chaves +- Marco Ciotola diff --git a/INSTALL.md b/INSTALL.md index fdb0ae10f6ff649b5e344c3aae43a12bf6e351b8..c14d9765963961cf4317d1cf494b190c67334c99 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,4 +1,8 @@ -* install PostgreSQL 8.3+ +* install PostgreSQL 9.5+ + +* install Rabbit MQ + This is needed for celery to work, which does background processing such as + the processing of uploaded list-of-voter CSV files. * make sure you have virtualenv installed: http://www.virtualenv.org/en/latest/ @@ -7,10 +11,22 @@ http://www.virtualenv.org/en/latest/ * cd into the helios-server directory -* create a virtualenv: +* install Python3.6 including dev, pip, and venv + +``` +sudo apt install python3.6 python3.6-venv python3.6-pip python3.6-venv +``` + +* create a virtualenv + +``` +python3.6 -m venv $(pwd)/venv +``` + +* you'll also need Postgres dev libraries. For example on Ubuntu: ``` -virtualenv venv +sudo apt install libpq-dev ``` * activate virtual environment @@ -45,6 +61,6 @@ python manage.py runserver ** set up oauth2 credentials as a web application, with your origin, e.g. https://myhelios.example.com, and your auth callback, which, based on our example, is https://myhelios.example.com/auth/after/ -** still in the developer console, enable the Google+ API. +** still in the developer console, enable the Google+ API and Google People API. -** set the GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET configuration variables accordingly. \ No newline at end of file +** set the GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET configuration variables accordingly. diff --git a/Procfile b/Procfile index 63d8d0cf6be1b07f29a15897a3f113b10c8355b3..428068263e8ce8fe1a8317ec153d6255bf03c4b3 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ web: gunicorn wsgi:application -b 0.0.0.0:$PORT -w 8 -worker: python manage.py celeryd -E -B --beat --concurrency=1 \ No newline at end of file +worker: celery worker --app helios --events --beat --concurrency 1 \ No newline at end of file diff --git a/README.md b/README.md index 5c90a9c206ed2b2657284dc76cb00f11f98a62ab..9a5519bfc40a91c5f56a03198ea0d38f3d81e5e8 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,6 @@ Helios is an end-to-end verifiable voting system. - +[](https://travis-ci.org/benadida/helios-server) [](https://waffle.io/benadida/helios-server) diff --git a/extract-passwords-for-email.py b/extract-passwords-for-email.py index d72705792a7691b2b26c863841e6c4bb01efeafc..dc9ac956425b1145d5432b2d8db848737e5fff7a 100644 --- a/extract-passwords-for-email.py +++ b/extract-passwords-for-email.py @@ -5,12 +5,16 @@ # python extract-passwords-for-email.py <election_uuid> <email_address> # -from django.core.management import setup_environ -import settings, sys, csv +import sys -setup_environ(settings) +import csv +import django +import os -from helios.models import * +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") +django.setup() + +from helios.models import Election election_uuid = sys.argv[1] email = sys.argv[2] diff --git a/helios/__init__.py b/helios/__init__.py index 06f05140bb83798916f63786610efa87d916e26a..21f21bc59da930240e0c88612f1afcc9c0a46178 100644 --- a/helios/__init__.py +++ b/helios/__init__.py @@ -1,7 +1,9 @@ - from django.conf import settings -from django.core.urlresolvers import reverse -from helios.views import election_shortcut +# This will make sure the app is always imported when +# Django starts so that shared_task will use this app. +from .celery_app import app as celery_app + +__all__ = ('celery_app', 'TEMPLATE_BASE', 'ADMIN_ONLY', 'VOTERS_UPLOAD', 'VOTERS_EMAIL',) TEMPLATE_BASE = settings.HELIOS_TEMPLATE_BASE or "helios/templates/base.html" diff --git a/helios/apps.py b/helios/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..90097432826dafdc8670f50a87eaa07403598c70 --- /dev/null +++ b/helios/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class HeliosConfig(AppConfig): + name = 'helios' + verbose_name = "Helios" diff --git a/helios/celery_app.py b/helios/celery_app.py new file mode 100644 index 0000000000000000000000000000000000000000..89f0ecb54d7f3f2cea1985ee8c5059a47866ca2a --- /dev/null +++ b/helios/celery_app.py @@ -0,0 +1,21 @@ +import os + +# set the default Django settings module for the 'celery' program. +from celery import Celery + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') + +app = Celery() + +# Using a string here means the worker doesn't have to serialize +# the configuration object to child processes. +# - namespace='CELERY' means all celery-related configuration keys +# should have a `CELERY_` prefix. +app.config_from_object('django.conf:settings', namespace='CELERY') + +# Load task modules from all registered Django app configs. +app.autodiscover_tasks() + +@app.task(bind=True) +def debug_task(self): + print('Request: {0!r}'.format(self.request)) diff --git a/helios/crypto/algs.py b/helios/crypto/algs.py index acac4f44b4c935a49f4873e8dea92bcb17da0bbc..b7439d9bd3d50cd738b354fcfbfe64495df0db78 100644 --- a/helios/crypto/algs.py +++ b/helios/crypto/algs.py @@ -7,157 +7,61 @@ Ben Adida ben@adida.net """ -import math, hashlib, logging -import randpool, number +import logging -import numtheory +from Crypto.Hash import SHA1 +from Crypto.Util import number -# some utilities -class Utils: - RAND = randpool.RandomPool() - - @classmethod - def random_seed(cls, data): - cls.RAND.add_event(data) - - @classmethod - def random_mpz(cls, n_bits): - low = 2**(n_bits-1) - high = low * 2 - - # increment and find a prime - # return randrange(low, high) - - return number.getRandomNumber(n_bits, cls.RAND.get_bytes) - - @classmethod - def random_mpz_lt(cls, max): - # return randrange(0, max) - n_bits = int(math.floor(math.log(max, 2))) - return (number.getRandomNumber(n_bits, cls.RAND.get_bytes) % max) - - @classmethod - def random_prime(cls, n_bits): - return number.getPrime(n_bits, cls.RAND.get_bytes) - - @classmethod - def is_prime(cls, mpz): - #return numtheory.miller_rabin(mpz) - return number.isPrime(mpz) - - @classmethod - def xgcd(cls, a, b): - """ - Euclid's Extended GCD algorithm - """ - mod = a%b - - if mod == 0: - return 0,1 - else: - x,y = cls.xgcd(b, mod) - return y, x-(y*(a/b)) - - @classmethod - def inverse(cls, mpz, mod): - # return cls.xgcd(mpz,mod)[0] - return number.inverse(mpz, mod) - - @classmethod - def random_safe_prime(cls, n_bits): - p = None - q = None - - while True: - p = cls.random_prime(n_bits) - q = (p-1)/2 - if cls.is_prime(q): - return p - - @classmethod - def random_special_prime(cls, q_n_bits, p_n_bits): - p = None - q = None - - z_n_bits = p_n_bits - q_n_bits - - q = cls.random_prime(q_n_bits) - - while True: - z = cls.random_mpz(z_n_bits) - p = q*z + 1 - if cls.is_prime(p): - return p, q, z +from helios.crypto.utils import random +from helios.utils import to_json class ElGamal: def __init__(self): - self.p = None - self.q = None - self.g = None - - @classmethod - def generate(cls, n_bits): - """ - generate an El-Gamal environment. Returns an instance - of ElGamal(), with prime p, group size q, and generator g - """ - - EG = ElGamal() - - # find a prime p such that (p-1)/2 is prime q - EG.p = Utils.random_safe_prime(n_bits) - - # q is the order of the group - # FIXME: not always p-1/2 - EG.q = (EG.p-1)/2 - - # find g that generates the q-order subgroup - while True: - EG.g = Utils.random_mpz_lt(EG.p) - if pow(EG.g, EG.q, EG.p) == 1: - break - - return EG + self.p = None + self.q = None + self.g = None def generate_keypair(self): - """ - generates a keypair in the setting - """ + """ + generates a keypair in the setting + """ - keypair = EGKeyPair() - keypair.generate(self.p, self.q, self.g) + keypair = EGKeyPair() + keypair.generate(self.p, self.q, self.g) - return keypair + return keypair def toJSONDict(self): - return {'p': str(self.p), 'q': str(self.q), 'g': str(self.g)} + return {'p': str(self.p), 'q': str(self.q), 'g': str(self.g)} @classmethod def fromJSONDict(cls, d): - eg = cls() - eg.p = int(d['p']) - eg.q = int(d['q']) - eg.g = int(d['g']) - return eg + eg = cls() + eg.p = int(d['p']) + eg.q = int(d['q']) + eg.g = int(d['g']) + return eg + class EGKeyPair: def __init__(self): - self.pk = EGPublicKey() - self.sk = EGSecretKey() + self.pk = EGPublicKey() + self.sk = EGSecretKey() def generate(self, p, q, g): - """ - Generate an ElGamal keypair - """ - self.pk.g = g - self.pk.p = p - self.pk.q = q + """ + Generate an ElGamal keypair + """ + self.pk.g = g + self.pk.p = p + self.pk.q = q + + self.sk.x = random.mpz_lt(q) + self.pk.y = pow(g, self.sk.x, p) - self.sk.x = Utils.random_mpz_lt(q) - self.pk.y = pow(g, self.sk.x, p) + self.sk.pk = self.pk - self.sk.pk = self.pk class EGPublicKey: def __init__(self): @@ -166,7 +70,7 @@ class EGPublicKey: self.g = None self.q = None - def encrypt_with_r(self, plaintext, r, encode_message= False): + def encrypt_with_r(self, plaintext, r, encode_message=False): """ expecting plaintext.m to be a big integer """ @@ -175,13 +79,13 @@ class EGPublicKey: # make sure m is in the right subgroup if encode_message: - y = plaintext.m + 1 - if pow(y, self.q, self.p) == 1: - m = y - else: - m = -y % self.p + y = plaintext.m + 1 + if pow(y, self.q, self.p) == 1: + m = y + else: + m = -y % self.p else: - m = plaintext.m + m = plaintext.m ciphertext.alpha = pow(self.g, r, self.p) ciphertext.beta = (m * pow(self.y, r, self.p)) % self.p @@ -192,7 +96,7 @@ class EGPublicKey: """ Encrypt a plaintext and return the randomness just generated and used. """ - r = Utils.random_mpz_lt(self.q) + r = random.mpz_lt(self.q) ciphertext = self.encrypt_with_r(plaintext, r) return [ciphertext, r] @@ -207,70 +111,69 @@ class EGPublicKey: """ Serialize to dictionary. """ - return {'y' : str(self.y), 'p' : str(self.p), 'g' : str(self.g) , 'q' : str(self.q)} + return {'y': str(self.y), 'p': str(self.p), 'g': str(self.g), 'q': str(self.q)} toJSONDict = to_dict # quick hack FIXME def toJSON(self): - import utils - return utils.to_json(self.toJSONDict()) + return to_json(self.toJSONDict()) - def __mul__(self,other): - if other == 0 or other == 1: - return self + def __mul__(self, other): + if other == 0 or other == 1: + return self - # check p and q - if self.p != other.p or self.q != other.q or self.g != other.g: - raise Exception("incompatible public keys") + # check p and q + if self.p != other.p or self.q != other.q or self.g != other.g: + raise Exception("incompatible public keys") - result = EGPublicKey() - result.p = self.p - result.q = self.q - result.g = self.g - result.y = (self.y * other.y) % result.p - return result + result = EGPublicKey() + result.p = self.p + result.q = self.q + result.g = self.g + result.y = (self.y * other.y) % result.p + return result - def verify_sk_proof(self, dlog_proof, challenge_generator = None): - """ - verify the proof of knowledge of the secret key - g^response = commitment * y^challenge - """ - left_side = pow(self.g, dlog_proof.response, self.p) - right_side = (dlog_proof.commitment * pow(self.y, dlog_proof.challenge, self.p)) % self.p + def verify_sk_proof(self, dlog_proof, challenge_generator=None): + """ + verify the proof of knowledge of the secret key + g^response = commitment * y^challenge + """ + left_side = pow(self.g, dlog_proof.response, self.p) + right_side = (dlog_proof.commitment * pow(self.y, dlog_proof.challenge, self.p)) % self.p - expected_challenge = challenge_generator(dlog_proof.commitment) % self.q + expected_challenge = challenge_generator(dlog_proof.commitment) % self.q - return ((left_side == right_side) and (dlog_proof.challenge == expected_challenge)) + return (left_side == right_side) and (dlog_proof.challenge == expected_challenge) def validate_pk_params(self): - # check primality of p - if not number.isPrime(self.p): - raise Exception("p is not prime.") + # check primality of p + if not number.isPrime(self.p): + raise Exception("p is not prime.") - # check length of p - if not (number.size(self.p) >= 2048): - raise Exception("p of insufficient length. Should be 2048 bits or greater.") + # check length of p + if not (number.size(self.p) >= 2048): + raise Exception("p of insufficient length. Should be 2048 bits or greater.") - # check primality of q - if not number.isPrime(self.q): - raise Exception("q is not prime.") + # check primality of q + if not number.isPrime(self.q): + raise Exception("q is not prime.") - # check length of q - if not (number.size(self.q) >= 256): - raise Exception("q of insufficient length. Should be 256 bits or greater.") + # check length of q + if not (number.size(self.q) >= 256): + raise Exception("q of insufficient length. Should be 256 bits or greater.") - if (pow(self.g,self.q,self.p)!=1): - raise Exception("g does not generate subgroup of order q.") + if pow(self.g, self.q, self.p) != 1: + raise Exception("g does not generate subgroup of order q.") - if not (1 < self.g < self.p-1): - raise Exception("g out of range.") + if not (1 < self.g < self.p - 1): + raise Exception("g out of range.") - if not (1 < self.y < self.p-1): - raise Exception("y out of range.") + if not (1 < self.y < self.p - 1): + raise Exception("y out of range.") - if (pow(self.y,self.q,self.p)!=1): - raise Exception("g does not generate proper group.") + if pow(self.y, self.q, self.p) != 1: + raise Exception("g does not generate proper group.") @classmethod def from_dict(cls, d): @@ -284,14 +187,15 @@ class EGPublicKey: pk.q = int(d['q']) try: - pk.validate_pk_params() + pk.validate_pk_params() except Exception as e: - raise + raise e return pk fromJSONDict = from_dict + class EGSecretKey: def __init__(self): self.x = None @@ -317,25 +221,25 @@ class EGSecretKey: return dec_factor, proof - def decrypt(self, ciphertext, dec_factor = None, decode_m=False): + def decrypt(self, ciphertext, dec_factor=None, decode_m=False): """ Decrypt a ciphertext. Optional parameter decides whether to encode the message into the proper subgroup. """ if not dec_factor: dec_factor = self.decryption_factor(ciphertext) - m = (Utils.inverse(dec_factor, self.pk.p) * ciphertext.beta) % self.pk.p + m = (number.inverse(dec_factor, self.pk.p) * ciphertext.beta) % self.pk.p if decode_m: - # get m back from the q-order subgroup - if m < self.pk.q: - y = m - else: - y = -m % self.pk.p + # get m back from the q-order subgroup + if m < self.pk.q: + y = m + else: + y = -m % self.pk.p - return EGPlaintext(y-1, self.pk) + return EGPlaintext(y - 1, self.pk) else: - return EGPlaintext(m, self.pk) + return EGPlaintext(m, self.pk) def prove_decryption(self, ciphertext): """ @@ -350,66 +254,66 @@ class EGSecretKey: and alpha^t = b * beta/m ^ c """ - m = (Utils.inverse(pow(ciphertext.alpha, self.x, self.pk.p), self.pk.p) * ciphertext.beta) % self.pk.p - beta_over_m = (ciphertext.beta * Utils.inverse(m, self.pk.p)) % self.pk.p + m = (number.inverse(pow(ciphertext.alpha, self.x, self.pk.p), self.pk.p) * ciphertext.beta) % self.pk.p + beta_over_m = (ciphertext.beta * number.inverse(m, self.pk.p)) % self.pk.p # pick a random w - w = Utils.random_mpz_lt(self.pk.q) + w = random.mpz_lt(self.pk.q) a = pow(self.pk.g, w, self.pk.p) b = pow(ciphertext.alpha, w, self.pk.p) - c = int(hashlib.sha1(str(a) + "," + str(b)).hexdigest(),16) + c = int(SHA1.new(bytes(str(a) + "," + str(b), 'utf-8')).hexdigest(), 16) t = (w + self.x * c) % self.pk.q return m, { - 'commitment' : {'A' : str(a), 'B': str(b)}, - 'challenge' : str(c), - 'response' : str(t) - } + 'commitment': {'A': str(a), 'B': str(b)}, + 'challenge': str(c), + 'response': str(t) + } def to_dict(self): - return {'x' : str(self.x), 'public_key' : self.pk.to_dict()} + return {'x': str(self.x), 'public_key': self.pk.to_dict()} toJSONDict = to_dict def prove_sk(self, challenge_generator): - """ - Generate a PoK of the secret key - Prover generates w, a random integer modulo q, and computes commitment = g^w mod p. - Verifier provides challenge modulo q. - Prover computes response = w + x*challenge mod q, where x is the secret key. - """ - w = Utils.random_mpz_lt(self.pk.q) - commitment = pow(self.pk.g, w, self.pk.p) - challenge = challenge_generator(commitment) % self.pk.q - response = (w + (self.x * challenge)) % self.pk.q - - return DLogProof(commitment, challenge, response) + """ + Generate a PoK of the secret key + Prover generates w, a random integer modulo q, and computes commitment = g^w mod p. + Verifier provides challenge modulo q. + Prover computes response = w + x*challenge mod q, where x is the secret key. + """ + w = random.mpz_lt(self.pk.q) + commitment = pow(self.pk.g, w, self.pk.p) + challenge = challenge_generator(commitment) % self.pk.q + response = (w + (self.x * challenge)) % self.pk.q + return DLogProof(commitment, challenge, response) @classmethod def from_dict(cls, d): if not d: - return None + return None sk = cls() sk.x = int(d['x']) - if d.has_key('public_key'): - sk.pk = EGPublicKey.from_dict(d['public_key']) + if 'public_key' in d: + sk.pk = EGPublicKey.from_dict(d['public_key']) else: - sk.pk = None + sk.pk = None return sk fromJSONDict = from_dict + class EGPlaintext: - def __init__(self, m = None, pk = None): + def __init__(self, m=None, pk=None): self.m = m self.pk = pk def to_dict(self): - return {'m' : self.m} + return {'m': self.m} @classmethod def from_dict(cls, d): @@ -424,17 +328,17 @@ class EGCiphertext: self.alpha = alpha self.beta = beta - def __mul__(self,other): + def __mul__(self, other): """ Homomorphic Multiplication of ciphertexts. """ - if type(other) == int and (other == 0 or other == 1): - return self + if isinstance(other, int) and (other == 0 or other == 1): + return self if self.pk != other.pk: - logging.info(self.pk) - logging.info(other.pk) - raise Exception('different PKs!') + logging.info(self.pk) + logging.info(other.pk) + raise Exception('different PKs!') new = EGCiphertext() @@ -460,7 +364,7 @@ class EGCiphertext: """ Reencryption with fresh randomness, which is returned. """ - r = Utils.random_mpz_lt(self.pk.q) + r = random.mpz_lt(self.pk.q) new_c = self.reenc_with_r(r) return [new_c, r] @@ -471,189 +375,195 @@ class EGCiphertext: return self.reenc_return_r()[0] def __eq__(self, other): - """ - Check for ciphertext equality. - """ - if other == None: - return False + """ + Check for ciphertext equality. + """ + if other is None: + return False - return (self.alpha == other.alpha and self.beta == other.beta) + return self.alpha == other.alpha and self.beta == other.beta def generate_encryption_proof(self, plaintext, randomness, challenge_generator): - """ - Generate the disjunctive encryption proof of encryption - """ - # random W - w = Utils.random_mpz_lt(self.pk.q) + """ + Generate the disjunctive encryption proof of encryption + """ + # random W + w = random.mpz_lt(self.pk.q) - # build the proof - proof = EGZKProof() + # build the proof + proof = EGZKProof() - # compute A=g^w, B=y^w - proof.commitment['A'] = pow(self.pk.g, w, self.pk.p) - proof.commitment['B'] = pow(self.pk.y, w, self.pk.p) + # compute A=g^w, B=y^w + proof.commitment['A'] = pow(self.pk.g, w, self.pk.p) + proof.commitment['B'] = pow(self.pk.y, w, self.pk.p) - # generate challenge - proof.challenge = challenge_generator(proof.commitment); + # generate challenge + proof.challenge = challenge_generator(proof.commitment) - # Compute response = w + randomness * challenge - proof.response = (w + (randomness * proof.challenge)) % self.pk.q; + # Compute response = w + randomness * challenge + proof.response = (w + (randomness * proof.challenge)) % self.pk.q - return proof; + return proof def simulate_encryption_proof(self, plaintext, challenge=None): - # generate a random challenge if not provided - if not challenge: - challenge = Utils.random_mpz_lt(self.pk.q) + # generate a random challenge if not provided + if not challenge: + challenge = random.mpz_lt(self.pk.q) - proof = EGZKProof() - proof.challenge = challenge + proof = EGZKProof() + proof.challenge = challenge - # compute beta/plaintext, the completion of the DH tuple - beta_over_plaintext = (self.beta * Utils.inverse(plaintext.m, self.pk.p)) % self.pk.p + # compute beta/plaintext, the completion of the DH tuple + beta_over_plaintext = (self.beta * number.inverse(plaintext.m, self.pk.p)) % self.pk.p - # random response, does not even need to depend on the challenge - proof.response = Utils.random_mpz_lt(self.pk.q); + # random response, does not even need to depend on the challenge + proof.response = random.mpz_lt(self.pk.q) - # now we compute A and B - proof.commitment['A'] = (Utils.inverse(pow(self.alpha, proof.challenge, self.pk.p), self.pk.p) * pow(self.pk.g, proof.response, self.pk.p)) % self.pk.p - proof.commitment['B'] = (Utils.inverse(pow(beta_over_plaintext, proof.challenge, self.pk.p), self.pk.p) * pow(self.pk.y, proof.response, self.pk.p)) % self.pk.p + # now we compute A and B + proof.commitment['A'] = (number.inverse(pow(self.alpha, proof.challenge, self.pk.p), self.pk.p) + * pow(self.pk.g, proof.response, self.pk.p) + ) % self.pk.p + proof.commitment['B'] = (number.inverse(pow(beta_over_plaintext, proof.challenge, self.pk.p), self.pk.p) * pow( + self.pk.y, proof.response, self.pk.p)) % self.pk.p - return proof + return proof def generate_disjunctive_encryption_proof(self, plaintexts, real_index, randomness, challenge_generator): - # note how the interface is as such so that the result does not reveal which is the real proof. + # note how the interface is as such so that the result does not reveal which is the real proof. - proofs = [None for p in plaintexts] + proofs = [None for _ in plaintexts] - # go through all plaintexts and simulate the ones that must be simulated. - for p_num in range(len(plaintexts)): - if p_num != real_index: - proofs[p_num] = self.simulate_encryption_proof(plaintexts[p_num]) + # go through all plaintexts and simulate the ones that must be simulated. + for p_num in range(len(plaintexts)): + if p_num != real_index: + proofs[p_num] = self.simulate_encryption_proof(plaintexts[p_num]) - # the function that generates the challenge - def real_challenge_generator(commitment): - # set up the partial real proof so we're ready to get the hash - proofs[real_index] = EGZKProof() - proofs[real_index].commitment = commitment + # the function that generates the challenge + def real_challenge_generator(commitment): + # set up the partial real proof so we're ready to get the hash + proofs[real_index] = EGZKProof() + proofs[real_index].commitment = commitment - # get the commitments in a list and generate the whole disjunctive challenge - commitments = [p.commitment for p in proofs] - disjunctive_challenge = challenge_generator(commitments); + # get the commitments in a list and generate the whole disjunctive challenge + commitments = [p.commitment for p in proofs] + disjunctive_challenge = challenge_generator(commitments) - # now we must subtract all of the other challenges from this challenge. - real_challenge = disjunctive_challenge - for p_num in range(len(proofs)): - if p_num != real_index: - real_challenge = real_challenge - proofs[p_num].challenge + # now we must subtract all of the other challenges from this challenge. + real_challenge = disjunctive_challenge + for p_num in range(len(proofs)): + if p_num != real_index: + real_challenge = real_challenge - proofs[p_num].challenge - # make sure we mod q, the exponent modulus - return real_challenge % self.pk.q + # make sure we mod q, the exponent modulus + return real_challenge % self.pk.q - # do the real proof - real_proof = self.generate_encryption_proof(plaintexts[real_index], randomness, real_challenge_generator) + # do the real proof + real_proof = self.generate_encryption_proof(plaintexts[real_index], randomness, real_challenge_generator) - # set the real proof - proofs[real_index] = real_proof + # set the real proof + proofs[real_index] = real_proof - return EGZKDisjunctiveProof(proofs) + return EGZKDisjunctiveProof(proofs) def verify_encryption_proof(self, plaintext, proof): - """ - Checks for the DDH tuple g, y, alpha, beta/plaintext. - (PoK of randomness r.) - - Proof contains commitment = {A, B}, challenge, response - """ - # check that A, B are in the correct group - if not (pow(proof.commitment['A'],self.pk.q,self.pk.p)==1 and pow(proof.commitment['B'],self.pk.q,self.pk.p)==1): - return False + """ + Checks for the DDH tuple g, y, alpha, beta/plaintext. + (PoK of randomness r.) + + Proof contains commitment = {A, B}, challenge, response + """ + # check that A, B are in the correct group + if not (pow(proof.commitment['A'], self.pk.q, self.pk.p) == 1 and pow(proof.commitment['B'], self.pk.q, + self.pk.p) == 1): + return False - # check that g^response = A * alpha^challenge - first_check = (pow(self.pk.g, proof.response, self.pk.p) == ((pow(self.alpha, proof.challenge, self.pk.p) * proof.commitment['A']) % self.pk.p)) + # check that g^response = A * alpha^challenge + first_check = (pow(self.pk.g, proof.response, self.pk.p) == ( + (pow(self.alpha, proof.challenge, self.pk.p) * proof.commitment['A']) % self.pk.p)) - # check that y^response = B * (beta/m)^challenge - beta_over_m = (self.beta * Utils.inverse(plaintext.m, self.pk.p)) % self.pk.p - second_check = (pow(self.pk.y, proof.response, self.pk.p) == ((pow(beta_over_m, proof.challenge, self.pk.p) * proof.commitment['B']) % self.pk.p)) + # check that y^response = B * (beta/m)^challenge + beta_over_m = (self.beta * number.inverse(plaintext.m, self.pk.p)) % self.pk.p + second_check = (pow(self.pk.y, proof.response, self.pk.p) == ( + (pow(beta_over_m, proof.challenge, self.pk.p) * proof.commitment['B']) % self.pk.p)) - # print "1,2: %s %s " % (first_check, second_check) - return (first_check and second_check) + # print "1,2: %s %s " % (first_check, second_check) + return first_check and second_check def verify_disjunctive_encryption_proof(self, plaintexts, proof, challenge_generator): - """ - plaintexts and proofs are all lists of equal length, with matching. + """ + plaintexts and proofs are all lists of equal length, with matching. - overall_challenge is what all of the challenges combined should yield. - """ - if len(plaintexts) != len(proof.proofs): - print("bad number of proofs (expected %s, found %s)" % (len(plaintexts), len(proof.proofs))) - return False + overall_challenge is what all of the challenges combined should yield. + """ + if len(plaintexts) != len(proof.proofs): + print("bad number of proofs (expected %s, found %s)" % (len(plaintexts), len(proof.proofs))) + return False - for i in range(len(plaintexts)): - # if a proof fails, stop right there - if not self.verify_encryption_proof(plaintexts[i], proof.proofs[i]): - print "bad proof %s, %s, %s" % (i, plaintexts[i], proof.proofs[i]) - return False + for i in range(len(plaintexts)): + # if a proof fails, stop right there + if not self.verify_encryption_proof(plaintexts[i], proof.proofs[i]): + print("bad proof %s, %s, %s" % (i, plaintexts[i], proof.proofs[i])) + return False - # logging.info("made it past the two encryption proofs") + # logging.info("made it past the two encryption proofs") - # check the overall challenge - return (challenge_generator([p.commitment for p in proof.proofs]) == (sum([p.challenge for p in proof.proofs]) % self.pk.q)) + # check the overall challenge + return (challenge_generator([p.commitment for p in proof.proofs]) == ( + sum([p.challenge for p in proof.proofs]) % self.pk.q)) def verify_decryption_proof(self, plaintext, proof): - """ - Checks for the DDH tuple g, alpha, y, beta/plaintext - (PoK of secret key x.) - """ - return False + """ + Checks for the DDH tuple g, alpha, y, beta/plaintext + (PoK of secret key x.) + """ + return False def verify_decryption_factor(self, dec_factor, dec_proof, public_key): - """ - when a ciphertext is decrypted by a dec factor, the proof needs to be checked - """ - pass + """ + when a ciphertext is decrypted by a dec factor, the proof needs to be checked + """ + pass def decrypt(self, decryption_factors, public_key): - """ - decrypt a ciphertext given a list of decryption factors (from multiple trustees) - For now, no support for threshold - """ - running_decryption = self.beta - for dec_factor in decryption_factors: - running_decryption = (running_decryption * Utils.inverse(dec_factor, public_key.p)) % public_key.p + """ + decrypt a ciphertext given a list of decryption factors (from multiple trustees) + For now, no support for threshold + """ + running_decryption = self.beta + for dec_factor in decryption_factors: + running_decryption = (running_decryption * number.inverse(dec_factor, public_key.p)) % public_key.p - return running_decryption + return running_decryption def check_group_membership(self, pk): - """ - checks to see if an ElGamal element belongs to the group in the pk - """ - if not (1 < self.alpha < pk.p-1): - return False - - elif not (1 < self.beta < pk.p-1): - return False + """ + checks to see if an ElGamal element belongs to the group in the pk + """ + if not (1 < self.alpha < pk.p - 1): + return False - elif (pow(self.alpha, pk.q, pk.p)!=1): - return False + elif not (1 < self.beta < pk.p - 1): + return False - elif (pow(self.beta, pk.q, pk.p)!=1): - return False + elif pow(self.alpha, pk.q, pk.p) != 1: + return False - else: - return True + elif pow(self.beta, pk.q, pk.p) != 1: + return False + else: + return True def to_dict(self): return {'alpha': str(self.alpha), 'beta': str(self.beta)} - toJSONDict= to_dict + toJSONDict = to_dict def to_string(self): return "%s,%s" % (self.alpha, self.beta) @classmethod - def from_dict(cls, d, pk = None): + def from_dict(cls, d, pk=None): result = cls() result.alpha = int(d['alpha']) result.beta = int(d['beta']) @@ -668,127 +578,134 @@ class EGCiphertext: expects alpha,beta """ split = str.split(",") - return cls.from_dict({'alpha' : split[0], 'beta' : split[1]}) + return cls.from_dict({'alpha': split[0], 'beta': split[1]}) + class EGZKProof(object): - def __init__(self): - self.commitment = {'A':None, 'B':None} - self.challenge = None - self.response = None + def __init__(self): + self.commitment = {'A': None, 'B': None} + self.challenge = None + self.response = None - @classmethod - def generate(cls, little_g, little_h, x, p, q, challenge_generator): - """ - generate a DDH tuple proof, where challenge generator is - almost certainly EG_fiatshamir_challenge_generator - """ + @classmethod + def generate(cls, little_g, little_h, x, p, q, challenge_generator): + """ + generate a DDH tuple proof, where challenge generator is + almost certainly EG_fiatshamir_challenge_generator + """ - # generate random w - w = Utils.random_mpz_lt(q) + # generate random w + w = random.mpz_lt(q) - # create proof instance - proof = cls() + # create proof instance + proof = cls() - # compute A = little_g^w, B=little_h^w - proof.commitment['A'] = pow(little_g, w, p) - proof.commitment['B'] = pow(little_h, w, p) + # compute A = little_g^w, B=little_h^w + proof.commitment['A'] = pow(little_g, w, p) + proof.commitment['B'] = pow(little_h, w, p) - # get challenge - proof.challenge = challenge_generator(proof.commitment) + # get challenge + proof.challenge = challenge_generator(proof.commitment) - # compute response - proof.response = (w + (x * proof.challenge)) % q + # compute response + proof.response = (w + (x * proof.challenge)) % q - # return proof - return proof + # return proof + return proof - @classmethod - def from_dict(cls, d): - p = cls() - p.commitment = {'A': int(d['commitment']['A']), 'B': int(d['commitment']['B'])} - p.challenge = int(d['challenge']) - p.response = int(d['response']) - return p + @classmethod + def from_dict(cls, d): + p = cls() + p.commitment = {'A': int(d['commitment']['A']), 'B': int(d['commitment']['B'])} + p.challenge = int(d['challenge']) + p.response = int(d['response']) + return p - fromJSONDict = from_dict + fromJSONDict = from_dict + + def to_dict(self): + return { + 'commitment': {'A': str(self.commitment['A']), 'B': str(self.commitment['B'])}, + 'challenge': str(self.challenge), + 'response': str(self.response) + } - def to_dict(self): - return { - 'commitment' : {'A' : str(self.commitment['A']), 'B' : str(self.commitment['B'])}, - 'challenge': str(self.challenge), - 'response': str(self.response) - } + toJSONDict = to_dict - def verify(self, little_g, little_h, big_g, big_h, p, q, challenge_generator=None): - """ - Verify a DH tuple proof - """ - # check that A, B are in the correct group - if not (pow(proof.commitment['A'],self.pk.q,self.pk.p)==1 and pow(proof.commitment['B'],self.pk.q,self.pk.p)==1): - return False + def verify(self, little_g, little_h, big_g, big_h, p, q, challenge_generator=None): + """ + Verify a DH tuple proof + """ + # check that A, B are in the correct group + if not (pow(self.commitment['A'], self.pk.q, self.pk.p) == 1 + and pow(self.commitment['B'], self.pk.q, self.pk.p) == 1): + return False - # check that little_g^response = A * big_g^challenge - first_check = (pow(little_g, self.response, p) == ((pow(big_g, self.challenge, p) * self.commitment['A']) % p)) + # check that little_g^response = A * big_g^challenge + first_check = (pow(little_g, self.response, p) == ((pow(big_g, self.challenge, p) * self.commitment['A']) % p)) - # check that little_h^response = B * big_h^challenge - second_check = (pow(little_h, self.response, p) == ((pow(big_h, self.challenge, p) * self.commitment['B']) % p)) + # check that little_h^response = B * big_h^challenge + second_check = (pow(little_h, self.response, p) == ((pow(big_h, self.challenge, p) * self.commitment['B']) % p)) - # check the challenge? - third_check = True + # check the challenge? + third_check = True - if challenge_generator: - third_check = (self.challenge == challenge_generator(self.commitment)) + if challenge_generator: + third_check = (self.challenge == challenge_generator(self.commitment)) - return (first_check and second_check and third_check) + return first_check and second_check and third_check - toJSONDict = to_dict class EGZKDisjunctiveProof: - def __init__(self, proofs = None): - self.proofs = proofs + def __init__(self, proofs=None): + self.proofs = proofs - @classmethod - def from_dict(cls, d): - dp = cls() - dp.proofs = [EGZKProof.from_dict(p) for p in d] - return dp + @classmethod + def from_dict(cls, d): + dp = cls() + dp.proofs = [EGZKProof.from_dict(p) for p in d] + return dp - def to_dict(self): - return [p.to_dict() for p in self.proofs] + def to_dict(self): + return [p.to_dict() for p in self.proofs] + + toJSONDict = to_dict - toJSONDict = to_dict class DLogProof(object): - def __init__(self, commitment, challenge, response): - self.commitment = commitment - self.challenge = challenge - self.response = response + def __init__(self, commitment, challenge, response): + self.commitment = commitment + self.challenge = challenge + self.response = response - def to_dict(self): - return {'challenge': str(self.challenge), 'commitment': str(self.commitment), 'response' : str(self.response)} + def to_dict(self): + return {'challenge': str(self.challenge), 'commitment': str(self.commitment), 'response': str(self.response)} - toJSONDict = to_dict + toJSONDict = to_dict - @classmethod - def from_dict(cls, d): - dlp = cls(int(d['commitment']), int(d['challenge']), int(d['response'])) - return dlp + @classmethod + def from_dict(cls, d): + dlp = cls(int(d['commitment']), int(d['challenge']), int(d['response'])) + return dlp + + fromJSONDict = from_dict - fromJSONDict = from_dict def EG_disjunctive_challenge_generator(commitments): - array_to_hash = [] - for commitment in commitments: - array_to_hash.append(str(commitment['A'])) - array_to_hash.append(str(commitment['B'])) + array_to_hash = [] + for commitment in commitments: + array_to_hash.append(str(commitment['A'])) + array_to_hash.append(str(commitment['B'])) + + string_to_hash = ",".join(array_to_hash) + return int(SHA1.new(bytes(string_to_hash, 'utf-8')).hexdigest(), 16) - string_to_hash = ",".join(array_to_hash) - return int(hashlib.sha1(string_to_hash).hexdigest(),16) # a challenge generator for Fiat-Shamir with A,B commitment def EG_fiatshamir_challenge_generator(commitment): - return EG_disjunctive_challenge_generator([commitment]) + return EG_disjunctive_challenge_generator([commitment]) + def DLog_challenge_generator(commitment): - string_to_hash = str(commitment) - return int(hashlib.sha1(string_to_hash).hexdigest(),16) + string_to_hash = str(commitment) + return int(SHA1.new(bytes(string_to_hash, 'utf-8')).hexdigest(), 16) diff --git a/helios/crypto/electionalgs.py b/helios/crypto/electionalgs.py index c677140432abd01bb6638ce09977de247c0cc5aa..de833f8dad05dee916c2a0a2e82e9242e1beb146 100644 --- a/helios/crypto/electionalgs.py +++ b/helios/crypto/electionalgs.py @@ -4,778 +4,807 @@ Election-specific algorithms for Helios Ben Adida 2008-08-30 """ - -import algs -import logging -import utils -import uuid import datetime +import uuid +import logging -class HeliosObject(object): - """ - A base class to ease serialization and de-serialization - crypto objects are kept as full-blown crypto objects, serialized to jsonobjects on the way out - and deserialized from jsonobjects on the way in - """ - FIELDS = [] - JSON_FIELDS = None - - def __init__(self, **kwargs): - self.set_from_args(**kwargs) - - # generate uuid if need be - if 'uuid' in self.FIELDS and (not hasattr(self, 'uuid') or self.uuid == None): - self.uuid = str(uuid.uuid4()) - - def set_from_args(self, **kwargs): - for f in self.FIELDS: - if kwargs.has_key(f): - new_val = self.process_value_in(f, kwargs[f]) - setattr(self, f, new_val) - else: - setattr(self, f, None) - - def set_from_other_object(self, o): - for f in self.FIELDS: - if hasattr(o, f): - setattr(self, f, self.process_value_in(f, getattr(o,f))) - else: - setattr(self, f, None) - - def toJSON(self): - return utils.to_json(self.toJSONDict()) - - def toJSONDict(self, alternate_fields=None): - val = {} - for f in (alternate_fields or self.JSON_FIELDS or self.FIELDS): - val[f] = self.process_value_out(f, getattr(self, f)) - return val - - @classmethod - def fromJSONDict(cls, d): - # go through the keys and fix them - new_d = {} - for k in d.keys(): - new_d[str(k)] = d[k] - - return cls(**new_d) - - @classmethod - def fromOtherObject(cls, o): - obj = cls() - obj.set_from_other_object(o) - return obj - - def toOtherObject(self, o): - for f in self.FIELDS: - # FIXME: why isn't this working? - if hasattr(o, f): - # BIG HAMMER - try: - setattr(o, f, self.process_value_out(f, getattr(self,f))) - except: - pass - - @property - def hash(self): - s = utils.to_json(self.toJSONDict()) - return utils.hash_b64(s) - - def process_value_in(self, field_name, field_value): - """ - process some fields on the way into the object - """ - if field_value == None: - return None - - val = self._process_value_in(field_name, field_value) - if val != None: - return val - else: - return field_value +from helios.utils import to_json +from . import algs +from . import utils - def _process_value_in(self, field_name, field_value): - return None - def process_value_out(self, field_name, field_value): +class HeliosObject(object): """ - process some fields on the way out of the object + A base class to ease serialization and de-serialization + crypto objects are kept as full-blown crypto objects, serialized to jsonobjects on the way out + and deserialized from jsonobjects on the way in """ - if field_value == None: - return None - - val = self._process_value_out(field_name, field_value) - if val != None: - return val - else: - return field_value - - def _process_value_out(self, field_name, field_value): - return None + FIELDS = [] + JSON_FIELDS = None + + def __init__(self, **kwargs): + self.set_from_args(**kwargs) + + # generate uuid if need be + if 'uuid' in self.FIELDS and (not hasattr(self, 'uuid') or self.uuid is None): + self.uuid = str(uuid.uuid4()) + + def set_from_args(self, **kwargs): + for f in self.FIELDS: + if f in kwargs: + new_val = self.process_value_in(f, kwargs[f]) + setattr(self, f, new_val) + else: + setattr(self, f, None) + + def set_from_other_object(self, o): + for f in self.FIELDS: + if hasattr(o, f): + setattr(self, f, self.process_value_in(f, getattr(o, f))) + else: + setattr(self, f, None) + + def toJSON(self): + return to_json(self.toJSONDict()) + + def toJSONDict(self, alternate_fields=None): + val = {} + for f in (alternate_fields or self.JSON_FIELDS or self.FIELDS): + val[f] = self.process_value_out(f, getattr(self, f)) + return val + + @classmethod + def fromJSONDict(cls, d): + # go through the keys and fix them + new_d = {} + for k in list(d.keys()): + new_d[str(k)] = d[k] + + return cls(**new_d) + + @classmethod + def fromOtherObject(cls, o): + obj = cls() + obj.set_from_other_object(o) + return obj + + def toOtherObject(self, o): + for f in self.FIELDS: + # FIXME: why isn't this working? + if hasattr(o, f): + # BIG HAMMER + try: + setattr(o, f, self.process_value_out(f, getattr(self, f))) + except: + pass + + @property + def hash(self): + s = to_json(self.toJSONDict()) + return utils.hash_b64(s) + + def process_value_in(self, field_name, field_value): + """ + process some fields on the way into the object + """ + if field_value is None: + return None + + val = self._process_value_in(field_name, field_value) + if val is not None: + return val + else: + return field_value + + def _process_value_in(self, field_name, field_value): + return None + + def process_value_out(self, field_name, field_value): + """ + process some fields on the way out of the object + """ + if field_value is None: + return None + + val = self._process_value_out(field_name, field_value) + if val is not None: + return val + else: + return field_value + + def _process_value_out(self, field_name, field_value): + return None + + def __eq__(self, other): + if not hasattr(self, 'uuid'): + return super(HeliosObject, self) == other + + return other is not None and self.uuid == other.uuid - def __eq__(self, other): - if not hasattr(self, 'uuid'): - return super(HeliosObject,self) == other - - return other != None and self.uuid == other.uuid class EncryptedAnswer(HeliosObject): - """ - An encrypted answer to a single election question - """ - - FIELDS = ['choices', 'individual_proofs', 'overall_proof', 'randomness', 'answer'] - - # FIXME: remove this constructor and use only named-var constructor from HeliosObject - def __init__(self, choices=None, individual_proofs=None, overall_proof=None, randomness=None, answer=None): - self.choices = choices - self.individual_proofs = individual_proofs - self.overall_proof = overall_proof - self.randomness = randomness - self.answer = answer - - @classmethod - def generate_plaintexts(cls, pk, min=0, max=1): - plaintexts = [] - running_product = 1 - - # run the product up to the min - for i in range(max+1): - # if we're in the range, add it to the array - if i >= min: - plaintexts.append(algs.EGPlaintext(running_product, pk)) - - # next value in running product - running_product = (running_product * pk.g) % pk.p - - return plaintexts - - def verify_plaintexts_and_randomness(self, pk): """ - this applies only if the explicit answers and randomness factors are given - we do not verify the proofs here, that is the verify() method + An encrypted answer to a single election question """ - if not hasattr(self, 'answer'): - return False - - for choice_num in range(len(self.choices)): - choice = self.choices[choice_num] - choice.pk = pk - # redo the encryption - # WORK HERE (paste from below encryption) + FIELDS = ['choices', 'individual_proofs', 'overall_proof', 'randomness', 'answer'] - return False + # FIXME: remove this constructor and use only named-var constructor from HeliosObject + def __init__(self, choices=None, individual_proofs=None, overall_proof=None, randomness=None, answer=None): + self.choices = choices + self.individual_proofs = individual_proofs + self.overall_proof = overall_proof + self.randomness = randomness + self.answer = answer - def verify(self, pk, min=0, max=1): - possible_plaintexts = self.generate_plaintexts(pk) - homomorphic_sum = 0 + @classmethod + def generate_plaintexts(cls, pk, min=0, max=1): + plaintexts = [] + running_product = 1 - for choice_num in range(len(self.choices)): - choice = self.choices[choice_num] - choice.pk = pk - individual_proof = self.individual_proofs[choice_num] + # run the product up to the min + for i in range(max + 1): + # if we're in the range, add it to the array + if i >= min: + plaintexts.append(algs.EGPlaintext(running_product, pk)) - # verify that elements belong to the proper group - if not choice.check_group_membership(pk): - return False - - # verify the proof on the encryption of that choice - if not choice.verify_disjunctive_encryption_proof(possible_plaintexts, individual_proof, algs.EG_disjunctive_challenge_generator): - return False + # next value in running product + running_product = (running_product * pk.g) % pk.p - # compute homomorphic sum if needed - if max != None: - homomorphic_sum = choice * homomorphic_sum + return plaintexts - if max != None: - # determine possible plaintexts for the sum - sum_possible_plaintexts = self.generate_plaintexts(pk, min=min, max=max) + def verify_plaintexts_and_randomness(self, pk): + """ + this applies only if the explicit answers and randomness factors are given + we do not verify the proofs here, that is the verify() method + """ + if not hasattr(self, 'answer'): + return False - # verify the sum - return homomorphic_sum.verify_disjunctive_encryption_proof(sum_possible_plaintexts, self.overall_proof, algs.EG_disjunctive_challenge_generator) - else: - # approval voting, no need for overall proof verification - return True + for choice_num in range(len(self.choices)): + choice = self.choices[choice_num] + choice.pk = pk - def toJSONDict(self, with_randomness=False): - value = { - 'choices': [c.to_dict() for c in self.choices], - 'individual_proofs' : [p.to_dict() for p in self.individual_proofs] - } + # redo the encryption + # WORK HERE (paste from below encryption) - if self.overall_proof: - value['overall_proof'] = self.overall_proof.to_dict() - else: - value['overall_proof'] = None - - if with_randomness: - value['randomness'] = [str(r) for r in self.randomness] - value['answer'] = self.answer - - return value - - @classmethod - def fromJSONDict(cls, d, pk=None): - ea = cls() - - ea.choices = [algs.EGCiphertext.from_dict(c, pk) for c in d['choices']] - ea.individual_proofs = [algs.EGZKDisjunctiveProof.from_dict(p) for p in d['individual_proofs']] - - if d['overall_proof']: - ea.overall_proof = algs.EGZKDisjunctiveProof.from_dict(d['overall_proof']) - else: - ea.overall_proof = None + return False - if d.has_key('randomness'): - ea.randomness = [int(r) for r in d['randomness']] - ea.answer = d['answer'] + def verify(self, pk, min=0, max=1): + possible_plaintexts = self.generate_plaintexts(pk) + homomorphic_sum = 0 + + for choice_num in range(len(self.choices)): + choice = self.choices[choice_num] + choice.pk = pk + individual_proof = self.individual_proofs[choice_num] + + # verify that elements belong to the proper group + if not choice.check_group_membership(pk): + return False + + # verify the proof on the encryption of that choice + if not choice.verify_disjunctive_encryption_proof(possible_plaintexts, individual_proof, + algs.EG_disjunctive_challenge_generator): + return False + + # compute homomorphic sum if needed + if max is not None: + homomorphic_sum = choice * homomorphic_sum + + if max is not None: + # determine possible plaintexts for the sum + sum_possible_plaintexts = self.generate_plaintexts(pk, min=min, max=max) + + # verify the sum + return homomorphic_sum.verify_disjunctive_encryption_proof(sum_possible_plaintexts, self.overall_proof, + algs.EG_disjunctive_challenge_generator) + else: + # approval voting, no need for overall proof verification + return True + + def toJSONDict(self, with_randomness=False): + value = { + 'choices': [c.to_dict() for c in self.choices], + 'individual_proofs': [p.to_dict() for p in self.individual_proofs] + } + + if self.overall_proof: + value['overall_proof'] = self.overall_proof.to_dict() + else: + value['overall_proof'] = None + + if with_randomness: + value['randomness'] = [str(r) for r in self.randomness] + value['answer'] = self.answer + + return value + + @classmethod + def fromJSONDict(cls, d, pk=None): + ea = cls() + + ea.choices = [algs.EGCiphertext.from_dict(c, pk) for c in d['choices']] + ea.individual_proofs = [algs.EGZKDisjunctiveProof.from_dict(p) for p in d['individual_proofs']] + + if d['overall_proof']: + ea.overall_proof = algs.EGZKDisjunctiveProof.from_dict(d['overall_proof']) + else: + ea.overall_proof = None + + if 'randomness' in d: + ea.randomness = [int(r) for r in d['randomness']] + ea.answer = d['answer'] + + return ea + + @classmethod + def fromElectionAndAnswer(cls, election, question_num, answer_indexes): + """ + Given an election, a question number, and a list of answers to that question + in the form of an array of 0-based indexes into the answer array, + produce an EncryptedAnswer that works. + """ + question = election.questions[question_num] + answers = question['answers'] + pk = election.public_key + + # initialize choices, individual proofs, randomness and overall proof + choices = [None for _ in range(len(answers))] + individual_proofs = [None for _ in range(len(answers))] + randomness = [None for _ in range(len(answers))] + + # possible plaintexts [0, 1] + plaintexts = cls.generate_plaintexts(pk) + + # keep track of number of options selected. + num_selected_answers = 0 + + # homomorphic sum of all + homomorphic_sum = 0 + randomness_sum = 0 + + # min and max for number of answers, useful later + min_answers = 0 + if 'min' in question: + min_answers = question['min'] + max_answers = question['max'] + + # go through each possible answer and encrypt either a g^0 or a g^1. + for answer_num in range(len(answers)): + plaintext_index = 0 + + # assuming a list of answers + if answer_num in answer_indexes: + plaintext_index = 1 + num_selected_answers += 1 + + # randomness and encryption + randomness[answer_num] = utils.random.mpz_lt(pk.q) + choices[answer_num] = pk.encrypt_with_r(plaintexts[plaintext_index], randomness[answer_num]) + + # generate proof + individual_proofs[answer_num] = choices[answer_num].generate_disjunctive_encryption_proof(plaintexts, + plaintext_index, + randomness[ + answer_num], + algs.EG_disjunctive_challenge_generator) + + # sum things up homomorphically if needed + if max_answers is not None: + homomorphic_sum = choices[answer_num] * homomorphic_sum + randomness_sum = (randomness_sum + randomness[answer_num]) % pk.q + + # prove that the sum is 0 or 1 (can be "blank vote" for this answer) + # num_selected_answers is 0 or 1, which is the index into the plaintext that is actually encoded + + if num_selected_answers < min_answers: + raise Exception("Need to select at least %s answer(s)" % min_answers) + + if max_answers is not None: + sum_plaintexts = cls.generate_plaintexts(pk, min=min_answers, max=max_answers) + + # need to subtract the min from the offset + overall_proof = homomorphic_sum.generate_disjunctive_encryption_proof(sum_plaintexts, + num_selected_answers - min_answers, + randomness_sum, + algs.EG_disjunctive_challenge_generator); + else: + # approval voting + overall_proof = None + + return cls(choices, individual_proofs, overall_proof, randomness, answer_indexes) - return ea - @classmethod - def fromElectionAndAnswer(cls, election, question_num, answer_indexes): +class EncryptedVote(HeliosObject): """ - Given an election, a question number, and a list of answers to that question - in the form of an array of 0-based indexes into the answer array, - produce an EncryptedAnswer that works. + An encrypted ballot """ - question = election.questions[question_num] - answers = question['answers'] - pk = election.public_key - - # initialize choices, individual proofs, randomness and overall proof - choices = [None for a in range(len(answers))] - individual_proofs = [None for a in range(len(answers))] - overall_proof = None - randomness = [None for a in range(len(answers))] - - # possible plaintexts [0, 1] - plaintexts = cls.generate_plaintexts(pk) - - # keep track of number of options selected. - num_selected_answers = 0; - - # homomorphic sum of all - homomorphic_sum = 0 - randomness_sum = 0 - - # min and max for number of answers, useful later - min_answers = 0 - if question.has_key('min'): - min_answers = question['min'] - max_answers = question['max'] + FIELDS = ['encrypted_answers', 'election_hash', 'election_uuid'] + + def verify(self, election): + # correct number of answers + # noinspection PyUnresolvedReferences + n_answers = len(self.encrypted_answers) if self.encrypted_answers is not None else 0 + n_questions = len(election.questions) if election.questions is not None else 0 + if n_answers != n_questions: + logging.error(f"Incorrect number of answers ({n_answers}) vs questions ({n_questions})") + return False + + # check hash + # noinspection PyUnresolvedReferences + our_election_hash = self.election_hash if isinstance(self.election_hash, str) else self.election_hash.decode() + actual_election_hash = election.hash if isinstance(election.hash, str) else election.hash.decode() + if our_election_hash != actual_election_hash: + logging.error(f"Incorrect election_hash {our_election_hash} vs {actual_election_hash} ") + return False + + # check ID + # noinspection PyUnresolvedReferences + our_election_uuid = self.election_uuid if isinstance(self.election_uuid, str) else self.election_uuid.decode() + actual_election_uuid = election.uuid if isinstance(election.uuid, str) else election.uuid.decode() + if our_election_uuid != actual_election_uuid: + logging.error(f"Incorrect election_uuid {our_election_uuid} vs {actual_election_uuid} ") + return False + + # check proofs on all of answers + for question_num in range(len(election.questions)): + ea = self.encrypted_answers[question_num] + + question = election.questions[question_num] + min_answers = 0 + if 'min' in question: + min_answers = question['min'] + + if not ea.verify(election.public_key, min=min_answers, max=question['max']): + return False + + return True + + def get_hash(self): + return utils.hash_b64(to_json(self.toJSONDict())) + + def toJSONDict(self, with_randomness=False): + return { + 'answers': [a.toJSONDict(with_randomness) for a in self.encrypted_answers], + 'election_hash': self.election_hash, + 'election_uuid': self.election_uuid + } + + @classmethod + def fromJSONDict(cls, d, pk=None): + ev = cls() + + ev.encrypted_answers = [EncryptedAnswer.fromJSONDict(ea, pk) for ea in d['answers']] + ev.election_hash = d['election_hash'] + ev.election_uuid = d['election_uuid'] + + return ev + + @classmethod + def fromElectionAndAnswers(cls, election, answers): + pk = election.public_key + + # each answer is an index into the answer array + encrypted_answers = [EncryptedAnswer.fromElectionAndAnswer(election, answer_num, answers[answer_num]) for + answer_num in range(len(answers))] + return cls(encrypted_answers=encrypted_answers, election_hash=election.hash, election_uuid=election.uuid) - # go through each possible answer and encrypt either a g^0 or a g^1. - for answer_num in range(len(answers)): - plaintext_index = 0 - # assuming a list of answers - if answer_num in answer_indexes: - plaintext_index = 1 - num_selected_answers += 1 - - # randomness and encryption - randomness[answer_num] = algs.Utils.random_mpz_lt(pk.q) - choices[answer_num] = pk.encrypt_with_r(plaintexts[plaintext_index], randomness[answer_num]) - - # generate proof - individual_proofs[answer_num] = choices[answer_num].generate_disjunctive_encryption_proof(plaintexts, plaintext_index, - randomness[answer_num], algs.EG_disjunctive_challenge_generator) - - # sum things up homomorphically if needed - if max_answers != None: - homomorphic_sum = choices[answer_num] * homomorphic_sum - randomness_sum = (randomness_sum + randomness[answer_num]) % pk.q - - # prove that the sum is 0 or 1 (can be "blank vote" for this answer) - # num_selected_answers is 0 or 1, which is the index into the plaintext that is actually encoded - - if num_selected_answers < min_answers: - raise Exception("Need to select at least %s answer(s)" % min_answers) - - if max_answers != None: - sum_plaintexts = cls.generate_plaintexts(pk, min=min_answers, max=max_answers) +def one_question_winner(question, result, num_cast_votes): + """ + determining the winner for one question + """ + # sort the answers , keep track of the index + counts = sorted(enumerate(result), key=lambda x: x[1]) + counts.reverse() - # need to subtract the min from the offset - overall_proof = homomorphic_sum.generate_disjunctive_encryption_proof(sum_plaintexts, num_selected_answers - min_answers, randomness_sum, algs.EG_disjunctive_challenge_generator); - else: - # approval voting - overall_proof = None + # if there's a max > 1, we assume that the top MAX win + if question['max'] > 1: + return [c[0] for c in counts[:question['max']]] - return cls(choices, individual_proofs, overall_proof, randomness, answer_indexes) + # if max = 1, then depends on absolute or relative + if question['result_type'] == 'absolute': + if counts[0][1] >= (num_cast_votes / 2 + 1): + return [counts[0][0]] + else: + return [] -class EncryptedVote(HeliosObject): - """ - An encrypted ballot - """ - FIELDS = ['encrypted_answers', 'election_hash', 'election_uuid'] - - def verify(self, election): - # right number of answers - if len(self.encrypted_answers) != len(election.questions): - return False - - # check hash - if self.election_hash != election.hash: - # print "%s / %s " % (self.election_hash, election.hash) - return False - - # check ID - if self.election_uuid != election.uuid: - return False - - # check proofs on all of answers - for question_num in range(len(election.questions)): - ea = self.encrypted_answers[question_num] - - question = election.questions[question_num] - min_answers = 0 - if question.has_key('min'): - min_answers = question['min'] - - if not ea.verify(election.public_key, min=min_answers, max=question['max']): - return False + if question['result_type'] == 'relative': + return [counts[0][0]] - return True - - def get_hash(self): - return utils.hash_b64(utils.to_json(self.toJSONDict())) - - def toJSONDict(self, with_randomness=False): - return { - 'answers': [a.toJSONDict(with_randomness) for a in self.encrypted_answers], - 'election_hash': self.election_hash, - 'election_uuid': self.election_uuid - } + +class Election(HeliosObject): + FIELDS = ['uuid', 'questions', 'name', 'short_name', 'description', 'voters_hash', 'openreg', + 'frozen_at', 'public_key', 'private_key', 'cast_url', 'result', 'result_proof', 'use_voter_aliases', + 'voting_starts_at', 'voting_ends_at', 'election_type'] - @classmethod - def fromJSONDict(cls, d, pk=None): - ev = cls() + JSON_FIELDS = ['uuid', 'questions', 'name', 'short_name', 'description', 'voters_hash', 'openreg', + 'frozen_at', 'public_key', 'cast_url', 'use_voter_aliases', 'voting_starts_at', 'voting_ends_at'] - ev.encrypted_answers = [EncryptedAnswer.fromJSONDict(ea, pk) for ea in d['answers']] - ev.election_hash = d['election_hash'] - ev.election_uuid = d['election_uuid'] + # need to add in v3.1: use_advanced_audit_features, election_type, and probably more - return ev + def init_tally(self): + return Tally(election=self) - @classmethod - def fromElectionAndAnswers(cls, election, answers): - pk = election.public_key + def _process_value_in(self, field_name, field_value): + if field_name == 'frozen_at' or field_name == 'voting_starts_at' or field_name == 'voting_ends_at': + if isinstance(field_value, str): + return datetime.datetime.strptime(field_value, '%Y-%m-%d %H:%M:%S') - # each answer is an index into the answer array - encrypted_answers = [EncryptedAnswer.fromElectionAndAnswer(election, answer_num, answers[answer_num]) for answer_num in range(len(answers))] - return cls(encrypted_answers=encrypted_answers, election_hash=election.hash, election_uuid = election.uuid) + if field_name == 'public_key': + return algs.EGPublicKey.fromJSONDict(field_value) + if field_name == 'private_key': + return algs.EGSecretKey.fromJSONDict(field_value) -def one_question_winner(question, result, num_cast_votes): - """ - determining the winner for one question - """ - # sort the answers , keep track of the index - counts = sorted(enumerate(result), key=lambda(x): x[1]) - counts.reverse() - - # if there's a max > 1, we assume that the top MAX win - if question['max'] > 1: - return [c[0] for c in counts[:question['max']]] - - # if max = 1, then depends on absolute or relative - if question['result_type'] == 'absolute': - if counts[0][1] >= (num_cast_votes/2 + 1): - return [counts[0][0]] - else: - return [] - - if question['result_type'] == 'relative': - return [counts[0][0]] + def _process_value_out(self, field_name, field_value): + # the date + if field_name == 'frozen_at' or field_name == 'voting_starts_at' or field_name == 'voting_ends_at': + return str(field_value) -class Election(HeliosObject): + if field_name == 'public_key' or field_name == 'private_key': + return field_value.toJSONDict() - FIELDS = ['uuid', 'questions', 'name', 'short_name', 'description', 'voters_hash', 'openreg', - 'frozen_at', 'public_key', 'private_key', 'cast_url', 'result', 'result_proof', 'use_voter_aliases', 'voting_starts_at', 'voting_ends_at', 'election_type'] + @property + def registration_status_pretty(self): + if self.openreg: + return "Open" + else: + return "Closed" - JSON_FIELDS = ['uuid', 'questions', 'name', 'short_name', 'description', 'voters_hash', 'openreg', - 'frozen_at', 'public_key', 'cast_url', 'use_voter_aliases', 'voting_starts_at', 'voting_ends_at'] + @property + def winners(self): + """ + Depending on the type of each question, determine the winners + returns an array of winners for each question, aka an array of arrays. + assumes that if there is a max to the question, that's how many winners there are. + """ + return [one_question_winner(self.questions[i], self.result[i], self.num_cast_votes) for i in + range(len(self.questions))] - # need to add in v3.1: use_advanced_audit_features, election_type, and probably more + @property + def pretty_result(self): + if not self.result: + return None - def init_tally(self): - return Tally(election=self) + # get the winners + winners = self.winners - def _process_value_in(self, field_name, field_value): - if field_name == 'frozen_at' or field_name == 'voting_starts_at' or field_name == 'voting_ends_at': - if type(field_value) == str or type(field_value) == unicode: - return datetime.datetime.strptime(field_value, '%Y-%m-%d %H:%M:%S') + raw_result = self.result + prettified_result = [] - if field_name == 'public_key': - return algs.EGPublicKey.fromJSONDict(field_value) + # loop through questions + for i in range(len(self.questions)): + q = self.questions[i] + pretty_question = [] - if field_name == 'private_key': - return algs.EGSecretKey.fromJSONDict(field_value) + # go through answers + for j in range(len(q['answers'])): + a = q['answers'][j] + count = raw_result[i][j] + pretty_question.append({'answer': a, 'count': count, 'winner': (j in winners[i])}) - def _process_value_out(self, field_name, field_value): - # the date - if field_name == 'frozen_at' or field_name == 'voting_starts_at' or field_name == 'voting_ends_at': - return str(field_value) + prettified_result.append({'question': q['short_name'], 'answers': pretty_question}) - if field_name == 'public_key' or field_name == 'private_key': - return field_value.toJSONDict() + return prettified_result - @property - def registration_status_pretty(self): - if self.openreg: - return "Open" - else: - return "Closed" - @property - def winners(self): +class Voter(HeliosObject): """ - Depending on the type of each question, determine the winners - returns an array of winners for each question, aka an array of arrays. - assumes that if there is a max to the question, that's how many winners there are. + A voter in an election """ - return [one_question_winner(self.questions[i], self.result[i], self.num_cast_votes) for i in range(len(self.questions))] + FIELDS = ['election_uuid', 'uuid', 'voter_type', 'voter_id', 'name', 'alias'] + JSON_FIELDS = ['election_uuid', 'uuid', 'voter_type', 'voter_id_hash', 'name'] - @property - def pretty_result(self): - if not self.result: - return None + # alternative, for when the voter is aliased + ALIASED_VOTER_JSON_FIELDS = ['election_uuid', 'uuid', 'alias'] - # get the winners - winners = self.winners + def toJSONDict(self): + if self.alias is not None: + return super(Voter, self).toJSONDict(self.ALIASED_VOTER_JSON_FIELDS) + else: + return super(Voter, self).toJSONDict() - raw_result = self.result - prettified_result = [] + @property + def voter_id_hash(self): + if self.voter_login_id: + # for backwards compatibility with v3.0, and since it doesn't matter + # too much if we hash the email or the unique login ID here. + return utils.hash_b64(self.voter_login_id) + else: + return utils.hash_b64(self.voter_id) - # loop through questions - for i in range(len(self.questions)): - q = self.questions[i] - pretty_question = [] - - # go through answers - for j in range(len(q['answers'])): - a = q['answers'][j] - count = raw_result[i][j] - pretty_question.append({'answer': a, 'count': count, 'winner': (j in winners[i])}) - - prettified_result.append({'question': q['short_name'], 'answers': pretty_question}) - - return prettified_result - - -class Voter(HeliosObject): - """ - A voter in an election - """ - FIELDS = ['election_uuid', 'uuid', 'voter_type', 'voter_id', 'name', 'alias'] - JSON_FIELDS = ['election_uuid', 'uuid', 'voter_type', 'voter_id_hash', 'name'] - - # alternative, for when the voter is aliased - ALIASED_VOTER_JSON_FIELDS = ['election_uuid', 'uuid', 'alias'] - - def toJSONDict(self): - fields = None - if self.alias != None: - return super(Voter, self).toJSONDict(self.ALIASED_VOTER_JSON_FIELDS) - else: - return super(Voter,self).toJSONDict() - - @property - def voter_id_hash(self): - if self.voter_login_id: - # for backwards compatibility with v3.0, and since it doesn't matter - # too much if we hash the email or the unique login ID here. - return utils.hash_b64(self.voter_login_id) - else: - return utils.hash_b64(self.voter_id) class Trustee(HeliosObject): - """ - a trustee - """ - FIELDS = ['uuid', 'public_key', 'public_key_hash', 'pok', 'decryption_factors', 'decryption_proofs', 'email'] - - def _process_value_in(self, field_name, field_value): - if field_name == 'public_key': - return algs.EGPublicKey.fromJSONDict(field_value) - - if field_name == 'pok': - return algs.DLogProof.fromJSONDict(field_value) - - def _process_value_out(self, field_name, field_value): - if field_name == 'public_key' or field_name == 'pok': - return field_value.toJSONDict() - -class CastVote(HeliosObject): - """ - A cast vote, which includes an encrypted vote and some cast metadata - """ - FIELDS = ['vote', 'cast_at', 'voter_uuid', 'voter_hash', 'vote_hash'] - - def __init__(self, *args, **kwargs): - super(CastVote, self).__init__(*args, **kwargs) - self.election = None - - @classmethod - def fromJSONDict(cls, d, election=None): - o = cls() - o.election = election - o.set_from_args(**d) - return o - - def toJSONDict(self, include_vote=True): - result = super(CastVote,self).toJSONDict() - if not include_vote: - del result['vote'] - return result - - @classmethod - def fromOtherObject(cls, o, election): - obj = cls() - obj.election = election - obj.set_from_other_object(o) - return obj - - def _process_value_in(self, field_name, field_value): - if field_name == 'cast_at': - if type(field_value) == str: - return datetime.datetime.strptime(field_value, '%Y-%m-%d %H:%M:%S') - - if field_name == 'vote': - return EncryptedVote.fromJSONDict(field_value, self.election.public_key) - - def _process_value_out(self, field_name, field_value): - # the date - if field_name == 'cast_at': - return str(field_value) - - if field_name == 'vote': - return field_value.toJSONDict() - - def issues(self, election): """ - Look for consistency problems + a trustee """ - issues = [] - - # check the election - if self.vote.election_uuid != election.uuid: - issues.append("the vote's election UUID does not match the election for which this vote is being cast") - - return issues - -class DLogTable(object): - """ - Keeping track of discrete logs - """ - - def __init__(self, base, modulus): - self.dlogs = {} - self.dlogs[1] = 0 - self.last_dlog_result = 1 - self.counter = 0 - - self.base = base - self.modulus = modulus + FIELDS = ['uuid', 'public_key', 'public_key_hash', 'pok', 'decryption_factors', 'decryption_proofs', 'email'] - def increment(self): - self.counter += 1 + def _process_value_in(self, field_name, field_value): + if field_name == 'public_key': + return algs.EGPublicKey.fromJSONDict(field_value) - # new value - new_value = (self.last_dlog_result * self.base) % self.modulus + if field_name == 'pok': + return algs.DLogProof.fromJSONDict(field_value) - # record the discrete log - self.dlogs[new_value] = self.counter + def _process_value_out(self, field_name, field_value): + if field_name == 'public_key' or field_name == 'pok': + return field_value.toJSONDict() - # record the last value - self.last_dlog_result = new_value - - def precompute(self, up_to): - while self.counter < up_to: - self.increment() - - def lookup(self, value): - return self.dlogs.get(value, None) +class CastVote(HeliosObject): + """ + A cast vote, which includes an encrypted vote and some cast metadata + """ + FIELDS = ['vote', 'cast_at', 'voter_uuid', 'voter_hash', 'vote_hash'] -class Tally(HeliosObject): - """ - A running homomorphic tally - """ + def __init__(self, *args, **kwargs): + super(CastVote, self).__init__(*args, **kwargs) + self.election = None - FIELDS = ['num_tallied', 'tally'] - JSON_FIELDS = ['num_tallied', 'tally'] + @classmethod + def fromJSONDict(cls, d, election=None): + o = cls() + o.election = election + o.set_from_args(**d) + return o - def __init__(self, *args, **kwargs): - super(Tally, self).__init__(*args, **kwargs) + def toJSONDict(self, include_vote=True): + result = super(CastVote, self).toJSONDict() + if not include_vote: + del result['vote'] + return result - self.election = kwargs.get('election',None) + @classmethod + def fromOtherObject(cls, o, election): + obj = cls() + obj.election = election + obj.set_from_other_object(o) + return obj - if self.election: - self.init_election(self.election) - else: - self.questions = None - self.public_key = None + def _process_value_in(self, field_name, field_value): + if field_name == 'cast_at': + if isinstance(field_value, str): + return datetime.datetime.strptime(field_value, '%Y-%m-%d %H:%M:%S') - if not self.tally: - self.tally = None + if field_name == 'vote': + return EncryptedVote.fromJSONDict(field_value, self.election.public_key) - # initialize - if self.num_tallied == None: - self.num_tallied = 0 + def _process_value_out(self, field_name, field_value): + # the date + if field_name == 'cast_at': + return str(field_value) - def init_election(self, election): - """ - given the election, initialize some params - """ - self.questions = election.questions - self.public_key = election.public_key + if field_name == 'vote': + return field_value.toJSONDict() - if not self.tally: - self.tally = [[0 for a in q['answers']] for q in self.questions] + def issues(self, election): + """ + Look for consistency problems + """ + issues = [] - def add_vote_batch(self, encrypted_votes, verify_p=True): - """ - Add a batch of votes. Eventually, this will be optimized to do an aggregate proof verification - rather than a whole proof verif for each vote. - """ - for vote in encrypted_votes: - self.add_vote(vote, verify_p) - - def add_vote(self, encrypted_vote, verify_p=True): - # do we verify? - if verify_p: - if not encrypted_vote.verify(self.election): - raise Exception('Bad Vote') - - # for each question - for question_num in range(len(self.questions)): - question = self.questions[question_num] - answers = question['answers'] - - # for each possible answer to each question - for answer_num in range(len(answers)): - # do the homomorphic addition into the tally - enc_vote_choice = encrypted_vote.encrypted_answers[question_num].choices[answer_num] - enc_vote_choice.pk = self.public_key - self.tally[question_num][answer_num] = encrypted_vote.encrypted_answers[question_num].choices[answer_num] * self.tally[question_num][answer_num] - - self.num_tallied += 1 - - def decryption_factors_and_proofs(self, sk): - """ - returns an array of decryption factors and a corresponding array of decryption proofs. - makes the decryption factors into strings, for general Helios / JS compatibility. - """ - # for all choices of all questions (double list comprehension) - decryption_factors = [] - decryption_proof = [] + # check the election + if self.vote.election_uuid != election.uuid: + issues.append("the vote's election UUID does not match the election for which this vote is being cast") - for question_num, question in enumerate(self.questions): - answers = question['answers'] - question_factors = [] - question_proof = [] + return issues - for answer_num, answer in enumerate(answers): - # do decryption and proof of it - dec_factor, proof = sk.decryption_factor_and_proof(self.tally[question_num][answer_num]) - # look up appropriate discrete log - # this is the string conversion - question_factors.append(str(dec_factor)) - question_proof.append(proof.toJSONDict()) - - decryption_factors.append(question_factors) - decryption_proof.append(question_proof) - - return decryption_factors, decryption_proof - - def decrypt_and_prove(self, sk, discrete_logs=None): +class DLogTable(object): """ - returns an array of tallies and a corresponding array of decryption proofs. + Keeping track of discrete logs """ - # who's keeping track of discrete logs? - if not discrete_logs: - discrete_logs = self.discrete_logs + def __init__(self, base, modulus): + self.dlogs = {1: 0} + self.last_dlog_result = 1 + self.counter = 0 - # for all choices of all questions (double list comprehension) - decrypted_tally = [] - decryption_proof = [] + self.base = base + self.modulus = modulus - for question_num in range(len(self.questions)): - question = self.questions[question_num] - answers = question['answers'] - question_tally = [] - question_proof = [] + def increment(self): + self.counter += 1 - for answer_num in range(len(answers)): - # do decryption and proof of it - plaintext, proof = sk.prove_decryption(self.tally[question_num][answer_num]) + # new value + new_value = (self.last_dlog_result * self.base) % self.modulus - # look up appropriate discrete log - question_tally.append(discrete_logs[plaintext]) - question_proof.append(proof) + # record the discrete log + self.dlogs[new_value] = self.counter - decrypted_tally.append(question_tally) - decryption_proof.append(question_proof) + # record the last value + self.last_dlog_result = new_value - return decrypted_tally, decryption_proof + def precompute(self, up_to): + while self.counter < up_to: + self.increment() - def verify_decryption_proofs(self, decryption_factors, decryption_proofs, public_key, challenge_generator): - """ - decryption_factors is a list of lists of dec factors - decryption_proofs are the corresponding proofs - public_key is, of course, the public key of the trustee - """ + def lookup(self, value): + return self.dlogs.get(value, None) - # go through each one - for q_num, q in enumerate(self.tally): - for a_num, answer_tally in enumerate(q): - # parse the proof - proof = algs.EGZKProof.fromJSONDict(decryption_proofs[q_num][a_num]) - # check that g, alpha, y, dec_factor is a DH tuple - if not proof.verify(public_key.g, answer_tally.alpha, public_key.y, int(decryption_factors[q_num][a_num]), public_key.p, public_key.q, challenge_generator): - return False - - return True - - def decrypt_from_factors(self, decryption_factors, public_key): +class Tally(HeliosObject): """ - decrypt a tally given decryption factors - - The decryption factors are a list of decryption factor sets, for each trustee. - Each decryption factor set is a list of lists of decryption factors (questions/answers). + A running homomorphic tally """ - # pre-compute a dlog table - dlog_table = DLogTable(base = public_key.g, modulus = public_key.p) - dlog_table.precompute(self.num_tallied) - - result = [] + FIELDS = ['num_tallied', 'tally'] + JSON_FIELDS = ['num_tallied', 'tally'] + + def __init__(self, *args, **kwargs): + super(Tally, self).__init__(*args, **kwargs) + + self.election = kwargs.get('election', None) + + if self.election: + self.init_election(self.election) + else: + self.questions = None + self.public_key = None + + if not self.tally: + self.tally = None + + # initialize + if self.num_tallied is None: + self.num_tallied = 0 + + def init_election(self, election): + """ + given the election, initialize some params + """ + self.questions = election.questions + self.public_key = election.public_key + + if not self.tally: + self.tally = [[0 for _ in q['answers']] for q in self.questions] + + def add_vote_batch(self, encrypted_votes, verify_p=True): + """ + Add a batch of votes. Eventually, this will be optimized to do an aggregate proof verification + rather than a whole proof verif for each vote. + """ + for vote in encrypted_votes: + self.add_vote(vote, verify_p) + + def add_vote(self, encrypted_vote, verify_p=True): + # do we verify? + if verify_p: + if not encrypted_vote.verify(self.election): + raise Exception('Bad Vote') + + # for each question + for question_num in range(len(self.questions)): + question = self.questions[question_num] + answers = question['answers'] + + # for each possible answer to each question + for answer_num in range(len(answers)): + # do the homomorphic addition into the tally + enc_vote_choice = encrypted_vote.encrypted_answers[question_num].choices[answer_num] + enc_vote_choice.pk = self.public_key + self.tally[question_num][answer_num] = encrypted_vote.encrypted_answers[question_num].choices[ + answer_num] * self.tally[question_num][answer_num] + + self.num_tallied += 1 + + def decryption_factors_and_proofs(self, sk): + """ + returns an array of decryption factors and a corresponding array of decryption proofs. + makes the decryption factors into strings, for general Helios / JS compatibility. + """ + # for all choices of all questions (double list comprehension) + decryption_factors = [] + decryption_proof = [] + + for question_num, question in enumerate(self.questions): + answers = question['answers'] + question_factors = [] + question_proof = [] + + for answer_num, answer in enumerate(answers): + # do decryption and proof of it + dec_factor, proof = sk.decryption_factor_and_proof(self.tally[question_num][answer_num]) + + # look up appropriate discrete log + # this is the string conversion + question_factors.append(str(dec_factor)) + question_proof.append(proof.toJSONDict()) + + decryption_factors.append(question_factors) + decryption_proof.append(question_proof) + + return decryption_factors, decryption_proof + + def decrypt_and_prove(self, sk, discrete_logs=None): + """ + returns an array of tallies and a corresponding array of decryption proofs. + """ + + # who's keeping track of discrete logs? + if not discrete_logs: + discrete_logs = self.discrete_logs + + # for all choices of all questions (double list comprehension) + decrypted_tally = [] + decryption_proof = [] + + for question_num in range(len(self.questions)): + question = self.questions[question_num] + answers = question['answers'] + question_tally = [] + question_proof = [] + + for answer_num in range(len(answers)): + # do decryption and proof of it + plaintext, proof = sk.prove_decryption(self.tally[question_num][answer_num]) + + # look up appropriate discrete log + question_tally.append(discrete_logs[plaintext]) + question_proof.append(proof) + + decrypted_tally.append(question_tally) + decryption_proof.append(question_proof) + + return decrypted_tally, decryption_proof + + def verify_decryption_proofs(self, decryption_factors, decryption_proofs, public_key, challenge_generator): + """ + decryption_factors is a list of lists of dec factors + decryption_proofs are the corresponding proofs + public_key is, of course, the public key of the trustee + """ + + # go through each one + for q_num, q in enumerate(self.tally): + for a_num, answer_tally in enumerate(q): + # parse the proof + proof = algs.EGZKProof.fromJSONDict(decryption_proofs[q_num][a_num]) + + # check that g, alpha, y, dec_factor is a DH tuple + if not proof.verify(public_key.g, answer_tally.alpha, public_key.y, + int(decryption_factors[q_num][a_num]), public_key.p, public_key.q, + challenge_generator): + return False + + return True + + def decrypt_from_factors(self, decryption_factors, public_key): + """ + decrypt a tally given decryption factors + + The decryption factors are a list of decryption factor sets, for each trustee. + Each decryption factor set is a list of lists of decryption factors (questions/answers). + """ + + # pre-compute a dlog table + dlog_table = DLogTable(base=public_key.g, modulus=public_key.p) + dlog_table.precompute(self.num_tallied) + + result = [] - # go through each one - for q_num, q in enumerate(self.tally): - q_result = [] + # go through each one + for q_num, q in enumerate(self.tally): + q_result = [] - for a_num, a in enumerate(q): - # coalesce the decryption factors into one list - dec_factor_list = [df[q_num][a_num] for df in decryption_factors] - raw_value = self.tally[q_num][a_num].decrypt(dec_factor_list, public_key) + for a_num, a in enumerate(q): + # coalesce the decryption factors into one list + dec_factor_list = [df[q_num][a_num] for df in decryption_factors] + raw_value = self.tally[q_num][a_num].decrypt(dec_factor_list, public_key) - q_result.append(dlog_table.lookup(raw_value)) + q_result.append(dlog_table.lookup(raw_value)) - result.append(q_result) + result.append(q_result) - return result + return result - def _process_value_in(self, field_name, field_value): - if field_name == 'tally': - return [[algs.EGCiphertext.fromJSONDict(a) for a in q] for q in field_value] + def _process_value_in(self, field_name, field_value): + if field_name == 'tally': + return [[algs.EGCiphertext.fromJSONDict(a) for a in q] for q in field_value] - def _process_value_out(self, field_name, field_value): - if field_name == 'tally': - return [[a.toJSONDict() for a in q] for q in field_value] + def _process_value_out(self, field_name, field_value): + if field_name == 'tally': + return [[a.toJSONDict() for a in q] for q in field_value] diff --git a/helios/crypto/elgamal.py b/helios/crypto/elgamal.py index 88a08c01ba6c7ab7366281faf989d72427c1d4a8..33eb03083319aa0b7f9ab61defbca3a50bd57440 100644 --- a/helios/crypto/elgamal.py +++ b/helios/crypto/elgamal.py @@ -8,12 +8,13 @@ Ben Adida ben@adida.net """ -import math, hashlib, logging -import randpool, number +import logging -import numtheory +from Crypto.Hash import SHA1 +from Crypto.Util.number import inverse + +from helios.crypto.utils import random -from algs import Utils class Cryptosystem(object): def __init__(self): @@ -21,30 +22,6 @@ class Cryptosystem(object): self.q = None self.g = None - @classmethod - def generate(cls, n_bits): - """ - generate an El-Gamal environment. Returns an instance - of ElGamal(), with prime p, group size q, and generator g - """ - - EG = cls() - - # find a prime p such that (p-1)/2 is prime q - EG.p = Utils.random_safe_prime(n_bits) - - # q is the order of the group - # FIXME: not always p-1/2 - EG.q = (EG.p-1)/2 - - # find g that generates the q-order subgroup - while True: - EG.g = Utils.random_mpz_lt(EG.p) - if pow(EG.g, EG.q, EG.p) == 1: - break - - return EG - def generate_keypair(self): """ generates a keypair in the setting @@ -68,7 +45,7 @@ class KeyPair(object): self.pk.p = p self.pk.q = q - self.sk.x = Utils.random_mpz_lt(q) + self.sk.x = random.mpz_lt(q) self.pk.y = pow(g, self.sk.x, p) self.sk.public_key = self.pk @@ -106,7 +83,7 @@ class PublicKey: """ Encrypt a plaintext and return the randomness just generated and used. """ - r = Utils.random_mpz_lt(self.q) + r = random.mpz_lt(self.q) ciphertext = self.encrypt_with_r(plaintext, r) return [ciphertext, r] @@ -181,7 +158,7 @@ class SecretKey: if not dec_factor: dec_factor = self.decryption_factor(ciphertext) - m = (Utils.inverse(dec_factor, self.pk.p) * ciphertext.beta) % self.pk.p + m = (inverse(dec_factor, self.pk.p) * ciphertext.beta) % self.pk.p if decode_m: # get m back from the q-order subgroup @@ -207,15 +184,15 @@ class SecretKey: and alpha^t = b * beta/m ^ c """ - m = (Utils.inverse(pow(ciphertext.alpha, self.x, self.pk.p), self.pk.p) * ciphertext.beta) % self.pk.p - beta_over_m = (ciphertext.beta * Utils.inverse(m, self.pk.p)) % self.pk.p + m = (inverse(pow(ciphertext.alpha, self.x, self.pk.p), self.pk.p) * ciphertext.beta) % self.pk.p + beta_over_m = (ciphertext.beta * inverse(m, self.pk.p)) % self.pk.p # pick a random w - w = Utils.random_mpz_lt(self.pk.q) + w = random.mpz_lt(self.pk.q) a = pow(self.pk.g, w, self.pk.p) b = pow(ciphertext.alpha, w, self.pk.p) - c = int(hashlib.sha1(str(a) + "," + str(b)).hexdigest(),16) + c = int(SHA1.new(bytes(str(a) + "," + str(b), 'utf-8')).hexdigest(),16) t = (w + self.x * c) % self.pk.q @@ -232,7 +209,7 @@ class SecretKey: Verifier provides challenge modulo q. Prover computes response = w + x*challenge mod q, where x is the secret key. """ - w = Utils.random_mpz_lt(self.pk.q) + w = random.mpz_lt(self.pk.q) commitment = pow(self.pk.g, w, self.pk.p) challenge = challenge_generator(commitment) % self.pk.q response = (w + (self.x * challenge)) % self.pk.q @@ -255,7 +232,7 @@ class Ciphertext: """ Homomorphic Multiplication of ciphertexts. """ - if type(other) == int and (other == 0 or other == 1): + if isinstance(other, int) and (other == 0 or other == 1): return self if self.pk != other.pk: @@ -287,7 +264,7 @@ class Ciphertext: """ Reencryption with fresh randomness, which is returned. """ - r = Utils.random_mpz_lt(self.pk.q) + r = random.mpz_lt(self.pk.q) new_c = self.reenc_with_r(r) return [new_c, r] @@ -301,17 +278,17 @@ class Ciphertext: """ Check for ciphertext equality. """ - if other == None: + if other is None: return False - return (self.alpha == other.alpha and self.beta == other.beta) + return self.alpha == other.alpha and self.beta == other.beta def generate_encryption_proof(self, plaintext, randomness, challenge_generator): """ Generate the disjunctive encryption proof of encryption """ # random W - w = Utils.random_mpz_lt(self.pk.q) + w = random.mpz_lt(self.pk.q) # build the proof proof = ZKProof() @@ -331,20 +308,20 @@ class Ciphertext: def simulate_encryption_proof(self, plaintext, challenge=None): # generate a random challenge if not provided if not challenge: - challenge = Utils.random_mpz_lt(self.pk.q) + challenge = random.mpz_lt(self.pk.q) proof = ZKProof() proof.challenge = challenge # compute beta/plaintext, the completion of the DH tuple - beta_over_plaintext = (self.beta * Utils.inverse(plaintext.m, self.pk.p)) % self.pk.p + beta_over_plaintext = (self.beta * inverse(plaintext.m, self.pk.p)) % self.pk.p # random response, does not even need to depend on the challenge - proof.response = Utils.random_mpz_lt(self.pk.q); + proof.response = random.mpz_lt(self.pk.q); # now we compute A and B - proof.commitment['A'] = (Utils.inverse(pow(self.alpha, proof.challenge, self.pk.p), self.pk.p) * pow(self.pk.g, proof.response, self.pk.p)) % self.pk.p - proof.commitment['B'] = (Utils.inverse(pow(beta_over_plaintext, proof.challenge, self.pk.p), self.pk.p) * pow(self.pk.y, proof.response, self.pk.p)) % self.pk.p + proof.commitment['A'] = (inverse(pow(self.alpha, proof.challenge, self.pk.p), self.pk.p) * pow(self.pk.g, proof.response, self.pk.p)) % self.pk.p + proof.commitment['B'] = (inverse(pow(beta_over_plaintext, proof.challenge, self.pk.p), self.pk.p) * pow(self.pk.y, proof.response, self.pk.p)) % self.pk.p return proof @@ -397,7 +374,7 @@ class Ciphertext: first_check = (pow(self.pk.g, proof.response, self.pk.p) == ((pow(self.alpha, proof.challenge, self.pk.p) * proof.commitment['A']) % self.pk.p)) # check that y^response = B * (beta/m)^challenge - beta_over_m = (self.beta * Utils.inverse(plaintext.m, self.pk.p)) % self.pk.p + beta_over_m = (self.beta * inverse(plaintext.m, self.pk.p)) % self.pk.p second_check = (pow(self.pk.y, proof.response, self.pk.p) == ((pow(beta_over_m, proof.challenge, self.pk.p) * proof.commitment['B']) % self.pk.p)) # print "1,2: %s %s " % (first_check, second_check) @@ -416,7 +393,7 @@ class Ciphertext: for i in range(len(plaintexts)): # if a proof fails, stop right there if not self.verify_encryption_proof(plaintexts[i], proof.proofs[i]): - print "bad proof %s, %s, %s" % (i, plaintexts[i], proof.proofs[i]) + print("bad proof %s, %s, %s" % (i, plaintexts[i], proof.proofs[i])) return False # logging.info("made it past the two encryption proofs") @@ -444,7 +421,7 @@ class Ciphertext: """ running_decryption = self.beta for dec_factor in decryption_factors: - running_decryption = (running_decryption * Utils.inverse(dec_factor, public_key.p)) % public_key.p + running_decryption = (running_decryption * inverse(dec_factor, public_key.p)) % public_key.p return running_decryption @@ -473,7 +450,7 @@ class ZKProof(object): """ # generate random w - w = Utils.random_mpz_lt(q) + w = random.mpz_lt(q) # create proof instance proof = cls() @@ -526,7 +503,7 @@ def disjunctive_challenge_generator(commitments): array_to_hash.append(str(commitment['B'])) string_to_hash = ",".join(array_to_hash) - return int(hashlib.sha1(string_to_hash).hexdigest(),16) + return int(SHA1.new(bytes(string_to_hash, 'utf-8')).hexdigest(),16) # a challenge generator for Fiat-Shamir with A,B commitment def fiatshamir_challenge_generator(commitment): @@ -534,5 +511,5 @@ def fiatshamir_challenge_generator(commitment): def DLog_challenge_generator(commitment): string_to_hash = str(commitment) - return int(hashlib.sha1(string_to_hash).hexdigest(),16) + return int(SHA1.new(bytes(string_to_hash, 'utf-8')).hexdigest(),16) diff --git a/helios/crypto/number.py b/helios/crypto/number.py deleted file mode 100644 index 9d50563e904ab50a5446916953b0091c2f228031..0000000000000000000000000000000000000000 --- a/helios/crypto/number.py +++ /dev/null @@ -1,201 +0,0 @@ -# -# number.py : Number-theoretic functions -# -# Part of the Python Cryptography Toolkit -# -# Distribute and use freely; there are no restrictions on further -# dissemination and usage except those imposed by the laws of your -# country of residence. This software is provided "as is" without -# warranty of fitness for use or suitability for any purpose, express -# or implied. Use at your own risk or not at all. -# - -__revision__ = "$Id: number.py,v 1.13 2003/04/04 18:21:07 akuchling Exp $" - -bignum = long -try: - from Crypto.PublicKey import _fastmath -except ImportError: - _fastmath = None - -# Commented out and replaced with faster versions below -## def long2str(n): -## s='' -## while n>0: -## s=chr(n & 255)+s -## n=n>>8 -## return s - -## import types -## def str2long(s): -## if type(s)!=types.StringType: return s # Integers will be left alone -## return reduce(lambda x,y : x*256+ord(y), s, 0L) - -def size (N): - """size(N:long) : int - Returns the size of the number N in bits. - """ - bits, power = 0,1L - while N >= power: - bits += 1 - power = power << 1 - return bits - -def getRandomNumber(N, randfunc): - """getRandomNumber(N:int, randfunc:callable):long - Return an N-bit random number.""" - - S = randfunc(N/8) - odd_bits = N % 8 - if odd_bits != 0: - char = ord(randfunc(1)) >> (8-odd_bits) - S = chr(char) + S - value = bytes_to_long(S) - value |= 2L ** (N-1) # Ensure high bit is set - assert size(value) >= N - return value - -def GCD(x,y): - """GCD(x:long, y:long): long - Return the GCD of x and y. - """ - x = abs(x) ; y = abs(y) - while x > 0: - x, y = y % x, x - return y - -def inverse(u, v): - """inverse(u:long, u:long):long - Return the inverse of u mod v. - """ - u3, v3 = long(u), long(v) - u1, v1 = 1L, 0L - while v3 > 0: - q=u3 / v3 - u1, v1 = v1, u1 - v1*q - u3, v3 = v3, u3 - v3*q - while u1<0: - u1 = u1 + v - return u1 - -# Given a number of bits to generate and a random generation function, -# find a prime number of the appropriate size. - -def getPrime(N, randfunc): - """getPrime(N:int, randfunc:callable):long - Return a random N-bit prime number. - """ - - number=getRandomNumber(N, randfunc) | 1 - while (not isPrime(number)): - number=number+2 - return number - -def isPrime(N): - """isPrime(N:long):bool - Return true if N is prime. - """ - if N == 1: - return 0 - if N in sieve: - return 1 - for i in sieve: - if (N % i)==0: - return 0 - - # Use the accelerator if available - if _fastmath is not None: - return _fastmath.isPrime(N) - - # Compute the highest bit that's set in N - N1 = N - 1L - n = 1L - while (n<N): - n=n<<1L - n = n >> 1L - - # Rabin-Miller test - for c in sieve[:7]: - a=long(c) ; d=1L ; t=n - while (t): # Iterate over the bits in N1 - x=(d*d) % N - if x==1L and d!=1L and d!=N1: - return 0 # Square root of 1 found - if N1 & t: - d=(x*a) % N - else: - d=x - t = t >> 1L - if d!=1L: - return 0 - return 1 - -# Small primes used for checking primality; these are all the primes -# less than 256. This should be enough to eliminate most of the odd -# numbers before needing to do a Rabin-Miller test at all. - -sieve=[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, - 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, - 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, - 197, 199, 211, 223, 227, 229, 233, 239, 241, 251] - -# Improved conversion functions contributed by Barry Warsaw, after -# careful benchmarking - -import struct - -def long_to_bytes(n, blocksize=0): - """long_to_bytes(n:long, blocksize:int) : string - Convert a long integer to a byte string. - - If optional blocksize is given and greater than zero, pad the front of the - byte string with binary zeros so that the length is a multiple of - blocksize. - """ - # after much testing, this algorithm was deemed to be the fastest - s = '' - n = long(n) - pack = struct.pack - while n > 0: - s = pack('>I', n & 0xffffffffL) + s - n = n >> 32 - # strip off leading zeros - for i in range(len(s)): - if s[i] != '\000': - break - else: - # only happens when n == 0 - s = '\000' - i = 0 - s = s[i:] - # add back some pad bytes. this could be done more efficiently w.r.t. the - # de-padding being done above, but sigh... - if blocksize > 0 and len(s) % blocksize: - s = (blocksize - len(s) % blocksize) * '\000' + s - return s - -def bytes_to_long(s): - """bytes_to_long(string) : long - Convert a byte string to a long integer. - - This is (essentially) the inverse of long_to_bytes(). - """ - acc = 0L - unpack = struct.unpack - length = len(s) - if length % 4: - extra = (4 - length % 4) - s = '\000' * extra + s - length = length + extra - for i in range(0, length, 4): - acc = (acc << 32) + unpack('>I', s[i:i+4])[0] - return acc - -# For backwards compatibility... -import warnings -def long2str(n, blocksize=0): - warnings.warn("long2str() has been replaced by long_to_bytes()") - return long_to_bytes(n, blocksize) -def str2long(s): - warnings.warn("str2long() has been replaced by bytes_to_long()") - return bytes_to_long(s) diff --git a/helios/crypto/numtheory.py b/helios/crypto/numtheory.py index 16fcf9aa0c2fe0017471546d0f353c9cb60878bd..e16691745cf7f9dd2713274b67c1a1a0091df12e 100644 --- a/helios/crypto/numtheory.py +++ b/helios/crypto/numtheory.py @@ -103,7 +103,7 @@ def trial_division(n, bound=None): if n == 1: return 1 for p in [2, 3, 5]: if n%p == 0: return p - if bound == None: bound = n + if bound is None: bound = n dif = [6, 4, 2, 4, 2, 4, 6, 2] m = 7; i = 1 while m <= bound and m*m <= n: @@ -207,7 +207,7 @@ def inversemod(a, n): """ g, x, y = xgcd(a, n) if g != 1: - raise ZeroDivisionError, (a,n) + raise ZeroDivisionError(a,n) assert g == 1, "a must be coprime to n." return x%n @@ -225,7 +225,7 @@ def solve_linear(a,b,n): Examples: >>> solve_linear(4, 2, 10) 8 - >>> solve_linear(2, 1, 4) == None + >>> solve_linear(2, 1, 4) is None True """ g, c, _ = xgcd(a,n) # (1) @@ -1014,7 +1014,7 @@ def elliptic_curve_method(N, m, tries=5): E, P = randcurve(N) # (2) try: # (3) Q = ellcurve_mul(E, m, P) # (4) - except ZeroDivisionError, x: # (5) + except ZeroDivisionError as x: # (5) g = gcd(x[0],N) # (6) if g != 1 or g != N: return g # (7) return N @@ -1153,7 +1153,7 @@ class Poly: # (1) return r def __neg__(self): v = {} - for m in self.v.keys(): + for m in list(self.v.keys()): v[m] = -self.v[m] return Poly(v) def __div__(self, other): @@ -1161,7 +1161,7 @@ class Poly: # (1) def __getitem__(self, m): # (6) m = tuple(m) - if not self.v.has_key(m): self.v[m] = 0 + if m not in self.v: self.v[m] = 0 return self.v[m] def __setitem__(self, m, c): self.v[tuple(m)] = c @@ -1169,7 +1169,7 @@ class Poly: # (1) del self.v[tuple(m)] def monomials(self): # (7) - return self.v.keys() + return list(self.v.keys()) def normalize(self): # (8) while True: finished = True @@ -1244,8 +1244,8 @@ def prove_associative(): # (15) - (x3 + x4)*(x3 - x4)*(x3 - x4)) s2 = (x3 - x4)*(x3 - x4)*((y1 - y5)*(y1 - y5) \ - (x1 + x5)*(x1 - x5)*(x1 - x5)) - print "Associative?" - print s1 == s2 # (17) + print("Associative?") + print(s1 == s2) # (17) diff --git a/helios/crypto/randpool.py b/helios/crypto/randpool.py deleted file mode 100644 index 53a8acc035c225b23ea9e6edd2a8b27b39b75082..0000000000000000000000000000000000000000 --- a/helios/crypto/randpool.py +++ /dev/null @@ -1,422 +0,0 @@ -# -# randpool.py : Cryptographically strong random number generation -# -# Part of the Python Cryptography Toolkit -# -# Distribute and use freely; there are no restrictions on further -# dissemination and usage except those imposed by the laws of your -# country of residence. This software is provided "as is" without -# warranty of fitness for use or suitability for any purpose, express -# or implied. Use at your own risk or not at all. -# - -__revision__ = "$Id: randpool.py,v 1.14 2004/05/06 12:56:54 akuchling Exp $" - -import time, array, types, warnings, os.path -from number import long_to_bytes -try: - import Crypto.Util.winrandom as winrandom -except: - winrandom = None - -STIRNUM = 3 - -class RandomPool: - """randpool.py : Cryptographically strong random number generation. - - The implementation here is similar to the one in PGP. To be - cryptographically strong, it must be difficult to determine the RNG's - output, whether in the future or the past. This is done by using - a cryptographic hash function to "stir" the random data. - - Entropy is gathered in the same fashion as PGP; the highest-resolution - clock around is read and the data is added to the random number pool. - A conservative estimate of the entropy is then kept. - - If a cryptographically secure random source is available (/dev/urandom - on many Unixes, Windows CryptGenRandom on most Windows), then use - it. - - Instance Attributes: - bits : int - Maximum size of pool in bits - bytes : int - Maximum size of pool in bytes - entropy : int - Number of bits of entropy in this pool. - - Methods: - add_event([s]) : add some entropy to the pool - get_bytes(int) : get N bytes of random data - randomize([N]) : get N bytes of randomness from external source - """ - - - def __init__(self, numbytes = 160, cipher=None, hash=None): - if hash is None: - from hashlib import sha1 as hash - - # The cipher argument is vestigial; it was removed from - # version 1.1 so RandomPool would work even in the limited - # exportable subset of the code - if cipher is not None: - warnings.warn("'cipher' parameter is no longer used") - - if isinstance(hash, types.StringType): - # ugly hack to force __import__ to give us the end-path module - hash = __import__('Crypto.Hash.'+hash, - None, None, ['new']) - warnings.warn("'hash' parameter should now be a hashing module") - - self.bytes = numbytes - self.bits = self.bytes*8 - self.entropy = 0 - self._hash = hash - - # Construct an array to hold the random pool, - # initializing it to 0. - self._randpool = array.array('B', [0]*self.bytes) - - self._event1 = self._event2 = 0 - self._addPos = 0 - self._getPos = hash().digest_size - self._lastcounter=time.time() - self.__counter = 0 - - self._measureTickSize() # Estimate timer resolution - self._randomize() - - def _updateEntropyEstimate(self, nbits): - self.entropy += nbits - if self.entropy < 0: - self.entropy = 0 - elif self.entropy > self.bits: - self.entropy = self.bits - - def _randomize(self, N = 0, devname = '/dev/urandom'): - """_randomize(N, DEVNAME:device-filepath) - collects N bits of randomness from some entropy source (e.g., - /dev/urandom on Unixes that have it, Windows CryptoAPI - CryptGenRandom, etc) - DEVNAME is optional, defaults to /dev/urandom. You can change it - to /dev/random if you want to block till you get enough - entropy. - """ - data = '' - if N <= 0: - nbytes = int((self.bits - self.entropy)/8+0.5) - else: - nbytes = int(N/8+0.5) - if winrandom: - # Windows CryptGenRandom provides random data. - data = winrandom.new().get_bytes(nbytes) - # GAE fix, benadida - #elif os.path.exists(devname): - # # Many OSes support a /dev/urandom device - # try: - # f=open(devname) - # data=f.read(nbytes) - # f.close() - # except IOError, (num, msg): - # if num!=2: raise IOError, (num, msg) - # # If the file wasn't found, ignore the error - if data: - self._addBytes(data) - # Entropy estimate: The number of bits of - # data obtained from the random source. - self._updateEntropyEstimate(8*len(data)) - self.stir_n() # Wash the random pool - - def randomize(self, N=0): - """randomize(N:int) - use the class entropy source to get some entropy data. - This is overridden by KeyboardRandomize(). - """ - return self._randomize(N) - - def stir_n(self, N = STIRNUM): - """stir_n(N) - stirs the random pool N times - """ - for i in xrange(N): - self.stir() - - def stir (self, s = ''): - """stir(s:string) - Mix up the randomness pool. This will call add_event() twice, - but out of paranoia the entropy attribute will not be - increased. The optional 's' parameter is a string that will - be hashed with the randomness pool. - """ - - entropy=self.entropy # Save inital entropy value - self.add_event() - - # Loop over the randomness pool: hash its contents - # along with a counter, and add the resulting digest - # back into the pool. - for i in range(self.bytes / self._hash().digest_size): - h = self._hash(self._randpool) - h.update(str(self.__counter) + str(i) + str(self._addPos) + s) - self._addBytes( h.digest() ) - self.__counter = (self.__counter + 1) & 0xFFFFffffL - - self._addPos, self._getPos = 0, self._hash().digest_size - self.add_event() - - # Restore the old value of the entropy. - self.entropy=entropy - - - def get_bytes (self, N): - """get_bytes(N:int) : string - Return N bytes of random data. - """ - - s='' - i, pool = self._getPos, self._randpool - h=self._hash() - dsize = self._hash().digest_size - num = N - while num > 0: - h.update( self._randpool[i:i+dsize] ) - s = s + h.digest() - num = num - dsize - i = (i + dsize) % self.bytes - if i<dsize: - self.stir() - i=self._getPos - - self._getPos = i - self._updateEntropyEstimate(- 8*N) - return s[:N] - - - def add_event(self, s=''): - """add_event(s:string) - Add an event to the random pool. The current time is stored - between calls and used to estimate the entropy. The optional - 's' parameter is a string that will also be XORed into the pool. - Returns the estimated number of additional bits of entropy gain. - """ - event = time.time()*1000 - delta = self._noise() - s = (s + long_to_bytes(event) + - 4*chr(0xaa) + long_to_bytes(delta) ) - self._addBytes(s) - if event==self._event1 and event==self._event2: - # If events are coming too closely together, assume there's - # no effective entropy being added. - bits=0 - else: - # Count the number of bits in delta, and assume that's the entropy. - bits=0 - while delta: - delta, bits = delta>>1, bits+1 - if bits>8: bits=8 - - self._event1, self._event2 = event, self._event1 - - self._updateEntropyEstimate(bits) - return bits - - # Private functions - def _noise(self): - # Adds a bit of noise to the random pool, by adding in the - # current time and CPU usage of this process. - # The difference from the previous call to _noise() is taken - # in an effort to estimate the entropy. - t=time.time() - delta = (t - self._lastcounter)/self._ticksize*1e6 - self._lastcounter = t - self._addBytes(long_to_bytes(long(1000*time.time()))) - self._addBytes(long_to_bytes(long(1000*time.clock()))) - self._addBytes(long_to_bytes(long(1000*time.time()))) - self._addBytes(long_to_bytes(long(delta))) - - # Reduce delta to a maximum of 8 bits so we don't add too much - # entropy as a result of this call. - delta=delta % 0xff - return int(delta) - - - def _measureTickSize(self): - # _measureTickSize() tries to estimate a rough average of the - # resolution of time that you can see from Python. It does - # this by measuring the time 100 times, computing the delay - # between measurements, and taking the median of the resulting - # list. (We also hash all the times and add them to the pool) - interval = [None] * 100 - h = self._hash(`(id(self),id(interval))`) - - # Compute 100 differences - t=time.time() - h.update(`t`) - i = 0 - j = 0 - while i < 100: - t2=time.time() - h.update(`(i,j,t2)`) - j += 1 - delta=int((t2-t)*1e6) - if delta: - interval[i] = delta - i += 1 - t=t2 - - # Take the median of the array of intervals - interval.sort() - self._ticksize=interval[len(interval)/2] - h.update(`(interval,self._ticksize)`) - # mix in the measurement times and wash the random pool - self.stir(h.digest()) - - def _addBytes(self, s): - "XOR the contents of the string S into the random pool" - i, pool = self._addPos, self._randpool - for j in range(0, len(s)): - pool[i]=pool[i] ^ ord(s[j]) - i=(i+1) % self.bytes - self._addPos = i - - # Deprecated method names: remove in PCT 2.1 or later. - def getBytes(self, N): - warnings.warn("getBytes() method replaced by get_bytes()", - DeprecationWarning) - return self.get_bytes(N) - - def addEvent (self, event, s=""): - warnings.warn("addEvent() method replaced by add_event()", - DeprecationWarning) - return self.add_event(s + str(event)) - -class PersistentRandomPool (RandomPool): - def __init__ (self, filename=None, *args, **kwargs): - RandomPool.__init__(self, *args, **kwargs) - self.filename = filename - if filename: - try: - # the time taken to open and read the file might have - # a little disk variability, modulo disk/kernel caching... - f=open(filename, 'rb') - self.add_event() - data = f.read() - self.add_event() - # mix in the data from the file and wash the random pool - self.stir(data) - f.close() - except IOError: - # Oh, well; the file doesn't exist or is unreadable, so - # we'll just ignore it. - pass - - def save(self): - if self.filename == "": - raise ValueError, "No filename set for this object" - # wash the random pool before save, provides some forward secrecy for - # old values of the pool. - self.stir_n() - f=open(self.filename, 'wb') - self.add_event() - f.write(self._randpool.tostring()) - f.close() - self.add_event() - # wash the pool again, provide some protection for future values - self.stir() - -# non-echoing Windows keyboard entry -_kb = 0 -if not _kb: - try: - import msvcrt - class KeyboardEntry: - def getch(self): - c = msvcrt.getch() - if c in ('\000', '\xe0'): - # function key - c += msvcrt.getch() - return c - def close(self, delay = 0): - if delay: - time.sleep(delay) - while msvcrt.kbhit(): - msvcrt.getch() - _kb = 1 - except: - pass - -# non-echoing Posix keyboard entry -if not _kb: - try: - import termios - class KeyboardEntry: - def __init__(self, fd = 0): - self._fd = fd - self._old = termios.tcgetattr(fd) - new = termios.tcgetattr(fd) - new[3]=new[3] & ~termios.ICANON & ~termios.ECHO - termios.tcsetattr(fd, termios.TCSANOW, new) - def getch(self): - termios.tcflush(0, termios.TCIFLUSH) # XXX Leave this in? - return os.read(self._fd, 1) - def close(self, delay = 0): - if delay: - time.sleep(delay) - termios.tcflush(self._fd, termios.TCIFLUSH) - termios.tcsetattr(self._fd, termios.TCSAFLUSH, self._old) - _kb = 1 - except: - pass - -class KeyboardRandomPool (PersistentRandomPool): - def __init__(self, *args, **kwargs): - PersistentRandomPool.__init__(self, *args, **kwargs) - - def randomize(self, N = 0): - "Adds N bits of entropy to random pool. If N is 0, fill up pool." - import os, string, time - if N <= 0: - bits = self.bits - self.entropy - else: - bits = N*8 - if bits == 0: - return - print bits,'bits of entropy are now required. Please type on the keyboard' - print 'until enough randomness has been accumulated.' - kb = KeyboardEntry() - s='' # We'll save the characters typed and add them to the pool. - hash = self._hash - e = 0 - try: - while e < bits: - temp=str(bits-e).rjust(6) - os.write(1, temp) - s=s+kb.getch() - e += self.add_event(s) - os.write(1, 6*chr(8)) - self.add_event(s+hash.new(s).digest() ) - finally: - kb.close() - print '\n\007 Enough. Please wait a moment.\n' - self.stir_n() # wash the random pool. - kb.close(4) - -if __name__ == '__main__': - pool = RandomPool() - print 'random pool entropy', pool.entropy, 'bits' - pool.add_event('something') - print `pool.get_bytes(100)` - import tempfile, os - fname = tempfile.mktemp() - pool = KeyboardRandomPool(filename=fname) - print 'keyboard random pool entropy', pool.entropy, 'bits' - pool.randomize() - print 'keyboard random pool entropy', pool.entropy, 'bits' - pool.randomize(128) - pool.save() - saved = open(fname, 'rb').read() - print 'saved', `saved` - print 'pool ', `pool._randpool.tostring()` - newpool = PersistentRandomPool(fname) - print 'persistent random pool entropy', pool.entropy, 'bits' - os.remove(fname) diff --git a/helios/crypto/utils.py b/helios/crypto/utils.py index dd395a598fbbec1df4bce90f81e7d95a9228b32b..d854c886bd6e6a0128fbdeec232305ee97870a2a 100644 --- a/helios/crypto/utils.py +++ b/helios/crypto/utils.py @@ -1,23 +1,31 @@ """ Crypto Utils """ +import base64 +import math + +from Crypto.Hash import SHA256 +from Crypto.Random.random import StrongRandom + +random = StrongRandom() + + +def random_mpz_lt(maximum, strong_random=random): + n_bits = int(math.floor(math.log(maximum, 2))) + res = strong_random.getrandbits(n_bits) + while res >= maximum: + res = strong_random.getrandbits(n_bits) + return res + + +random.mpz_lt = random_mpz_lt -import hmac, base64, json -from hashlib import sha256 - def hash_b64(s): - """ - hash the string using sha1 and produce a base64 output - removes the trailing "=" - """ - hasher = sha256(s) - result= base64.b64encode(hasher.digest())[:-1] - return result - -def to_json(d): - return json.dumps(d, sort_keys=True) - -def from_json(json_str): - if not json_str: return None - return json.loads(json_str) + """ + hash the string using sha256 and produce a base64 output + removes the trailing "=" + """ + hasher = SHA256.new(s.encode('utf-8')) + result = base64.b64encode(hasher.digest())[:-1] + return result diff --git a/helios/datatypes/__init__.py b/helios/datatypes/__init__.py index b0ede25f2ed3569bc28d6bf414be7844b714b517..574f8eb9355bcec57a4a60e40f9f8c2154a2af25 100644 --- a/helios/datatypes/__init__.py +++ b/helios/datatypes/__init__.py @@ -25,6 +25,7 @@ And when data comes in: # but is not necessary for full JSON-LD objects. LDObject.deserialize(json_string, type=...) """ +import importlib from helios import utils from helios.crypto import utils as cryptoutils @@ -33,32 +34,32 @@ from helios.crypto import utils as cryptoutils ## utility function ## def recursiveToDict(obj): - if obj == None: + if obj is None: return None - if type(obj) == list: + if isinstance(obj, list): return [recursiveToDict(el) for el in obj] else: return obj.toDict() def get_class(datatype): # already done? - if not isinstance(datatype, basestring): + if not isinstance(datatype, str): return datatype # parse datatype string "v31/Election" --> from v31 import Election parsed_datatype = datatype.split("/") # get the module - dynamic_module = __import__(".".join(parsed_datatype[:-1]), globals(), locals(), [], level=-1) + dynamic_module = importlib.import_module("helios.datatypes." + (".".join(parsed_datatype[:-1]))) if not dynamic_module: - raise Exception("no module for %s" % datatpye) + raise Exception("no module for %s" % datatype) # go down the attributes to get to the class try: dynamic_ptr = dynamic_module - for attr in parsed_datatype[1:]: + for attr in parsed_datatype[-1:]: dynamic_ptr = getattr(dynamic_ptr, attr) dynamic_cls = dynamic_ptr except AttributeError: @@ -119,7 +120,7 @@ class LDObject(object): @classmethod def instantiate(cls, obj, datatype=None): - "FIXME: should datatype override the object's internal datatype? probably not" + """FIXME: should datatype override the object's internal datatype? probably not""" if isinstance(obj, LDObject): return obj @@ -130,7 +131,7 @@ class LDObject(object): raise Exception("no datatype found") # nulls - if obj == None: + if obj is None: return None # the class @@ -149,9 +150,11 @@ class LDObject(object): setattr(self.wrapped_obj, attr, val) def loadData(self): - "load data using from the wrapped object" + """ + load data using from the wrapped object + """ # go through the subfields and instantiate them too - for subfield_name, subfield_type in self.STRUCTURED_FIELDS.iteritems(): + for subfield_name, subfield_type in self.STRUCTURED_FIELDS.items(): self.structured_fields[subfield_name] = self.instantiate(self._getattr_wrapped(subfield_name), datatype = subfield_type) def loadDataFromDict(self, d): @@ -160,7 +163,7 @@ class LDObject(object): """ # the structured fields - structured_fields = self.STRUCTURED_FIELDS.keys() + structured_fields = list(self.STRUCTURED_FIELDS.keys()) # go through the fields and set them properly # on the newly instantiated object @@ -171,7 +174,7 @@ class LDObject(object): self.structured_fields[f] = sub_ld_object # set the field on the wrapped object too - if sub_ld_object != None: + if sub_ld_object is not None: self._setattr_wrapped(f, sub_ld_object.wrapped_obj) else: self._setattr_wrapped(f, None) @@ -190,12 +193,12 @@ class LDObject(object): fields = self.FIELDS if not self.structured_fields: - if self.wrapped_obj.alias != None: + if self.wrapped_obj.alias is not None: fields = self.ALIASED_VOTER_FIELDS for f in (alternate_fields or fields): # is it a structured subfield? - if self.structured_fields.has_key(f): + if f in self.structured_fields: val[f] = recursiveToDict(self.structured_fields[f]) else: val[f] = self.process_value_out(f, self._getattr_wrapped(f)) @@ -214,7 +217,7 @@ class LDObject(object): @classmethod def fromDict(cls, d, type_hint=None): # null objects - if d == None: + if d is None: return None # the LD type is either in d or in type_hint @@ -248,11 +251,11 @@ class LDObject(object): """ process some fields on the way into the object """ - if field_value == None: + if field_value is None: return None val = self._process_value_in(field_name, field_value) - if val != None: + if val is not None: return val else: return field_value @@ -264,23 +267,25 @@ class LDObject(object): """ process some fields on the way out of the object """ - if field_value == None: + if field_value is None: return None val = self._process_value_out(field_name, field_value) - if val != None: + if val is not None: return val else: return field_value def _process_value_out(self, field_name, field_value): + if isinstance(field_value, bytes): + return field_value.decode('utf-8') return None - + def __eq__(self, other): if not hasattr(self, 'uuid'): - return super(LDObject,self) == other + return super(LDObject, self) == other - return other != None and self.uuid == other.uuid + return other is not None and self.uuid == other.uuid class BaseArrayOfObjects(LDObject): diff --git a/helios/datatypes/djangofield.py b/helios/datatypes/djangofield.py index e0eb1b4fbccbd437ac5d4d77a37bfaadabffba5d..a299ace6c6a7b8f55e231a5e645d238aceb7f51c 100644 --- a/helios/datatypes/djangofield.py +++ b/helios/datatypes/djangofield.py @@ -6,15 +6,12 @@ http://www.djangosnippets.org/snippets/377/ and adapted to LDObject """ -import datetime -import json from django.db import models -from django.db.models import signals -from django.conf import settings -from django.core.serializers.json import DjangoJSONEncoder +from helios import utils from . import LDObject + class LDObjectField(models.TextField): """ LDObject is a generic textfield that neatly serializes/unserializes @@ -23,9 +20,6 @@ class LDObjectField(models.TextField): deserialization_params added on 2011-01-09 to provide additional hints at deserialization time """ - # Used so to_python() is called - __metaclass__ = models.SubfieldBase - def __init__(self, type_hint=None, **kwargs): self.type_hint = type_hint super(LDObjectField, self).__init__(**kwargs) @@ -34,35 +28,29 @@ class LDObjectField(models.TextField): """Convert our string value to LDObject after we load it from the DB""" # did we already convert this? - if not isinstance(value, basestring): + if not isinstance(value, str): return value - if value == None: - return None + return self.from_db_value(value) + # noinspection PyUnusedLocal + def from_db_value(self, value, *args, **kwargs): # in some cases, we're loading an existing array or dict, - # we skip this part but instantiate the LD object - if isinstance(value, basestring): - try: - parsed_value = json.loads(value) - except: - raise Exception("value is not JSON parseable, that's bad news") - else: - parsed_value = value - - if parsed_value != None: - "we give the wrapped object back because we're not dealing with serialization types" - return_val = LDObject.fromDict(parsed_value, type_hint = self.type_hint).wrapped_obj - return return_val - else: + # from_json takes care of this duality + parsed_value = utils.from_json(value) + if parsed_value is None: return None + # we give the wrapped object back because we're not dealing with serialization types + return_val = LDObject.fromDict(parsed_value, type_hint=self.type_hint).wrapped_obj + return return_val + def get_prep_value(self, value): """Convert our JSON object to a string before we save""" - if isinstance(value, basestring): + if isinstance(value, str): return value - if value == None: + if value is None: return None # instantiate the proper LDObject to dump it appropriately @@ -71,4 +59,4 @@ class LDObjectField(models.TextField): def value_to_string(self, obj): value = self._get_val_from_obj(obj) - return self.get_db_prep_value(value) + return self.get_db_prep_value(value, None) diff --git a/helios/datatypes/legacy.py b/helios/datatypes/legacy.py index c0a24ffc2ab2d0540d2953adf7469cd2f7c98e66..d469b4418b663456b17b695e751799b3390e5af4 100644 --- a/helios/datatypes/legacy.py +++ b/helios/datatypes/legacy.py @@ -77,7 +77,7 @@ class Voter(LegacyObject): """ depending on whether the voter is aliased, use different fields """ - if self.wrapped_obj.alias != None: + if self.wrapped_obj.alias is not None: return super(Voter, self).toDict(self.ALIASED_VOTER_FIELDS, complete = complete) else: return super(Voter,self).toDict(complete = complete) diff --git a/helios/datetimewidget.py b/helios/datetimewidget.py index 5a9e0d40a3aa46066e1539cf207125a904372b9d..dfd7ec04a42118460a216988fa2a9f5a323c1093 100644 --- a/helios/datetimewidget.py +++ b/helios/datetimewidget.py @@ -14,7 +14,7 @@ import datetime, time from django.utils.safestring import mark_safe # DATETIMEWIDGET -calbtn = u'''<img src="%smedia/admin/img/admin/icon_calendar.gif" alt="calendar" id="%s_btn" style="cursor: pointer;" title="Select date" /> +calbtn = '''<img src="%smedia/admin/img/admin/icon_calendar.gif" alt="calendar" id="%s_btn" style="cursor: pointer;" title="Select date" /> <script type="text/javascript"> Calendar.setup({ inputField : "%s", @@ -25,6 +25,8 @@ calbtn = u'''<img src="%smedia/admin/img/admin/icon_calendar.gif" alt="calendar" </script>''' class DateTimeWidget(forms.widgets.TextInput): + template_name = '' + class Media: css = { 'all': ( @@ -49,13 +51,13 @@ class DateTimeWidget(forms.widgets.TextInput): except: final_attrs['value'] = \ force_unicode(value) - if not final_attrs.has_key('id'): - final_attrs['id'] = u'%s_id' % (name) + if 'id' not in final_attrs: + final_attrs['id'] = '%s_id' % (name) id = final_attrs['id'] jsdformat = self.dformat #.replace('%', '%%') cal = calbtn % (settings.MEDIA_URL, id, id, jsdformat, id) - a = u'<input%s />%s%s' % (forms.util.flatatt(final_attrs), self.media, cal) + a = '<input%s />%s%s' % (forms.util.flatatt(final_attrs), self.media, cal) return mark_safe(a) def value_from_datadict(self, data, files, name): @@ -82,12 +84,12 @@ class DateTimeWidget(forms.widgets.TextInput): Copy of parent's method, but modify value with strftime function before final comparsion """ if data is None: - data_value = u'' + data_value = '' else: data_value = data if initial is None: - initial_value = u'' + initial_value = '' else: initial_value = initial diff --git a/helios/election_url_names.py b/helios/election_url_names.py new file mode 100644 index 0000000000000000000000000000000000000000..eff9a5ea232956f1e613bac9126dc51e2b3519ac --- /dev/null +++ b/helios/election_url_names.py @@ -0,0 +1,62 @@ +ELECTION_HOME="election@home" +ELECTION_VIEW="election@view" +ELECTION_META="election@meta" +ELECTION_EDIT="election@edit" +ELECTION_SCHEDULE="election@schedule" +ELECTION_EXTEND="election@extend" +ELECTION_ARCHIVE="election@archive" +ELECTION_COPY="election@copy" +ELECTION_BADGE="election@badge" + +ELECTION_TRUSTEES_HOME="election@trustees" +ELECTION_TRUSTEES_VIEW="election@trustees@view" +ELECTION_TRUSTEES_NEW="election@trustees@new" +ELECTION_TRUSTEES_ADD_HELIOS="election@trustees@add-helios" +ELECTION_TRUSTEES_DELETE="election@trustees@delete" + +ELECTION_TRUSTEE_HOME="election@trustee" +ELECTION_TRUSTEE_SEND_URL="election@trustee@send-url" +ELECTION_TRUSTEE_KEY_GENERATOR="election@trustee@key-generator" +ELECTION_TRUSTEE_CHECK_SK="election@trustee@check-sk" +ELECTION_TRUSTEE_UPLOAD_PK="election@trustee@upload-pk" +ELECTION_TRUSTEE_DECRYPT_AND_PROVE="election@trustee@decrypt-and-prove" +ELECTION_TRUSTEE_UPLOAD_DECRYPTION="election@trustee@upload-decryption" + +ELECTION_RESULT="election@result" +ELECTION_RESULT_PROOF="election@result@proof" +ELECTION_BBOARD="election@bboard" +ELECTION_AUDITED_BALLOTS="election@audited-ballots" + +ELECTION_GET_RANDOMNESS="election@get-randomness" +ELECTION_ENCRYPT_BALLOT="election@encrypt-ballot" +ELECTION_QUESTIONS="election@questions" +ELECTION_SET_REG="election@set-reg" +ELECTION_SET_FEATURED="election@set-featured" +ELECTION_SAVE_QUESTIONS="election@save-questions" +ELECTION_REGISTER="election@register" +ELECTION_FREEZE="election@freeze" + +ELECTION_COMPUTE_TALLY="election@compute-tally" +ELECTION_COMBINE_DECRYPTIONS="election@combine-decryptions" +ELECTION_RELEASE_RESULT="election@release-result" + +ELECTION_CAST="election@cast" +ELECTION_CAST_CONFIRM="election@cast-confirm" +ELECTION_PASSWORD_VOTER_LOGIN="election@password-voter-login" +ELECTION_CAST_DONE="election@cast-done" + +ELECTION_POST_AUDITED_BALLOT="election@post-audited-ballot" + +ELECTION_VOTERS_HOME="election@voters" +ELECTION_VOTERS_UPLOAD="election@voters@upload" +ELECTION_VOTERS_UPLOAD_CANCEL="election@voters@upload-cancel" +ELECTION_VOTERS_LIST="election@voters@list" +ELECTION_VOTERS_LIST_PRETTY="election@voters@list-pretty" +ELECTION_VOTERS_ELIGIBILITY="election@voters@eligibility" +ELECTION_VOTERS_EMAIL="election@voters@email" +ELECTION_VOTER="election@voter" +ELECTION_VOTER_DELETE="election@voter@delete" + +ELECTION_BALLOTS_LIST="election@ballots@list" +ELECTION_BALLOTS_VOTER="election@ballots@voter" +ELECTION_BALLOTS_VOTER_LAST="election@ballots@voter@last" diff --git a/helios/election_urls.py b/helios/election_urls.py index 6622c5568a2564d986fdf916b401de7651281796..6fc07efaac9854557b8df6243b4ec1370fdd7a89 100644 --- a/helios/election_urls.py +++ b/helios/election_urls.py @@ -4,91 +4,99 @@ Helios URLs for Election related stuff Ben Adida (ben@adida.net) """ -from django.conf.urls import * +from django.conf.urls import url -from helios.views import * +from helios import views +from helios import election_url_names as names -urlpatterns = patterns('', +urlpatterns = [ # election data that is cryptographically verified - (r'^$', one_election), + url(r'^$', views.one_election, name=names.ELECTION_HOME), # metadata that need not be verified - (r'^/meta$', one_election_meta), + url(r'^/meta$', views.one_election_meta, name=names.ELECTION_META), # edit election params - (r'^/edit$', one_election_edit), - (r'^/schedule$', one_election_schedule), - (r'^/extend$', one_election_extend), - (r'^/archive$', one_election_archive), - (r'^/copy$', one_election_copy), + url(r'^/edit$', views.one_election_edit, name=names.ELECTION_EDIT), + url(r'^/schedule$', views.one_election_schedule, name=names.ELECTION_SCHEDULE), + url(r'^/extend$', views.one_election_extend, name=names.ELECTION_EXTEND), + url(r'^/archive$', views.one_election_archive, name=names.ELECTION_ARCHIVE), + url(r'^/copy$', views.one_election_copy, name=names.ELECTION_COPY), # badge - (r'^/badge$', election_badge), + url(r'^/badge$', views.election_badge, name=names.ELECTION_BADGE), # adding trustees - (r'^/trustees/$', list_trustees), - (r'^/trustees/view$', list_trustees_view), - (r'^/trustees/new$', new_trustee), - (r'^/trustees/add-helios$', new_trustee_helios), - (r'^/trustees/delete$', delete_trustee), + url(r'^/trustees/$', views.list_trustees, name=names.ELECTION_TRUSTEES_HOME), + url(r'^/trustees/view$', views.list_trustees_view, name=names.ELECTION_TRUSTEES_VIEW), + url(r'^/trustees/new$', views.new_trustee, name=names.ELECTION_TRUSTEES_NEW), + url(r'^/trustees/add-helios$', views.new_trustee_helios, name=names.ELECTION_TRUSTEES_ADD_HELIOS), + url(r'^/trustees/delete$', views.delete_trustee, name=names.ELECTION_TRUSTEES_DELETE), # trustee pages - (r'^/trustees/(?P<trustee_uuid>[^/]+)/home$', trustee_home), - (r'^/trustees/(?P<trustee_uuid>[^/]+)/sendurl$', trustee_send_url), - (r'^/trustees/(?P<trustee_uuid>[^/]+)/keygenerator$', trustee_keygenerator), - (r'^/trustees/(?P<trustee_uuid>[^/]+)/check-sk$', trustee_check_sk), - (r'^/trustees/(?P<trustee_uuid>[^/]+)/upoad-pk$', trustee_upload_pk), - (r'^/trustees/(?P<trustee_uuid>[^/]+)/decrypt-and-prove$', trustee_decrypt_and_prove), - (r'^/trustees/(?P<trustee_uuid>[^/]+)/upload-decryption$', trustee_upload_decryption), + url(r'^/trustees/(?P<trustee_uuid>[^/]+)/home$', + views.trustee_home, name=names.ELECTION_TRUSTEE_HOME), + url(r'^/trustees/(?P<trustee_uuid>[^/]+)/sendurl$', + views.trustee_send_url, name=names.ELECTION_TRUSTEE_SEND_URL), + url(r'^/trustees/(?P<trustee_uuid>[^/]+)/keygenerator$', + views.trustee_keygenerator, name=names.ELECTION_TRUSTEE_KEY_GENERATOR), + url(r'^/trustees/(?P<trustee_uuid>[^/]+)/check-sk$', + views.trustee_check_sk, name=names.ELECTION_TRUSTEE_CHECK_SK), + url(r'^/trustees/(?P<trustee_uuid>[^/]+)/upoad-pk$', + views.trustee_upload_pk, name=names.ELECTION_TRUSTEE_UPLOAD_PK), + url(r'^/trustees/(?P<trustee_uuid>[^/]+)/decrypt-and-prove$', + views.trustee_decrypt_and_prove, name=names.ELECTION_TRUSTEE_DECRYPT_AND_PROVE), + url(r'^/trustees/(?P<trustee_uuid>[^/]+)/upload-decryption$', + views.trustee_upload_decryption, name=names.ELECTION_TRUSTEE_UPLOAD_DECRYPTION), # election voting-process actions - (r'^/view$', one_election_view), - (r'^/result$', one_election_result), - (r'^/result_proof$', one_election_result_proof), - # (r'^/bboard$', one_election_bboard), - (r'^/audited-ballots/$', one_election_audited_ballots), + url(r'^/view$', views.one_election_view, name=names.ELECTION_VIEW), + url(r'^/result$', views.one_election_result, name=names.ELECTION_RESULT), + url(r'^/result_proof$', views.one_election_result_proof, name=names.ELECTION_RESULT_PROOF), + # url(r'^/bboard$', views.one_election_bboard, name=names.ELECTION_BBOARD), + url(r'^/audited-ballots/$', views.one_election_audited_ballots, name=names.ELECTION_AUDITED_BALLOTS), # get randomness - (r'^/get-randomness$', get_randomness), + url(r'^/get-randomness$', views.get_randomness, name=names.ELECTION_GET_RANDOMNESS), # server-side encryption - (r'^/encrypt-ballot$', encrypt_ballot), + url(r'^/encrypt-ballot$', views.encrypt_ballot, name=names.ELECTION_ENCRYPT_BALLOT), # construct election - (r'^/questions$', one_election_questions), - (r'^/set_reg$', one_election_set_reg), - (r'^/set_featured$', one_election_set_featured), - (r'^/save_questions$', one_election_save_questions), - (r'^/register$', one_election_register), - (r'^/freeze$', one_election_freeze), # includes freeze_2 as POST target + url(r'^/questions$', views.one_election_questions, name=names.ELECTION_QUESTIONS), + url(r'^/set_reg$', views.one_election_set_reg, name=names.ELECTION_SET_REG), + url(r'^/set_featured$', views.one_election_set_featured, name=names.ELECTION_SET_FEATURED), + url(r'^/save_questions$', views.one_election_save_questions, name=names.ELECTION_SAVE_QUESTIONS), + url(r'^/register$', views.one_election_register, name=names.ELECTION_REGISTER), + url(r'^/freeze$', views.one_election_freeze, name=names.ELECTION_FREEZE), # includes freeze_2 as POST target # computing tally - (r'^/compute_tally$', one_election_compute_tally), - (r'^/combine_decryptions$', combine_decryptions), - (r'^/release_result$', release_result), + url(r'^/compute_tally$', views.one_election_compute_tally, name=names.ELECTION_COMPUTE_TALLY), + url(r'^/combine_decryptions$', views.combine_decryptions, name=names.ELECTION_COMBINE_DECRYPTIONS), + url(r'^/release_result$', views.release_result, name=names.ELECTION_RELEASE_RESULT), # casting a ballot before we know who the voter is - (r'^/cast$', one_election_cast), - (r'^/cast_confirm$', one_election_cast_confirm), - (r'^/password_voter_login$', password_voter_login), - (r'^/cast_done$', one_election_cast_done), + url(r'^/cast$', views.one_election_cast, name=names.ELECTION_CAST), + url(r'^/cast_confirm$', views.one_election_cast_confirm, name=names.ELECTION_CAST_CONFIRM), + url(r'^/password_voter_login$', views.password_voter_login, name=names.ELECTION_PASSWORD_VOTER_LOGIN), + url(r'^/cast_done$', views.one_election_cast_done, name=names.ELECTION_CAST_DONE), # post audited ballot - (r'^/post-audited-ballot', post_audited_ballot), + url(r'^/post-audited-ballot', views.post_audited_ballot, name=names.ELECTION_POST_AUDITED_BALLOT), # managing voters - (r'^/voters/$', voter_list), - (r'^/voters/upload$', voters_upload), - (r'^/voters/upload-cancel$', voters_upload_cancel), - (r'^/voters/list$', voters_list_pretty), - (r'^/voters/eligibility$', voters_eligibility), - (r'^/voters/email$', voters_email), - (r'^/voters/(?P<voter_uuid>[^/]+)$', one_voter), - (r'^/voters/(?P<voter_uuid>[^/]+)/delete$', voter_delete), + url(r'^/voters/$', views.voter_list, name=names.ELECTION_VOTERS_LIST), + url(r'^/voters/upload$', views.voters_upload, name=names.ELECTION_VOTERS_UPLOAD), + url(r'^/voters/upload-cancel$', views.voters_upload_cancel, name=names.ELECTION_VOTERS_UPLOAD_CANCEL), + url(r'^/voters/list$', views.voters_list_pretty, name=names.ELECTION_VOTERS_LIST_PRETTY), + url(r'^/voters/eligibility$', views.voters_eligibility, name=names.ELECTION_VOTERS_ELIGIBILITY), + url(r'^/voters/email$', views.voters_email, name=names.ELECTION_VOTERS_EMAIL), + url(r'^/voters/(?P<voter_uuid>[^/]+)$', views.one_voter, name=names.ELECTION_VOTER), + url(r'^/voters/(?P<voter_uuid>[^/]+)/delete$', views.voter_delete, name=names.ELECTION_VOTER_DELETE), # ballots - (r'^/ballots/$', ballot_list), - (r'^/ballots/(?P<voter_uuid>[^/]+)/all$', voter_votes), - (r'^/ballots/(?P<voter_uuid>[^/]+)/last$', voter_last_vote), + url(r'^/ballots/$', views.ballot_list, name=names.ELECTION_BALLOTS_LIST), + url(r'^/ballots/(?P<voter_uuid>[^/]+)/all$', views.voter_votes, name=names.ELECTION_BALLOTS_VOTER), + url(r'^/ballots/(?P<voter_uuid>[^/]+)/last$', views.voter_last_vote, name=names.ELECTION_BALLOTS_VOTER_LAST), -) +] diff --git a/helios/fields.py b/helios/fields.py index cf2ad6c3e7c2c6b506c0080b432f6207f4e37807..8d8e885fec3c13f72a0c24cd6ea0d7a39ab80d5c 100644 --- a/helios/fields.py +++ b/helios/fields.py @@ -1,9 +1,9 @@ -from time import strptime, strftime import datetime -from django import forms -from django.db import models + from django.forms import fields -from widgets import SplitSelectDateTimeWidget + +from .widgets import SplitSelectDateTimeWidget + class SplitDateTimeField(fields.MultiValueField): widget = SplitSelectDateTimeWidget diff --git a/helios/fixtures/users.json b/helios/fixtures/users.json index c588ce47507d1f61348d614ee99640dcbf6bc66d..c6e18b53da75aa9efbf3db52560c1e5044a1394a 100644 --- a/helios/fixtures/users.json +++ b/helios/fixtures/users.json @@ -1 +1,40 @@ -[{"pk": 1, "model": "helios_auth.user", "fields": {"info": "{}", "user_id": "ben@adida.net", "name": "Ben Adida", "user_type": "google", "token": null, "admin_p": false}},{"pk": 2, "model": "helios_auth.user", "fields": {"info": "{}", "user_id": "12345", "name": "Ben Adida", "user_type": "facebook", "token": {"access_token":"1234"}, "admin_p": false}}] \ No newline at end of file +[ + { + "pk": 1, + "model": "helios_auth.user", + "fields": { + "info": "{}", + "user_id": "ben@adida.net", + "name": "Ben Adida", + "user_type": "google", + "token": null, + "admin_p": false + } + }, + { + "pk": 2, + "model": "helios_auth.user", + "fields": { + "info": "{}", + "user_id": "12345", + "name": "Ben Adida", + "user_type": "facebook", + "token": { + "access_token": "1234" + }, + "admin_p": false + } + }, + { + "pk": 3, + "model": "helios_auth.user", + "fields": { + "info": "{}", + "user_id": "mccio@github.com", + "name": "Marco Ciotola", + "user_type": "google", + "token": null, + "admin_p": true + } + } +] \ No newline at end of file diff --git a/helios/fixtures/voter-file-latin1.csv b/helios/fixtures/voter-file-latin1.csv new file mode 100644 index 0000000000000000000000000000000000000000..fdbc9b3acdefe00c8febbbe07dc012aa17c90619 --- /dev/null +++ b/helios/fixtures/voter-file-latin1.csv @@ -0,0 +1,4 @@ + benadida5,ben5@adida.net , Ben5 Adida +benadida6,ben6@adida.net,Ben6 Adida +benadida7,ben7@adida.net,Ben7 Adida +testlatin1,Test Latin1,J�NIO LUIZ CORREIA J�NIOR diff --git a/helios/forms.py b/helios/forms.py index cb10cfab8b7179b315f866de6ff1705dcb0d83f5..d7919675863641cd24abd5ba3082b0e6014d4e84 100644 --- a/helios/forms.py +++ b/helios/forms.py @@ -3,11 +3,12 @@ Forms for Helios """ from django import forms -from models import Election -from widgets import * -from fields import * from django.conf import settings +from .fields import SplitDateTimeField +from .models import Election +from .widgets import SplitSelectDateTimeWidget + class ElectionForm(forms.Form): short_name = forms.SlugField(max_length=40, help_text='no spaces, will be part of the URL for your election, e.g. my-club-2010') diff --git a/helios/management/commands/helios_trustee_decrypt.py b/helios/management/commands/helios_trustee_decrypt.py index 3dc75a4af8cd60562fed548483878603f7dd2875..4788330201a8d450a93be278d18506c86b32ff49 100644 --- a/helios/management/commands/helios_trustee_decrypt.py +++ b/helios/management/commands/helios_trustee_decrypt.py @@ -8,12 +8,10 @@ ben@adida.net 2010-05-22 """ -from django.core.management.base import BaseCommand, CommandError -import csv, datetime +from django.core.management.base import BaseCommand -from helios import utils as helios_utils +from helios.models import Trustee -from helios.models import * class Command(BaseCommand): args = '' diff --git a/helios/management/commands/load_voter_files.py b/helios/management/commands/load_voter_files.py index 5b82285a68650b1a06ae36c0f3bb3dd0cea236e7..1e3a79beeecf537b24963a5f2b80f1b46f653dfe 100644 --- a/helios/management/commands/load_voter_files.py +++ b/helios/management/commands/load_voter_files.py @@ -8,12 +8,15 @@ ben@adida.net 2010-05-22 """ -from django.core.management.base import BaseCommand, CommandError -import csv, datetime +import datetime + +import csv +import uuid +from django.core.management.base import BaseCommand from helios import utils as helios_utils +from helios.models import User, Voter, VoterFile -from helios.models import * ## ## UTF8 craziness for CSV @@ -25,44 +28,47 @@ def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs): dialect=dialect, **kwargs) for row in csv_reader: # decode UTF-8 back to Unicode, cell by cell: - yield [unicode(cell, 'utf-8') for cell in row] + yield [str(cell, 'utf-8') for cell in row] + def utf_8_encoder(unicode_csv_data): for line in unicode_csv_data: yield line.encode('utf-8') - + + def process_csv_file(election, f): reader = unicode_csv_reader(f) - + num_voters = 0 for voter in reader: - # bad line - if len(voter) < 1: - continue - - num_voters += 1 - voter_id = voter[0] - name = voter_id - email = voter_id - - if len(voter) > 1: - email = voter[1] - - if len(voter) > 2: - name = voter[2] - - # create the user - user = User.update_or_create(user_type='password', user_id=voter_id, info = {'password': helios_utils.random_string(10), 'email': email, 'name': name}) - user.save() - - # does voter for this user already exist - voter = Voter.get_by_election_and_user(election, user) - - # create the voter - if not voter: - voter_uuid = str(uuid.uuid1()) - voter = Voter(uuid= voter_uuid, voter_type = 'password', voter_id = voter_id, name = name, election = election) - voter.save() + # bad line + if len(voter) < 1: + continue + + num_voters += 1 + voter_id = voter[0] + name = voter_id + email = voter_id + + if len(voter) > 1: + email = voter[1] + + if len(voter) > 2: + name = voter[2] + + # create the user + user = User.update_or_create(user_type='password', user_id=voter_id, + info={'password': helios_utils.random_string(10), 'email': email, 'name': name}) + user.save() + + # does voter for this user already exist + voter = Voter.get_by_election_and_user(election, user) + + # create the voter + if not voter: + voter_uuid = str(uuid.uuid1()) + voter = Voter(uuid=voter_uuid, voter_type='password', voter_id=voter_id, name=name, election=election) + voter.save() return num_voters @@ -70,7 +76,7 @@ def process_csv_file(election, f): class Command(BaseCommand): args = '' help = 'load up voters from unprocessed voter files' - + def handle(self, *args, **options): # load up the voter files in order of last uploaded files_to_process = VoterFile.objects.filter(processing_started_at=None).order_by('uploaded_at') @@ -86,5 +92,3 @@ class Command(BaseCommand): file_to_process.processing_finished_at = datetime.datetime.utcnow() file_to_process.num_voters = num_voters file_to_process.save() - - diff --git a/helios/management/commands/verify_cast_votes.py b/helios/management/commands/verify_cast_votes.py index 5b7f39253615b82637937467db7fccf8b91c9e36..e2fab7186d318b4c67b500c345b93eb5d5d3dd79 100644 --- a/helios/management/commands/verify_cast_votes.py +++ b/helios/management/commands/verify_cast_votes.py @@ -6,12 +6,10 @@ ben@adida.net 2010-05-22 """ -from django.core.management.base import BaseCommand, CommandError -import csv, datetime +from django.core.management.base import BaseCommand -from helios import utils as helios_utils +from helios.models import CastVote -from helios.models import * def get_cast_vote_to_verify(): # fixme: add "select for update" functionality here diff --git a/helios/migrations/0001_initial.py b/helios/migrations/0001_initial.py index cb2a41e6779c24b6b4022ac951810fc0b1b978a5..886b370219ab7fd2fc6fb218148761ae5c4d026c 100644 --- a/helios/migrations/0001_initial.py +++ b/helios/migrations/0001_initial.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from django.db import models, migrations + +import helios.datatypes import helios.datatypes.djangofield import helios_auth.jsonfield -import helios.datatypes class Migration(migrations.Migration): @@ -87,7 +87,7 @@ class Migration(migrations.Migration): ('result_proof', helios_auth.jsonfield.JSONField(null=True)), ('help_email', models.EmailField(max_length=75, null=True)), ('election_info_url', models.CharField(max_length=300, null=True)), - ('admin', models.ForeignKey(to='helios_auth.User')), + ('admin', models.ForeignKey(to='helios_auth.User', on_delete=models.CASCADE)), ], options={ 'abstract': False, @@ -100,7 +100,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('log', models.CharField(max_length=500)), ('at', models.DateTimeField(auto_now_add=True)), - ('election', models.ForeignKey(to='helios.Election')), + ('election', models.ForeignKey(to='helios.Election', on_delete=models.CASCADE)), ], options={ }, @@ -120,7 +120,7 @@ class Migration(migrations.Migration): ('pok', helios.datatypes.djangofield.LDObjectField(null=True)), ('decryption_factors', helios.datatypes.djangofield.LDObjectField(null=True)), ('decryption_proofs', helios.datatypes.djangofield.LDObjectField(null=True)), - ('election', models.ForeignKey(to='helios.Election')), + ('election', models.ForeignKey(to='helios.Election', on_delete=models.CASCADE)), ], options={ }, @@ -139,8 +139,8 @@ class Migration(migrations.Migration): ('vote', helios.datatypes.djangofield.LDObjectField(null=True)), ('vote_hash', models.CharField(max_length=100, null=True)), ('cast_at', models.DateTimeField(null=True)), - ('election', models.ForeignKey(to='helios.Election')), - ('user', models.ForeignKey(to='helios_auth.User', null=True)), + ('election', models.ForeignKey(to='helios.Election', on_delete=models.CASCADE)), + ('user', models.ForeignKey(to='helios_auth.User', null=True, on_delete=models.CASCADE)), ], options={ }, @@ -156,7 +156,7 @@ class Migration(migrations.Migration): ('processing_started_at', models.DateTimeField(null=True)), ('processing_finished_at', models.DateTimeField(null=True)), ('num_voters', models.IntegerField(null=True)), - ('election', models.ForeignKey(to='helios.Election')), + ('election', models.ForeignKey(to='helios.Election', on_delete=models.CASCADE)), ], options={ }, @@ -173,13 +173,13 @@ class Migration(migrations.Migration): migrations.AddField( model_name='castvote', name='voter', - field=models.ForeignKey(to='helios.Voter'), + field=models.ForeignKey(to='helios.Voter', on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( model_name='auditedballot', name='election', - field=models.ForeignKey(to='helios.Election'), + field=models.ForeignKey(to='helios.Election', on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/helios/migrations/0002_castvote_cast_ip.py b/helios/migrations/0002_castvote_cast_ip.py index 47db5b169eccee318b2ccb4fc18f125e984a9c83..bb7a422639f66cd81974549a95c000204ebe13da 100644 --- a/helios/migrations/0002_castvote_cast_ip.py +++ b/helios/migrations/0002_castvote_cast_ip.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from django.db import models, migrations diff --git a/helios/migrations/0003_auto_20160507_1948.py b/helios/migrations/0003_auto_20160507_1948.py index 162d6bb54dcb6c1e74c99e859615d7fe51dc179d..8e6f65266e5c6f04bf3cb07b8927ac94ea2ece95 100644 --- a/helios/migrations/0003_auto_20160507_1948.py +++ b/helios/migrations/0003_auto_20160507_1948.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from django.db import models, migrations diff --git a/helios/migrations/0004_auto_20170528_2025.py b/helios/migrations/0004_auto_20170528_2025.py index d49bb63753f4438ab79c0ce4101f7e56f7bcf8be..a437c5f11f03a5a0766ecd970a031bda409fe3fd 100644 --- a/helios/migrations/0004_auto_20170528_2025.py +++ b/helios/migrations/0004_auto_20170528_2025.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from django.db import migrations, models diff --git a/helios/migrations/0005_auto_20210123_0941.py b/helios/migrations/0005_auto_20210123_0941.py new file mode 100644 index 0000000000000000000000000000000000000000..355687c60a0129c2ed948815d938bfb1d67ed581 --- /dev/null +++ b/helios/migrations/0005_auto_20210123_0941.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2021-01-23 09:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('helios', '0004_auto_20170528_2025'), + ] + + operations = [ + migrations.AlterField( + model_name='election', + name='datatype', + field=models.CharField(default='legacy/Election', max_length=250), + ), + migrations.AlterField( + model_name='election', + name='election_type', + field=models.CharField(choices=[('election', 'Election'), ('referendum', 'Referendum')], default='election', max_length=250), + ), + migrations.AlterField( + model_name='voterfile', + name='voter_file', + field=models.FileField(max_length=250, null=True, upload_to='voters/%Y/%m/%d'), + ), + ] diff --git a/helios/models.py b/helios/models.py index eeb7f624f9c94ceea27026206afb0979009ebd3e..db27f5a1ca37c569a8252812ec9177e690809f5d 100644 --- a/helios/models.py +++ b/helios/models.py @@ -6,35 +6,34 @@ Ben Adida (ben@adida.net) """ -from django.db import models, transaction -import json -from django.conf import settings -from django.core.mail import send_mail +import copy +import csv +import datetime +import io +import uuid -import datetime, logging, uuid, random, io import bleach - -from crypto import electionalgs, algs, utils -from helios import utils as heliosutils -import helios.views +import unicodecsv +from django.conf import settings +from django.db import models, transaction from helios import datatypes - - +from helios import utils +from helios.datatypes.djangofield import LDObjectField # useful stuff in helios_auth -from helios_auth.models import User, AUTH_SYSTEMS from helios_auth.jsonfield import JSONField -from helios.datatypes.djangofield import LDObjectField +from helios_auth.models import User, AUTH_SYSTEMS +from .crypto import algs +from .crypto.elgamal import Cryptosystem +from .crypto.utils import random, hash_b64 -import csv, copy -import unicodecsv class HeliosModel(models.Model, datatypes.LDObjectContainer): class Meta: abstract = True class Election(HeliosModel): - admin = models.ForeignKey(User) + admin = models.ForeignKey(User, on_delete=models.CASCADE) uuid = models.CharField(max_length=50, null=False) @@ -145,6 +144,9 @@ class Election(HeliosModel): # downloadable election info election_info_url = models.CharField(max_length=300, null=True) + class Meta: + app_label = 'helios' + # metadata for the election @property def metadata(self): @@ -180,22 +182,26 @@ class Election(HeliosModel): if not self.use_voter_aliases: return None - return heliosutils.one_val_raw_sql("select max(cast(substring(alias, 2) as integer)) from " + Voter._meta.db_table + " where election_id = %s", [self.id]) or 0 + return utils.one_val_raw_sql("select max(cast(substring(alias, 2) as integer)) from " + Voter._meta.db_table + " where election_id = %s", [self.id]) or 0 @property def encrypted_tally_hash(self): if not self.encrypted_tally: return None - return utils.hash_b64(self.encrypted_tally.toJSON()) + return hash_b64(self.encrypted_tally.toJSON()) @property def is_archived(self): - return self.archived_at != None + return self.archived_at is not None @property def description_bleached(self): - return bleach.clean(self.description, tags = bleach.ALLOWED_TAGS + ['p', 'h4', 'h5', 'h3', 'h2', 'br', 'u']) + return bleach.clean(self.description, + tags=bleach.ALLOWED_TAGS + ['p', 'h4', 'h5', 'h3', 'h2', 'br', 'u'], + strip=True, + strip_comments=True, + ) @classmethod def get_featured(cls): @@ -208,9 +214,9 @@ class Election(HeliosModel): @classmethod def get_by_user_as_admin(cls, user, archived_p=None, limit=None): query = cls.objects.filter(admin = user) - if archived_p == True: + if archived_p is True: query = query.exclude(archived_at= None) - if archived_p == False: + if archived_p is False: query = query.filter(archived_at= None) query = query.order_by('-created_at') if limit: @@ -221,9 +227,9 @@ class Election(HeliosModel): @classmethod def get_by_user_as_voter(cls, user, archived_p=None, limit=None): query = cls.objects.filter(voter__user = user) - if archived_p == True: + if archived_p is True: query = query.exclude(archived_at= None) - if archived_p == False: + if archived_p is False: query = query.filter(archived_at= None) query = query.order_by('-created_at') if limit: @@ -266,11 +272,15 @@ class Election(HeliosModel): """ expects a django uploaded_file data structure, which has filename, content, size... """ - # now we're just storing the content - # random_filename = str(uuid.uuid4()) - # new_voter_file.voter_file.save(random_filename, uploaded_file) + voter_file_content_bytes = uploaded_file.read() - new_voter_file = VoterFile(election = self, voter_file_content = uploaded_file.read()) + # usually it's utf-8 encoded, but occasionally it's latin-1 + try: + voter_file_content = voter_file_content_bytes.decode('utf-8') + except: + voter_file_content = voter_file_content_bytes.decode('latin-1') + + new_voter_file = VoterFile(election = self, voter_file_content = voter_file_content) new_voter_file.save() self.append_log(ElectionLog.VOTER_FILE_ADDED) @@ -284,7 +294,7 @@ class Election(HeliosModel): if not self.openreg: return False - if self.eligibility == None: + if self.eligibility is None: return True # is the user eligible for one of these cases? @@ -299,7 +309,7 @@ class Election(HeliosModel): return [] # constraints that are relevant - relevant_constraints = [constraint['constraint'] for constraint in self.eligibility if constraint['auth_system'] == user_type and constraint.has_key('constraint')] + relevant_constraints = [constraint['constraint'] for constraint in self.eligibility if constraint['auth_system'] == user_type and 'constraint' in constraint] if len(relevant_constraints) > 0: return relevant_constraints[0] else: @@ -325,7 +335,7 @@ class Election(HeliosModel): return_val = "<ul>" for constraint in self.eligibility: - if constraint.has_key('constraint'): + if 'constraint' in constraint: for one_constraint in constraint['constraint']: return_val += "<li>%s</li>" % AUTH_SYSTEMS[constraint['auth_system']].pretty_eligibility(one_constraint) else: @@ -355,7 +365,7 @@ class Election(HeliosModel): """ has voting begun? voting begins if the election is frozen, at the prescribed date or at the date that voting was forced to start """ - return self.frozen_at != None and (self.voting_starts_at == None or (datetime.datetime.utcnow() >= (self.voting_started_at or self.voting_starts_at))) + return self.frozen_at is not None and (self.voting_starts_at is None or (datetime.datetime.utcnow() >= (self.voting_started_at or self.voting_starts_at))) def voting_has_stopped(self): """ @@ -363,12 +373,12 @@ class Election(HeliosModel): or failing that the date voting was extended until, or failing that the date voting is scheduled to end at. """ voting_end = self.voting_ended_at or self.voting_extended_until or self.voting_ends_at - return (voting_end != None and datetime.datetime.utcnow() >= voting_end) or self.encrypted_tally + return (voting_end is not None and datetime.datetime.utcnow() >= voting_end) or self.encrypted_tally @property def issues_before_freeze(self): issues = [] - if self.questions == None or len(self.questions) == 0: + if self.questions is None or len(self.questions) == 0: issues.append( {'type': 'questions', 'action': "add questions to the ballot"} @@ -382,7 +392,7 @@ class Election(HeliosModel): }) for t in trustees: - if t.public_key == None: + if t.public_key is None: issues.append({ 'type': 'trustee keypairs', 'action': 'have trustee %s generate a keypair' % t.name @@ -411,8 +421,8 @@ class Election(HeliosModel): self.save() def ready_for_decryption(self): - return self.encrypted_tally != None - + return self.encrypted_tally is not None + def ready_for_decryption_combination(self): """ do we have a tally from all trustees? @@ -460,7 +470,7 @@ class Election(HeliosModel): else: voters = Voter.get_by_election(self) voters_json = utils.to_json([v.toJSONDict() for v in voters]) - self.voters_hash = utils.hash_b64(voters_json) + self.voters_hash = hash_b64(voters_json) def increment_voters(self): ## FIXME @@ -484,15 +494,16 @@ class Election(HeliosModel): """ # don't override existing eligibility - if self.eligibility != None: + if self.eligibility is not None: return # enable this ONLY once the cast_confirm screen makes sense #if self.voter_set.count() == 0: # return - auth_systems = copy.copy(settings.AUTH_ENABLED_AUTH_SYSTEMS) - voter_types = [r['user__user_type'] for r in self.voter_set.values('user__user_type').distinct() if r['user__user_type'] != None] + auth_systems = copy.copy(settings.AUTH_ENABLED_SYSTEMS) + voter_types = [r['user__user_type'] for r in self.voter_set.values('user__user_type').distinct() if + r['user__user_type'] is not None] # password is now separate, not an explicit voter type if self.voter_set.filter(user=None).count() > 0: @@ -525,7 +536,7 @@ class Election(HeliosModel): self.set_eligibility() # public key for trustees - trustees = Trustee.get_by_election(self) + trustees = list(Trustee.get_by_election(self)) combined_pk = trustees[0].public_key for t in trustees[1:]: combined_pk = combined_pk * t.public_key @@ -541,6 +552,7 @@ class Election(HeliosModel): """ generate a trustee including the secret key, thus a helios-based trustee + :type params: Cryptosystem """ # FIXME: generate the keypair keypair = params.generate_keypair() @@ -568,7 +580,7 @@ class Election(HeliosModel): return None def has_helios_trustee(self): - return self.get_helios_trustee() != None + return self.get_helios_trustee() is not None def helios_trustee_decrypt(self): tally = self.encrypted_tally @@ -591,6 +603,7 @@ class Election(HeliosModel): @property def url(self): + import helios.views return helios.views.get_election_url(self) def init_tally(self): @@ -611,7 +624,7 @@ class Election(HeliosModel): determining the winner for one question """ # sort the answers , keep track of the index - counts = sorted(enumerate(result), key=lambda(x): x[1]) + counts = sorted(enumerate(result), key=lambda x: x[1]) counts.reverse() the_max = question['max'] or 1 @@ -665,7 +678,8 @@ class Election(HeliosModel): prettified_result.append({'question': q['short_name'], 'answers': pretty_question}) return prettified_result - + + class ElectionLog(models.Model): """ a log of events for an election @@ -675,10 +689,13 @@ class ElectionLog(models.Model): VOTER_FILE_ADDED = "voter file added" DECRYPTIONS_COMBINED = "decryptions combined" - election = models.ForeignKey(Election) + election = models.ForeignKey(Election, on_delete=models.CASCADE) log = models.CharField(max_length=500) at = models.DateTimeField(auto_now_add=True) + class Meta: + app_label = 'helios' + ## ## UTF8 craziness for CSV ## @@ -690,9 +707,9 @@ def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs): for row in csv_reader: # decode UTF-8 back to Unicode, cell by cell: try: - yield [unicode(cell, 'utf-8') for cell in row] + yield [str(cell, 'utf-8') for cell in row] except: - yield [unicode(cell, 'latin-1') for cell in row] + yield [str(cell, 'latin-1') for cell in row] def utf_8_encoder(unicode_csv_data): for line in unicode_csv_data: @@ -707,7 +724,7 @@ class VoterFile(models.Model): # path where we store voter upload PATH = settings.VOTER_UPLOAD_REL_PATH - election = models.ForeignKey(Election) + election = models.ForeignKey(Election, on_delete=models.CASCADE) # we move to storing the content in the DB voter_file = models.FileField(upload_to=PATH, max_length=250,null=True) @@ -718,22 +735,30 @@ class VoterFile(models.Model): processing_finished_at = models.DateTimeField(auto_now_add=False, null=True) num_voters = models.IntegerField(null=True) + class Meta: + app_label = 'helios' + def itervoters(self): if self.voter_file_content: - if type(self.voter_file_content) == unicode: - content = self.voter_file_content.encode('utf-8') - else: + if isinstance(self.voter_file_content, str): + content = self.voter_file_content.encode(encoding='utf-8') + elif isinstance(self.voter_file_content, bytes): content = self.voter_file_content + else: + raise TypeError("voter_file_content is of type {0} instead of str or bytes" + .format(str(type(self.voter_file_content)))) # now we have to handle non-universal-newline stuff # we do this in a simple way: replace all \r with \n # then, replace all double \n with single \n # this should leave us with only \n - content = content.replace('\r','\n').replace('\n\n','\n') + content = content.replace(b'\r',b'\n').replace(b'\n\n',b'\n') + close = False voter_stream = io.BytesIO(content) else: - voter_stream = open(self.voter_file.path, "rU") + close = True + voter_stream = open(self.voter_file.path, "rb") #reader = unicode_csv_reader(voter_stream) reader = unicodecsv.reader(voter_stream, encoding='utf-8') @@ -757,6 +782,8 @@ class VoterFile(models.Model): return_dict['name'] = return_dict['email'] yield return_dict + if close: + voter_stream.close() def process(self): self.processing_started_at = datetime.datetime.utcnow() @@ -783,7 +810,7 @@ class VoterFile(models.Model): existing_voter.save() if election.use_voter_aliases: - voter_alias_integers = range(last_alias_num+1, last_alias_num+1+num_voters) + voter_alias_integers = list(range(last_alias_num+1, last_alias_num+1+num_voters)) random.shuffle(voter_alias_integers) for i, voter in enumerate(new_voters): voter.alias = 'V%s' % voter_alias_integers[i] @@ -795,10 +822,9 @@ class VoterFile(models.Model): return num_voters - class Voter(HeliosModel): - election = models.ForeignKey(Election) + election = models.ForeignKey(Election, on_delete=models.CASCADE) # let's link directly to the user now # FIXME: delete this as soon as migrations are set up @@ -810,7 +836,7 @@ class Voter(HeliosModel): # for users of type password, no user object is created # but a dynamic user object is created automatically - user = models.ForeignKey('helios_auth.User', null=True) + user = models.ForeignKey('helios_auth.User', null=True, on_delete=models.CASCADE) # if user is null, then you need a voter login ID and password voter_login_id = models.CharField(max_length = 100, null=True) @@ -822,13 +848,13 @@ class Voter(HeliosModel): alias = models.CharField(max_length = 100, null=True) # we keep a copy here for easy tallying - vote = LDObjectField(type_hint = 'legacy/EncryptedVote', - null=True) + vote = LDObjectField(type_hint = 'legacy/EncryptedVote', null=True) vote_hash = models.CharField(max_length = 100, null=True) cast_at = models.DateTimeField(auto_now_add=False, null=True) class Meta: unique_together = (('election', 'voter_login_id')) + app_label = 'helios' def __init__(self, *args, **kwargs): super(Voter, self).__init__(*args, **kwargs) @@ -845,7 +871,7 @@ class Voter(HeliosModel): # do we need to generate an alias? if election.use_voter_aliases: - heliosutils.lock_row(Election, election.id) + utils.lock_row(Election, election.id) alias_num = election.last_alias_num + 1 voter.alias = "V%s" % alias_num @@ -861,14 +887,14 @@ class Voter(HeliosModel): # the boolean check is not stupid, this is ternary logic # none means don't care if it's cast or not - if cast == True: + if cast is True: query = query.exclude(cast_at = None) - elif cast == False: + elif cast is False: query = query.filter(cast_at = None) # little trick to get around GAE limitation # order by uuid only when no inequality has been added - if cast == None or order_by == 'cast_at' or order_by =='-cast_at': + if cast is None or order_by == 'cast_at' or order_by == '-cast_at': query = query.order_by(order_by) # if we want the list after a certain UUID, add the inequality here @@ -952,12 +978,12 @@ class Voter(HeliosModel): value_to_hash = self.voter_id try: - return utils.hash_b64(value_to_hash) + return hash_b64(value_to_hash) except: try: - return utils.hash_b64(value_to_hash.encode('latin-1')) + return hash_b64(value_to_hash.encode('latin-1')) except: - return utils.hash_b64(value_to_hash.encode('utf-8')) + return hash_b64(value_to_hash.encode('utf-8')) @property def voter_type(self): @@ -977,7 +1003,7 @@ class Voter(HeliosModel): if self.voter_password: raise Exception("password already exists") - self.voter_password = heliosutils.random_string(length, alphabet='abcdefghjkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789') + self.voter_password = utils.random_string(length, alphabet='abcdefghjkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789') def store_vote(self, cast_vote): # only store the vote if it's cast later than the current one @@ -995,7 +1021,7 @@ class Voter(HeliosModel): class CastVote(HeliosModel): # the reference to the voter provides the voter_uuid - voter = models.ForeignKey(Voter) + voter = models.ForeignKey(Voter, on_delete=models.CASCADE) # the actual encrypted vote vote = LDObjectField(type_hint = 'legacy/EncryptedVote') @@ -1019,6 +1045,9 @@ class CastVote(HeliosModel): # auditing purposes, like too many votes from the same IP, if it isn't expected cast_ip = models.GenericIPAddressField(null=True) + class Meta: + app_label = 'helios' + @property def datatype(self): return self.voter.datatype.replace('Voter', 'CastVote') @@ -1039,9 +1068,9 @@ class CastVote(HeliosModel): """ find a tiny version of the hash for a URL slug. """ - safe_hash = self.vote_hash - for c in ['/', '+']: - safe_hash = safe_hash.replace(c,'') + safe_hash = self.vote_hash.decode() if isinstance(self.vote_hash, bytes) else self.vote_hash + for c in ['/', '+', '#']: + safe_hash = safe_hash.replace(c, '') length = 8 while True: @@ -1102,11 +1131,14 @@ class AuditedBallot(models.Model): """ ballots for auditing """ - election = models.ForeignKey(Election) + election = models.ForeignKey(Election, on_delete=models.CASCADE) raw_vote = models.TextField() vote_hash = models.CharField(max_length=100) added_at = models.DateTimeField(auto_now_add=True) + class Meta: + app_label = 'helios' + @classmethod def get(cls, election, vote_hash): return cls.objects.get(election = election, vote_hash = vote_hash) @@ -1123,9 +1155,10 @@ class AuditedBallot(models.Model): query = query[:limit] return query - + + class Trustee(HeliosModel): - election = models.ForeignKey(Election) + election = models.ForeignKey(Election, on_delete=models.CASCADE) uuid = models.CharField(max_length=50) name = models.CharField(max_length=200) @@ -1156,14 +1189,15 @@ class Trustee(HeliosModel): class Meta: unique_together = (('election', 'email')) - + app_label = 'helios' + def save(self, *args, **kwargs): """ override this just to get a hook """ # not saved yet? if not self.secret: - self.secret = heliosutils.random_string(12) + self.secret = utils.random_string(12) self.election.append_log("Trustee %s added" % self.name) super(Trustee, self).save(*args, **kwargs) diff --git a/helios/security.py b/helios/security.py index c0228213b3db56ff51a8e6e59c97b32ea38ce8c7..2f0852d95eb92485c155f359463162b665c1e00a 100644 --- a/helios/security.py +++ b/helios/security.py @@ -4,36 +4,46 @@ Helios Security -- mostly access control Ben Adida (ben@adida.net) """ +import urllib.parse # nicely update the wrapper function from functools import update_wrapper -from django.core.urlresolvers import reverse -from django.core.exceptions import * -from django.http import * from django.conf import settings - -from models import * -from helios_auth.security import get_user - +from django.core.exceptions import PermissionDenied +from django.http import Http404 from django.http import HttpResponseRedirect -import urllib +from django.urls import reverse import helios +from helios_auth.security import get_user +from .models import Voter, Trustee, Election class HSTSMiddleware: - def process_response(self, request, response): + def __init__(self, get_response): + self.get_response = get_response + # One-time configuration and initialization. + + def __call__(self, request): + # Code to be executed for each request before + # the view (and later middleware) are called. + + response = self.get_response(request) + + # Code to be executed for each request/response after + # the view is called. + if settings.STS: response['Strict-Transport-Security'] = "max-age=31536000; includeSubDomains; preload" return response - + # current voter def get_voter(request, user, election): """ return the current voter """ voter = None - if request.session.has_key('CURRENT_VOTER_ID'): + if 'CURRENT_VOTER_ID' in request.session: voter = Voter.objects.get(id=request.session['CURRENT_VOTER_ID']) if voter.election != election: voter = None @@ -47,7 +57,7 @@ def get_voter(request, user, election): # a function to check if the current user is a trustee HELIOS_TRUSTEE_UUID = 'helios_trustee_uuid' def get_logged_in_trustee(request): - if request.session.has_key(HELIOS_TRUSTEE_UUID): + if HELIOS_TRUSTEE_UUID in request.session: return Trustee.get_by_uuid(request.session[HELIOS_TRUSTEE_UUID]) else: return None @@ -60,26 +70,26 @@ def set_logged_in_trustee(request, trustee): # def do_election_checks(election, props): # frozen - if props.has_key('frozen'): + if 'frozen' in props: frozen = props['frozen'] else: frozen = None # newvoters (open for registration) - if props.has_key('newvoters'): + if 'newvoters' in props: newvoters = props['newvoters'] else: newvoters = None # frozen check - if frozen != None: + if frozen is not None: if frozen and not election.frozen_at: raise PermissionDenied() if not frozen and election.frozen_at: raise PermissionDenied() # open for new voters check - if newvoters != None: + if newvoters is not None: if election.can_add_voters() != newvoters: raise PermissionDenied() @@ -108,10 +118,10 @@ def election_view(**checks): # if private election, only logged in voters if election.private_p and not checks.get('allow_logins',False): - from views import password_voter_login + from .views import password_voter_login if not user_can_see_election(request, election): return_url = request.get_full_path() - return HttpResponseRedirect("%s?%s" % (reverse(password_voter_login, args=[election.uuid]), urllib.urlencode({ + return HttpResponseRedirect("%s?%s" % (reverse(password_voter_login, args=[election.uuid]), urllib.parse.urlencode({ 'return_url' : return_url }))) @@ -146,11 +156,13 @@ def user_can_see_election(request, election): return True # then this user has to be a voter - return (get_voter(request, user, election) != None) + return get_voter(request, user, election) is not None + def api_client_can_admin_election(api_client, election): - return election.api_client == api_client and api_client != None - + return election.api_client == api_client and api_client is not None + + # decorator for checking election admin access, and some properties of the election # frozen - is the election frozen # newvoters - does the election accept new voters diff --git a/helios/south_migrations/0001_initial.py b/helios/south_migrations/0001_initial.py deleted file mode 100644 index 4fa118cc427dc22993b9d8aba912c1bf778b51ac..0000000000000000000000000000000000000000 --- a/helios/south_migrations/0001_initial.py +++ /dev/null @@ -1,259 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Adding model 'Election' - db.create_table('helios_election', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('admin', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['helios_auth.User'])), - ('uuid', self.gf('django.db.models.fields.CharField')(max_length=50)), - ('short_name', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('name', self.gf('django.db.models.fields.CharField')(max_length=250)), - ('description', self.gf('django.db.models.fields.TextField')()), - ('public_key', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - ('private_key', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - ('questions', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - ('eligibility', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - ('openreg', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('featured_p', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('use_voter_aliases', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('cast_url', self.gf('django.db.models.fields.CharField')(max_length=500)), - ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - ('modified_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - ('frozen_at', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True)), - ('archived_at', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True)), - ('registration_starts_at', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True)), - ('voting_starts_at', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True)), - ('voting_ends_at', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True)), - ('tallying_starts_at', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True)), - ('voting_started_at', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True)), - ('voting_extended_until', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True)), - ('voting_ended_at', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True)), - ('tallying_started_at', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True)), - ('tallying_finished_at', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True)), - ('tallies_combined_at', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True)), - ('voters_hash', self.gf('django.db.models.fields.CharField')(max_length=100, null=True)), - ('encrypted_tally', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - ('result', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - ('result_proof', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - )) - db.send_create_signal('helios', ['Election']) - - # Adding model 'ElectionLog' - db.create_table('helios_electionlog', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('election', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['helios.Election'])), - ('log', self.gf('django.db.models.fields.CharField')(max_length=500)), - ('at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - )) - db.send_create_signal('helios', ['ElectionLog']) - - # Adding model 'VoterFile' - db.create_table('helios_voterfile', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('election', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['helios.Election'])), - ('voter_file', self.gf('django.db.models.fields.files.FileField')(max_length=250)), - ('uploaded_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - ('processing_started_at', self.gf('django.db.models.fields.DateTimeField')(null=True)), - ('processing_finished_at', self.gf('django.db.models.fields.DateTimeField')(null=True)), - ('num_voters', self.gf('django.db.models.fields.IntegerField')(null=True)), - )) - db.send_create_signal('helios', ['VoterFile']) - - # Adding model 'Voter' - db.create_table('helios_voter', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('election', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['helios.Election'])), - ('name', self.gf('django.db.models.fields.CharField')(max_length=200, null=True)), - ('voter_type', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('voter_id', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('uuid', self.gf('django.db.models.fields.CharField')(max_length=50)), - ('alias', self.gf('django.db.models.fields.CharField')(max_length=100, null=True)), - ('vote', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - ('vote_hash', self.gf('django.db.models.fields.CharField')(max_length=100, null=True)), - ('cast_at', self.gf('django.db.models.fields.DateTimeField')(null=True)), - )) - db.send_create_signal('helios', ['Voter']) - - # Adding model 'CastVote' - db.create_table('helios_castvote', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('voter', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['helios.Voter'])), - ('vote', self.gf('helios_auth.jsonfield.JSONField')()), - ('vote_hash', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('cast_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - ('verified_at', self.gf('django.db.models.fields.DateTimeField')(null=True)), - ('invalidated_at', self.gf('django.db.models.fields.DateTimeField')(null=True)), - )) - db.send_create_signal('helios', ['CastVote']) - - # Adding model 'AuditedBallot' - db.create_table('helios_auditedballot', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('election', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['helios.Election'])), - ('raw_vote', self.gf('django.db.models.fields.TextField')()), - ('vote_hash', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('added_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - )) - db.send_create_signal('helios', ['AuditedBallot']) - - # Adding model 'Trustee' - db.create_table('helios_trustee', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('election', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['helios.Election'])), - ('uuid', self.gf('django.db.models.fields.CharField')(max_length=50)), - ('name', self.gf('django.db.models.fields.CharField')(max_length=200)), - ('email', self.gf('django.db.models.fields.EmailField')(max_length=75)), - ('secret', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('public_key', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - ('public_key_hash', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('secret_key', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - ('pok', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - ('decryption_factors', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - ('decryption_proofs', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - )) - db.send_create_signal('helios', ['Trustee']) - - - def backwards(self, orm): - - # Deleting model 'Election' - db.delete_table('helios_election') - - # Deleting model 'ElectionLog' - db.delete_table('helios_electionlog') - - # Deleting model 'VoterFile' - db.delete_table('helios_voterfile') - - # Deleting model 'Voter' - db.delete_table('helios_voter') - - # Deleting model 'CastVote' - db.delete_table('helios_castvote') - - # Deleting model 'AuditedBallot' - db.delete_table('helios_auditedballot') - - # Deleting model 'Trustee' - db.delete_table('helios_trustee') - - - models = { - 'helios_auth.user': { - 'Meta': {'unique_together': "(('user_type', 'user_id'),)", 'object_name': 'User'}, - 'admin_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'info': ('helios_auth.jsonfield.JSONField', [], {}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'token': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'user_type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.auditedballot': { - 'Meta': {'object_name': 'AuditedBallot'}, - 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'raw_vote': ('django.db.models.fields.TextField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.castvote': { - 'Meta': {'object_name': 'CastVote'}, - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invalidated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'vote': ('helios_auth.jsonfield.JSONField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'voter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Voter']"}) - }, - 'helios.election': { - 'Meta': {'object_name': 'Election'}, - 'admin': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']"}), - 'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'eligibility': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'encrypted_tally': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'featured_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'frozen_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}), - 'openreg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'private_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'public_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'questions': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'registration_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'result': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'result_proof': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'tallies_combined_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_finished_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'use_voter_aliases': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'voters_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voting_ended_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_extended_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) - }, - 'helios.electionlog': { - 'Meta': {'object_name': 'ElectionLog'}, - 'at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'log': ('django.db.models.fields.CharField', [], {'max_length': '500'}) - }, - 'helios.trustee': { - 'Meta': {'object_name': 'Trustee'}, - 'decryption_factors': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'decryption_proofs': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), - 'pok': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'public_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'public_key_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.voter': { - 'Meta': {'object_name': 'Voter'}, - 'alias': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'vote': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'voter_type': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.voterfile': { - 'Meta': {'object_name': 'VoterFile'}, - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'num_voters': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'processing_finished_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'processing_started_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'voter_file': ('django.db.models.fields.files.FileField', [], {'max_length': '250'}) - } - } - - complete_apps = ['helios'] diff --git a/helios/south_migrations/0002_v3_1_new_election_and_voter_fields.py b/helios/south_migrations/0002_v3_1_new_election_and_voter_fields.py deleted file mode 100644 index a5d4e1d355477afbc53a5053fa0a0fd4acef0b8d..0000000000000000000000000000000000000000 --- a/helios/south_migrations/0002_v3_1_new_election_and_voter_fields.py +++ /dev/null @@ -1,196 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Adding field 'Voter.user' - db.add_column('helios_voter', 'user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['helios_auth.User'], null=True), keep_default=False) - - # Adding field 'Voter.voter_login_id' - db.add_column('helios_voter', 'voter_login_id', self.gf('django.db.models.fields.CharField')(max_length=100, null=True), keep_default=False) - - # Adding field 'Voter.voter_password' - db.add_column('helios_voter', 'voter_password', self.gf('django.db.models.fields.CharField')(max_length=100, null=True), keep_default=False) - - # Adding field 'Voter.voter_name' - db.add_column('helios_voter', 'voter_name', self.gf('django.db.models.fields.CharField')(max_length=200, null=True), keep_default=False) - - # Adding field 'Voter.voter_email' - db.add_column('helios_voter', 'voter_email', self.gf('django.db.models.fields.CharField')(max_length=250, null=True), keep_default=False) - - # Adding field 'Election.datatype' - db.add_column('helios_election', 'datatype', self.gf('django.db.models.fields.CharField')(default='legacy/Election', max_length=250), keep_default=False) - - # Adding field 'Election.election_type' - db.add_column('helios_election', 'election_type', self.gf('django.db.models.fields.CharField')(default='election', max_length=250), keep_default=False) - - # Adding field 'Election.private_p' - db.add_column('helios_election', 'private_p', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False) - - # Adding field 'Election.use_advanced_audit_features' - db.add_column('helios_election', 'use_advanced_audit_features', self.gf('django.db.models.fields.BooleanField')(default=True), keep_default=False) - - # Adding field 'CastVote.vote_tinyhash' - db.add_column('helios_castvote', 'vote_tinyhash', self.gf('django.db.models.fields.CharField')(max_length=50, unique=True, null=True), keep_default=False) - - - def backwards(self, orm): - - # Deleting field 'Voter.user' - db.delete_column('helios_voter', 'user_id') - - # Deleting field 'Voter.voter_login_id' - db.delete_column('helios_voter', 'voter_login_id') - - # Deleting field 'Voter.voter_password' - db.delete_column('helios_voter', 'voter_password') - - # Deleting field 'Voter.voter_name' - db.delete_column('helios_voter', 'voter_name') - - # Deleting field 'Voter.voter_email' - db.delete_column('helios_voter', 'voter_email') - - # Deleting field 'Election.datatype' - db.delete_column('helios_election', 'datatype') - - # Deleting field 'Election.election_type' - db.delete_column('helios_election', 'election_type') - - # Deleting field 'Election.private_p' - db.delete_column('helios_election', 'private_p') - - # Deleting field 'Election.use_advanced_audit_features' - db.delete_column('helios_election', 'use_advanced_audit_features') - - # Deleting field 'CastVote.vote_tinyhash' - db.delete_column('helios_castvote', 'vote_tinyhash') - - - models = { - 'helios_auth.user': { - 'Meta': {'unique_together': "(('user_type', 'user_id'),)", 'object_name': 'User'}, - 'admin_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'info': ('helios_auth.jsonfield.JSONField', [], {}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'token': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'user_type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.auditedballot': { - 'Meta': {'object_name': 'AuditedBallot'}, - 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'raw_vote': ('django.db.models.fields.TextField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.castvote': { - 'Meta': {'object_name': 'CastVote'}, - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invalidated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'vote': ('helios_auth.jsonfield.JSONField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'vote_tinyhash': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True'}), - 'voter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Voter']"}) - }, - 'helios.election': { - 'Meta': {'object_name': 'Election'}, - 'admin': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']"}), - 'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'datatype': ('django.db.models.fields.CharField', [], {'default': "'legacy/Election'", 'max_length': '250'}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'election_type': ('django.db.models.fields.CharField', [], {'default': "'election'", 'max_length': '250'}), - 'eligibility': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'encrypted_tally': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'featured_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'frozen_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}), - 'openreg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'private_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'private_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'public_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'questions': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'registration_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'result': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'result_proof': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'tallies_combined_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_finished_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'use_advanced_audit_features': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'use_voter_aliases': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'voters_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voting_ended_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_extended_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) - }, - 'helios.electionlog': { - 'Meta': {'object_name': 'ElectionLog'}, - 'at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'log': ('django.db.models.fields.CharField', [], {'max_length': '500'}) - }, - 'helios.trustee': { - 'Meta': {'object_name': 'Trustee'}, - 'decryption_factors': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'decryption_proofs': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), - 'pok': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'public_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'public_key_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.voter': { - 'Meta': {'object_name': 'Voter'}, - 'alias': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']", 'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'vote': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_email': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True'}), - 'voter_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'voter_login_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'voter_password': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_type': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.voterfile': { - 'Meta': {'object_name': 'VoterFile'}, - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'num_voters': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'processing_finished_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'processing_started_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'voter_file': ('django.db.models.fields.files.FileField', [], {'max_length': '250'}) - } - } - - complete_apps = ['helios'] diff --git a/helios/south_migrations/0003_v3_1_election_specific_voters_with_passwords.py b/helios/south_migrations/0003_v3_1_election_specific_voters_with_passwords.py deleted file mode 100644 index 573279f7c0576a403224c4b2f955140af36f00a9..0000000000000000000000000000000000000000 --- a/helios/south_migrations/0003_v3_1_election_specific_voters_with_passwords.py +++ /dev/null @@ -1,178 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import DataMigration -from django.db import models - -class Migration(DataMigration): - - def forwards(self, orm): - """ - update the voters data objects to point to users when it makes sense, - and otherwise to copy the data needed from the users table. - make all elections legacy, because before now they are. - """ - for e in orm.Election.objects.all(): - e.datatype = 'legacy/Election' - e.save() - - # use the .iterator() call to reduce caching and make this more efficient - # so as not to trigger a memory error - for v in orm.Voter.objects.all().iterator(): - user = orm['helios_auth.User'].objects.get(user_type = v.voter_type, user_id = v.voter_id) - - if v.voter_type == 'password': - v.voter_login_id = v.voter_id - v.voter_name = v.name - - v.voter_email = user.info['email'] - v.voter_password = user.info['password'] - else: - v.user = user - - v.save() - - # also, update tinyhash for all votes - for cv in orm.CastVote.objects.all().iterator(): - safe_hash = cv.vote_hash - for c in ['/', '+']: - safe_hash = safe_hash.replace(c,'') - - length = 8 - while True: - vote_tinyhash = safe_hash[:length] - if orm.CastVote.objects.filter(vote_tinyhash = vote_tinyhash).count() == 0: - break - length += 1 - - cv.vote_tinyhash = vote_tinyhash - cv.save() - - - def backwards(self, orm): - "Write your backwards methods here." - raise Exception("can't revert to system-wide user passwords, rather than election specific") - - - models = { - 'helios_auth.user': { - 'Meta': {'unique_together': "(('user_type', 'user_id'),)", 'object_name': 'User'}, - 'admin_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'info': ('helios_auth.jsonfield.JSONField', [], {}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'token': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'user_type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.auditedballot': { - 'Meta': {'object_name': 'AuditedBallot'}, - 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'raw_vote': ('django.db.models.fields.TextField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.castvote': { - 'Meta': {'object_name': 'CastVote'}, - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invalidated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'vote': ('helios_auth.jsonfield.JSONField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'vote_tinyhash': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True'}), - 'voter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Voter']"}) - }, - 'helios.election': { - 'Meta': {'object_name': 'Election'}, - 'admin': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']"}), - 'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'datatype': ('django.db.models.fields.CharField', [], {'default': "'legacy/Election'", 'max_length': '250'}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'election_type': ('django.db.models.fields.CharField', [], {'default': "'election'", 'max_length': '250'}), - 'eligibility': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'encrypted_tally': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'featured_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'frozen_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}), - 'openreg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'private_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'private_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'public_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'questions': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'registration_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'result': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'result_proof': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'tallies_combined_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_finished_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'use_advanced_audit_features': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'use_voter_aliases': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'voters_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voting_ended_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_extended_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) - }, - 'helios.electionlog': { - 'Meta': {'object_name': 'ElectionLog'}, - 'at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'log': ('django.db.models.fields.CharField', [], {'max_length': '500'}) - }, - 'helios.trustee': { - 'Meta': {'object_name': 'Trustee'}, - 'decryption_factors': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'decryption_proofs': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), - 'pok': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'public_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'public_key_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.voter': { - 'Meta': {'object_name': 'Voter'}, - 'alias': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']", 'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'vote': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_email': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True'}), - 'voter_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'voter_login_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'voter_password': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_type': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.voterfile': { - 'Meta': {'object_name': 'VoterFile'}, - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'num_voters': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'processing_finished_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'processing_started_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'voter_file': ('django.db.models.fields.files.FileField', [], {'max_length': '250'}) - } - } - - complete_apps = ['helios'] diff --git a/helios/south_migrations/0004_v3_1_remove_voter_fields.py b/helios/south_migrations/0004_v3_1_remove_voter_fields.py deleted file mode 100644 index b934e945a081acbeab5da9054a16aafb793a9ee3..0000000000000000000000000000000000000000 --- a/helios/south_migrations/0004_v3_1_remove_voter_fields.py +++ /dev/null @@ -1,153 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Deleting field 'Voter.name' - db.delete_column('helios_voter', 'name') - - # Deleting field 'Voter.voter_id' - db.delete_column('helios_voter', 'voter_id') - - # Deleting field 'Voter.voter_type' - db.delete_column('helios_voter', 'voter_type') - - - def backwards(self, orm): - - # Adding field 'Voter.name' - db.add_column('helios_voter', 'name', self.gf('django.db.models.fields.CharField')(max_length=200, null=True), keep_default=False) - - # We cannot add back in field 'Voter.voter_id' - raise RuntimeError( - "Cannot reverse this migration. 'Voter.voter_id' and its values cannot be restored.") - - # We cannot add back in field 'Voter.voter_type' - raise RuntimeError( - "Cannot reverse this migration. 'Voter.voter_type' and its values cannot be restored.") - - - models = { - 'helios_auth.user': { - 'Meta': {'unique_together': "(('user_type', 'user_id'),)", 'object_name': 'User'}, - 'admin_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'info': ('helios_auth.jsonfield.JSONField', [], {}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'token': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'user_type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.auditedballot': { - 'Meta': {'object_name': 'AuditedBallot'}, - 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'raw_vote': ('django.db.models.fields.TextField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.castvote': { - 'Meta': {'object_name': 'CastVote'}, - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invalidated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'vote': ('helios_auth.jsonfield.JSONField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'vote_tinyhash': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True'}), - 'voter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Voter']"}) - }, - 'helios.election': { - 'Meta': {'object_name': 'Election'}, - 'admin': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']"}), - 'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'datatype': ('django.db.models.fields.CharField', [], {'default': "'legacy/Election'", 'max_length': '250'}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'election_type': ('django.db.models.fields.CharField', [], {'default': "'election'", 'max_length': '250'}), - 'eligibility': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'encrypted_tally': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'featured_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'frozen_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}), - 'openreg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'private_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'private_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'public_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'questions': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'registration_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'result': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'result_proof': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'tallies_combined_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_finished_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'use_advanced_audit_features': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'use_voter_aliases': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'voters_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voting_ended_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_extended_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) - }, - 'helios.electionlog': { - 'Meta': {'object_name': 'ElectionLog'}, - 'at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'log': ('django.db.models.fields.CharField', [], {'max_length': '500'}) - }, - 'helios.trustee': { - 'Meta': {'object_name': 'Trustee'}, - 'decryption_factors': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'decryption_proofs': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), - 'pok': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'public_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'public_key_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.voter': { - 'Meta': {'object_name': 'Voter'}, - 'alias': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']", 'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'vote': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_email': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True'}), - 'voter_login_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'voter_password': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}) - }, - 'helios.voterfile': { - 'Meta': {'object_name': 'VoterFile'}, - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'num_voters': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'processing_finished_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'processing_started_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'voter_file': ('django.db.models.fields.files.FileField', [], {'max_length': '250'}) - } - } - - complete_apps = ['helios'] diff --git a/helios/south_migrations/0005_add_quarantine_fields.py b/helios/south_migrations/0005_add_quarantine_fields.py deleted file mode 100644 index b0f9f1dccd4bcaa4db810bbf5ab614eafe40fc28..0000000000000000000000000000000000000000 --- a/helios/south_migrations/0005_add_quarantine_fields.py +++ /dev/null @@ -1,154 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Adding field 'Election.complaint_period_ends_at' - db.add_column('helios_election', 'complaint_period_ends_at', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True), keep_default=False) - - # Adding field 'CastVote.quarantined_p' - db.add_column('helios_castvote', 'quarantined_p', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False) - - # Adding field 'CastVote.released_from_quarantine_at' - db.add_column('helios_castvote', 'released_from_quarantine_at', self.gf('django.db.models.fields.DateTimeField')(null=True), keep_default=False) - - - def backwards(self, orm): - - # Deleting field 'Election.complaint_period_ends_at' - db.delete_column('helios_election', 'complaint_period_ends_at') - - # Deleting field 'CastVote.quarantined_p' - db.delete_column('helios_castvote', 'quarantined_p') - - # Deleting field 'CastVote.released_from_quarantine_at' - db.delete_column('helios_castvote', 'released_from_quarantine_at') - - - models = { - 'helios_auth.user': { - 'Meta': {'unique_together': "(('user_type', 'user_id'),)", 'object_name': 'User'}, - 'admin_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'info': ('helios_auth.jsonfield.JSONField', [], {}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'token': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'user_type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.auditedballot': { - 'Meta': {'object_name': 'AuditedBallot'}, - 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'raw_vote': ('django.db.models.fields.TextField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.castvote': { - 'Meta': {'object_name': 'CastVote'}, - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invalidated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'quarantined_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'released_from_quarantine_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'vote': ('helios_auth.jsonfield.JSONField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'vote_tinyhash': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True'}), - 'voter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Voter']"}) - }, - 'helios.election': { - 'Meta': {'object_name': 'Election'}, - 'admin': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']"}), - 'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}), - 'complaint_period_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'datatype': ('django.db.models.fields.CharField', [], {'default': "'legacy/Election'", 'max_length': '250'}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'election_type': ('django.db.models.fields.CharField', [], {'default': "'election'", 'max_length': '250'}), - 'eligibility': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'encrypted_tally': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'featured_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'frozen_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}), - 'openreg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'private_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'private_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'public_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'questions': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'registration_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'result': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'result_proof': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'tallies_combined_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_finished_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'use_advanced_audit_features': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'use_voter_aliases': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'voters_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voting_ended_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_extended_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) - }, - 'helios.electionlog': { - 'Meta': {'object_name': 'ElectionLog'}, - 'at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'log': ('django.db.models.fields.CharField', [], {'max_length': '500'}) - }, - 'helios.trustee': { - 'Meta': {'object_name': 'Trustee'}, - 'decryption_factors': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'decryption_proofs': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), - 'pok': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'public_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'public_key_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret_key': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.voter': { - 'Meta': {'object_name': 'Voter'}, - 'alias': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']", 'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'vote': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_email': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True'}), - 'voter_login_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'voter_password': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}) - }, - 'helios.voterfile': { - 'Meta': {'object_name': 'VoterFile'}, - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'num_voters': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'processing_finished_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'processing_started_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'voter_file': ('django.db.models.fields.files.FileField', [], {'max_length': '250'}) - } - } - - complete_apps = ['helios'] diff --git a/helios/south_migrations/0006_auto__chg_field_voter_vote__add_unique_voter_voter_login_id_election__.py b/helios/south_migrations/0006_auto__chg_field_voter_vote__add_unique_voter_voter_login_id_election__.py deleted file mode 100644 index 2791e493af921dc4694966c0ce7ae973e281afbb..0000000000000000000000000000000000000000 --- a/helios/south_migrations/0006_auto__chg_field_voter_vote__add_unique_voter_voter_login_id_election__.py +++ /dev/null @@ -1,220 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Changing field 'Voter.vote' - db.alter_column('helios_voter', 'vote', self.gf('helios.datatypes.djangofield.LDObjectField')(null=True)) - - # Adding unique constraint on 'Voter', fields ['voter_login_id', 'election'] - db.create_unique('helios_voter', ['voter_login_id', 'election_id']) - - # Changing field 'Election.result' - db.alter_column('helios_election', 'result', self.gf('helios.datatypes.djangofield.LDObjectField')(null=True)) - - # Changing field 'Election.questions' - db.alter_column('helios_election', 'questions', self.gf('helios.datatypes.djangofield.LDObjectField')(null=True)) - - # Changing field 'Election.encrypted_tally' - db.alter_column('helios_election', 'encrypted_tally', self.gf('helios.datatypes.djangofield.LDObjectField')(null=True)) - - # Changing field 'Election.eligibility' - db.alter_column('helios_election', 'eligibility', self.gf('helios.datatypes.djangofield.LDObjectField')(null=True)) - - # Changing field 'Election.private_key' - db.alter_column('helios_election', 'private_key', self.gf('helios.datatypes.djangofield.LDObjectField')(null=True)) - - # Changing field 'Election.public_key' - db.alter_column('helios_election', 'public_key', self.gf('helios.datatypes.djangofield.LDObjectField')(null=True)) - - # Changing field 'Trustee.public_key' - db.alter_column('helios_trustee', 'public_key', self.gf('helios.datatypes.djangofield.LDObjectField')(null=True)) - - # Changing field 'Trustee.decryption_proofs' - db.alter_column('helios_trustee', 'decryption_proofs', self.gf('helios.datatypes.djangofield.LDObjectField')(null=True)) - - # Changing field 'Trustee.pok' - db.alter_column('helios_trustee', 'pok', self.gf('helios.datatypes.djangofield.LDObjectField')(null=True)) - - # Changing field 'Trustee.secret_key' - db.alter_column('helios_trustee', 'secret_key', self.gf('helios.datatypes.djangofield.LDObjectField')(null=True)) - - # Changing field 'Trustee.decryption_factors' - db.alter_column('helios_trustee', 'decryption_factors', self.gf('helios.datatypes.djangofield.LDObjectField')(null=True)) - - # Changing field 'CastVote.vote' - db.alter_column('helios_castvote', 'vote', self.gf('helios.datatypes.djangofield.LDObjectField')()) - - - def backwards(self, orm): - - # Removing unique constraint on 'Voter', fields ['voter_login_id', 'election'] - db.delete_unique('helios_voter', ['voter_login_id', 'election_id']) - - # Changing field 'Voter.vote' - db.alter_column('helios_voter', 'vote', self.gf('helios_auth.jsonfield.JSONField')(null=True)) - - # Changing field 'Election.result' - db.alter_column('helios_election', 'result', self.gf('helios_auth.jsonfield.JSONField')(null=True)) - - # Changing field 'Election.questions' - db.alter_column('helios_election', 'questions', self.gf('helios_auth.jsonfield.JSONField')(null=True)) - - # Changing field 'Election.encrypted_tally' - db.alter_column('helios_election', 'encrypted_tally', self.gf('helios_auth.jsonfield.JSONField')(null=True)) - - # Changing field 'Election.eligibility' - db.alter_column('helios_election', 'eligibility', self.gf('helios_auth.jsonfield.JSONField')(null=True)) - - # Changing field 'Election.private_key' - db.alter_column('helios_election', 'private_key', self.gf('helios_auth.jsonfield.JSONField')(null=True)) - - # Changing field 'Election.public_key' - db.alter_column('helios_election', 'public_key', self.gf('helios_auth.jsonfield.JSONField')(null=True)) - - # Changing field 'Trustee.public_key' - db.alter_column('helios_trustee', 'public_key', self.gf('helios_auth.jsonfield.JSONField')(null=True)) - - # Changing field 'Trustee.decryption_proofs' - db.alter_column('helios_trustee', 'decryption_proofs', self.gf('helios_auth.jsonfield.JSONField')(null=True)) - - # Changing field 'Trustee.pok' - db.alter_column('helios_trustee', 'pok', self.gf('helios_auth.jsonfield.JSONField')(null=True)) - - # Changing field 'Trustee.secret_key' - db.alter_column('helios_trustee', 'secret_key', self.gf('helios_auth.jsonfield.JSONField')(null=True)) - - # Changing field 'Trustee.decryption_factors' - db.alter_column('helios_trustee', 'decryption_factors', self.gf('helios_auth.jsonfield.JSONField')(null=True)) - - # Changing field 'CastVote.vote' - db.alter_column('helios_castvote', 'vote', self.gf('helios_auth.jsonfield.JSONField')()) - - - models = { - 'helios_auth.user': { - 'Meta': {'unique_together': "(('user_type', 'user_id'),)", 'object_name': 'User'}, - 'admin_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'info': ('helios_auth.jsonfield.JSONField', [], {}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'token': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'user_type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.auditedballot': { - 'Meta': {'object_name': 'AuditedBallot'}, - 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'raw_vote': ('django.db.models.fields.TextField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.castvote': { - 'Meta': {'object_name': 'CastVote'}, - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invalidated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'quarantined_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'released_from_quarantine_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'vote_tinyhash': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True'}), - 'voter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Voter']"}) - }, - 'helios.election': { - 'Meta': {'object_name': 'Election'}, - 'admin': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']"}), - 'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}), - 'complaint_period_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'datatype': ('django.db.models.fields.CharField', [], {'default': "'legacy/Election'", 'max_length': '250'}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'election_type': ('django.db.models.fields.CharField', [], {'default': "'election'", 'max_length': '250'}), - 'eligibility': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'encrypted_tally': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'featured_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'frozen_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}), - 'openreg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'private_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'private_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'questions': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'registration_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'result': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'result_proof': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'tallies_combined_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_finished_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'use_advanced_audit_features': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'use_voter_aliases': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'voters_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voting_ended_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_extended_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) - }, - 'helios.electionlog': { - 'Meta': {'object_name': 'ElectionLog'}, - 'at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'log': ('django.db.models.fields.CharField', [], {'max_length': '500'}) - }, - 'helios.trustee': { - 'Meta': {'object_name': 'Trustee'}, - 'decryption_factors': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'decryption_proofs': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), - 'pok': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.voter': { - 'Meta': {'unique_together': "(('election', 'voter_login_id'),)", 'object_name': 'Voter'}, - 'alias': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']", 'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_email': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True'}), - 'voter_login_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'voter_password': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}) - }, - 'helios.voterfile': { - 'Meta': {'object_name': 'VoterFile'}, - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'num_voters': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'processing_finished_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'processing_started_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'voter_file': ('django.db.models.fields.files.FileField', [], {'max_length': '250'}) - } - } - - complete_apps = ['helios'] diff --git a/helios/south_migrations/0007_auto__add_field_voterfile_voter_file_content__chg_field_voterfile_vote.py b/helios/south_migrations/0007_auto__add_field_voterfile_voter_file_content__chg_field_voterfile_vote.py deleted file mode 100644 index 04a7f00b078c6d5954c11596a734feaf7afadad1..0000000000000000000000000000000000000000 --- a/helios/south_migrations/0007_auto__add_field_voterfile_voter_file_content__chg_field_voterfile_vote.py +++ /dev/null @@ -1,149 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Adding field 'VoterFile.voter_file_content' - db.add_column('helios_voterfile', 'voter_file_content', self.gf('django.db.models.fields.TextField')(null=True), keep_default=False) - - # Changing field 'VoterFile.voter_file' - db.alter_column('helios_voterfile', 'voter_file', self.gf('django.db.models.fields.files.FileField')(max_length=250, null=True)) - - - def backwards(self, orm): - - # Deleting field 'VoterFile.voter_file_content' - db.delete_column('helios_voterfile', 'voter_file_content') - - # User chose to not deal with backwards NULL issues for 'VoterFile.voter_file' - raise RuntimeError("Cannot reverse this migration. 'VoterFile.voter_file' and its values cannot be restored.") - - - models = { - 'helios_auth.user': { - 'Meta': {'unique_together': "(('user_type', 'user_id'),)", 'object_name': 'User'}, - 'admin_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'info': ('helios_auth.jsonfield.JSONField', [], {}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'token': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'user_type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.auditedballot': { - 'Meta': {'object_name': 'AuditedBallot'}, - 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'raw_vote': ('django.db.models.fields.TextField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.castvote': { - 'Meta': {'object_name': 'CastVote'}, - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invalidated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'quarantined_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'released_from_quarantine_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'vote_tinyhash': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True'}), - 'voter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Voter']"}) - }, - 'helios.election': { - 'Meta': {'object_name': 'Election'}, - 'admin': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']"}), - 'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}), - 'complaint_period_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'datatype': ('django.db.models.fields.CharField', [], {'default': "'legacy/Election'", 'max_length': '250'}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'election_type': ('django.db.models.fields.CharField', [], {'default': "'election'", 'max_length': '250'}), - 'eligibility': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'encrypted_tally': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'featured_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'frozen_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}), - 'openreg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'private_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'private_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'questions': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'registration_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'result': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'result_proof': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'tallies_combined_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_finished_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'use_advanced_audit_features': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'use_voter_aliases': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'voters_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voting_ended_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_extended_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) - }, - 'helios.electionlog': { - 'Meta': {'object_name': 'ElectionLog'}, - 'at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'log': ('django.db.models.fields.CharField', [], {'max_length': '500'}) - }, - 'helios.trustee': { - 'Meta': {'object_name': 'Trustee'}, - 'decryption_factors': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'decryption_proofs': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), - 'pok': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.voter': { - 'Meta': {'unique_together': "(('election', 'voter_login_id'),)", 'object_name': 'Voter'}, - 'alias': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']", 'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_email': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True'}), - 'voter_login_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'voter_password': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}) - }, - 'helios.voterfile': { - 'Meta': {'object_name': 'VoterFile'}, - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'num_voters': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'processing_finished_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'processing_started_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'voter_file': ('django.db.models.fields.files.FileField', [], {'max_length': '250', 'null': 'True'}), - 'voter_file_content': ('django.db.models.fields.TextField', [], {'null': 'True'}) - } - } - - complete_apps = ['helios'] diff --git a/helios/south_migrations/0008_auto__add_unique_trustee_election_email.py b/helios/south_migrations/0008_auto__add_unique_trustee_election_email.py deleted file mode 100644 index 974dfd2db1c3e58fe78188645a8a54673d5febfb..0000000000000000000000000000000000000000 --- a/helios/south_migrations/0008_auto__add_unique_trustee_election_email.py +++ /dev/null @@ -1,143 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Adding unique constraint on 'Trustee', fields ['election', 'email'] - db.create_unique('helios_trustee', ['election_id', 'email']) - - - def backwards(self, orm): - - # Removing unique constraint on 'Trustee', fields ['election', 'email'] - db.delete_unique('helios_trustee', ['election_id', 'email']) - - - models = { - 'helios.auditedballot': { - 'Meta': {'object_name': 'AuditedBallot'}, - 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'raw_vote': ('django.db.models.fields.TextField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.castvote': { - 'Meta': {'object_name': 'CastVote'}, - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invalidated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'quarantined_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'released_from_quarantine_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'vote_tinyhash': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True'}), - 'voter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Voter']"}) - }, - 'helios.election': { - 'Meta': {'object_name': 'Election'}, - 'admin': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']"}), - 'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}), - 'complaint_period_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'datatype': ('django.db.models.fields.CharField', [], {'default': "'legacy/Election'", 'max_length': '250'}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'election_type': ('django.db.models.fields.CharField', [], {'default': "'election'", 'max_length': '250'}), - 'eligibility': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'encrypted_tally': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'featured_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'frozen_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}), - 'openreg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'private_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'private_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'questions': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'registration_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'result': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'result_proof': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'tallies_combined_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_finished_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'use_advanced_audit_features': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'use_voter_aliases': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'voters_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voting_ended_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_extended_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) - }, - 'helios.electionlog': { - 'Meta': {'object_name': 'ElectionLog'}, - 'at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'log': ('django.db.models.fields.CharField', [], {'max_length': '500'}) - }, - 'helios.trustee': { - 'Meta': {'unique_together': "(('election', 'email'),)", 'object_name': 'Trustee'}, - 'decryption_factors': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'decryption_proofs': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), - 'pok': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.voter': { - 'Meta': {'unique_together': "(('election', 'voter_login_id'),)", 'object_name': 'Voter'}, - 'alias': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']", 'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_email': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True'}), - 'voter_login_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'voter_password': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}) - }, - 'helios.voterfile': { - 'Meta': {'object_name': 'VoterFile'}, - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'num_voters': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'processing_finished_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'processing_started_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'voter_file': ('django.db.models.fields.files.FileField', [], {'max_length': '250', 'null': 'True'}), - 'voter_file_content': ('django.db.models.fields.TextField', [], {'null': 'True'}) - }, - 'helios_auth.user': { - 'Meta': {'unique_together': "(('user_type', 'user_id'),)", 'object_name': 'User'}, - 'admin_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'info': ('helios_auth.jsonfield.JSONField', [], {}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'token': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'user_type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - } - } - - complete_apps = ['helios'] diff --git a/helios/south_migrations/0009_auto__add_field_election_help_email.py b/helios/south_migrations/0009_auto__add_field_election_help_email.py deleted file mode 100644 index 1500f0e43d7fadd46bc702e1d9ac77aaa9dca60a..0000000000000000000000000000000000000000 --- a/helios/south_migrations/0009_auto__add_field_election_help_email.py +++ /dev/null @@ -1,144 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Adding field 'Election.help_email' - db.add_column('helios_election', 'help_email', self.gf('django.db.models.fields.EmailField')(max_length=75, null=True), keep_default=False) - - - def backwards(self, orm): - - # Deleting field 'Election.help_email' - db.delete_column('helios_election', 'help_email') - - - models = { - 'helios.auditedballot': { - 'Meta': {'object_name': 'AuditedBallot'}, - 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'raw_vote': ('django.db.models.fields.TextField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.castvote': { - 'Meta': {'object_name': 'CastVote'}, - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invalidated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'quarantined_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'released_from_quarantine_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'vote_tinyhash': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True'}), - 'voter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Voter']"}) - }, - 'helios.election': { - 'Meta': {'object_name': 'Election'}, - 'admin': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']"}), - 'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}), - 'complaint_period_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'datatype': ('django.db.models.fields.CharField', [], {'default': "'legacy/Election'", 'max_length': '250'}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'election_type': ('django.db.models.fields.CharField', [], {'default': "'election'", 'max_length': '250'}), - 'eligibility': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'encrypted_tally': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'featured_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'frozen_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'help_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}), - 'openreg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'private_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'private_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'questions': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'registration_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'result': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'result_proof': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'tallies_combined_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_finished_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'use_advanced_audit_features': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'use_voter_aliases': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'voters_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voting_ended_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_extended_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) - }, - 'helios.electionlog': { - 'Meta': {'object_name': 'ElectionLog'}, - 'at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'log': ('django.db.models.fields.CharField', [], {'max_length': '500'}) - }, - 'helios.trustee': { - 'Meta': {'unique_together': "(('election', 'email'),)", 'object_name': 'Trustee'}, - 'decryption_factors': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'decryption_proofs': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), - 'pok': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.voter': { - 'Meta': {'unique_together': "(('election', 'voter_login_id'),)", 'object_name': 'Voter'}, - 'alias': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']", 'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_email': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True'}), - 'voter_login_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'voter_password': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}) - }, - 'helios.voterfile': { - 'Meta': {'object_name': 'VoterFile'}, - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'num_voters': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'processing_finished_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'processing_started_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'voter_file': ('django.db.models.fields.files.FileField', [], {'max_length': '250', 'null': 'True'}), - 'voter_file_content': ('django.db.models.fields.TextField', [], {'null': 'True'}) - }, - 'helios_auth.user': { - 'Meta': {'unique_together': "(('user_type', 'user_id'),)", 'object_name': 'User'}, - 'admin_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'info': ('helios_auth.jsonfield.JSONField', [], {}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'token': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'user_type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - } - } - - complete_apps = ['helios'] diff --git a/helios/south_migrations/0010_auto__add_field_election_randomize_answer_order.py b/helios/south_migrations/0010_auto__add_field_election_randomize_answer_order.py deleted file mode 100644 index 96d1eb0662c93b3c1b2c77d1a34feefee5c4d526..0000000000000000000000000000000000000000 --- a/helios/south_migrations/0010_auto__add_field_election_randomize_answer_order.py +++ /dev/null @@ -1,146 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding field 'Election.randomize_answer_order' - db.add_column('helios_election', 'randomize_answer_order', - self.gf('django.db.models.fields.BooleanField')(default=False), - keep_default=False) - - - def backwards(self, orm): - # Deleting field 'Election.randomize_answer_order' - db.delete_column('helios_election', 'randomize_answer_order') - - - models = { - 'helios.auditedballot': { - 'Meta': {'object_name': 'AuditedBallot'}, - 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'raw_vote': ('django.db.models.fields.TextField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.castvote': { - 'Meta': {'object_name': 'CastVote'}, - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invalidated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'quarantined_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'released_from_quarantine_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'vote_tinyhash': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True'}), - 'voter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Voter']"}) - }, - 'helios.election': { - 'Meta': {'object_name': 'Election'}, - 'admin': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']"}), - 'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}), - 'complaint_period_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'datatype': ('django.db.models.fields.CharField', [], {'default': "'legacy/Election'", 'max_length': '250'}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'election_type': ('django.db.models.fields.CharField', [], {'default': "'election'", 'max_length': '250'}), - 'eligibility': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'encrypted_tally': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'featured_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'frozen_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'help_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}), - 'openreg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'private_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'private_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'questions': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'randomize_answer_order': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'registration_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'result': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'result_proof': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'tallies_combined_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_finished_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'use_advanced_audit_features': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'use_voter_aliases': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'voters_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voting_ended_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_extended_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) - }, - 'helios.electionlog': { - 'Meta': {'object_name': 'ElectionLog'}, - 'at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'log': ('django.db.models.fields.CharField', [], {'max_length': '500'}) - }, - 'helios.trustee': { - 'Meta': {'unique_together': "(('election', 'email'),)", 'object_name': 'Trustee'}, - 'decryption_factors': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'decryption_proofs': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), - 'pok': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.voter': { - 'Meta': {'unique_together': "(('election', 'voter_login_id'),)", 'object_name': 'Voter'}, - 'alias': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']", 'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_email': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True'}), - 'voter_login_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'voter_password': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}) - }, - 'helios.voterfile': { - 'Meta': {'object_name': 'VoterFile'}, - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'num_voters': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'processing_finished_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'processing_started_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'voter_file': ('django.db.models.fields.files.FileField', [], {'max_length': '250', 'null': 'True'}), - 'voter_file_content': ('django.db.models.fields.TextField', [], {'null': 'True'}) - }, - 'helios_auth.user': { - 'Meta': {'unique_together': "(('user_type', 'user_id'),)", 'object_name': 'User'}, - 'admin_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'info': ('helios_auth.jsonfield.JSONField', [], {}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'token': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'user_type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - } - } - - complete_apps = ['helios'] \ No newline at end of file diff --git a/helios/south_migrations/0011_auto__add_field_election_election_info_url.py b/helios/south_migrations/0011_auto__add_field_election_election_info_url.py deleted file mode 100644 index 48c98fb14b0e962f86ca66326dee9687c4c7bdf9..0000000000000000000000000000000000000000 --- a/helios/south_migrations/0011_auto__add_field_election_election_info_url.py +++ /dev/null @@ -1,147 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding field 'Election.election_info_url' - db.add_column('helios_election', 'election_info_url', - self.gf('django.db.models.fields.CharField')(max_length=300, null=True), - keep_default=False) - - - def backwards(self, orm): - # Deleting field 'Election.election_info_url' - db.delete_column('helios_election', 'election_info_url') - - - models = { - 'helios.auditedballot': { - 'Meta': {'object_name': 'AuditedBallot'}, - 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'raw_vote': ('django.db.models.fields.TextField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.castvote': { - 'Meta': {'object_name': 'CastVote'}, - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invalidated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'quarantined_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'released_from_quarantine_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'vote_tinyhash': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True'}), - 'voter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Voter']"}) - }, - 'helios.election': { - 'Meta': {'object_name': 'Election'}, - 'admin': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']"}), - 'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}), - 'complaint_period_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'datatype': ('django.db.models.fields.CharField', [], {'default': "'legacy/Election'", 'max_length': '250'}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'election_info_url': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True'}), - 'election_type': ('django.db.models.fields.CharField', [], {'default': "'election'", 'max_length': '250'}), - 'eligibility': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'encrypted_tally': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'featured_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'frozen_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'help_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}), - 'openreg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'private_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'private_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'questions': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'randomize_answer_order': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'registration_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'result': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'result_proof': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'tallies_combined_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_finished_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'use_advanced_audit_features': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'use_voter_aliases': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'voters_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voting_ended_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_extended_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) - }, - 'helios.electionlog': { - 'Meta': {'object_name': 'ElectionLog'}, - 'at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'log': ('django.db.models.fields.CharField', [], {'max_length': '500'}) - }, - 'helios.trustee': { - 'Meta': {'unique_together': "(('election', 'email'),)", 'object_name': 'Trustee'}, - 'decryption_factors': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'decryption_proofs': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), - 'pok': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.voter': { - 'Meta': {'unique_together': "(('election', 'voter_login_id'),)", 'object_name': 'Voter'}, - 'alias': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']", 'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_email': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True'}), - 'voter_login_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'voter_password': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}) - }, - 'helios.voterfile': { - 'Meta': {'object_name': 'VoterFile'}, - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'num_voters': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'processing_finished_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'processing_started_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'voter_file': ('django.db.models.fields.files.FileField', [], {'max_length': '250', 'null': 'True'}), - 'voter_file_content': ('django.db.models.fields.TextField', [], {'null': 'True'}) - }, - 'helios_auth.user': { - 'Meta': {'unique_together': "(('user_type', 'user_id'),)", 'object_name': 'User'}, - 'admin_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'info': ('helios_auth.jsonfield.JSONField', [], {}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'token': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'user_type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - } - } - - complete_apps = ['helios'] \ No newline at end of file diff --git a/helios/south_migrations/0012_auto__add_field_election_result_released_at.py b/helios/south_migrations/0012_auto__add_field_election_result_released_at.py deleted file mode 100644 index 0cc6853ee5188ff223a6c321abd332cf57c84765..0000000000000000000000000000000000000000 --- a/helios/south_migrations/0012_auto__add_field_election_result_released_at.py +++ /dev/null @@ -1,148 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding field 'Election.result_released_at' - db.add_column('helios_election', 'result_released_at', - self.gf('django.db.models.fields.DateTimeField')(default=None, null=True), - keep_default=False) - - - def backwards(self, orm): - # Deleting field 'Election.result_released_at' - db.delete_column('helios_election', 'result_released_at') - - - models = { - 'helios.auditedballot': { - 'Meta': {'object_name': 'AuditedBallot'}, - 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'raw_vote': ('django.db.models.fields.TextField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'helios.castvote': { - 'Meta': {'object_name': 'CastVote'}, - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invalidated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'quarantined_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'released_from_quarantine_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'vote_tinyhash': ('django.db.models.fields.CharField', [], {'max_length': '50', 'unique': 'True', 'null': 'True'}), - 'voter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Voter']"}) - }, - 'helios.election': { - 'Meta': {'object_name': 'Election'}, - 'admin': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']"}), - 'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}), - 'complaint_period_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'datatype': ('django.db.models.fields.CharField', [], {'default': "'legacy/Election'", 'max_length': '250'}), - 'description': ('django.db.models.fields.TextField', [], {}), - 'election_info_url': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True'}), - 'election_type': ('django.db.models.fields.CharField', [], {'default': "'election'", 'max_length': '250'}), - 'eligibility': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'encrypted_tally': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'featured_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'frozen_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'help_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}), - 'openreg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'private_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'private_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'questions': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'randomize_answer_order': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'registration_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'result': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'result_proof': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'result_released_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'tallies_combined_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_finished_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'tallying_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'use_advanced_audit_features': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'use_voter_aliases': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'voters_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voting_ended_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_ends_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_extended_until': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_started_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), - 'voting_starts_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}) - }, - 'helios.electionlog': { - 'Meta': {'object_name': 'ElectionLog'}, - 'at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'log': ('django.db.models.fields.CharField', [], {'max_length': '500'}) - }, - 'helios.trustee': { - 'Meta': {'unique_together': "(('election', 'email'),)", 'object_name': 'Trustee'}, - 'decryption_factors': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'decryption_proofs': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), - 'pok': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'public_key_hash': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'secret_key': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'helios.voter': { - 'Meta': {'unique_together': "(('election', 'voter_login_id'),)", 'object_name': 'Voter'}, - 'alias': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'cast_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios_auth.User']", 'null': 'True'}), - 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'vote': ('helios.datatypes.djangofield.LDObjectField', [], {'null': 'True'}), - 'vote_hash': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_email': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True'}), - 'voter_login_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), - 'voter_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'voter_password': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}) - }, - 'helios.voterfile': { - 'Meta': {'object_name': 'VoterFile'}, - 'election': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['helios.Election']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'num_voters': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'processing_finished_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'processing_started_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'voter_file': ('django.db.models.fields.files.FileField', [], {'max_length': '250', 'null': 'True'}), - 'voter_file_content': ('django.db.models.fields.TextField', [], {'null': 'True'}) - }, - 'helios_auth.user': { - 'Meta': {'unique_together': "(('user_type', 'user_id'),)", 'object_name': 'User'}, - 'admin_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'info': ('helios_auth.jsonfield.JSONField', [], {}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'token': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'user_type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - } - } - - complete_apps = ['helios'] \ No newline at end of file diff --git a/helios/south_migrations/__init__.py b/helios/south_migrations/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/helios/stats_url_names.py b/helios/stats_url_names.py new file mode 100644 index 0000000000000000000000000000000000000000..2b8ffe2cde27da4c9d8f21341a3c826ee9c4512f --- /dev/null +++ b/helios/stats_url_names.py @@ -0,0 +1,5 @@ +STATS_HOME="stats@home" +STATS_FORCE_QUEUE="stats@force-queue" +STATS_ELECTIONS="stats@elections" +STATS_ELECTIONS_PROBLEMS="stats@elections-problems" +STATS_RECENT_VOTES="stats@recent-votes" diff --git a/helios/stats_urls.py b/helios/stats_urls.py index 2d26d1961aabae3a75bef2e16117d9dd3eea7566..ac3b1439f9dae0acd8702b414772c691dbdc2c4f 100644 --- a/helios/stats_urls.py +++ b/helios/stats_urls.py @@ -4,15 +4,15 @@ Helios URLs for Election related stuff Ben Adida (ben@adida.net) """ -from django.conf.urls import * +from django.conf.urls import url -from helios.stats_views import * +from helios.stats_views import (home, force_queue, elections, recent_problem_elections, recent_votes) +import helios.stats_url_names as names -urlpatterns = patterns( - '', - (r'^$', home), - (r'^force-queue$', force_queue), - (r'^elections$', elections), - (r'^problem-elections$', recent_problem_elections), - (r'^recent-votes$', recent_votes), -) +urlpatterns = [ + url(r'^$', home, name=names.STATS_HOME), + url(r'^force-queue$', force_queue, name=names.STATS_FORCE_QUEUE), + url(r'^elections$', elections, name=names.STATS_ELECTIONS), + url(r'^problem-elections$', recent_problem_elections, name=names.STATS_ELECTIONS_PROBLEMS), + url(r'^recent-votes$', recent_votes, name=names.STATS_RECENT_VOTES), +] diff --git a/helios/stats_views.py b/helios/stats_views.py index 7d2a62ec6f4f7be47c01127dba95ba1013c167e3..e506637fa7b8ba224bf96ce6a544758dbdaacd70 100644 --- a/helios/stats_views.py +++ b/helios/stats_views.py @@ -2,18 +2,19 @@ Helios stats views """ -from django.core.urlresolvers import reverse -from django.core.mail import send_mail +import datetime + from django.core.paginator import Paginator -from django.http import * -from django.db import transaction -from django.db.models import * +from django.urls import reverse +from django.db.models import Max, Count +from django.http import HttpResponseRedirect -from security import * -from helios_auth.security import get_user, save_in_session_across_logouts -from view_utils import * +from helios import tasks, url_names +from helios.models import CastVote, Election +from helios_auth.security import get_user +from .security import PermissionDenied +from .view_utils import render_template -from helios import tasks def require_admin(request): user = get_user(request) @@ -33,7 +34,7 @@ def force_queue(request): for cv in votes_in_queue: tasks.cast_vote_verify_and_store.delay(cv.id) - return HttpResponseRedirect(reverse(home)) + return HttpResponseRedirect(reverse(url_names.stats.STATS_HOME)) def elections(request): user = require_admin(request) @@ -42,8 +43,7 @@ def elections(request): limit = int(request.GET.get('limit', 25)) q = request.GET.get('q','') - elections = Election.objects.filter(name__icontains = q) - elections.all().order_by('-created_at') + elections = Election.objects.filter(name__icontains = q).order_by('-created_at') elections_paginator = Paginator(elections, limit) elections_page = elections_paginator.page(page) diff --git a/helios/tasks.py b/helios/tasks.py index 5f7bba8730120e732a48f7467314e05dcfd7b639..a1079c1d33c321747d05d5d8b317015f7c928ba1 100644 --- a/helios/tasks.py +++ b/helios/tasks.py @@ -4,20 +4,18 @@ Celery queued tasks for Helios 2010-08-01 ben@adida.net """ - -from celery.decorators import task - -from models import * -from view_utils import render_template_raw -import signals - import copy +from celery import shared_task +from celery.utils.log import get_logger + +from . import signals +from .models import CastVote, Election, Voter, VoterFile +from .view_utils import render_template_raw -from django.conf import settings -@task() +@shared_task def cast_vote_verify_and_store(cast_vote_id, status_update_message=None, **kwargs): - cast_vote = CastVote.objects.get(id = cast_vote_id) + cast_vote = CastVote.objects.get(id=cast_vote_id) result = cast_vote.verify_and_store() voter = cast_vote.voter @@ -27,23 +25,22 @@ def cast_vote_verify_and_store(cast_vote_id, status_update_message=None, **kwarg if result: # send the signal signals.vote_cast.send(sender=election, election=election, user=user, voter=voter, cast_vote=cast_vote) - - if status_update_message and user.can_update_status(): - from views import get_election_url + if status_update_message and user.can_update_status(): user.update_status(status_update_message) else: - logger = cast_vote_verify_and_store.get_logger(**kwargs) + logger = get_logger(cast_vote_verify_and_store.__name__) logger.error("Failed to verify and store %d" % cast_vote_id) - -@task() + + +@shared_task def voters_email(election_id, subject_template, body_template, extra_vars={}, voter_constraints_include=None, voter_constraints_exclude=None): """ voter_constraints_include are conditions on including voters voter_constraints_exclude are conditions on excluding voters """ - election = Election.objects.get(id = election_id) + election = Election.objects.get(id=election_id) # select the right list of voters voters = election.voter_set.all() @@ -53,61 +50,66 @@ def voters_email(election_id, subject_template, body_template, extra_vars={}, voters = voters.exclude(**voter_constraints_exclude) for voter in voters: - single_voter_email.delay(voter.uuid, subject_template, body_template, extra_vars) + single_voter_email.delay(voter.uuid, subject_template, body_template, extra_vars) -@task() + +@shared_task def voters_notify(election_id, notification_template, extra_vars={}): - election = Election.objects.get(id = election_id) + election = Election.objects.get(id=election_id) for voter in election.voter_set.all(): single_voter_notify.delay(voter.uuid, notification_template, extra_vars) -@task() + +@shared_task def single_voter_email(voter_uuid, subject_template, body_template, extra_vars={}): - voter = Voter.objects.get(uuid = voter_uuid) + voter = Voter.objects.get(uuid=voter_uuid) the_vars = copy.copy(extra_vars) - the_vars.update({'voter' : voter}) + the_vars.update({'voter': voter}) subject = render_template_raw(None, subject_template, the_vars) body = render_template_raw(None, body_template, the_vars) voter.send_message(subject, body) -@task() + +@shared_task def single_voter_notify(voter_uuid, notification_template, extra_vars={}): - voter = Voter.objects.get(uuid = voter_uuid) + voter = Voter.objects.get(uuid=voter_uuid) the_vars = copy.copy(extra_vars) - the_vars.update({'voter' : voter}) + the_vars.update({'voter': voter}) notification = render_template_raw(None, notification_template, the_vars) voter.send_notification(notification) -@task() + +@shared_task def election_compute_tally(election_id): - election = Election.objects.get(id = election_id) + election = Election.objects.get(id=election_id) election.compute_tally() - election_notify_admin.delay(election_id = election_id, - subject = "encrypted tally computed", - body = """ + election_notify_admin.delay(election_id=election_id, + subject="encrypted tally computed", + body=""" The encrypted tally for election %s has been computed. -- Helios """ % election.name) - + if election.has_helios_trustee(): - tally_helios_decrypt.delay(election_id = election.id) + tally_helios_decrypt.delay(election_id=election.id) + -@task() +@shared_task def tally_helios_decrypt(election_id): - election = Election.objects.get(id = election_id) + election = Election.objects.get(id=election_id) election.helios_trustee_decrypt() - election_notify_admin.delay(election_id = election_id, - subject = 'Helios Decrypt', - body = """ + election_notify_admin.delay(election_id=election_id, + subject='Helios Decrypt', + body=""" Helios has decrypted its portion of the tally for election %s. @@ -115,13 +117,14 @@ for election %s. Helios """ % election.name) -@task() + +@shared_task def voter_file_process(voter_file_id): - voter_file = VoterFile.objects.get(id = voter_file_id) + voter_file = VoterFile.objects.get(id=voter_file_id) voter_file.process() - election_notify_admin.delay(election_id = voter_file.election.id, - subject = 'voter file processed', - body = """ + election_notify_admin.delay(election_id=voter_file.election.id, + subject='voter file processed', + body=""" Your voter file upload for election %s has been processed. @@ -131,7 +134,8 @@ has been processed. Helios """ % (voter_file.election.name, voter_file.num_voters)) -@task() + +@shared_task def election_notify_admin(election_id, subject, body): - election = Election.objects.get(id = election_id) + election = Election.objects.get(id=election_id) election.admin.send_message(subject, body) diff --git a/helios/templates/_castconfirm_docast.html b/helios/templates/_castconfirm_docast.html index fa6b3e70a48f53299dfa4215c3e6577f1effd014..1a91b213767ad733344124fe487ddc3ec67cfe14 100644 --- a/helios/templates/_castconfirm_docast.html +++ b/helios/templates/_castconfirm_docast.html @@ -23,7 +23,7 @@ You are logged in as <u>{{voter.display_html_big|safe}}</u><br /><br /> </form> <p> - <button class="tiny" onclick="document.location='./view';">cancel</button><br /> + <button class="tiny" onclick="document.location='{% url "election@view" election.uuid %}';">cancel</button><br /> <span style="font-size:0.8em;">If you cancel now, your ballot will <em>NOT</em> be recorded.<br /> You can start the voting process over again, of course.</span> </p> diff --git a/helios/templates/_castconfirm_password.html b/helios/templates/_castconfirm_password.html index 25e31de2145cfba6ca39ffb009667ea14993cac8..03e698af7800a10ffae24aec3bd5eacc032d1162 100644 --- a/helios/templates/_castconfirm_password.html +++ b/helios/templates/_castconfirm_password.html @@ -1,5 +1,5 @@ Please provide the voter ID and password you received by email.<br /><br /> -<form method="post" action="{% url "helios.views.password_voter_login" election.uuid %}" onsubmit="show_waiting()"> +<form method="post" action="{% url "election@password-voter-login" election.uuid %}" onsubmit="show_waiting()"> <input type="hidden" name="csrf_token" value="{{csrf_token}}" /> <input type="hidden" name="return_url" value="{{return_url}}" /> <input type="hidden" name="cast_ballot" value="{{cast_ballot}}" /> diff --git a/helios/templates/cast_done.html b/helios/templates/cast_done.html index 75265abb060e5c9b3e0582330e1261e09efaedf8..dc40805d268b492561f02f036bc64a13970cbbcc 100644 --- a/helios/templates/cast_done.html +++ b/helios/templates/cast_done.html @@ -15,12 +15,12 @@ {% if logout %} <p><b>For your safety, we have logged you out.</b></p> -<iframe width="0" height="0" border="0" frameborder="0" src="/auth/logout"> +<iframe width="0" height="0" border="0" frameborder="0" src="{% url "auth@logout" %}"> </iframe> {% endif %} <p style="font-size: 1.4em;"> -[ <a href="{% url "helios.views.one_election_view" election.uuid %}">return to election info</a> ] +[ <a href="{% url "election@view" election.uuid %}">return to election info</a> ] </p> {% endblock %} diff --git a/helios/templates/castvote.html b/helios/templates/castvote.html index 1c21d73f50e12e267c2ffbe247600813f12fb40d..21b5fa72fa719c811a449dd881c5e6d33dec3232 100644 --- a/helios/templates/castvote.html +++ b/helios/templates/castvote.html @@ -3,7 +3,7 @@ {% block title %}{{cast_vote.vote_tinyhash}} — {{election.name}}{% endblock %} {% block content %} <h2 class="title">Cast Vote {{cast_vote.vote_tinyhash}}</h2> -cast in <a href="{% url "helios.views.one_election_view" election.uuid %}">{{election.name}}</a><br /> +cast in <a href="{% url "election@view" election.uuid %}">{{election.name}}</a><br /> Fingerprint: <tt>{{cast_vote.vote_hash}}</tt><br /> by <b><u> {% if the_voter.alias %} diff --git a/helios/templates/combine_decryptions.html b/helios/templates/combine_decryptions.html index 3530867df835c5fbeb0d9d591b50d0842be7eb95..89d60f120b61cc4c70976d1f74f52a641890f2a7 100644 --- a/helios/templates/combine_decryptions.html +++ b/helios/templates/combine_decryptions.html @@ -2,7 +2,7 @@ {% block title %}Compute Tally — {{election.name}}{% endblock %} {% block content %} - <h2 class="title">{{election.name}} — Compute Tally <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election.uuid %}">cancel</a>]</span></h2> + <h2 class="title">{{election.name}} — Compute Tally <span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">cancel</a>]</span></h2> <p> You are about to compute the tally for this election. You only will then see the results. diff --git a/helios/templates/election_audited_ballots.html b/helios/templates/election_audited_ballots.html index 2e01f9b3254d46ad750322e1996baa5c99bb3d60..8225c1bc03a8ccf3f58da8d26572bbf454d4dbed 100644 --- a/helios/templates/election_audited_ballots.html +++ b/helios/templates/election_audited_ballots.html @@ -3,7 +3,7 @@ {% block title %}Audited Ballots for {{election.name}}{% endblock %} {% block content %} - <h2 class="title">{{election.name}} — Audited Ballots <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election_uuid=election.uuid %}">back to election</a>]</span></h2> + <h2 class="title">{{election.name}} — Audited Ballots <span style="font-size:0.7em;">[<a href="{% url "election@view" election_uuid=election.uuid %}">back to election</a>]</span></h2> <p> When you prepare a ballot with Helios, you immediately receive a smart ballot tracker. Before you choose to cast that ballot, you have the option to ask Helios to "break open" that encrypted ballot and verify that Helios encrypted your ballot correctly. Once that's done, you can post that opened ballot here, on the audited ballots' list, for everyone to verify (your identity is not included). Once you've done this, you have to re-encrypt your choices and obtain a different smart ballot tracker. This helps reduce the chance that someone might coerce you to vote differently from your true choice. @@ -14,7 +14,7 @@ These ballots are <em>not cast</em>, and they will not be counted. They are just </p> <p> - To verify an audited ballot, copy its entire content and paste it in the <a target="_new" href="/booth/single-ballot-verify.html?election_url={% url "helios.views.one_election" election.uuid %}">single ballot verifier</a>. + To verify an audited ballot, copy its entire content and paste it in the <a target="_new" href="/booth/single-ballot-verify.html?election_url={% url "election@home" election.uuid %}">single ballot verifier</a>. </p> {% if audited_ballots %} diff --git a/helios/templates/election_bboard.html b/helios/templates/election_bboard.html index a5fd7e7d9dd14bf16f26373bb97c4c277906707a..25821934c5227c7a9695d5552c6018787a8afdf4 100644 --- a/helios/templates/election_bboard.html +++ b/helios/templates/election_bboard.html @@ -3,7 +3,7 @@ {% block title %}Ballot Tracking Center for {{election.name}}{% endblock %} {% block content %} - <h2 class="title">{{election.name}} — Ballot Tracking Center <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election_uuid=election.uuid %}">back to election</a>]</span></h2> + <h2 class="title">{{election.name}} — Ballot Tracking Center <span style="font-size:0.7em;">[<a href="{% url "election@view" election_uuid=election.uuid %}">back to election</a>]</span></h2> <p> This is the ballot tracking center, which displays the tracking numbers of all cast ballots in this election. @@ -16,15 +16,15 @@ Voters {{offset_plus_one}} - {{offset_plus_limit}} {% if next_after %} -<a href="./bboard?after={{next_after}}&offset={{offset_plus_limit}}">next {{limit}}</a> +<a href="{% url "election@bboard" election.uuid %}?after={{next_after}}&offset={{offset_plus_limit}}">next {{limit}}</a> {% endif %} {% ifequal offset 0 %} {% else %} -<a href="./bboard">back to start</a> +<a href="{% url "election@bboard" election.uuid %}">back to start</a> {% endifequal %} {% if more_p %} -<a href="./bboard?after={{next_after}}&offset={{next_offset}}">next {{limit}}</a> +<a href="{% url "election@bboard" election.uuid %}?after={{next_after}}&offset={{next_offset}}">next {{limit}}</a> {% endif %} <table class="pretty"> <tr><th> @@ -40,7 +40,7 @@ Name {{voter.alias}} {% else %} <img border="0" height="20" src="/static/auth/login-icons/{{voter.voter_type}}.png" alt="{{voter.voter_type}}" /> {% if voter.name %}{{voter.name}}{% else %}{{voter.voter_id}}{% endif %} -{% endif %}</td><td><tt style="font-size: 1.4em;;">{% if voter.vote_hash %}{{voter.vote_hash}} <span style="font-size:0.8em;">[<a href="{% url "helios.views.voter_last_vote" election_uuid=election.uuid voter_uuid=voter.uuid %}">view</a>]</span>{% else %}—{% endif %}</tt></td></tr> +{% endif %}</td><td><tt style="font-size: 1.4em;;">{% if voter.vote_hash %}{{voter.vote_hash}} <span style="font-size:0.8em;">[<a href="{% url "election@ballots@voter@last" election_uuid=election.uuid voter_uuid=voter.uuid %}">view</a>]</span>{% else %}—{% endif %}</tt></td></tr> {% endfor %} </table> diff --git a/helios/templates/election_build.html b/helios/templates/election_build.html index 4526d6a05fa75ff5c2b194b8c85ace21635f4335..8f56185fcb93e2c5092168794bfae0cb4c15df72 100644 --- a/helios/templates/election_build.html +++ b/helios/templates/election_build.html @@ -1,7 +1,7 @@ {% extends "helios/templates/cryptobase.html" %} {% block content %} - <h2 class="title">{{election.name}} — Questions <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election.uuid %}">back to election</a>]</span></h2> + <h2 class="title">{{election.name}} — Questions <span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">back to election</a>]</span></h2> <script language="javascript"> {% if election.questions %} diff --git a/helios/templates/election_cast_confirm.html b/helios/templates/election_cast_confirm.html index 346284c6b9670de2e8968a9ed9a6c24cdb733d28..173d729c62997569d22c4b20fbcf081e775cc8c5 100644 --- a/helios/templates/election_cast_confirm.html +++ b/helios/templates/election_cast_confirm.html @@ -73,7 +73,7 @@ requires election-specific credentials. {% endif %} </b><br /></p> <p> - [<a href="{% url "helios.views.one_election_view" election.uuid %}">return to the main election page</a>] + [<a href="{% url "election@view" election.uuid %}">return to the main election page</a>] </p> {% else %} <p> diff --git a/helios/templates/election_compute_tally.html b/helios/templates/election_compute_tally.html index fd98440e3466bdd0d5b1cbfbf8dcdaa263514289..2ad050a0286f6787f84c9349a0b4b3b7170bf8e4 100644 --- a/helios/templates/election_compute_tally.html +++ b/helios/templates/election_compute_tally.html @@ -19,12 +19,12 @@ <input type="hidden" name="csrf_token" value="{{csrf_token}}" /> <input class="button" type="submit" value="compute encrypted tally!" /> -<button onclick="document.location='./view'; return false;">never mind</button> +<button onclick="document.location='{% url "election@view" election.uuid %}'; return false;">never mind</button> </form> {% else %} <p> No votes have been cast in this election. At least one vote must be cast before you compute the tally.<br /><br /> -<a href="./view">back to election</a> +<a href="{% url "election@view" election.uuid %}">back to election</a> </p> {% endif %} </div> diff --git a/helios/templates/election_edit.html b/helios/templates/election_edit.html index 2519fb704d4e74a821a43cd792505179961cd8f4..025b2585513d0240f0005e58de3b80e88f24f375 100644 --- a/helios/templates/election_edit.html +++ b/helios/templates/election_edit.html @@ -2,7 +2,7 @@ {% block content %} - <h2 class="title">{{election.name}} — Update <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election.uuid %}">cancel</a>]</span></h2> + <h2 class="title">{{election.name}} — Update <span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">cancel</a>]</span></h2> {% if error %} <p style="color: red;"> diff --git a/helios/templates/election_extend.html b/helios/templates/election_extend.html index 0db2c448ba96143d1f11d709f6ff924e4da8c46a..813afe7fd8309d449a63f505ef0327d3be23bef1 100644 --- a/helios/templates/election_extend.html +++ b/helios/templates/election_extend.html @@ -2,7 +2,7 @@ {% block content %} - <h2 class="title">{{election.name}} — Extend Voting <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election.uuid %}">cancel</a>]</span></h2> + <h2 class="title">{{election.name}} — Extend Voting <span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">cancel</a>]</span></h2> <form class="prettyform" action="" method="POST" id="edit_election_form"> <input type="hidden" name="csrf_token" value="{{csrf_token}}" /> diff --git a/helios/templates/election_freeze.html b/helios/templates/election_freeze.html index a0aad4dd0eb5b03443c166a996efdf84269f1811..f0229be99638b268af28613b7fc0e83a302f799c 100644 --- a/helios/templates/election_freeze.html +++ b/helios/templates/election_freeze.html @@ -29,14 +29,14 @@ You must freeze the ballot before you can contact voters. <li>{{issue.action}}</li> {% endfor %} </ul> - <a href="{% url "helios.views.one_election_view" election.uuid %}">go back to the election</a> + <a href="{% url "election@view" election.uuid %}">go back to the election</a> </p> {% else %} <form method="post" action=""> <input type="hidden" name="csrf_token" value="{{csrf_token}}" /> <input class="button" type="submit" value="Freeze the ballot" /> -<button onclick="document.location='./view'; return false;">never mind</button> +<button onclick="document.location='{% url "election@view" election.uuid %}'; return false;">never mind</button> </form> {% endif %} diff --git a/helios/templates/election_keygenerator.html b/helios/templates/election_keygenerator.html index 60a2c55c703964884b93c44739c301ed47541561..bce34b8755bce301212faba9ce284908a1d495b6 100644 --- a/helios/templates/election_keygenerator.html +++ b/helios/templates/election_keygenerator.html @@ -35,7 +35,7 @@ $(document).ready(function() { $('#generator').hide(); // get some more server-side randomness for keygen - $.getJSON('../../get-randomness', function(result) { + $.getJSON('{% url "election@get-randomness" election.uuid %}', function(result) { sjcl.random.addEntropy(result.randomness); BigInt.setup(function() { ELGAMAL_PARAMS = ElGamal.Params.fromJSONObject({{eg_params_json|safe}}); @@ -76,11 +76,23 @@ function show_sk() { } function download_sk() { - UTILS.open_window_with_content(jQuery.toJSON(SECRET_KEY), "application/json"); + $('#pk_content').show(); + $('#sk_content').html(jQuery.toJSON(SECRET_KEY)); +} + +function download_sk_to_file(filename) { + var element = document.createElement('a'); + element.setAttribute('href','data:text/plain;charset=utf-8,'+ encodeURIComponent(jQuery.toJSON(SECRET_KEY))); + element.setAttribute('download', filename); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); } function show_pk() { $('#sk_download').hide(); + $('#pk_content').hide(); $('#pk_hash').show(); $('#pk_form').show(); } @@ -101,7 +113,7 @@ function show_pk() { <span id="buttons"><button onclick="generate_keypair(); return false;" id="generate_button">Generate Election Keys</button></span> -<br /><br /> +<br /> If you've already generated a keypair, you can <a href="javascript:show_key_reuse()">reuse it</a>. </p> @@ -126,15 +138,28 @@ Your key has been generated, but you may choose to<br /><a href="javascript:clea </span> <p> - <button style="font-size:16pt;" onclick="download_sk(); $('#pk_link').show();">Save your secret key</button> + <button style="font-size:16pt;" onclick="download_sk(); $('#pk_link').show();">Show my secret key</button> </p> +</div> -<p style="display: none;" id="pk_link"> - <a href="javascript:show_pk();">ok, I've saved the key, let's move on</a>. +<div style="display:none;" id="pk_content"> + <p>Bellow is your trustee secret key content. Please copy its content and save it securely. <br> + You can also click to dowload it to a file. + And please don't lose it! Otherwise it will not be possible to decrypt the election tally.<br> + </p> + <textarea id="sk_content" rows="5" wrap="soft" cols="50" style="height: 25em;"></textarea> +</div> + +<div style="display:none;" id="pk_link"> +<p> +<a id="download_to_file" href="javascript:download_sk_to_file('trustee_key_for_{{election.name}}.txt');">download private key to a file</a> +</p> +<p> + <a href="javascript:show_pk();">ok, I've saved the key, let's move on</a> </p> </div> -<form method="post" id="pk_form" action="{% url "helios.views.trustee_upload_pk" election.uuid trustee.uuid %}"> +<form method="post" id="pk_form" action="{% url "election@trustee@upload-pk" election.uuid trustee.uuid %}"> <h3>Your Public Key</h3> <p> It's time to upload the public key to the server. diff --git a/helios/templates/election_not_started.html b/helios/templates/election_not_started.html index d7a0d73bbbc185f665a2f486da44a4e4a2a1b9d9..595b9d1e80bb43d6b7c4f7f8969003a0a78eba6a 100644 --- a/helios/templates/election_not_started.html +++ b/helios/templates/election_not_started.html @@ -14,6 +14,6 @@ </p> <p> - <a href="{% url "helios.views.one_election_view" election.uuid %}">back to the election</a> + <a href="{% url "election@view" election.uuid %}">back to the election</a> </p> {% endblock %} diff --git a/helios/templates/election_questions.html b/helios/templates/election_questions.html index d7b8f7353c69025b2c4d04d2f7318b3bdeede240..468c65ecf751335f11968aabc5f2e0e9dd8a45d8 100644 --- a/helios/templates/election_questions.html +++ b/helios/templates/election_questions.html @@ -2,7 +2,7 @@ {% block title %}Questions for {{election.name}}{% endblock %} {% block content %} - <h3 class="title">{{election.name}} — Questions <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election.uuid %}">back to election</a>]</span></h3> + <h3 class="title">{{election.name}} — Questions <span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">back to election</a>]</span></h3> <script language="javascript"> {% if election.questions %} diff --git a/helios/templates/election_register.html b/helios/templates/election_register.html index e1c731e0ca219ede8118caddb3fb0bca29dd1333..96a6004559183e637159bf056f614f1ef9da47be 100644 --- a/helios/templates/election_register.html +++ b/helios/templates/election_register.html @@ -16,7 +16,7 @@ You are <em>not</em> registered for this election. </p> -<form method="post" action="{% url "helios.views.one_election_register" election.uuid %}"> +<form method="post" action="{% url "election@register" election.uuid %}"> <input type="submit" value="register!" /> </form> {% endif %} diff --git a/helios/templates/election_tallied.html b/helios/templates/election_tallied.html index b01317f7abf2822865e460a6693da40428a95888..a187e5625d434c90d09ecdcbcbac37242675490f 100644 --- a/helios/templates/election_tallied.html +++ b/helios/templates/election_tallied.html @@ -14,6 +14,6 @@ </p> <p> - <a href="{% url "helios.views.one_election_view" election.uuid %}">view the election tally</a> + <a href="{% url "election@view" election.uuid %}">view the election tally</a> </p> {% endblock %} \ No newline at end of file diff --git a/helios/templates/election_view.html b/helios/templates/election_view.html index ee5c4a1f379b2f5d032c1221de941ef11f9f6682..c89721c7aad7a9e47816587710aebd8524dd5c24 100644 --- a/helios/templates/election_view.html +++ b/helios/templates/election_view.html @@ -5,7 +5,7 @@ <h3 class="title">{{ election.name }} {% if admin_p %} {% if not election.frozen_at %} -<small><a class="small button" href="{% url "helios.views.one_election_edit" election.uuid %}">edit</a></small> +<small><a class="small button" href="{% url "election@edit" election.uuid %}">edit</a></small> {% endif %} {% endif %}</h3> <p style="padding-top:0px; margin-top:0px"> @@ -14,8 +14,8 @@ [archived] {% endif %} {% if admin_p %} - {% if election.is_archived %}<a class="small button" href="{% url "helios.views.one_election_archive" election_uuid=election.uuid %}?archive_p=0">unarchive it</a>{% else %}<a class="small button" href="{% url "helios.views.one_election_archive" election_uuid=election.uuid %}?archive_p=1">archive it</a>{% endif %} -<a class="small button" onclick="return window.confirm('Are you sure you want to copy this election?');" href="{% url "helios.views.one_election_copy" election_uuid=election.uuid %}">copy</a> + {% if election.is_archived %}<a class="small button" href="{% url "election@archive" election_uuid=election.uuid %}?archive_p=0">unarchive it</a>{% else %}<a class="small button" href="{% url "election@archive" election_uuid=election.uuid %}?archive_p=1">archive it</a>{% endif %} +<a class="small button" onclick="return window.confirm('Are you sure you want to copy this election?');" href="{% url "election@copy" election_uuid=election.uuid %}">copy</a> {% endif %} <br /> {% if admin_p %} @@ -23,12 +23,12 @@ {% if election.featured_p %} this {{election.election_type}} is featured on the front page. {% if can_feature_p %} -[<a href="{% url "helios.views.one_election_set_featured" election.uuid %}?featured_p=0">unfeature it</a>] +[<a href="{% url "election@set-featured" election.uuid %}?featured_p=0">unfeature it</a>] {% endif %} {% else %} this {{election.election_type}} is <u>not</u> featured on the front page. {% if can_feature_p %} -[<a href="{% url "helios.views.one_election_set_featured" election.uuid %}?featured_p=1">feature it</a>] +[<a href="{% url "election@set-featured" election.uuid %}?featured_p=1">feature it</a>] {% endif %} {% endif %} {% endif %} @@ -57,11 +57,11 @@ this {{election.election_type}} is <u>not</u> featured on the front page. {% endif %} <p align="center" style="font-size: 1.5em;"> -<a href="{% url "helios.views.one_election_questions" election.uuid %}">questions ({% if election.questions %}{{election.questions|length}}{% else %}0{% endif %})</a> +<a href="{% url "election@questions" election.uuid %}">questions ({% if election.questions %}{{election.questions|length}}{% else %}0{% endif %})</a> | -<a href="{% url "helios.views.voters_list_pretty" election.uuid %}">voters & ballots</a> +<a href="{% url "election@voters@list-pretty" election.uuid %}">voters & ballots</a> | -<a href="{% url "helios.views.list_trustees_view" election.uuid %}">trustees ({{trustees|length}})</a> +<a href="{% url "election@trustees@view" election.uuid %}">trustees ({{trustees|length}})</a> </p> {% if admin_p %} @@ -93,7 +93,7 @@ this {{election.election_type}} is <u>not</u> featured on the front page. {{issue.action}}{% if forloop.last %}{% else %}, and{% endif %}<br /> {% endfor %} {% else %} -<a href="{% url "helios.views.one_election_freeze" election.uuid %}">freeze ballot and open election.</a> +<a href="{% url "election@freeze" election.uuid %}">freeze ballot and open election.</a> <br /> {% if election.voting_starts_at %} once you do this, the election will be ready for voting and will open automatically<br /> @@ -110,20 +110,20 @@ once you do this, the election will be immediately open for voting. Tally computation is under way.<br /> Reload this page in a couple of minutes. {% else %} -<a href="{% url "helios.views.one_election_compute_tally" election.uuid %}">compute encrypted tally</a><br /> +<a href="{% url "election@compute-tally" election.uuid %}">compute encrypted tally</a><br /> The encrypted votes will be combined into an encrypted tally. Once this is done,<br /> trustees will be asked to provide their share of the decryption. {% endif %} {% else %} {% if election.result %} -<a href="{% url "helios.views.release_result" election.uuid %}">release result</a><br /> +<a href="{% url "election@release-result" election.uuid %}">release result</a><br /> The result displayed below is visible only to you.<br /> Once you release the result, it will be visible to everyone. {% else %} {% if election.ready_for_decryption_combination %} -<a href="{% url "helios.views.combine_decryptions" election.uuid %}"> +<a href="{% url "election@combine-decryptions" election.uuid %}"> {% if election.num_trustees == 1 %} compute results {% else %} @@ -137,7 +137,7 @@ The decryption shares from the trustees will be combined and the tally computed. Once you do this, the tally will visible to you, the administrator, only. {% endif %} {% else %} -<a href="{% url "helios.views.list_trustees_view" election.uuid %}">trustees (for decryption)</a> +<a href="{% url "election@trustees@view" election.uuid %}">trustees (for decryption)</a> {% endif %} {% endif %} @@ -242,7 +242,7 @@ You are <em>not eligible</em> to vote in this {{election.election_type}}. {% if election.openreg %} {% if election.eligibility %} This election is open to: {{election.pretty_eligibility|safe}} -<a href="{{settings.SECURE_URL_HOST}}{% url "helios_auth.views.index" %}?return_url={{CURRENT_URL}}">Log in</a> to check your eligibility. +<a href="{{settings.SECURE_URL_HOST}}{% url "auth@index" %}?return_url={{CURRENT_URL}}">Log in</a> to check your eligibility. {% else %} Anyone can vote in this election. {% endif %} @@ -255,7 +255,7 @@ Anyone can vote in this election. {% endif %} {% if admin_p and election.voting_ends_at and not election.tallying_started_at %} -<br /><a href="{% url "helios.views.one_election_extend" election.uuid %}">extend voting</a><br /> +<br /><a href="{% url "election@extend" election.uuid %}">extend voting</a><br /> {% endif %} <div style="background: lightyellow; padding:5px; padding-left: 10px; margin-top: 15px; border: 1px solid #aaa; width: 720px;" class="round"> @@ -277,22 +277,22 @@ Anyone can vote in this election. {% endif %} <p style="font-size: 1.3em;"> -<a href="{% url "helios.views.voters_list_pretty" election.uuid %}">Ballot Tracking Center</a> | -<a href="{% url "helios.views.one_election_audited_ballots" election.uuid %}">Audited Ballots</a> +<a href="{% url "election@voters@list-pretty" election.uuid %}">Ballot Tracking Center</a> | +<a href="{% url "election@audited-ballots" election.uuid %}">Audited Ballots</a> </p> {% endif %} {% if not election.voting_has_started %} <p style="font-size: 1.2em;"> - <a href="{{SECURE_URL_HOST}}/booth/vote.html?election_url={% url "helios.views.one_election" election.uuid %}">preview booth</a> + <a href="{{SECURE_URL_HOST}}/booth/vote.html?election_url={% url "election@home" election.uuid %}">preview booth</a> </p> {% endif %} {% if election.voting_has_stopped %} <p style="font-size: 1.2em;"> {% if election.result %} -verify <a target="_blank" href="/verifier/verify.html?election_url={% url "helios.views.one_election" election.uuid %}">election tally</a>. +verify <a target="_blank" href="/verifier/verify.html?election_url={% url "election@home" election.uuid %}">election tally</a>. {% endif %} review the <a href="{{vote_url}}">voting booth</a>. diff --git a/helios/templates/elections_administered.html b/helios/templates/elections_administered.html index 03bc226c6583e2ab94b516b05bd16efbf1128b30..563ecc9fe5450e42a2b2ae9ed144648b4eb5e9bb 100644 --- a/helios/templates/elections_administered.html +++ b/helios/templates/elections_administered.html @@ -5,7 +5,7 @@ <ul> {% for election in elections %} -<li> <a href="{% url "helios.views.one_election_view" election.uuid %}">{{election.name}}</a><em> - {{election.num_voters}} voters / {{election.num_cast_votes}} cast votes</em></li> +<li> <a href="{% url "election@view" election.uuid %}">{{election.name}}</a><em> - {{election.num_voters}} voters / {{election.num_cast_votes}} cast votes</em></li> {% endfor %} </ul> {% endblock %} \ No newline at end of file diff --git a/helios/templates/elections_voted.html b/helios/templates/elections_voted.html index 7447eb09f63ef179d5e9afa0166b8f5736cf2b05..f013a983b72c4991f9d4a6a353747a55b5708193 100644 --- a/helios/templates/elections_voted.html +++ b/helios/templates/elections_voted.html @@ -5,7 +5,7 @@ <ul> {% for election in elections %} -<li> <a href="{% url "helios.views.one_election_view" election.uuid %}">{{election.name}}</a></li> +<li> <a href="{% url "election@view" election.uuid %}">{{election.name}}</a></li> {% endfor %} </ul> {% endblock %} diff --git a/helios/templates/list_trustees.html b/helios/templates/list_trustees.html index 39ddd5553f74b66896be55c57693f5449df41305..47032c4d1c5e65cf2b476003edba3d24069968b6 100644 --- a/helios/templates/list_trustees.html +++ b/helios/templates/list_trustees.html @@ -3,7 +3,7 @@ {% block title %}Trustees for {{election.name}}{% endblock %} {% block content %} - <h3 class="title">{{election.name}} — Trustees <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election.uuid %}">back to election</a>]</span></h3> + <h3 class="title">{{election.name}} — Trustees <span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">back to election</a>]</span></h3> <p> Trustees are responsible for decrypting the election result.<br /> @@ -21,11 +21,11 @@ {% if admin_p %} <p> - [ <a onclick="return(confirm('Adding your own trustee requires a good bit more work to tally the election.\nYou will need to have trustees generate keypairs and safeguard their secret key.\n\nIf you are not sure what that means, we strongly recommend\nclicking Cancel and letting Helios tally the election for you.'));" href="{% url "helios.views.new_trustee" election.uuid %}">add a trustee</a> ] + [ <a onclick="return(confirm('Adding your own trustee requires a good bit more work to tally the election.\nYou will need to have trustees generate keypairs and safeguard their secret key.\n\nIf you are not sure what that means, we strongly recommend\nclicking Cancel and letting Helios tally the election for you.'));" href="{% url "election@trustees@new" election.uuid %}">add a trustee</a> ] </p> {% if not election.has_helios_trustee %} <p> - <a href="{% url "helios.views.new_trustee_helios" election.uuid %}">add Helios as a trustee</a> + <a href="{% url "election@trustees@add-helios" election.uuid %}">add Helios as a trustee</a> </p> {% endif %} {% endif %} @@ -39,11 +39,11 @@ <h5> Trustee #{{forloop.counter}}: {{t.name}} {% if admin_p %} {% if t.secret_key %} -{% if not election.frozen_at %}[<a onclick="return confirm('Are you sure you want to remove Helios as a trustee?');" href="{% url "helios.views.delete_trustee" election.uuid %}?uuid={{t.uuid}}">x</a>]{% endif %} +{% if not election.frozen_at %}[<a onclick="return confirm('Are you sure you want to remove Helios as a trustee?');" href="{% url "election@trustees@delete" election.uuid %}?uuid={{t.uuid}}">x</a>]{% endif %} {% else %} ({{t.email}}) -{% if not election.frozen_at %}[<a onclick="return confirm('Are you sure you want to remove this Trustee?');" href="{% url "helios.views.delete_trustee" election.uuid %}?uuid={{t.uuid}}">x</a>]{% endif %} -[<a onclick="return confirm('Are you sure you want to send this trustee his/her admin URL?');" href="{% url "helios.views.trustee_send_url" election.uuid t.uuid %}">send login</a>] +{% if not election.frozen_at %}[<a onclick="return confirm('Are you sure you want to remove this Trustee?');" href="{% url "election@trustees@delete" election.uuid %}?uuid={{t.uuid}}">x</a>]{% endif %} +[<a onclick="return confirm('Are you sure you want to send this trustee his/her admin URL?');" href="{% url "election@trustee@send-url" election.uuid t.uuid %}">send login</a>] {% endif %} {% endif %} </h5> diff --git a/helios/templates/new_trustee.html b/helios/templates/new_trustee.html index 0df1a27c287fcb2a8a960315604ea7e94ec37370..75af0545bd60262c22a9349ea8ae33581cf73a22 100644 --- a/helios/templates/new_trustee.html +++ b/helios/templates/new_trustee.html @@ -1,7 +1,7 @@ {% extends "helios/templates/cryptobase.html" %} {% block content %} - <h2 class="title">{{election.name}} — New Trustee <span style="font-size:0.7em;">[<a href="{% url "helios.views.list_trustees_view" election.uuid %}">cancel</a>]</span></h2> + <h2 class="title">{{election.name}} — New Trustee <span style="font-size:0.7em;">[<a href="{% url "election@trustees@view" election.uuid %}">cancel</a>]</span></h2> <form method="post" action=""> <input type="hidden" name="csrf_token" value="{{csrf_token}}" /> diff --git a/helios/templates/release_result.html b/helios/templates/release_result.html index cac6c64a7a6e62d07691ecbf877d09780607c495..5e467e471cb58ccee1319e362109a96d461946a5 100644 --- a/helios/templates/release_result.html +++ b/helios/templates/release_result.html @@ -2,7 +2,7 @@ {% block title %}Release Result — {{election.name}}{% endblock %} {% block content %} - <h2 class="title">{{election.name}} — Release Result <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election.uuid %}">cancel</a>]</span></h2> + <h2 class="title">{{election.name}} — Release Result <span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">cancel</a>]</span></h2> <p> You are about to release the result for this election. diff --git a/helios/templates/stats.html b/helios/templates/stats.html index 37468b7150a3e90bf27c1ea7c89028c8a089d231..3f2bffe2b415bf3148bcd716640cbfa7199b9a45 100644 --- a/helios/templates/stats.html +++ b/helios/templates/stats.html @@ -5,11 +5,11 @@ <h1>Admin</h1> <ul> -<li> <a href="{% url "helios.stats_views.elections" %}">elections</a></li> -<li> <a href="{% url "helios.stats_views.recent_votes" %}">recent votes</a></li> -<li> <a href="{% url "helios.stats_views.recent_problem_elections" %}">recent problem elections</a></li> +<li> <a href="{% url "stats@elections" %}">elections</a></li> +<li> <a href="{% url "stats@recent-votes" %}">recent votes</a></li> +<li> <a href="{% url "stats@elections-problems" %}">recent problem elections</a></li> </ul> -<p><b>{{num_votes_in_queue}}</b> votes in queue. {% if num_votes_in_queue %}[<a href="{% url "helios.stats_views.force_queue" %}">force it</a>]{% endif %}</p> +<p><b>{{num_votes_in_queue}}</b> votes in queue. {% if num_votes_in_queue %}[<a href="{% url "stats@force-queue" %}">force it</a>]{% endif %}</p> {% endblock %} diff --git a/helios/templates/stats_elections.html b/helios/templates/stats_elections.html index 7179698334bbaecfc80f458b9b5061d560ddd9ea..e9a498ef8e9a5a21d5069eb3ac5861ff4726dd41 100644 --- a/helios/templates/stats_elections.html +++ b/helios/templates/stats_elections.html @@ -5,7 +5,7 @@ <h1>Elections</h1> <p> -<form method="get" action="{% url "helios.stats_views.elections" %}"> +<form method="get" action="{% url "stats@elections" %}"> <b>search</b>: <input type="text" name="q" value="{{q}}"/> <input class="small button" type="submit" value="search" /> <a class="small button" href="?">clear search</a> </form> @@ -26,7 +26,7 @@ Elections {{elections_page.start_index}} - {{elections_page.end_index}} (of {{to {% for election in elections %} <p> -<b><a href="{% url "helios.views.one_election_view" election.uuid %}">{{election.name}}</a></b> by <a href="mailto:{{election.admin.info.email}}">{{election.admin.pretty_name}}</a> -- {{election.num_voters}} voters / {{election.num_cast_votes}} cast votes +<b><a href="{% url "election@view" election.uuid %}">{{election.name}}</a></b> by <a href="mailto:{{election.admin.info.email}}">{{election.admin.pretty_name}}</a> -- {{election.num_voters}} voters / {{election.num_cast_votes}} cast votes </p> {% endfor %} diff --git a/helios/templates/stats_problem_elections.html b/helios/templates/stats_problem_elections.html index 9f8c1dadd43bf52905d1c844088443ea72da07b0..2f82f74b92050540ce84ab43f4e522d87ea0f92d 100644 --- a/helios/templates/stats_problem_elections.html +++ b/helios/templates/stats_problem_elections.html @@ -8,7 +8,7 @@ Unfrozen for more than a day. {% for election in elections %} <p> -<b><a href="{% url "helios.views.one_election_view" election.uuid %}">{{election.name}}</a></b> -- {{election.num_voters}} voters +<b><a href="{% url "election@view" election.uuid %}">{{election.name}}</a></b> -- {{election.num_voters}} voters </p> {% endfor %} diff --git a/helios/templates/stats_recent_votes.html b/helios/templates/stats_recent_votes.html index 37c074161f3f973c4bab1d9c4f2d2118c79f327c..bf360a28482a477bcecfb0766045f39563167f71 100644 --- a/helios/templates/stats_recent_votes.html +++ b/helios/templates/stats_recent_votes.html @@ -8,7 +8,7 @@ Last 24 hours {% for election in elections %} <p> -<b><a href="{% url "helios.views.one_election_view" election.uuid %}">{{election.name}}</a></b> -- {{election.last_cast_vote}} {{election.num_recent_cast_votes}} recently cast votes +<b><a href="{% url "election@view" election.uuid %}">{{election.name}}</a></b> -- {{election.last_cast_vote}} {{election.num_recent_cast_votes}} recently cast votes </p> {% endfor %} diff --git a/helios/templates/trustee_check_sk.html b/helios/templates/trustee_check_sk.html index f6b615e43b9f1400b752a39df6dfa491e5ce8de9..99380d31c5e8024fab0c51edbbb785c233d3c73c 100644 --- a/helios/templates/trustee_check_sk.html +++ b/helios/templates/trustee_check_sk.html @@ -43,7 +43,7 @@ function check_sk(sk_value) { } } </script> - <h2 class="title">{{election.name}} — Trustee {{trustee.name}} — Check Secret Key <span style="font-size:0.7em;">[<a href="./home">back to trustee home</a>]</span></h2> + <h2 class="title">{{election.name}} — Trustee {{trustee.name}} — Check Secret Key <span style="font-size:0.7em;">[<a href="{% url "election@trustee" election_uuid=election.uuid trustee_uuid=trustee.uuid %}">back to trustee home</a>]</span></h2> <p> Your public key fingerprint is: <b>{{trustee.public_key_hash}}</b> @@ -59,7 +59,7 @@ To verify that you have the right secret key, paste it here: <p> <form onsubmit="check_sk(this.secret_key.value); this.secret_key.value=''; return false;"> -<textarea name="secret_key" cols="60" rows ="5" wrap="soft"> +<textarea name="secret_key" cols="60" rows ="5" wrap="soft" style="height: 25em;"> </textarea> <br /> <input type="submit" value="check" /> diff --git a/helios/templates/trustee_decrypt_and_prove.html b/helios/templates/trustee_decrypt_and_prove.html index d4cbe438493aa0c6c9a00778acccaece94bc5956..cb4c2e68caa5b627ffdd12284ba1805105a1f5a8 100644 --- a/helios/templates/trustee_decrypt_and_prove.html +++ b/helios/templates/trustee_decrypt_and_prove.html @@ -157,7 +157,7 @@ function reset() { <form onsubmit="return false;"> <h3>FIRST STEP: enter your secret key</h3> - <textarea id="sk_textarea" cols="60" rows="5"></textarea> + <textarea id="sk_textarea" cols="60" rows="5" style="height: 25em;"></textarea> </form> <p id="tally_section"> <button onclick="do_tally();">Generate partial decryption</button> @@ -187,17 +187,15 @@ function reset() { When you're ready, you can submit this result to the server. </p> Your partial decryption:<br /> - <form action="javascript:submit_result();"> - <textarea id="result_textarea" cols="60" rows="5" wrap="soft"></textarea><br /><br /> - <input type="submit" value="Upload decryption factors to server" /> - </form> - <br /> - <a href="javascript:reset()">reset and restart decryption process</a> - <br /> + <p> + <textarea id="result_textarea" cols="60" rows="5" wrap="soft" style="height: 25em;"></textarea> + <button onclick="submit_result();">Upload decryption factors to server</button> + </p> + <p><a href="javascript:reset()">reset and restart decryption process</a></p> </div> <div id="done_div"> - Done! <a href="{% url "helios.views.one_election_view" election.uuid %}">Back to election</a> + Done! <a href="{% url "election@view" election.uuid %}">Back to election</a> </div> <div id="error_div"> diff --git a/helios/templates/trustee_home.html b/helios/templates/trustee_home.html index 5f6ee9c54e66c1884114827b208f518888c96bc8..8d6dd115179c563bd90cb2886813e6285dd97d71 100644 --- a/helios/templates/trustee_home.html +++ b/helios/templates/trustee_home.html @@ -7,9 +7,9 @@ {% if trustee.public_key_hash %} You have successfully uploaded your public key.<br /> Your public key fingerprint is: <b>{{trustee.public_key_hash}}</b>.<br /> -You can <a href="{% url "helios.views.trustee_check_sk" election.uuid trustee.uuid %}">verify that you have the right secret key</a>. +You can <a href="{% url "election@trustee@check-sk" election.uuid trustee.uuid %}">verify that you have the right secret key</a>. {% else %} -<a href="{% url "helios.views.trustee_keygenerator" election.uuid trustee.uuid %}">setup your key</a> +<a href="{% url "election@trustee@key-generator" election.uuid trustee.uuid %}">setup your key</a> {% endif %} </p> @@ -19,7 +19,7 @@ You can <a href="{% url "helios.views.trustee_check_sk" election.uuid trustee.uu You have successfully uploaded your decryption. {% else %} The encrypted tally for this election is ready.<br /> - <a href="{% url "helios.views.trustee_decrypt_and_prove" election.uuid trustee.uuid %}">decrypt with your key</a> + <a href="{% url "election@trustee@decrypt-and-prove" election.uuid trustee.uuid %}">decrypt with your key</a> {% endif %} {% else %} Once the tally is computed, come back here to provide your secret key for decryption purposes.<br /> diff --git a/helios/templates/voters_eligibility.html b/helios/templates/voters_eligibility.html index 51343befd47591e0b937a484a32b4a3dfe7675ea..0baa6f7f968ddf5067580f18e75c28d69c353730 100644 --- a/helios/templates/voters_eligibility.html +++ b/helios/templates/voters_eligibility.html @@ -2,7 +2,7 @@ {% block title %}Voter Eligibility for {{election.name}}{% endblock %} {% block content %} - <h2 class="title">{{election.name}} — Voter Eligibility <span style="font-size:0.7em;">[<a href="{% url "helios.views.voters_list_pretty" election.uuid %}">back to voters</a>]</span></h2> + <h2 class="title">{{election.name}} — Voter Eligibility <span style="font-size:0.7em;">[<a href="{% url "election@voters@list-pretty" election.uuid %}">back to voters</a>]</span></h2> <p> <em>{{election.pretty_eligibility|safe}}</em> diff --git a/helios/templates/voters_email.html b/helios/templates/voters_email.html index 535f7533fbeaf6cd87753c2f5f0c8788f03f4a95..62a6c7d63bc52eb57da0c0353a1151ba193b0938 100644 --- a/helios/templates/voters_email.html +++ b/helios/templates/voters_email.html @@ -9,7 +9,7 @@ voter_id = '{{voter.voter_id}}'; {% endif %} </script> - <h2 class="title">{{election.name}} — Contact Voters <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election.uuid %}">back to election</a>]</span></h2> + <h2 class="title">{{election.name}} — Contact Voters <span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">back to election</a>]</span></h2> {% if voter %} <p> @@ -55,7 +55,7 @@ You may tweak the subject and add a custom message using the form below. </div> <div id="done" style="display:none;"> - Done, go <a href="{% url "helios.views.one_election_view" election.uuid %}">back to election</a>. + Done, go <a href="{% url "election@view" election.uuid %}">back to election</a>. </div> <div id="error" style="display:none;"> diff --git a/helios/templates/voters_list.html b/helios/templates/voters_list.html index fffe57fdbc717c6c97cb39f4527938bccdacf811..3b345a06bc4f0d05eed048bf76cf17c333be897d 100644 --- a/helios/templates/voters_list.html +++ b/helios/templates/voters_list.html @@ -2,7 +2,7 @@ {% block title %}Voters & Ballot Tracking Center for {{election.name}}{% endblock %} {% block content %} - <h3 class="title">{{election.name}} — Voters and Ballot Tracking Center <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election.uuid %}">back to election</a>]</span></h3> + <h3 class="title">{{election.name}} — Voters and Ballot Tracking Center <span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">back to election</a>]</span></h3> <p> <b>Who can vote?</b> @@ -18,7 +18,7 @@ <em>Your election is marked private, which means you cannot open registration up more widely</em>.<br /> {% else %} You can change this setting: -<form method="post" action="{% url "helios.views.voters_eligibility" election.uuid %}"> +<form method="post" action="{% url "election@voters@eligibility" election.uuid %}"> <input type="hidden" name="csrf_token" value="{{csrf_token}}" /> <input type="radio" name="eligibility" value="openreg" {% if election.openreg and not election.eligibility %}CHECKED{% endif %} /> anyone can vote<br /> <input type="radio" name="eligibility" value="closedreg" {% if not election.openreg %}CHECKED{% endif %} /> only voters listed explicitly below can vote<br /> @@ -37,7 +37,7 @@ You can change this setting: {% endif %} {% if email_voters and election.frozen_at and admin_p %} -<p><a class="button" href="{% url "helios.views.voters_email" election.uuid %}">email voters</a></p> +<p><a class="button" href="{% url "election@voters@email" election.uuid %}">email voters</a></p> {% endif %} {% if election.num_voters > 20 %} @@ -45,7 +45,7 @@ You can change this setting: {% if q %} <p><em>searching for <u>{{q}}</u>.</em> [<a href="?">clear search</a>]</p> {% else %} -<form method="get" action="{% url "helios.views.voters_list_pretty" election.uuid %}"><b>search</b>: <input type="text" name="q" /> <input type="submit" value="search" /></form> +<form method="get" action="{% url "election@voters@list-pretty" election.uuid %}"><b>search</b>: <input type="text" name="q" /> <input type="submit" value="search" /></form> {% endif %} </p> {% endif %} @@ -54,7 +54,7 @@ You can change this setting: <!-- Add a Voter: WORK HERE--> {% if upload_p and not election.openreg %} <p> -<a class="button" href="{% url "helios.views.voters_upload" election_uuid=election.uuid %}">bulk upload voters</a> +<a class="button" href="{% url "election@voters@upload" election_uuid=election.uuid %}">bulk upload voters</a> </p> {% if voter_files %} @@ -99,14 +99,14 @@ no votes yet </p> {% if voters_page.has_previous %} -<a href="./list?page={{voters_page.previous_page_number}}&limit={{limit}}&q={{q|urlencode}}">previous {{limit}}</a> +<a href="{% url "election@voters@list-pretty" election.uuid %}?page={{voters_page.previous_page_number}}&limit={{limit}}&q={{q|urlencode}}">previous {{limit}}</a> {% endif %} Voters {{voters_page.start_index}} - {{voters_page.end_index}} (of {{total_voters}}) {% if voters_page.has_next %} -<a href="./list?page={{voters_page.next_page_number}}&limit={{limit}}&q={{q|urlencode}}">next {{limit}}</a> +<a href="{% url "election@voters@list-pretty" election.uuid %}?page={{voters_page.next_page_number}}&limit={{limit}}&q={{q|urlencode}}">next {{limit}}</a> {% endif %} <table class="pretty"> @@ -131,9 +131,9 @@ Voters {{voters_page.start_index}} - {{voters_page.end_index}} (of {{total_voter {% if admin_p %} <td style="white-space: nowrap;"> {% if election.frozen_at %} -[<a href="{% url "helios.views.voters_email" election.uuid %}?voter_id={{voter.voter_login_id}}">email</a>] +[<a href="{% url "election@voters@email" election.uuid %}?voter_id={{voter.voter_login_id}}">email</a>] {% endif %} -[<a onclick="return confirm('are you sure you want to remove {{voter.name}} ?');" href="{% url "helios.views.voter_delete" election.uuid voter.uuid %}">x</a>] +[<a onclick="return confirm('are you sure you want to remove {{voter.name}} ?');" href="{% url "election@voter@delete" election.uuid voter.uuid %}">x</a>] </td> <td>{{voter.voter_login_id}}</td> <td>{{voter.voter_email}}</td> @@ -143,7 +143,7 @@ Voters {{voters_page.start_index}} - {{voters_page.end_index}} (of {{total_voter {% if election.use_voter_aliases %} <td>{{voter.alias}}</td> {% endif %} -<td><tt style="font-size: 1.4em;">{% if voter.vote_hash %}{{voter.vote_hash}} <span style="font-size:0.8em;">[<a href="{% url "helios.views.castvote_shortcut" vote_tinyhash=voter.vote_tinyhash %}">view</a>]</span>{% else %}—{% endif %}</tt></td> +<td><tt style="font-size: 1.4em;">{% if voter.vote_hash %}{{voter.vote_hash}} <span style="font-size:0.8em;">[<a href="{% url "shortcut@vote" vote_tinyhash=voter.vote_tinyhash %}">view</a>]</span>{% else %}—{% endif %}</tt></td> </tr> {% endfor %} </table> diff --git a/helios/templates/voters_manage.html b/helios/templates/voters_manage.html index ccd54906ab945a00ec8b8cb5b57fd59ae71523a5..f2a83c794091be3b5268ad0734f9a8135d61cb1c 100644 --- a/helios/templates/voters_manage.html +++ b/helios/templates/voters_manage.html @@ -1,12 +1,12 @@ {% extends TEMPLATE_BASE %} {% block content %} - <h2 class="title">{{election.name}} — Manage Voters <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election.uuid %}">back to election</a>]</span></h2> + <h2 class="title">{{election.name}} — Manage Voters <span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">back to election</a>]</span></h2> <form method="get" action="{% url "helios.views.voters_search" election.uuid %}"><b>search</b>: <input type="text" name="q" /> <input type="submit" value="search" /></form> {% if upload_p %} -<p><a href="{% url "helios.views.voters_upload" election_uuid=election.uuid %}">bulk upload voters</a></p> +<p><a href="{% url "election@voters@upload" election_uuid=election.uuid %}">bulk upload voters</a></p> {% endif %} Voters {{offset_plus_one}} - {{offset_plus_limit}} @@ -29,7 +29,7 @@ Voters {{offset_plus_one}} - {{offset_plus_limit}} <tr><td>{{voter.alias}}</td><td>{{voter.name}}</td><td>{{voter.voter_id}} {% if election.frozen_at %} {% else %} -[<a onclick="return confirm('are you sure you want to remove {{voter.name}} ?');" href="{% url "helios.views.voter_delete" election.uuid voter.uuid %}">x</a>] +[<a onclick="return confirm('are you sure you want to remove {{voter.name}} ?');" href="{% url "election@voter@delete" election.uuid voter.uuid %}">x</a>] {% endif %} </td></tr> {% endfor %} diff --git a/helios/templates/voters_search.html b/helios/templates/voters_search.html index 1adfb33fe1bf5123a30bae6eb03915644a23740b..162051f7095b9af671faa20000246c592968b673 100644 --- a/helios/templates/voters_search.html +++ b/helios/templates/voters_search.html @@ -1,12 +1,12 @@ {% extends TEMPLATE_BASE %} {% block content %} - <h2 class="title">{{election.name}} — Search Voters for '{{search_term}}' <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election.uuid %}">back to election</a>]</span></h2> + <h2 class="title">{{election.name}} — Search Voters for '{{search_term}}' <span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">back to election</a>]</span></h2> {% if voter %} Voter Found: {{voter.name}} ({{voter.voter_id}})<br /><br /> {% if election.frozen_at %} -<a href="{% url "helios.views.voters_email" election.uuid %}?voter_id={{voter.voter_id}}">email this voter</a> +<a href="{% url "election@voters@email" election.uuid %}?voter_id={{voter.voter_id}}">email this voter</a> {% else %} once this election is frozen, you'll be able to email this voter. {% endif %} diff --git a/helios/templates/voters_upload.html b/helios/templates/voters_upload.html index a38956d019d1d02b90f43c184239e072f38353cd..908c147f124f4c342b034b6845bd406f3e5b8048 100644 --- a/helios/templates/voters_upload.html +++ b/helios/templates/voters_upload.html @@ -1,7 +1,7 @@ {% extends TEMPLATE_BASE %} {% block content %} - <h2 class="title">{{election.name}} — Bulk Upload Voters <span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election.uuid %}">back to election</a>]</span></h2> + <h2 class="title">{{election.name}} — Bulk Upload Voters <span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">back to election</a>]</span></h2> <form method="post" action="" id="upload_form" enctype="multipart/form-data"> <p> diff --git a/helios/templates/voters_upload_confirm.html b/helios/templates/voters_upload_confirm.html index c3d7c084b58b285b09959d3e72fc48ef28309cf6..911cb259d9d321e6bd0cc69ddc9cde95acb7320c 100644 --- a/helios/templates/voters_upload_confirm.html +++ b/helios/templates/voters_upload_confirm.html @@ -1,7 +1,7 @@ {% extends TEMPLATE_BASE %} {% block content %} - <h2 class="title">{{election.name}} — Bulk Upload Voters — Confirm<span style="font-size:0.7em;">[<a href="{% url "helios.views.one_election_view" election.uuid %}">back to election</a>]</span></h2> + <h2 class="title">{{election.name}} — Bulk Upload Voters — Confirm<span style="font-size:0.7em;">[<a href="{% url "election@view" election.uuid %}">back to election</a>]</span></h2> <p> You have uploaded a file of voters. The first few rows of this file are: @@ -23,7 +23,7 @@ HOLD ON:<br /> </p> <br /> -<a href="{% url "helios.views.voters_upload_cancel" election.uuid %}">never mind, upload a different file</a> +<a href="{% url "election@voters@upload-cancel" election.uuid %}">never mind, upload a different file</a> </p> {% else %} @@ -34,7 +34,7 @@ HOLD ON:<br /> <input type="submit" value="Yes, let's go" /> </form> -<a href="{% url "helios.views.voters_upload_cancel" election.uuid %}">no, let me upload a different file</a> +<a href="{% url "election@voters@upload-cancel" election.uuid %}">no, let me upload a different file</a> {% endif %} diff --git a/helios/test.py b/helios/test.py index 086f117c139e3bc4a88a518bfe999a07d3527ec7..11fde1c1799c63e07d7f4ea3a8f279ff78a9fd03 100644 --- a/helios/test.py +++ b/helios/test.py @@ -2,18 +2,21 @@ Testing Helios Features """ -from helios.models import * -from helios_auth.models import * import uuid -def generate_voters(election, num_voters = 1000, start_with = 1): - # generate the user - for v_num in range(start_with, start_with + num_voters): - user = User(user_type='password', user_id='testuser%s' % v_num, name='Test User %s' % v_num) - user.put() - voter = Voter(uuid=str(uuid.uuid1()), election = election, voter_type=user.user_type, voter_id = user.user_id) - voter.put() +from helios.models import Voter +from helios_auth.models import User + + +def generate_voters(election, num_voters=1000, start_with=1): + # generate the user + for v_num in range(start_with, start_with + num_voters): + user = User(user_type='password', user_id='testuser%s' % v_num, name='Test User %s' % v_num) + user.save() + voter = Voter(uuid=str(uuid.uuid1()), election=election, voter_type=user.user_type, voter_id=user.user_id) + voter.save() + def delete_voters(election): - for v in Voter.get_by_election(election): - v.delete() \ No newline at end of file + for v in Voter.get_by_election(election): + v.delete() diff --git a/helios/tests.py b/helios/tests.py index 3bd573197b062287d6dd34896d03c8f904156f66..97d7dba6d989681838e809779e096c6a6f7bbbc9 100644 --- a/helios/tests.py +++ b/helios/tests.py @@ -2,32 +2,29 @@ Unit Tests for Helios """ -import unittest, datetime, re, urllib -import django_webtest - -import models -import datatypes - -from helios_auth import models as auth_models -from views import ELGAMAL_PARAMS -import views -import utils +import datetime +import logging +import re +import uuid +from urllib.parse import urlencode -from django.db import IntegrityError, transaction -from django.test.client import Client +import django_webtest +from django.conf import settings +from django.core import mail +from django.core.files import File from django.test import TestCase from django.utils.html import escape as html_escape -from django.core import mail -from django.core.files import File -from django.core.urlresolvers import reverse -from django.conf import settings -from django.core.exceptions import PermissionDenied +import helios.datatypes as datatypes +import helios.models as models +import helios.utils as utils +import helios.views as views +from helios_auth import models as auth_models -import uuid class ElectionModelTests(TestCase): fixtures = ['users.json'] + allow_database_queries = True def create_election(self): return models.Election.get_or_create( @@ -41,7 +38,7 @@ class ElectionModelTests(TestCase): self.election.questions = QUESTIONS def setup_trustee(self): - self.election.generate_trustee(ELGAMAL_PARAMS) + self.election.generate_trustee(views.ELGAMAL_PARAMS) def setup_openreg(self): self.election.openreg=True @@ -57,45 +54,46 @@ class ElectionModelTests(TestCase): self.assertTrue(self.created_p) # should have a creation time - self.assertNotEquals(self.election.created_at, None) + self.assertNotEqual(self.election.created_at, None) self.assertTrue(self.election.created_at < datetime.datetime.utcnow()) def test_find_election(self): election = models.Election.get_by_user_as_admin(self.user)[0] - self.assertEquals(self.election, election) + self.assertEqual(self.election, election) election = models.Election.get_by_uuid(self.election.uuid) - self.assertEquals(self.election, election) + self.assertEqual(self.election, election) election = models.Election.get_by_short_name(self.election.short_name) - self.assertEquals(self.election, election) + self.assertEqual(self.election, election) def test_setup_trustee(self): self.setup_trustee() - self.assertEquals(self.election.num_trustees, 1) + self.assertEqual(self.election.num_trustees, 1) def test_add_voters_file(self): election = self.election FILE = "helios/fixtures/voter-file.csv" - vf = models.VoterFile.objects.create(election = election, voter_file = File(open(FILE), "voter_file.css")) - vf.process() + with open(FILE, 'r', encoding='utf-8') as f: + vf = models.VoterFile.objects.create(election = election, voter_file = File(f, "voter_file.css")) + vf.process() # make sure that we stripped things correctly voter = election.voter_set.get(voter_login_id = 'benadida5') - self.assertEquals(voter.voter_email, 'ben5@adida.net') - self.assertEquals(voter.voter_name, 'Ben5 Adida') + self.assertEqual(voter.voter_email, 'ben5@adida.net') + self.assertEqual(voter.voter_name, 'Ben5 Adida') def test_check_issues_before_freeze(self): # should be three issues: no trustees, and no questions, and no voters issues = self.election.issues_before_freeze - self.assertEquals(len(issues), 3) + self.assertEqual(len(issues), 3) self.setup_questions() # should be two issues: no trustees, and no voters issues = self.election.issues_before_freeze - self.assertEquals(len(issues), 2) + self.assertEqual(len(issues), 2) self.election.questions = None @@ -103,7 +101,7 @@ class ElectionModelTests(TestCase): # should be two issues: no questions, and no voters issues = self.election.issues_before_freeze - self.assertEquals(len(issues), 2) + self.assertEqual(len(issues), 2) self.setup_questions() @@ -111,15 +109,15 @@ class ElectionModelTests(TestCase): self.setup_openreg() issues = self.election.issues_before_freeze - self.assertEquals(len(issues), 0) + self.assertEqual(len(issues), 0) def test_helios_trustee(self): - self.election.generate_trustee(ELGAMAL_PARAMS) + self.election.generate_trustee(views.ELGAMAL_PARAMS) self.assertTrue(self.election.has_helios_trustee()) trustee = self.election.get_helios_trustee() - self.assertNotEquals(trustee, None) + self.assertNotEqual(trustee, None) def test_log(self): LOGS = ["testing 1", "testing 2", "testing 3"] @@ -130,7 +128,7 @@ class ElectionModelTests(TestCase): pulled_logs = [l.log for l in self.election.get_log().all()] pulled_logs.reverse() - self.assertEquals(LOGS,pulled_logs) + self.assertEqual(LOGS,pulled_logs) def test_eligibility(self): self.election.eligibility = [{'auth_system': self.user.user_type}] @@ -141,7 +139,7 @@ class ElectionModelTests(TestCase): # what about after saving? self.election.save() e = models.Election.objects.get(uuid = self.election.uuid) - self.assertEquals(e.eligibility, [{'auth_system': self.user.user_type}]) + self.assertEqual(e.eligibility, [{'auth_system': self.user.user_type}]) self.election.openreg = True @@ -154,6 +152,13 @@ class ElectionModelTests(TestCase): def test_facebook_eligibility(self): self.election.eligibility = [{'auth_system': 'facebook', 'constraint':[{'group': {'id': '123', 'name':'Fake Group'}}]}] + import settings + fb_enabled = 'facebook' in settings.AUTH_ENABLED_SYSTEMS + if not fb_enabled: + logging.error("'facebook' not enabled for auth, cannot its constraints.") + self.assertFalse(self.election.user_eligible_p(self.fb_user)) + return + # without openreg, this should be false self.assertFalse(self.election.user_eligible_p(self.fb_user)) @@ -170,7 +175,7 @@ class ElectionModelTests(TestCase): self.assertTrue(self.election.user_eligible_p(self.fb_user)) # also check that eligibility_category_id does the right thing - self.assertEquals(self.election.eligibility_category_id('facebook'), '123') + self.assertEqual(self.election.eligibility_category_id('facebook'), '123') def test_freeze(self): # freezing without trustees and questions, no good @@ -198,15 +203,15 @@ class ElectionModelTests(TestCase): def test_voter_registration(self): # before adding a voter voters = models.Voter.get_by_election(self.election) - self.assertTrue(len(voters) == 0) + self.assertEqual(0, len(voters)) # make sure no voter yet voter = models.Voter.get_by_election_and_user(self.election, self.user) - self.assertTrue(voter == None) + self.assertIsNone(voter) # make sure no voter at all across all elections voters = models.Voter.get_by_user(self.user) - self.assertTrue(len(voters) == 0) + self.assertEqual(0, len(voters)) # register the voter voter = models.Voter.register_user_in_election(self.user, self.election) @@ -214,24 +219,25 @@ class ElectionModelTests(TestCase): # make sure voter is there now voter_2 = models.Voter.get_by_election_and_user(self.election, self.user) - self.assertFalse(voter == None) - self.assertFalse(voter_2 == None) - self.assertEquals(voter, voter_2) + self.assertIsNotNone(voter) + self.assertIsNotNone(voter_2) + self.assertEqual(voter, voter_2) # make sure voter is there in this call too voters = models.Voter.get_by_user(self.user) - self.assertTrue(len(voters) == 1) - self.assertEquals(voter, voters[0]) + self.assertEqual(1, len(voters)) + self.assertEqual(voter, voters[0]) voter_2 = models.Voter.get_by_election_and_uuid(self.election, voter.uuid) - self.assertEquals(voter, voter_2) + self.assertEqual(voter, voter_2) - self.assertEquals(voter.user, self.user) + self.assertEqual(voter.user, self.user) class VoterModelTests(TestCase): fixtures = ['users.json', 'election.json'] + allow_database_queries = True def setUp(self): self.election = models.Election.objects.get(short_name='test') @@ -244,17 +250,18 @@ class VoterModelTests(TestCase): v.save() # password has been generated! - self.assertFalse(v.voter_password == None) + self.assertFalse(v.voter_password is None) # can't generate passwords twice self.assertRaises(Exception, lambda: v.generate_password()) # check that you can get at the voter user structure - self.assertEquals(v.get_user().user_id, v.voter_email) + self.assertEqual(v.get_user().user_id, v.voter_email) class CastVoteModelTests(TestCase): fixtures = ['users.json', 'election.json'] + allow_database_queries = True def setUp(self): self.election = models.Election.objects.get(short_name='test') @@ -268,10 +275,11 @@ class CastVoteModelTests(TestCase): class DatatypeTests(TestCase): fixtures = ['users.json', 'election.json'] + allow_database_queries = True def setUp(self): self.election = models.Election.objects.all()[0] - self.election.generate_trustee(ELGAMAL_PARAMS) + self.election.generate_trustee(views.ELGAMAL_PARAMS) def test_instantiate(self): ld_obj = datatypes.LDObject.instantiate(self.election.get_helios_trustee(), '2011/01/Trustee') @@ -290,7 +298,7 @@ class DatatypeTests(TestCase): 'B' : '234324243'} ld_obj = datatypes.LDObject.fromDict(original_dict, type_hint = 'legacy/EGZKProofCommitment') - self.assertEquals(original_dict, ld_obj.toDict()) + self.assertEqual(original_dict, ld_obj.toDict()) @@ -304,9 +312,8 @@ class DataFormatBlackboxTests(object): self.election = models.Election.objects.all()[0] def assertEqualsToFile(self, response, file_path): - expected = open(file_path) - self.assertEquals(response.content, expected.read()) - expected.close() + with open(file_path) as expected: + self.assertEqual(response.content, expected.read().encode('utf-8')) def test_election(self): response = self.client.get("/helios/elections/%s" % self.election.uuid, follow=False) @@ -334,6 +341,7 @@ class DataFormatBlackboxTests(object): class LegacyElectionBlackboxTests(DataFormatBlackboxTests, TestCase): fixtures = ['legacy-data.json'] + allow_database_queries = True EXPECTED_ELECTION_FILE = 'helios/fixtures/legacy-election-expected.json' EXPECTED_ELECTION_METADATA_FILE = 'helios/fixtures/legacy-election-metadata-expected.json' EXPECTED_VOTERS_FILE = 'helios/fixtures/legacy-election-voters-expected.json' @@ -348,45 +356,47 @@ class LegacyElectionBlackboxTests(DataFormatBlackboxTests, TestCase): # EXPECTED_BALLOTS_FILE = 'helios/fixtures/v3.1-ballots-expected.json' class WebTest(django_webtest.WebTest): - def assertRedirects(self, response, url): + def assertStatusCode(self, response, status_code): + actual_code = response.status_code if hasattr(response, 'status_code') else response.status_int + if isinstance(status_code, (list, tuple)): + assert actual_code in status_code, "%s instad of %s" % (actual_code, status_code) + else: + assert actual_code == status_code, "%s instad of %s" % (actual_code, status_code) + + + def assertRedirects(self, response, url=None): """ reimplement this in case it's a WebOp response and it seems to be screwing up in a few places too thus the localhost exception """ + self.assertStatusCode(response, (301, 302)) + location = None if hasattr(response, 'location'): - assert url in response.location + location = response.location else: - assert url in response._headers['location'][1] - - if hasattr(response, 'status_code'): - assert response.status_code == 302 - else: - assert response.status_int == 302 - - #self.assertEqual(response.status_code, 302) - + location = response['location'] + if url is not None: + assert url in location, location #return super(django_webtest.WebTest, self).assertRedirects(response, url) - #if hasattr(response, 'status_code') and hasattr(response, 'location'): - - #assert url in response.location, "redirected to %s instead of %s" % (response.location, url) + def assertContains(self, response, text): - if hasattr(response, 'status_code'): - assert response.status_code == 200 -# return super(django_webtest.WebTest, self).assertContains(response, text) - else: - assert response.status_int == 200 + self.assertStatusCode(response, 200) - if hasattr(response, "testbody"): - assert text in response.testbody, "missing text %s" % text + t = response.testbody + elif hasattr(response, "body"): + t = response.body else: - if hasattr(response, "body"): - assert text in response.body, "missing text %s" % text - else: - assert text in response.content, "missing text %s" % text + t = response.content + + if isinstance(text, bytes): + text = text.decode() + if isinstance(t, bytes): + t = t.decode() + assert text in t, "missing text %s" % text ## @@ -395,31 +405,23 @@ class WebTest(django_webtest.WebTest): class ElectionBlackboxTests(WebTest): fixtures = ['users.json', 'election.json'] + allow_database_queries = True def setUp(self): self.election = models.Election.objects.all()[0] self.user = auth_models.User.objects.get(user_id='ben@adida.net', user_type='google') - def assertContains(self, response, text): - if hasattr(response, 'status_code'): - assert response.status_code == 200 -# return super(django_webtest.WebTest, self).assertContains(response, text) - else: - assert response.status_int == 200 - - - if hasattr(response, "testbody"): - assert text in response.testbody, "missing text %s" % text - else: - if hasattr(response, "body"): - assert text in response.body, "missing text %s" % text - else: - assert text in response.content, "missing text %s" % text - - def setup_login(self): + def setup_login(self, from_scratch=False, **kwargs): + if from_scratch: + # a bogus call to set up the session + self.client.get("/") # set up the session session = self.client.session - session['user'] = {'type': self.user.user_type, 'user_id': self.user.user_id} + if kwargs: + user = auth_models.User.objects.get(**kwargs) + else: + user = self.user + session['user'] = {'type': user.user_type, 'user_id': user.user_id} session.save() # set up the app, too @@ -434,27 +436,27 @@ class ElectionBlackboxTests(WebTest): def test_election_params(self): response = self.client.get("/helios/elections/params") - self.assertEquals(response.content, views.ELGAMAL_PARAMS_LD_OBJECT.serialize()) + self.assertEqual(response.content, views.ELGAMAL_PARAMS_LD_OBJECT.serialize().encode('utf-8')) def test_election_404(self): response = self.client.get("/helios/elections/foobar") - self.assertEquals(response.status_code, 404) + self.assertStatusCode(response, 404) def test_election_bad_trustee(self): response = self.client.get("/helios/t/%s/foobar@bar.com/badsecret" % self.election.short_name) - self.assertEquals(response.status_code, 404) + self.assertStatusCode(response, 404) def test_get_election_shortcut(self): response = self.client.get("/helios/e/%s" % self.election.short_name, follow=True) - self.assertContains(response, self.election.description) + self.assertContains(response, self.election.description_bleached) def test_get_election_raw(self): response = self.client.get("/helios/elections/%s" % self.election.uuid, follow=False) - self.assertEquals(response.content, self.election.toJSON()) + self.assertEqual(response.content, self.election.toJSON().encode('utf-8')) def test_get_election(self): response = self.client.get("/helios/elections/%s/view" % self.election.uuid, follow=False) - self.assertContains(response, self.election.description) + self.assertContains(response, self.election.description_bleached) def test_get_election_questions(self): response = self.client.get("/helios/elections/%s/questions" % self.election.uuid, follow=False) @@ -476,7 +478,7 @@ class ElectionBlackboxTests(WebTest): def test_get_election_voters_raw(self): response = self.client.get("/helios/elections/%s/voters/" % self.election.uuid, follow=False) - self.assertEquals(len(utils.from_json(response.content)), self.election.num_voters) + self.assertEqual(len(response.json()), self.election.num_voters) def test_election_creation_not_logged_in(self): response = self.client.post("/helios/elections/new", { @@ -491,10 +493,7 @@ class ElectionBlackboxTests(WebTest): self.assertRedirects(response, "/auth/?return_url=/helios/elections/new") def test_election_edit(self): - # a bogus call to set up the session - self.client.get("/") - - self.setup_login() + self.setup_login(from_scratch=True) response = self.client.get("/helios/elections/%s/edit" % self.election.uuid) response = self.client.post("/helios/elections/%s/edit" % self.election.uuid, { "short_name" : self.election.short_name + "-2", @@ -508,16 +507,33 @@ class ElectionBlackboxTests(WebTest): self.assertRedirects(response, "/helios/elections/%s/view" % self.election.uuid) new_election = models.Election.objects.get(uuid = self.election.uuid) - self.assertEquals(new_election.short_name, self.election.short_name + "-2") + self.assertEqual(new_election.short_name, self.election.short_name + "-2") + + def test_get_election_stats(self): + self.setup_login(from_scratch=True, user_id='mccio@github.com', user_type='google') + response = self.client.get("/helios/stats/", follow=False) + self.assertStatusCode(response, 200) + response = self.client.get("/helios/stats/force-queue", follow=False) + self.assertRedirects(response, "/helios/stats/") + response = self.client.get("/helios/stats/elections", follow=False) + self.assertStatusCode(response, 200) + response = self.client.get("/helios/stats/problem-elections", follow=False) + self.assertStatusCode(response, 200) + response = self.client.get("/helios/stats/recent-votes", follow=False) + self.assertStatusCode(response, 200) + self.clear_login() + response = self.client.get("/helios/stats/", follow=False) + self.assertStatusCode(response, 403) + self.setup_login() + response = self.client.get("/helios/stats/", follow=False) + self.assertStatusCode(response, 403) + self.clear_login() - def _setup_complete_election(self, election_params={}): + def _setup_complete_election(self, election_params=None): "do the setup part of a whole election" - # a bogus call to set up the session - self.client.get("/") - # REPLACE with params? - self.setup_login() + self.setup_login(from_scratch=True) # create the election full_election_params = { @@ -532,12 +548,15 @@ class ElectionBlackboxTests(WebTest): } # override with the given - full_election_params.update(election_params) + full_election_params.update(election_params or {}) response = self.client.post("/helios/elections/new", full_election_params) + self.assertRedirects(response) # we are redirected to the election, let's extract the ID out of the URL - election_id = re.search('/elections/([^/]+)/', str(response['Location'])).group(1) + election_id = re.search('/elections/([^/]+)/', str(response['location'])) + self.assertIsNotNone(election_id, "Election id not found in redirect: %s" % str(response['location'])) + election_id = election_id.group(1) # helios is automatically added as a trustee @@ -566,10 +585,17 @@ class ElectionBlackboxTests(WebTest): response = self.client.post("/helios/elections/%s/voters/upload" % election_id, {'confirm_p': "1"}) self.assertRedirects(response, "/helios/elections/%s/voters/list" % election_id) + # Try a latin-1 encoded file + FILE = "helios/fixtures/voter-file-latin1.csv" + voters_file = open(FILE, mode='rb') + response = self.client.post("/helios/elections/%s/voters/upload" % election_id, {'voters_file': voters_file}) + voters_file.close() + self.assertContains(response, "first few rows of this file") + # and we want to check that there are now voters response = self.client.get("/helios/elections/%s/voters/" % election_id) NUM_VOTERS = 4 - self.assertEquals(len(utils.from_json(response.content)), NUM_VOTERS) + self.assertEqual(len(response.json()), NUM_VOTERS) # let's get a single voter single_voter = models.Election.objects.get(uuid = election_id).voter_set.all()[0] @@ -577,7 +603,7 @@ class ElectionBlackboxTests(WebTest): self.assertContains(response, '"uuid": "%s"' % single_voter.uuid) response = self.client.get("/helios/elections/%s/voters/foobar" % election_id) - self.assertEquals(response.status_code, 404) + self.assertStatusCode(response, 404) # add questions response = self.client.post("/helios/elections/%s/save_questions" % election_id, { @@ -602,7 +628,7 @@ class ElectionBlackboxTests(WebTest): }) self.assertRedirects(response, "/helios/elections/%s/view" % election_id) num_messages_after = len(mail.outbox) - self.assertEquals(num_messages_after - num_messages_before, NUM_VOTERS) + self.assertEqual(num_messages_after - num_messages_before, NUM_VOTERS) email_message = mail.outbox[num_messages_before] assert "your password" in email_message.subject, "bad subject in email" @@ -613,7 +639,7 @@ class ElectionBlackboxTests(WebTest): # now log out as administrator self.clear_login() - self.assertEquals(self.client.session.has_key('user'), False) + self.assertEqual('user' in self.client.session, False) # return the voter username and password to vote return election_id, username, password @@ -629,11 +655,11 @@ class ElectionBlackboxTests(WebTest): # parse it as an encrypted vote with randomness, and make sure randomness is there the_ballot = utils.from_json(response.testbody) - assert the_ballot['answers'][0].has_key('randomness'), "no randomness" + assert 'randomness' in the_ballot['answers'][0], "no randomness" assert len(the_ballot['answers'][0]['randomness']) == 2, "not enough randomness" # parse it as an encrypted vote, and re-serialize it - ballot = datatypes.LDObject.fromDict(utils.from_json(response.testbody), type_hint='legacy/EncryptedVote') + ballot = datatypes.LDObject.fromDict(the_ballot, type_hint='legacy/EncryptedVote') encrypted_vote = ballot.serialize() # cast the ballot @@ -661,7 +687,7 @@ class ElectionBlackboxTests(WebTest): # confirm the vote, now with the actual form cast_form = cast_confirm_page.form - if 'status_update' in cast_form.fields.keys(): + if 'status_update' in list(cast_form.fields.keys()): cast_form['status_update'] = False response = cast_form.submit() @@ -670,7 +696,7 @@ class ElectionBlackboxTests(WebTest): # at this point an email should have gone out to the user # at position num_messages after, since that was the len() before we cast this ballot email_message = mail.outbox[len(mail.outbox) - 1] - url = re.search('http://[^/]+(/[^ \n]*)', email_message.body).group(1) + url = re.search('https?://[^/]+(/[^ \n]*)', email_message.body).group(1) # check that we can get at that URL if not need_login: @@ -690,7 +716,7 @@ class ElectionBlackboxTests(WebTest): login_form['password'] = ' ' + password + ' ' login_form.submit() - response = self.app.get(url) + response = self.app.get(url, auto_follow=True) self.assertContains(response, ballot.hash) self.assertContains(response, html_escape(encrypted_vote)) @@ -711,7 +737,7 @@ class ElectionBlackboxTests(WebTest): self.assertRedirects(response, "/helios/elections/%s/view" % election_id) # should trigger helios decryption automatically - self.assertNotEquals(models.Election.objects.get(uuid=election_id).get_helios_trustee().decryption_proofs, None) + self.assertNotEqual(models.Election.objects.get(uuid=election_id).get_helios_trustee().decryption_proofs, None) # combine decryptions response = self.client.post("/helios/elections/%s/combine_decryptions" % election_id, { @@ -723,7 +749,7 @@ class ElectionBlackboxTests(WebTest): # check that we can't get the tally yet response = self.client.get("/helios/elections/%s/result" % election_id) - self.assertEquals(response.status_code, 403) + self.assertStatusCode(response, 403) # release response = self.client.post("/helios/elections/%s/release_result" % election_id, { @@ -732,7 +758,7 @@ class ElectionBlackboxTests(WebTest): # check that tally matches response = self.client.get("/helios/elections/%s/result" % election_id) - self.assertEquals(utils.from_json(response.content), [[0,1]]) + self.assertEqual(response.json(), [[0,1]]) def test_do_complete_election(self): election_id, username, password = self._setup_complete_election() @@ -761,7 +787,7 @@ class ElectionBlackboxTests(WebTest): response = self.app.get("/helios/elections/%s/view" % election_id) # ensure it redirects - self.assertRedirects(response, "/helios/elections/%s/password_voter_login?%s" % (election_id, urllib.urlencode({"return_url": "/helios/elections/%s/view" % election_id}))) + self.assertRedirects(response, "/helios/elections/%s/password_voter_login?%s" % (election_id, urlencode({"return_url": "/helios/elections/%s/view" % election_id}))) login_form = response.follow().form @@ -776,8 +802,7 @@ class ElectionBlackboxTests(WebTest): def test_election_voters_eligibility(self): # create the election - self.client.get("/") - self.setup_login() + self.setup_login(from_scratch=True) response = self.client.post("/helios/elections/new", { "short_name" : "test-eligibility", "name" : "Test Eligibility", @@ -788,7 +813,9 @@ class ElectionBlackboxTests(WebTest): "private_p" : "False", 'csrf_token': self.client.session['csrf_token']}) - election_id = re.match("(.*)/elections/(.*)/view", response['Location']).group(2) + election_id = re.match("(.*)/elections/(.*)/view", str(response['Location'])) + self.assertIsNotNone(election_id, "Election id not found in redirect: %s" % str(response['Location'])) + election_id = election_id.group(2) # update eligiblity response = self.client.post("/helios/elections/%s/voters/eligibility" % election_id, { diff --git a/helios/url_names.py b/helios/url_names.py new file mode 100644 index 0000000000000000000000000000000000000000..319a9be5bd72a11144e4917823e3433b378d405a --- /dev/null +++ b/helios/url_names.py @@ -0,0 +1,27 @@ +from helios import election_url_names as election, stats_url_names as stats + +__all__ = [ + "election", "stats", + "COOKIE_TEST", "COOKIE_TEST_2", "COOKIE_NO", + "ELECTION_SHORTCUT", "ELECTION_SHORTCUT_VOTE", "CAST_VOTE_SHORTCUT", + "TRUSTEE_LOGIN", + "ELECTIONS_PARAMS", "ELECTIONS_VERIFIER", "ELECTIONS_VERIFIER_SINGLE_BALLOT", + "ELECTIONS_NEW", "ELECTIONS_ADMINISTERED", "ELECTIONS_VOTED", +] + +COOKIE_TEST="cookie@test" +COOKIE_TEST_2="cookie@test2" +COOKIE_NO="cookie@no" + +ELECTION_SHORTCUT="shortcut@election" +ELECTION_SHORTCUT_VOTE="shortcut@election@vote" +CAST_VOTE_SHORTCUT="shortcut@vote" + +TRUSTEE_LOGIN="trustee@login" + +ELECTIONS_PARAMS="elections@params" +ELECTIONS_VERIFIER="elections@verifier" +ELECTIONS_VERIFIER_SINGLE_BALLOT="elections@verifier@single-ballot" +ELECTIONS_NEW="elections@new" +ELECTIONS_ADMINISTERED="elections@administered" +ELECTIONS_VOTED="elections@voted" diff --git a/helios/urls.py b/helios/urls.py index 67c0077672cd476a705ca58d4bf9ab6ca4288e07..9183eedd1fdc4e2c7fd89535b557d972106fc216 100644 --- a/helios/urls.py +++ b/helios/urls.py @@ -1,39 +1,33 @@ # -*- coding: utf-8 -*- -from django.conf.urls import * +from django.conf.urls import url, include -from django.conf import settings +from . import views, url_names as names -from views import * - -urlpatterns = None - -urlpatterns = patterns('', - (r'^autologin$', admin_autologin), - (r'^testcookie$', test_cookie), - (r'^testcookie_2$', test_cookie_2), - (r'^nocookies$', nocookies), - (r'^stats/', include('helios.stats_urls')), +urlpatterns = [ + url(r'^autologin$', views.admin_autologin), + url(r'^testcookie$', views.test_cookie, name=names.COOKIE_TEST), + url(r'^testcookie_2$', views.test_cookie_2, name=names.COOKIE_TEST_2), + url(r'^nocookies$', views.nocookies, name=names.COOKIE_NO), + url(r'^stats/', include('helios.stats_urls')), # election shortcut by shortname - (r'^e/(?P<election_short_name>[^/]+)$', election_shortcut), - (r'^e/(?P<election_short_name>[^/]+)/vote$', election_vote_shortcut), + url(r'^e/(?P<election_short_name>[^/]+)$', views.election_shortcut, name=names.ELECTION_SHORTCUT), + url(r'^e/(?P<election_short_name>[^/]+)/vote$', views.election_vote_shortcut, name=names.ELECTION_SHORTCUT_VOTE), # vote shortcut - (r'^v/(?P<vote_tinyhash>[^/]+)$', castvote_shortcut), + url(r'^v/(?P<vote_tinyhash>[^/]+)$', views.castvote_shortcut, name=names.CAST_VOTE_SHORTCUT), # trustee login - (r'^t/(?P<election_short_name>[^/]+)/(?P<trustee_email>[^/]+)/(?P<trustee_secret>[^/]+)$', trustee_login), + url(r'^t/(?P<election_short_name>[^/]+)/(?P<trustee_email>[^/]+)/(?P<trustee_secret>[^/]+)$', views.trustee_login, + name=names.TRUSTEE_LOGIN), # election - (r'^elections/params$', election_params), - (r'^elections/verifier$', election_verifier), - (r'^elections/single_ballot_verifier$', election_single_ballot_verifier), - (r'^elections/new$', election_new), - (r'^elections/administered$', elections_administered), - (r'^elections/voted$', elections_voted), - - (r'^elections/(?P<election_uuid>[^/]+)', include('helios.election_urls')), + url(r'^elections/params$', views.election_params, name=names.ELECTIONS_PARAMS), + url(r'^elections/verifier$', views.election_verifier, name=names.ELECTIONS_VERIFIER), + url(r'^elections/single_ballot_verifier$', views.election_single_ballot_verifier, name=names.ELECTIONS_VERIFIER_SINGLE_BALLOT), + url(r'^elections/new$', views.election_new, name=names.ELECTIONS_NEW), + url(r'^elections/administered$', views.elections_administered, name=names.ELECTIONS_ADMINISTERED), + url(r'^elections/voted$', views.elections_voted, name=names.ELECTIONS_VOTED), -) - - + url(r'^elections/(?P<election_uuid>[^/]+)', include('helios.election_urls')), +] diff --git a/helios/utils.py b/helios/utils.py index d053dc11fec961d94b6edeee4879826107622d5c..0080c5f0a3d979d0efd6854f9e7304ec62bd43d1 100644 --- a/helios/utils.py +++ b/helios/utils.py @@ -5,25 +5,17 @@ Ben Adida - ben@adida.net 2005-04-11 """ -import urllib, re, sys, datetime, urlparse, string +import datetime +import re +import string +import urllib.parse -import boto.ses +from django.conf import settings +from helios.crypto.utils import random # utils from helios_auth, too from helios_auth.utils import * -from django.conf import settings - -import random, logging -import hashlib, hmac, base64 - -def do_hmac(k,s): - """ - HMAC a value with a key, hex output - """ - mac = hmac.new(k, s, hashlib.sha1) - return mac.hexdigest() - def split_by_length(str, length, rejoin_with=None): """ @@ -48,7 +40,7 @@ def urlencode(str): if not str: return "" - return urllib.quote(str) + return urllib.parse.quote(str) def urlencodeall(str): """ @@ -63,11 +55,11 @@ def urldecode(str): if not str: return "" - return urllib.unquote(str) + return urllib.parse.unquote(str) def dictToURLParams(d): if d: - return '&'.join([i + '=' + urlencode(v) for i,v in d.items()]) + return '&'.join([i + '=' + urlencode(v) for i,v in list(d.items())]) else: return None ## @@ -97,31 +89,28 @@ def xss_strip_all_tags(s): if text[:2] == "&#": try: if text[:3] == "&#x": - return unichr(int(text[3:-1], 16)) + return chr(int(text[3:-1], 16)) else: - return unichr(int(text[2:-1])) + return chr(int(text[2:-1])) except ValueError: pass elif text[:1] == "&": - import htmlentitydefs - entity = htmlentitydefs.entitydefs.get(text[1:-1]) + import html.entities + entity = html.entities.entitydefs.get(text[1:-1]) if entity: if entity[:2] == "&#": try: - return unichr(int(entity[2:-1])) + return chr(int(entity[2:-1])) except ValueError: pass else: - return unicode(entity, "iso-8859-1") + return str(entity, "iso-8859-1") return text # leave as is return re.sub("(?s)<[^>]*>|&#?\w+;", fixup, s) - -random.seed() def random_string(length=20, alphabet=None): - random.seed() ALPHABET = alphabet or 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' r_string = '' for i in range(length): @@ -141,7 +130,7 @@ def get_prefix(): ## def string_to_datetime(str, fmt="%Y-%m-%d %H:%M"): - if str == None: + if str is None: return None return datetime.datetime.strptime(str, fmt) @@ -167,7 +156,7 @@ def one_val_raw_sql(raw_sql, values=[]): """ for a simple aggregate """ - from django.db import connection, transaction + from django.db import connection cursor = connection.cursor() cursor.execute(raw_sql, values) diff --git a/helios/view_utils.py b/helios/view_utils.py index 3e3fb5a81461e0616af9b9ac2b937b2196071924..1c8c9a9011e44404633dcfd17e5706ce86bdb2b3 100644 --- a/helios/view_utils.py +++ b/helios/view_utils.py @@ -4,22 +4,16 @@ Utilities for all views Ben Adida (12-30-2008) """ -from django.template import Context, Template, loader -from django.http import HttpResponse, Http404 +from django.conf import settings +from django.http import HttpResponse from django.shortcuts import render_to_response - -import utils - -from helios import datatypes - +from django.template import loader # nicely update the wrapper function from functools import update_wrapper -from helios_auth.security import get_user - import helios - -from django.conf import settings +from . import utils +from helios_auth.security import get_user ## ## BASICS @@ -33,14 +27,14 @@ FAILURE = HttpResponse("FAILURE") ## ## template abstraction ## -def prepare_vars(request, vars): - vars_with_user = vars.copy() +def prepare_vars(request, values): + vars_with_user = values.copy() if values is not None else {} vars_with_user['user'] = get_user(request) - + # csrf protection - if request.session.has_key('csrf_token'): + if 'csrf_token' in request.session: vars_with_user['csrf_token'] = request.session['csrf_token'] - + vars_with_user['utils'] = utils vars_with_user['settings'] = settings vars_with_user['HELIOS_STATIC'] = '/static/helios/helios' @@ -50,31 +44,31 @@ def prepare_vars(request, vars): return vars_with_user -def render_template(request, template_name, vars = {}, include_user=True): - t = loader.get_template(template_name + '.html') - - vars_with_user = prepare_vars(request, vars) - + +def render_template(request, template_name, values = None, include_user=True): + vars_with_user = prepare_vars(request, values) + if not include_user: del vars_with_user['user'] - + return render_to_response('helios/templates/%s.html' % template_name, vars_with_user) - -def render_template_raw(request, template_name, vars={}): + + +def render_template_raw(request, template_name, values=None): t = loader.get_template(template_name) - + # if there's a request, prep the vars, otherwise can't do it. if request: - full_vars = prepare_vars(request, vars) + full_vars = prepare_vars(request, values) else: - full_vars = vars + full_vars = values or {} - c = Context(full_vars) - return t.render(c) + return t.render(context=full_vars, request=request) def render_json(json_txt): - return HttpResponse(json_txt, "application/json") + return HttpResponse(utils.to_json(json_txt), content_type="application/json") + # decorator def return_json(func): @@ -85,11 +79,10 @@ def return_json(func): def convert_to_json(self, *args, **kwargs): return_val = func(self, *args, **kwargs) try: - return render_json(utils.to_json(return_val)) - except Exception, e: + return render_json(return_val) + except Exception as e: import logging logging.error("problem with serialization: " + str(return_val) + " / " + str(e)) raise e return update_wrapper(convert_to_json,func) - diff --git a/helios/views.py b/helios/views.py index ad84131429d89b07d08710eb7fce8625f6d8f503..3b19d8ca680f2e5cba9082758d25784472de2770 100644 --- a/helios/views.py +++ b/helios/views.py @@ -5,50 +5,46 @@ Helios Django Views Ben Adida (ben@adida.net) """ -from django.core.urlresolvers import reverse -from django.core.mail import send_mail -from django.core.paginator import Paginator +import base64 +import datetime +import logging +import os +import uuid +from urllib.parse import urlencode + from django.core.exceptions import PermissionDenied -from django.http import * +from django.core.paginator import Paginator from django.db import transaction, IntegrityError - -from mimetypes import guess_type - +from django.http import HttpResponse, Http404, HttpResponseRedirect, HttpResponseForbidden +from django.urls import reverse from validate_email import validate_email -import csv, urllib, os, base64 - -from crypto import algs, electionalgs, elgamal -from crypto import utils as cryptoutils -from workflows import homomorphic -from helios import utils as helios_utils -from view_utils import * - -from helios_auth.security import * +import helios_auth.url_names as helios_auth_urls +from helios import utils, VOTERS_EMAIL, VOTERS_UPLOAD, url_names +from helios_auth import views as auth_views from helios_auth.auth_systems import AUTH_SYSTEMS, can_list_categories from helios_auth.models import AuthenticationExpired - -from helios import security -from helios_auth import views as auth_views - -import tasks - -from security import * -from helios_auth.security import get_user, save_in_session_across_logouts - -import uuid, datetime - -from models import * - -import forms, signals +from helios_auth.security import check_csrf, login_required, get_user, save_in_session_across_logouts +from . import datatypes +from . import forms +from . import tasks +from .crypto import algs, electionalgs, elgamal +from .crypto import utils as cryptoutils +from .models import User, Election, CastVote, Voter, VoterFile, Trustee, AuditedBallot +from .security import (election_view, election_admin, + trustee_check, set_logged_in_trustee, + can_create_election, user_can_see_election, get_voter, + user_can_admin_election, user_can_feature_election) +from .view_utils import SUCCESS, FAILURE, return_json, render_template, render_template_raw +from .workflows import homomorphic # Parameters for everything ELGAMAL_PARAMS = elgamal.Cryptosystem() # trying new ones from OlivierP -ELGAMAL_PARAMS.p = 16328632084933010002384055033805457329601614771185955389739167309086214800406465799038583634953752941675645562182498120750264980492381375579367675648771293800310370964745767014243638518442553823973482995267304044326777047662957480269391322789378384619428596446446984694306187644767462460965622580087564339212631775817895958409016676398975671266179637898557687317076177218843233150695157881061257053019133078545928983562221396313169622475509818442661047018436264806901023966236718367204710755935899013750306107738002364137917426595737403871114187750804346564731250609196846638183903982387884578266136503697493474682071L -ELGAMAL_PARAMS.q = 61329566248342901292543872769978950870633559608669337131139375508370458778917L -ELGAMAL_PARAMS.g = 14887492224963187634282421537186040801304008017743492304481737382571933937568724473847106029915040150784031882206090286938661464458896494215273989547889201144857352611058572236578734319505128042602372864570426550855201448111746579871811249114781674309062693442442368697449970648232621880001709535143047913661432883287150003429802392229361583608686643243349727791976247247948618930423866180410558458272606627111270040091203073580238905303994472202930783207472394578498507764703191288249547659899997131166130259700604433891232298182348403175947450284433411265966789131024573629546048637848902243503970966798589660808533L +ELGAMAL_PARAMS.p = 16328632084933010002384055033805457329601614771185955389739167309086214800406465799038583634953752941675645562182498120750264980492381375579367675648771293800310370964745767014243638518442553823973482995267304044326777047662957480269391322789378384619428596446446984694306187644767462460965622580087564339212631775817895958409016676398975671266179637898557687317076177218843233150695157881061257053019133078545928983562221396313169622475509818442661047018436264806901023966236718367204710755935899013750306107738002364137917426595737403871114187750804346564731250609196846638183903982387884578266136503697493474682071 +ELGAMAL_PARAMS.q = 61329566248342901292543872769978950870633559608669337131139375508370458778917 +ELGAMAL_PARAMS.g = 14887492224963187634282421537186040801304008017743492304481737382571933937568724473847106029915040150784031882206090286938661464458896494215273989547889201144857352611058572236578734319505128042602372864570426550855201448111746579871811249114781674309062693442442368697449970648232621880001709535143047913661432883287150003429802392229361583608686643243349727791976247247948618930423866180410558458272606627111270040091203073580238905303994472202930783207472394578498507764703191288249547659899997131166130259700604433891232298182348403175947450284433411265966789131024573629546048637848902243503970966798589660808533 # object ready for serialization ELGAMAL_PARAMS_LD_OBJECT = datatypes.LDObject.instantiate(ELGAMAL_PARAMS, datatype='legacy/EGParams') @@ -57,16 +53,16 @@ ELGAMAL_PARAMS_LD_OBJECT = datatypes.LDObject.instantiate(ELGAMAL_PARAMS, dataty from django.conf import settings def get_election_url(election): - return settings.URL_HOST + reverse(election_shortcut, args=[election.short_name]) + return settings.URL_HOST + reverse(url_names.ELECTION_SHORTCUT, args=[election.short_name]) def get_election_badge_url(election): - return settings.URL_HOST + reverse(election_badge, args=[election.uuid]) + return settings.URL_HOST + reverse(url_names.election.ELECTION_BADGE, args=[election.uuid]) def get_election_govote_url(election): - return settings.URL_HOST + reverse(election_vote_shortcut, args=[election.short_name]) + return settings.URL_HOST + reverse(url_names.ELECTION_SHORTCUT_VOTE, args=[election.short_name]) def get_castvote_url(cast_vote): - return settings.URL_HOST + reverse(castvote_shortcut, args=[cast_vote.vote_tinyhash]) + return settings.URL_HOST + reverse(url_names.CAST_VOTE_SHORTCUT, args=[cast_vote.vote_tinyhash]) ## @@ -76,8 +72,8 @@ def user_reauth(request, user): # FIXME: should we be wary of infinite redirects here, and # add a parameter to prevent it? Maybe. login_url = "%s%s?%s" % (settings.SECURE_URL_HOST, - reverse(auth_views.start, args=[user.user_type]), - urllib.urlencode({'return_url': + reverse(helios_auth_urls.AUTH_START, args=[user.user_type]), + urlencode({'return_url': request.get_full_path()})) return HttpResponseRedirect(login_url) @@ -116,16 +112,16 @@ def election_single_ballot_verifier(request): def election_shortcut(request, election_short_name): election = Election.get_by_short_name(election_short_name) if election: - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) else: raise Http404 # a hidden view behind the shortcut that performs the actual perm check @election_view() def _election_vote_shortcut(request, election): - vote_url = "%s/booth/vote.html?%s" % (settings.SECURE_URL_HOST, urllib.urlencode({'election_url' : reverse(one_election, args=[election.uuid])})) + vote_url = "%s/booth/vote.html?%s" % (settings.SECURE_URL_HOST, urlencode({'election_url' : reverse(url_names.election.ELECTION_HOME, args=[election.uuid])})) - test_cookie_url = "%s?%s" % (reverse(test_cookie), urllib.urlencode({'continue_url' : vote_url})) + test_cookie_url = "%s?%s" % (reverse(url_names.COOKIE_TEST), urlencode({'continue_url' : vote_url})) return HttpResponseRedirect(test_cookie_url) @@ -200,7 +196,7 @@ def election_new(request): election_params = dict(election_form.cleaned_data) # is the short name valid - if helios_utils.urlencode(election_params['short_name']) == election_params['short_name']: + if utils.urlencode(election_params['short_name']) == election_params['short_name']: election_params['uuid'] = str(uuid.uuid1()) election_params['cast_url'] = settings.SECURE_URL_HOST + reverse(one_election_cast, args=[election_params['uuid']]) @@ -212,7 +208,7 @@ def election_new(request): try: election = Election.objects.create(**election_params) election.generate_trustee(ELGAMAL_PARAMS) - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) except IntegrityError: error = "An election with short name %s already exists" % election_params['short_name'] else: @@ -245,7 +241,7 @@ def one_election_edit(request, election): setattr(election, attr_name, clean_data[attr_name]) try: election.save() - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) except IntegrityError: error = "An election with short name %s already exists" % clean_data['short_name'] @@ -268,7 +264,7 @@ def one_election_extend(request, election): election.voting_extended_until = clean_data['voting_extended_until'] election.save() - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) return render_template(request, "election_extend", {'election_form' : election_form, 'election' : election}) @@ -297,8 +293,8 @@ def election_badge(request, election): @election_view() def one_election_view(request, election): user = get_user(request) - admin_p = security.user_can_admin_election(user, election) - can_feature_p = security.user_can_feature_election(user, election) + admin_p = user_can_admin_election(user, election) + can_feature_p = user_can_feature_election(user, election) notregistered = False eligible_p = True @@ -307,9 +303,9 @@ def one_election_view(request, election): election_badge_url = get_election_badge_url(election) status_update_message = None - vote_url = "%s/booth/vote.html?%s" % (settings.SECURE_URL_HOST, urllib.urlencode({'election_url' : reverse(one_election, args=[election.uuid])})) + vote_url = "%s/booth/vote.html?%s" % (settings.SECURE_URL_HOST, urlencode({'election_url' : reverse(url_names.election.ELECTION_HOME, args=[election.uuid])})) - test_cookie_url = "%s?%s" % (reverse(test_cookie), urllib.urlencode({'continue_url' : vote_url})) + test_cookie_url = "%s?%s" % (reverse(url_names.COOKIE_TEST), urlencode({'continue_url' : vote_url})) if user: voter = Voter.get_by_election_and_user(election, user) @@ -332,13 +328,13 @@ def one_election_view(request, election): # status update message? if election.openreg: if election.voting_has_started: - status_update_message = u"Vote in %s" % election.name + status_update_message = "Vote in %s" % election.name else: - status_update_message = u"Register to vote in %s" % election.name + status_update_message = "Register to vote in %s" % election.name # result! if election.result: - status_update_message = u"Results are in for %s" % election.name + status_update_message = "Results are in for %s" % election.name trustees = Trustee.get_by_election(election) @@ -356,20 +352,20 @@ def one_election_view(request, election): def test_cookie(request): continue_url = request.GET['continue_url'] request.session.set_test_cookie() - next_url = "%s?%s" % (reverse(test_cookie_2), urllib.urlencode({'continue_url': continue_url})) + next_url = "%s?%s" % (reverse(url_names.COOKIE_TEST_2), urlencode({'continue_url': continue_url})) return HttpResponseRedirect(settings.SECURE_URL_HOST + next_url) def test_cookie_2(request): continue_url = request.GET['continue_url'] if not request.session.test_cookie_worked(): - return HttpResponseRedirect(settings.SECURE_URL_HOST + ("%s?%s" % (reverse(nocookies), urllib.urlencode({'continue_url': continue_url})))) + return HttpResponseRedirect(settings.SECURE_URL_HOST + ("%s?%s" % (reverse(url_names.COOKIE_NO), urlencode({'continue_url': continue_url})))) request.session.delete_test_cookie() return HttpResponseRedirect(continue_url) def nocookies(request): - retest_url = "%s?%s" % (reverse(test_cookie), urllib.urlencode({'continue_url' : request.GET['continue_url']})) + retest_url = "%s?%s" % (reverse(url_names.COOKIE_TEST), urlencode({'continue_url' : request.GET['continue_url']})) return render_template(request, 'nocookies', {'retest_url': retest_url}) ## @@ -387,7 +383,7 @@ def list_trustees(request, election): def list_trustees_view(request, election): trustees = Trustee.get_by_election(election) user = get_user(request) - admin_p = security.user_can_admin_election(user, election) + admin_p = user_can_admin_election(user, election) return render_template(request, 'list_trustees', {'election': election, 'trustees': trustees, 'admin_p':admin_p}) @@ -403,7 +399,7 @@ def new_trustee(request, election): trustee = Trustee(uuid = str(uuid.uuid1()), election = election, name=name, email=email) trustee.save() - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(list_trustees_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_TRUSTEES_VIEW, args=[election.uuid])) @election_admin(frozen=False) def new_trustee_helios(request, election): @@ -411,13 +407,13 @@ def new_trustee_helios(request, election): Make Helios a trustee of the election """ election.generate_trustee(ELGAMAL_PARAMS) - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(list_trustees_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_TRUSTEES_VIEW, args=[election.uuid])) @election_admin(frozen=False) def delete_trustee(request, election): trustee = Trustee.get_by_election_and_uuid(election, request.GET['uuid']) trustee.delete() - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(list_trustees_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_TRUSTEES_VIEW, args=[election.uuid])) def trustee_login(request, election_short_name, trustee_email, trustee_secret): election = Election.get_by_short_name(election_short_name) @@ -427,21 +423,16 @@ def trustee_login(request, election_short_name, trustee_email, trustee_secret): if trustee: if trustee.secret == trustee_secret: set_logged_in_trustee(request, trustee) - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(trustee_home, args=[election.uuid, trustee.uuid])) - else: - # bad secret, we'll let that redirect to the front page - pass - else: - # no such trustee - raise Http404 - - return HttpResponseRedirect(settings.SECURE_URL_HOST + "/") + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_TRUSTEE_HOME, args=[election.uuid, trustee.uuid])) + # bad secret or no such trustee + raise Http404("Trustee not recognized.") + raise Http404("No election {} found.".format(election_short_name)) @election_admin() def trustee_send_url(request, election, trustee_uuid): trustee = Trustee.get_by_election_and_uuid(election, trustee_uuid) - url = settings.SECURE_URL_HOST + reverse(trustee_login, args=[election.short_name, trustee.email, trustee.secret]) + url = settings.SECURE_URL_HOST + reverse(url_names.TRUSTEE_LOGIN, args=[election.short_name, trustee.email, trustee.secret]) body = """ @@ -455,10 +446,10 @@ Your trustee dashboard is at Helios """ % (election.name, url) - helios_utils.send_email(settings.SERVER_EMAIL, ["%s <%s>" % (trustee.name, trustee.email)], 'your trustee homepage for %s' % election.name, body) + utils.send_email(settings.SERVER_EMAIL, ["%s <%s>" % (trustee.name, trustee.email)], 'your trustee homepage for %s' % election.name, body) logging.info("URL %s " % url) - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(list_trustees_view, args = [election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_TRUSTEES_VIEW, args = [election.uuid])) @trustee_check def trustee_home(request, election, trustee): @@ -480,7 +471,7 @@ def trustee_upload_pk(request, election, trustee): if not trustee.public_key.verify_sk_proof(trustee.pok, algs.DLog_challenge_generator): raise Exception("bad pok for this public key") - trustee.public_key_hash = utils.hash_b64(utils.to_json(trustee.public_key.toJSONDict())) + trustee.public_key_hash = cryptoutils.hash_b64(utils.to_json(trustee.public_key.toJSONDict())) trustee.save() @@ -491,7 +482,7 @@ def trustee_upload_pk(request, election, trustee): # oh well, no message sent pass - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(trustee_home, args=[election.uuid, trustee.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_TRUSTEE_HOME, args=[election.uuid, trustee.uuid])) ## ## Ballot Management @@ -504,10 +495,8 @@ def get_randomness(request, election): get some randomness to sprinkle into the sjcl entropy pool """ return { - # back to urandom, it's fine - "randomness" : base64.b64encode(os.urandom(32)) - #"randomness" : base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes) - } + "randomness" : base64.b64encode(os.urandom(32)).decode('utf-8') + } @election_view(frozen=True) @return_json @@ -539,14 +528,14 @@ def one_election_cast(request, election): on a GET, this is a cancellation, on a POST it's a cast """ if request.method == "GET": - return HttpResponseRedirect("%s%s" % (settings.SECURE_URL_HOST, reverse(one_election_view, args = [election.uuid]))) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args = [election.uuid])) user = get_user(request) encrypted_vote = request.POST['encrypted_vote'] save_in_session_across_logouts(request, 'encrypted_vote', encrypted_vote) - return HttpResponseRedirect("%s%s" % (settings.SECURE_URL_HOST, reverse(one_election_cast_confirm, args=[election.uuid]))) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_cast_confirm, args=[election.uuid])) @election_view(allow_logins=True) def password_voter_login(request, election): @@ -568,7 +557,7 @@ def password_voter_login(request, election): # if user logged in somehow in the interim, e.g. using the login link for administration, # then go! if user_can_see_election(request, election): - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args = [election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args = [election.uuid])) password_login_form = forms.VoterPasswordForm() return render_template(request, 'password_voter_login', @@ -583,7 +572,7 @@ def password_voter_login(request, election): # login depending on whether this is a private election # cause if it's private the login is happening on the front page if election.private_p: - login_url = reverse(password_voter_login, args=[election.uuid]) + login_url = reverse(url_names.election.ELECTION_PASSWORD_VOTER_LOGIN, args=[election.uuid]) else: login_url = reverse(one_election_cast_confirm, args=[election.uuid]) @@ -601,7 +590,7 @@ def password_voter_login(request, election): return one_election_cast_confirm(request, election.uuid) except Voter.DoesNotExist: - redirect_url = login_url + "?" + urllib.urlencode({ + redirect_url = login_url + "?" + urlencode({ 'bad_voter_login' : '1', 'return_url' : return_url }) @@ -609,7 +598,7 @@ def password_voter_login(request, election): return HttpResponseRedirect(settings.SECURE_URL_HOST + redirect_url) else: # bad form, bad voter login - redirect_url = login_url + "?" + urllib.urlencode({ + redirect_url = login_url + "?" + urlencode({ 'bad_voter_login' : '1', 'return_url' : return_url }) @@ -623,7 +612,7 @@ def one_election_cast_confirm(request, election): user = get_user(request) # if no encrypted vote, the user is reloading this page or otherwise getting here in a bad way - if (not request.session.has_key('encrypted_vote')) or request.session['encrypted_vote'] == None: + if ('encrypted_vote' not in request.session) or request.session['encrypted_vote'] is None: return HttpResponseRedirect(settings.URL_HOST) # election not frozen or started @@ -701,7 +690,7 @@ def one_election_cast_confirm(request, election): password_only = False - if auth_systems == None or 'password' in auth_systems: + if auth_systems is None or 'password' in auth_systems: show_password = True password_login_form = forms.VoterPasswordForm() @@ -751,7 +740,7 @@ def one_election_cast_confirm(request, election): # remove the vote from the store del request.session['encrypted_vote'] - return HttpResponseRedirect("%s%s" % (settings.URL_HOST, reverse(one_election_cast_done, args=[election.uuid]))) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_cast_done, args=[election.uuid])) @election_view() def one_election_cast_done(request, election): @@ -771,7 +760,7 @@ def one_election_cast_done(request, election): # only log out if the setting says so *and* we're dealing # with a site-wide voter. Definitely remove current_voter # checking that voter.user != None is needed because voter.user may now be None if voter is password only - if voter.user == user and voter.user != None: + if voter.user == user and voter.user is not None: logout = settings.LOGOUT_ON_CONFIRMATION else: logout = False @@ -827,7 +816,7 @@ def one_election_bboard(request, election): order_by = 'alias' # if there's a specific voter - if request.GET.has_key('q'): + if 'q' in request.GET: # FIXME: figure out the voter by voter_id voters = [] else: @@ -851,7 +840,7 @@ def one_election_audited_ballots(request, election): UI to show election audited ballots """ - if request.GET.has_key('vote_hash'): + if 'vote_hash' in request.GET: b = AuditedBallot.get(election, request.GET['vote_hash']) return HttpResponse(b.raw_vote, content_type="text/plain") @@ -932,14 +921,14 @@ def one_election_set_featured(request, election): """ user = get_user(request) - if not security.user_can_feature_election(user, election): + if not user_can_feature_election(user, election): raise PermissionDenied() featured_p = bool(int(request.GET['featured_p'])) election.featured_p = featured_p election.save() - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) @election_admin() def one_election_archive(request, election): @@ -953,7 +942,7 @@ def one_election_archive(request, election): election.save() - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) @election_admin() def one_election_copy(request, election): @@ -972,7 +961,7 @@ def one_election_copy(request, election): name = "Copy of " + election.name, election_type = election.election_type, private_p = election.private_p, - description = election.description, + description = election.description_bleached, questions = election.questions, eligibility = election.eligibility, openreg = election.openreg, @@ -987,7 +976,7 @@ def one_election_copy(request, election): new_election.generate_trustee(ELGAMAL_PARAMS) - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[new_election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[new_election.uuid])) # changed from admin to view because # anyone can see the questions, the administration aspect is now @@ -996,7 +985,7 @@ def one_election_copy(request, election): def one_election_questions(request, election): questions_json = utils.to_json(election.questions) user = get_user(request) - admin_p = security.user_can_admin_election(user, election) + admin_p = user_can_admin_election(user, election) return render_template(request, 'election_questions', {'election': election, 'questions_json' : questions_json, 'admin_p': admin_p}) @@ -1026,7 +1015,7 @@ def one_election_register(request, election): if not voter: voter = _register_voter(election, user) - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) @election_admin(frozen=False) def one_election_save_questions(request, election): @@ -1055,7 +1044,7 @@ def one_election_freeze(request, election): election.freeze() if get_user(request): - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) else: return SUCCESS @@ -1071,7 +1060,7 @@ def one_election_compute_tally(request, election): tallying is done all at a time now """ if not _check_election_tally_type(election): - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view,args=[election.election_id])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW,args=[election.election_id])) if request.method == "GET": return render_template(request, 'election_compute_tally', {'election': election}) @@ -1086,18 +1075,18 @@ def one_election_compute_tally(request, election): tasks.election_compute_tally.delay(election_id = election.id) - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view,args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW,args=[election.uuid])) @trustee_check def trustee_decrypt_and_prove(request, election, trustee): - if not _check_election_tally_type(election) or election.encrypted_tally == None: - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view,args=[election.uuid])) + if not _check_election_tally_type(election) or election.encrypted_tally is None: + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW,args=[election.uuid])) return render_template(request, 'trustee_decrypt_and_prove', {'election': election, 'trustee': trustee}) @election_view(frozen=True) def trustee_upload_decryption(request, election, trustee_uuid): - if not _check_election_tally_type(election) or election.encrypted_tally == None: + if not _check_election_tally_type(election) or election.encrypted_tally is None: return FAILURE trustee = Trustee.get_by_election_and_uuid(election, trustee_uuid) @@ -1138,9 +1127,9 @@ def release_result(request, election): election.save() if request.POST.get('send_email', ''): - return HttpResponseRedirect("%s?%s" % (settings.SECURE_URL_HOST + reverse(voters_email, args=[election.uuid]),urllib.urlencode({'template': 'result'}))) + return HttpResponseRedirect("%s?%s" % (settings.SECURE_URL_HOST + reverse(voters_email, args=[election.uuid]),urlencode({'template': 'result'}))) else: - return HttpResponseRedirect("%s" % (settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid]))) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) # if just viewing the form or the form is not valid return render_template(request, 'release_result', {'election': election}) @@ -1159,7 +1148,7 @@ def combine_decryptions(request, election): election.combine_decryptions() election.save() - return HttpResponseRedirect("%s" % (settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid]))) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) # if just viewing the form or the form is not valid return render_template(request, 'combine_decryptions', {'election': election}) @@ -1167,7 +1156,7 @@ def combine_decryptions(request, election): @election_admin(frozen=True) def one_election_set_result_and_proof(request, election): if election.tally_type != "homomorphic" or election.encrypted_tally == None: - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view,args=[election.election_id])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.election_id])) # FIXME: check csrf @@ -1176,7 +1165,7 @@ def one_election_set_result_and_proof(request, election): election.save() if get_user(request): - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) else: return SUCCESS @@ -1200,7 +1189,7 @@ def voters_list_pretty(request, election): order_by = 'alias' user = get_user(request) - admin_p = security.user_can_admin_election(user, election) + admin_p = user_can_admin_election(user, election) categories = None eligibility_category_id = None @@ -1213,7 +1202,7 @@ def voters_list_pretty(request, election): return user_reauth(request, user) # files being processed - voter_files = election.voterfile_set.all() + voter_files = election.voterfile_set.all().order_by('-uploaded_at') # load a bunch of voters # voters = Voter.get_by_election(election, order_by=order_by) @@ -1233,9 +1222,9 @@ def voters_list_pretty(request, election): return render_template(request, 'voters_list', {'election': election, 'voters_page': voters_page, 'voters': voters_page.object_list, 'admin_p': admin_p, - 'email_voters': helios.VOTERS_EMAIL, + 'email_voters': VOTERS_EMAIL, 'limit': limit, 'total_voters': total_voters, - 'upload_p': helios.VOTERS_UPLOAD, 'q' : q, + 'upload_p': VOTERS_UPLOAD, 'q' : q, 'voter_files': voter_files, 'categories': categories, 'eligibility_category_id' : eligibility_category_id}) @@ -1301,7 +1290,7 @@ def voters_upload(request, election): return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(voters_list_pretty, args=[election.uuid])) else: # we need to confirm - if request.FILES.has_key('voters_file'): + if 'voters_file' in request.FILES: voters_file = request.FILES['voters_file'] voter_file_obj = election.add_voters_file(voters_file) @@ -1322,7 +1311,7 @@ def voters_upload(request, election): return render_template(request, 'voters_upload_confirm', {'election': election, 'voters': voters, 'problems': problems}) else: - return HttpResponseRedirect("%s?%s" % (settings.SECURE_URL_HOST + reverse(voters_upload, args=[election.uuid]), urllib.urlencode({'e':'no voter file specified, try again'}))) + return HttpResponseRedirect("%s?%s" % (settings.SECURE_URL_HOST + reverse(voters_upload, args=[election.uuid]), urlencode({'e':'no voter file specified, try again'}))) @election_admin() def voters_upload_cancel(request, election): @@ -1335,12 +1324,12 @@ def voters_upload_cancel(request, election): vf.delete() del request.session['voter_file_id'] - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) @election_admin(frozen=True) def voters_email(request, election): - if not helios.VOTERS_EMAIL: - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])) + if not VOTERS_EMAIL: + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) TEMPLATES = [ ('vote', 'Time to Vote'), ('simple', 'Simple'), @@ -1396,8 +1385,7 @@ def voters_email(request, election): 'custom_subject' : email_form.cleaned_data['subject'], 'custom_message' : email_form.cleaned_data['body'], 'election_vote_url' : election_vote_url, - 'election_url' : election_url, - 'election' : election + 'election_url' : election_url } voter_constraints_include = None @@ -1417,7 +1405,7 @@ def voters_email(request, election): tasks.voters_email.delay(election_id = election.id, subject_template = subject_template, body_template = body_template, extra_vars = extra_vars, voter_constraints_include = voter_constraints_include, voter_constraints_exclude = voter_constraints_exclude) # this batch process is all async, so we can return a nice note - return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])) + return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(url_names.election.ELECTION_VIEW, args=[election.uuid])) return render_template(request, "voters_email", { 'email_form': email_form, 'election': election, @@ -1480,9 +1468,9 @@ def ballot_list(request, election): and optionally take a after parameter. """ limit = after = None - if request.GET.has_key('limit'): + if 'limit' in request.GET: limit = int(request.GET['limit']) - if request.GET.has_key('after'): + if 'after' in request.GET: after = datetime.datetime.strptime(request.GET['after'], '%Y-%m-%d %H:%M:%S') voters = Voter.get_by_election(election, cast=True, order_by='cast_at', limit=limit, after=after) diff --git a/helios/widgets.py b/helios/widgets.py index 7eff891c13b6601a9bd77c78185e25415463a094..ba271ce70e67b0485b4fc6b4daef75941fc2d8de 100644 --- a/helios/widgets.py +++ b/helios/widgets.py @@ -5,8 +5,7 @@ Widget for datetime split, with calendar for date, and drop-downs for times. from django import forms from django.db import models from django.template.loader import render_to_string -from django.forms.widgets import Select, MultiWidget, DateInput, TextInput, Widget -from django.forms.extras.widgets import SelectDateWidget +from django.forms.widgets import Select, MultiWidget, DateInput, TextInput, Widget, SelectDateWidget from time import strftime import re @@ -36,38 +35,39 @@ class SelectTimeWidget(Widget): Also allows user-defined increments for minutes/seconds """ + template_name = '' hour_field = '%s_hour' minute_field = '%s_minute' meridiem_field = '%s_meridiem' twelve_hr = False # Default to 24hr. - + def __init__(self, attrs=None, hour_step=None, minute_step=None, twelve_hr=False): """ hour_step, minute_step, second_step are optional step values for for the range of values for the associated select element twelve_hr: If True, forces the output to be in 12-hr format (rather than 24-hr) """ - self.attrs = attrs or {} - + super(SelectTimeWidget, self).__init__(attrs) + if twelve_hr: self.twelve_hr = True # Do 12hr (rather than 24hr) self.meridiem_val = 'a.m.' # Default to Morning (A.M.) if hour_step and twelve_hr: - self.hours = range(1,13,hour_step) + self.hours = list(range(1,13,hour_step)) elif hour_step: # 24hr, with stepping. - self.hours = range(0,24,hour_step) + self.hours = list(range(0,24,hour_step)) elif twelve_hr: # 12hr, no stepping - self.hours = range(1,13) + self.hours = list(range(1,13)) else: # 24hr, no stepping - self.hours = range(0,24) + self.hours = list(range(0,24)) if minute_step: - self.minutes = range(0,60,minute_step) + self.minutes = list(range(0,60,minute_step)) else: - self.minutes = range(0,60) + self.minutes = list(range(0,60)) - def render(self, name, value, attrs=None): + def render(self, name, value, attrs=None, renderer=None): try: # try to get time values from a datetime.time object (value) hour_val, minute_val = value.hour, value.minute if self.twelve_hr: @@ -77,10 +77,10 @@ class SelectTimeWidget(Widget): self.meridiem_val = 'a.m.' except AttributeError: hour_val = minute_val = 0 - if isinstance(value, basestring): + if isinstance(value, str): match = RE_TIME.match(value) if match: - time_groups = match.groups(); + time_groups = match.groups() hour_val = int(time_groups[HOURS]) % 24 # force to range(0-24) minute_val = int(time_groups[MINUTES]) @@ -113,11 +113,11 @@ class SelectTimeWidget(Widget): # For times to get displayed correctly, the values MUST be converted to unicode # When Select builds a list of options, it checks against Unicode values - hour_val = u"%.2d" % hour_val - minute_val = u"%.2d" % minute_val + hour_val = "%.2d" % hour_val + minute_val = "%.2d" % minute_val hour_choices = [("%.2d"%i, "%.2d"%i) for i in self.hours] - local_attrs = self.build_attrs(id=self.hour_field % id_) + local_attrs = self.build_attrs({'id': self.hour_field % id_}) select_html = Select(choices=hour_choices).render(self.hour_field % name, hour_val, local_attrs) output.append(select_html) @@ -137,7 +137,7 @@ class SelectTimeWidget(Widget): select_html = Select(choices=meridiem_choices).render(self.meridiem_field % name, self.meridiem_val, local_attrs) output.append(select_html) - return mark_safe(u'\n'.join(output)) + return mark_safe('\n'.join(output)) def id_for_label(self, id_): return '%s_hour' % id_ @@ -169,6 +169,8 @@ class SplitSelectDateTimeWidget(MultiWidget): This class combines SelectTimeWidget and SelectDateWidget so we have something like SpliteDateTimeWidget (in django.forms.widgets), but with Select elements. """ + template_name = '' + def __init__(self, attrs=None, hour_step=None, minute_step=None, twelve_hr=None, years=None): """ pass all these parameters to their respective widget constructors...""" widgets = (SelectDateWidget(attrs=attrs, years=years), SelectTimeWidget(attrs=attrs, hour_step=hour_step, minute_step=minute_step, twelve_hr=twelve_hr)) @@ -177,7 +179,7 @@ class SplitSelectDateTimeWidget(MultiWidget): # See https://stackoverflow.com/questions/4324676/django-multiwidget-subclass-not-calling-decompress def value_from_datadict(self, data, files, name): if data.get(name, None) is None: - return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)] + return [widget.value_from_datadict(data, files, name ) for widget in self.widgets] return self.decompress(data.get(name, None)) def decompress(self, value): @@ -185,13 +187,27 @@ class SplitSelectDateTimeWidget(MultiWidget): return [value.date(), value.time().replace(microsecond=0)] return [None, None] - def format_output(self, rendered_widgets): + def compress(self, data_list): """ - Given a list of rendered widgets (as strings), it inserts an HTML - linebreak between them. - - Returns a Unicode string representing the HTML for the whole lot. + Takes the values from the MultiWidget and passes them as a + list to this function. This function needs to compress the + list into a single object in order to be correctly rendered by the widget. + For instace, django.forms.widgets.SelectDateWidget.format_value(value) + expects a date object or a string, not a list. + This method was taken from helios/fields.py """ - rendered_widgets.insert(-1, '<br/>') - return u''.join(rendered_widgets) - + if data_list: + import datetime + if not (data_list[0] and data_list[1]): + return None + try: + return datetime.datetime.combine(*data_list) + except: + # badly formed date + return None + return None + + def render(self, name, value, attrs=None, renderer=None): + value = self.compress(value) + rendered_widgets = list(widget.render(name, value, attrs=attrs, renderer=renderer) for widget in self.widgets) + return '<br/>'.join(rendered_widgets) diff --git a/helios/workflows/homomorphic.py b/helios/workflows/homomorphic.py index 63e2d0c42ec1153afac7a6304e3824975be8a646..9c8039679bff10f9b0178fc16f1b27bafdbc16be 100644 --- a/helios/workflows/homomorphic.py +++ b/helios/workflows/homomorphic.py @@ -6,11 +6,8 @@ Ben Adida reworked 2011-01-09 """ -from helios.crypto import algs, utils import logging -import uuid -import datetime -from helios import models +from helios.crypto import algs from . import WorkflowObject class EncryptedAnswer(WorkflowObject): @@ -72,10 +69,10 @@ class EncryptedAnswer(WorkflowObject): return False # compute homomorphic sum if needed - if max != None: + if max is not None: homomorphic_sum = choice * homomorphic_sum - if max != None: + if max is not None: # determine possible plaintexts for the sum sum_possible_plaintexts = self.generate_plaintexts(pk, min=min, max=max) @@ -114,7 +111,7 @@ class EncryptedAnswer(WorkflowObject): # min and max for number of answers, useful later min_answers = 0 - if question.has_key('min'): + if 'min' in question: min_answers = question['min'] max_answers = question['max'] @@ -128,7 +125,7 @@ class EncryptedAnswer(WorkflowObject): num_selected_answers += 1 # randomness and encryption - randomness[answer_num] = algs.Utils.random_mpz_lt(pk.q) + randomness[answer_num] = algs.random.mpz_lt(pk.q) choices[answer_num] = pk.encrypt_with_r(plaintexts[plaintext_index], randomness[answer_num]) # generate proof @@ -136,7 +133,7 @@ class EncryptedAnswer(WorkflowObject): randomness[answer_num], algs.EG_disjunctive_challenge_generator) # sum things up homomorphically if needed - if max_answers != None: + if max_answers is not None: homomorphic_sum = choices[answer_num] * homomorphic_sum randomness_sum = (randomness_sum + randomness[answer_num]) % pk.q @@ -146,7 +143,7 @@ class EncryptedAnswer(WorkflowObject): if num_selected_answers < min_answers: raise Exception("Need to select at least %s answer(s)" % min_answers) - if max_answers != None: + if max_answers is not None: sum_plaintexts = cls.generate_plaintexts(pk, min=min_answers, max=max_answers) # need to subtract the min from the offset @@ -164,7 +161,7 @@ class EncryptedVote(WorkflowObject): An encrypted ballot """ def __init__(self): - self.encrypted_answers = None + self.encrypted_answers = [] @property def datatype(self): @@ -180,26 +177,37 @@ class EncryptedVote(WorkflowObject): answers = property(_answers_get, _answers_set) def verify(self, election): - # right number of answers - if len(self.encrypted_answers) != len(election.questions): + # correct number of answers + # noinspection PyUnresolvedReferences + n_answers = len(self.encrypted_answers) if self.encrypted_answers is not None else 0 + n_questions = len(election.questions) if election.questions is not None else 0 + if n_answers != n_questions: + logging.error(f"Incorrect number of answers ({n_answers}) vs questions ({n_questions})") return False - + # check hash - if self.election_hash != election.hash: - # print "%s / %s " % (self.election_hash, election.hash) + # noinspection PyUnresolvedReferences + our_election_hash = self.election_hash if isinstance(self.election_hash, str) else self.election_hash.decode() + actual_election_hash = election.hash if isinstance(election.hash, str) else election.hash.decode() + if our_election_hash != actual_election_hash: + logging.error(f"Incorrect election_hash {our_election_hash} vs {actual_election_hash} ") return False - + # check ID - if self.election_uuid != election.uuid: + # noinspection PyUnresolvedReferences + our_election_uuid = self.election_uuid if isinstance(self.election_uuid, str) else self.election_uuid.decode() + actual_election_uuid = election.uuid if isinstance(election.uuid, str) else election.uuid.decode() + if our_election_uuid != actual_election_uuid: + logging.error(f"Incorrect election_uuid {our_election_uuid} vs {actual_election_uuid} ") return False - + # check proofs on all of answers for question_num in range(len(election.questions)): ea = self.encrypted_answers[question_num] question = election.questions[question_num] min_answers = 0 - if question.has_key('min'): + if 'min' in question: min_answers = question['min'] if not ea.verify(election.public_key, min=min_answers, max=question['max']): diff --git a/helios_auth/__init__.py b/helios_auth/__init__.py index ca245ff38f820db528afbed3a43d597bc8642b44..d5e23fa983fe0874f005481d1cddae929efde928 100644 --- a/helios_auth/__init__.py +++ b/helios_auth/__init__.py @@ -4,7 +4,7 @@ from django.conf import settings TEMPLATE_BASE = settings.AUTH_TEMPLATE_BASE or "helios_auth/templates/base.html" # enabled auth systems -import auth_systems -ENABLED_AUTH_SYSTEMS = settings.AUTH_ENABLED_AUTH_SYSTEMS or auth_systems.AUTH_SYSTEMS.keys() -DEFAULT_AUTH_SYSTEM = settings.AUTH_DEFAULT_AUTH_SYSTEM or None +from . import auth_systems +ENABLED_AUTH_SYSTEMS = settings.AUTH_ENABLED_SYSTEMS or list(auth_systems.AUTH_SYSTEMS.keys()) +DEFAULT_AUTH_SYSTEM = settings.AUTH_DEFAULT_SYSTEM or None diff --git a/helios_auth/apps.py b/helios_auth/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..7d1472ae311ac4ed3066ab8c76f5603ada1f4814 --- /dev/null +++ b/helios_auth/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class HeliosAuthConfig(AppConfig): + name = 'helios_auth' + verbose_name = "Helios Authentication" diff --git a/helios_auth/auth_systems/__init__.py b/helios_auth/auth_systems/__init__.py index 5a0e9233ba4df89067688283e90649aef4f1ae70..84382e84a4f394b8e16c1d326d637f9dedf18c55 100644 --- a/helios_auth/auth_systems/__init__.py +++ b/helios_auth/auth_systems/__init__.py @@ -1,10 +1,11 @@ +from django.conf import settings +from . import password, twitter, linkedin, cas, facebook, google, yahoo, clever AUTH_SYSTEMS = {} -import twitter, password, cas, facebook, google, yahoo, linkedin, clever +AUTH_SYSTEMS['password'] = password AUTH_SYSTEMS['twitter'] = twitter AUTH_SYSTEMS['linkedin'] = linkedin -AUTH_SYSTEMS['password'] = password AUTH_SYSTEMS['cas'] = cas AUTH_SYSTEMS['facebook'] = facebook AUTH_SYSTEMS['google'] = google @@ -16,7 +17,7 @@ AUTH_SYSTEMS['clever'] = clever #AUTH_SYSTEMS['live'] = live def can_check_constraint(auth_system): - return hasattr(AUTH_SYSTEMS[auth_system], 'check_constraint') + return auth_system in AUTH_SYSTEMS and hasattr(AUTH_SYSTEMS[auth_system], 'check_constraint') def can_list_categories(auth_system): - return hasattr(AUTH_SYSTEMS[auth_system], 'list_categories') + return auth_system in AUTH_SYSTEMS and hasattr(AUTH_SYSTEMS[auth_system], 'list_categories') diff --git a/helios_auth/auth_systems/cas.py b/helios_auth/auth_systems/cas.py index 8202ad262712f527b8f8896b02b7a2b47b445917..21c86cfe73577021491bacfd1c20f58f36c94fe2 100644 --- a/helios_auth/auth_systems/cas.py +++ b/helios_auth/auth_systems/cas.py @@ -5,13 +5,17 @@ Some code borrowed from https://sp.princeton.edu/oit/sdp/CAS/Wiki%20Pages/Python.aspx """ -from django.http import * -from django.core.mail import send_mail -from django.conf import settings - -import sys, os, cgi, urllib, urllib2, re, uuid, datetime +import datetime +import re +import urllib.parse +import urllib.request +import uuid from xml.etree import ElementTree +from django.conf import settings +from django.core.mail import send_mail +from django.http import HttpResponseRedirect + CAS_EMAIL_DOMAIN = "princeton.edu" CAS_URL= 'https://fed.princeton.edu/cas/' CAS_LOGOUT_URL = 'https://fed.princeton.edu/cas/logout?service=%s' @@ -31,25 +35,25 @@ STATUS_UPDATES = False def _get_service_url(): # FIXME current URL - from helios_auth.views import after + from helios_auth import url_names from django.conf import settings - from django.core.urlresolvers import reverse + from django.urls import reverse - return settings.SECURE_URL_HOST + reverse(after) + return settings.SECURE_URL_HOST + reverse(url_names.AUTH_AFTER) def get_auth_url(request, redirect_url): request.session['cas_redirect_url'] = redirect_url - return CAS_URL + 'login?service=' + urllib.quote(_get_service_url()) + return CAS_URL + 'login?service=' + urllib.parse.quote(_get_service_url()) def get_user_category(user_id): theurl = CAS_ELIGIBILITY_URL % user_id - auth_handler = urllib2.HTTPBasicAuthHandler() + auth_handler = urllib.request.HTTPBasicAuthHandler() auth_handler.add_password(realm=CAS_ELIGIBILITY_REALM, uri= theurl, user= CAS_USERNAME, passwd = CAS_PASSWORD) - opener = urllib2.build_opener(auth_handler) - urllib2.install_opener(opener) + opener = urllib.request.build_opener(auth_handler) + urllib.request.install_opener(opener) - result = urllib2.urlopen(CAS_ELIGIBILITY_URL % user_id).read().strip() + result = urllib.request.urlopen(CAS_ELIGIBILITY_URL % user_id).read().strip() parsed_result = ElementTree.fromstring(result) return parsed_result.text @@ -75,11 +79,11 @@ def get_saml_info(ticket): </soap-env:Envelope> """ % (uuid.uuid1(), datetime.datetime.utcnow().isoformat(), ticket) - url = CAS_SAML_VALIDATE_URL % urllib.quote(_get_service_url()) + url = CAS_SAML_VALIDATE_URL % urllib.parse.quote(_get_service_url()) # by virtue of having a body, this is a POST - req = urllib2.Request(url, saml_request) - raw_response = urllib2.urlopen(req).read() + req = urllib.request.Request(url, saml_request) + raw_response = urllib.request.urlopen(req).read() logging.info("RESP:\n%s\n\n" % raw_response) @@ -127,8 +131,8 @@ def get_user_info(user_id): </soap-env:Envelope> """ % user_id - req = urllib2.Request(url, request_body, headers) - response = urllib2.urlopen(req).read() + req = urllib.request.Request(url, request_body, headers) + response = urllib.request.urlopen(req).read() # parse the result from xml.dom.minidom import parseString @@ -146,12 +150,12 @@ def get_user_info(user_id): def get_user_info_special(ticket): # fetch the information from the CAS server val_url = CAS_URL + "validate" + \ - '?service=' + urllib.quote(_get_service_url()) + \ - '&ticket=' + urllib.quote(ticket) - r = urllib.urlopen(val_url).readlines() # returns 2 lines + '?service=' + urllib.parse.quote(_get_service_url()) + \ + '&ticket=' + urllib.parse.quote(ticket) + r = urllib.request.urlopen(val_url).readlines() # returns 2 lines # success - if len(r) == 2 and re.match("yes", r[0]) != None: + if len(r) == 2 and re.match("yes", r[0]) is not None: netid = r[1].strip() category = get_user_category(netid) @@ -209,7 +213,7 @@ def send_message(user_id, name, user_info, subject, body): else: email = "%s@%s" % (user_id, CAS_EMAIL_DOMAIN) - if user_info.has_key('name'): + if 'name' in user_info: name = user_info["name"] else: name = email @@ -221,7 +225,7 @@ def send_message(user_id, name, user_info, subject, body): # def check_constraint(constraint, user): - if not user.info.has_key('category'): + if 'category' not in user.info: return False return constraint['year'] == user.info['category'] diff --git a/helios_auth/auth_systems/clever.py b/helios_auth/auth_systems/clever.py index dcd52d0e522b4bf2039a6d5752dca26090eefa9c..48648a0b07a55add763512be3beb457ed08bb298 100644 --- a/helios_auth/auth_systems/clever.py +++ b/helios_auth/auth_systems/clever.py @@ -3,16 +3,15 @@ Clever Authentication """ -from django.http import * -from django.core.mail import send_mail -from django.conf import settings - -import httplib2,json,base64 - -import sys, os, cgi, urllib, urllib2, re +import base64 +import urllib.parse +import httplib2 +from django.conf import settings from oauth2client.client import OAuth2WebServerFlow, OAuth2Credentials +from helios_auth import utils + # some parameters to indicate that status updating is not possible STATUS_UPDATES = False @@ -45,7 +44,7 @@ def get_user_info_after_auth(request): # do the POST manually, because OAuth2WebFlow can't do auth header for token exchange http = httplib2.Http(".cache") auth_header = "Basic %s" % base64.b64encode(settings.CLEVER_CLIENT_ID + ":" + settings.CLEVER_CLIENT_SECRET) - resp_headers, content = http.request("https://clever.com/oauth/tokens", "POST", urllib.urlencode({ + resp_headers, content = http.request("https://clever.com/oauth/tokens", "POST", urllib.parse.urlencode({ "code" : code, "grant_type": "authorization_code", "redirect_uri": redirect_uri @@ -54,7 +53,7 @@ def get_user_info_after_auth(request): 'Content-Type': "application/x-www-form-urlencoded" }) - token_response = json.loads(content) + token_response = utils.from_json(content) access_token = token_response['access_token'] # package the credentials @@ -65,7 +64,7 @@ def get_user_info_after_auth(request): (resp_headers, content) = http.request("https://api.clever.com/me", "GET") # {"type":"student","data":{"id":"563395179f7408755c0006b7","district":"5633941748c07c0100000aac","type":"student","created":"2015-10-30T16:04:39.262Z","credentials":{"district_password":"eel7Thohd","district_username":"dianes10"},"dob":"1998-11-01T00:00:00.000Z","ell_status":"Y","email":"diane.s@example.org","gender":"F","grade":"9","hispanic_ethnicity":"Y","last_modified":"2015-10-30T16:04:39.274Z","location":{"zip":"11433"},"name":{"first":"Diane","last":"Schmeler","middle":"J"},"race":"Asian","school":"5633950c62fc41c041000005","sis_id":"738733110","state_id":"114327752","student_number":"738733110"},"links":[{"rel":"self","uri":"/me"},{"rel":"canonical","uri":"/v1.1/students/563395179f7408755c0006b7"},{"rel":"district","uri":"/v1.1/districts/5633941748c07c0100000aac"}]} - response = json.loads(content) + response = utils.from_json(content) user_id = response['data']['id'] user_name = "%s %s" % (response['data']['name']['first'], response['data']['name']['last']) @@ -73,7 +72,7 @@ def get_user_info_after_auth(request): user_district = response['data']['district'] user_grade = response['data'].get('grade', None) - print content + print(content) # watch out, response also contains email addresses, but not sure whether thsoe are verified or not # so for email address we will only look at the id_token @@ -103,7 +102,7 @@ def send_message(user_id, name, user_info, subject, body): # def check_constraint(constraint, user): - if not user.info.has_key('grade'): + if 'grade' not in user.info: return False return constraint['grade'] == user.info['grade'] diff --git a/helios_auth/auth_systems/facebook.py b/helios_auth/auth_systems/facebook.py index 4c50c6594336f60c2999303c57088e6feeb77b4c..4810ec20494fef34aba4e5017d092c88e185c39c 100644 --- a/helios_auth/auth_systems/facebook.py +++ b/helios_auth/auth_systems/facebook.py @@ -2,8 +2,6 @@ Facebook Authentication """ -import logging - from django.conf import settings from django.core.mail import send_mail @@ -12,7 +10,7 @@ API_KEY = settings.FACEBOOK_API_KEY API_SECRET = settings.FACEBOOK_API_SECRET #from facebookclient import Facebook -import urllib, urllib2, cgi +import urllib.request, urllib.error, urllib.parse # some parameters to indicate that status updating is possible STATUS_UPDATES = True @@ -22,21 +20,21 @@ from helios_auth import utils def facebook_url(url, params): if params: - return "https://graph.facebook.com%s?%s" % (url, urllib.urlencode(params)) + return "https://graph.facebook.com%s?%s" % (url, urllib.parse.urlencode(params)) else: return "https://graph.facebook.com%s" % url def facebook_get(url, params): full_url = facebook_url(url,params) try: - return urllib2.urlopen(full_url).read() - except urllib2.HTTPError: + return urllib.request.urlopen(full_url).read() + except urllib.error.HTTPError: from helios_auth.models import AuthenticationExpired raise AuthenticationExpired() def facebook_post(url, params): full_url = facebook_url(url, None) - return urllib2.urlopen(full_url, urllib.urlencode(params)).read() + return urllib.request.urlopen(full_url, urllib.parse.urlencode(params)).read() def get_auth_url(request, redirect_url): request.session['fb_redirect_uri'] = redirect_url @@ -69,7 +67,7 @@ def update_status(user_id, user_info, token, message): }) def send_message(user_id, user_name, user_info, subject, body): - if user_info.has_key('email'): + if 'email' in user_info: send_mail(subject, body, settings.SERVER_EMAIL, ["%s <%s>" % (user_name, user_info['email'])], fail_silently=False) diff --git a/helios_auth/auth_systems/facebookclient/__init__.py b/helios_auth/auth_systems/facebookclient/__init__.py deleted file mode 100644 index 03264730c9ab284b3500793fc232ad15175a945e..0000000000000000000000000000000000000000 --- a/helios_auth/auth_systems/facebookclient/__init__.py +++ /dev/null @@ -1,1431 +0,0 @@ -#! /usr/bin/env python -# -# pyfacebook - Python bindings for the Facebook API -# -# Copyright (c) 2008, Samuel Cormier-Iijima -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of the author nor the names of its contributors may -# be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS``AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -""" -Python bindings for the Facebook API (pyfacebook - http://code.google.com/p/pyfacebook) - -PyFacebook is a client library that wraps the Facebook API. - -For more information, see - -Home Page: http://code.google.com/p/pyfacebook -Developer Wiki: http://wiki.developers.facebook.com/index.php/Python -Facebook IRC Channel: #facebook on irc.freenode.net - -PyFacebook can use simplejson if it is installed, which -is much faster than XML and also uses less bandwith. Go to -http://undefined.org/python/#simplejson to download it, or do -apt-get install python-simplejson on a Debian-like system. -""" - -import sys -import time -import struct -import urllib -import urllib2 -import httplib -import hashlib -import binascii -import urlparse -import mimetypes - -# try to use simplejson first, otherwise fallback to XML -RESPONSE_FORMAT = 'JSON' - -import json - -# try: -# import json as simplejson -# except ImportError: -# try: -# import simplejson -# except ImportError: -# try: -# from django.utils import simplejson -# except ImportError: -# try: -# import jsonlib as simplejson -# simplejson.loads -# except (ImportError, AttributeError): -# from xml.dom import minidom -# RESPONSE_FORMAT = 'XML' - -# support Google App Engine. GAE does not have a working urllib.urlopen. -try: - from google.appengine.api import urlfetch - - def urlread(url, data=None, headers=None): - if data is not None: - if headers is None: - headers = {"Content-type": "application/x-www-form-urlencoded"} - method = urlfetch.POST - else: - if headers is None: - headers = {} - method = urlfetch.GET - - result = urlfetch.fetch(url, method=method, - payload=data, headers=headers) - - if result.status_code == 200: - return result.content - else: - raise urllib2.URLError("fetch error url=%s, code=%d" % (url, result.status_code)) - -except ImportError: - def urlread(url, data=None): - res = urllib2.urlopen(url, data=data) - return res.read() - -__all__ = ['Facebook'] - -VERSION = '0.1' - -FACEBOOK_URL = 'http://api.facebook.com/restserver.php' -FACEBOOK_SECURE_URL = 'https://api.facebook.com/restserver.php' - -class json(object): pass - -# simple IDL for the Facebook API -METHODS = { - 'application': { - 'getPublicInfo': [ - ('application_id', int, ['optional']), - ('application_api_key', str, ['optional']), - ('application_canvas_name', str,['optional']), - ], - }, - - # admin methods - 'admin': { - 'getAllocation': [ - ('integration_point_name', str, []), - ], - }, - - # auth methods - 'auth': { - 'revokeAuthorization': [ - ('uid', int, ['optional']), - ], - }, - - # feed methods - 'feed': { - 'publishStoryToUser': [ - ('title', str, []), - ('body', str, ['optional']), - ('image_1', str, ['optional']), - ('image_1_link', str, ['optional']), - ('image_2', str, ['optional']), - ('image_2_link', str, ['optional']), - ('image_3', str, ['optional']), - ('image_3_link', str, ['optional']), - ('image_4', str, ['optional']), - ('image_4_link', str, ['optional']), - ('priority', int, ['optional']), - ], - - 'publishActionOfUser': [ - ('title', str, []), - ('body', str, ['optional']), - ('image_1', str, ['optional']), - ('image_1_link', str, ['optional']), - ('image_2', str, ['optional']), - ('image_2_link', str, ['optional']), - ('image_3', str, ['optional']), - ('image_3_link', str, ['optional']), - ('image_4', str, ['optional']), - ('image_4_link', str, ['optional']), - ('priority', int, ['optional']), - ], - - 'publishTemplatizedAction': [ - ('title_template', str, []), - ('page_actor_id', int, ['optional']), - ('title_data', json, ['optional']), - ('body_template', str, ['optional']), - ('body_data', json, ['optional']), - ('body_general', str, ['optional']), - ('image_1', str, ['optional']), - ('image_1_link', str, ['optional']), - ('image_2', str, ['optional']), - ('image_2_link', str, ['optional']), - ('image_3', str, ['optional']), - ('image_3_link', str, ['optional']), - ('image_4', str, ['optional']), - ('image_4_link', str, ['optional']), - ('target_ids', list, ['optional']), - ], - - 'registerTemplateBundle': [ - ('one_line_story_templates', json, []), - ('short_story_templates', json, ['optional']), - ('full_story_template', json, ['optional']), - ('action_links', json, ['optional']), - ], - - 'deactivateTemplateBundleByID': [ - ('template_bundle_id', int, []), - ], - - 'getRegisteredTemplateBundles': [], - - 'getRegisteredTemplateBundleByID': [ - ('template_bundle_id', str, []), - ], - - 'publishUserAction': [ - ('template_bundle_id', int, []), - ('template_data', json, ['optional']), - ('target_ids', list, ['optional']), - ('body_general', str, ['optional']), - ('story_size', int, ['optional']), - ], - }, - - # fql methods - 'fql': { - 'query': [ - ('query', str, []), - ], - }, - - # friends methods - 'friends': { - 'areFriends': [ - ('uids1', list, []), - ('uids2', list, []), - ], - - 'get': [ - ('flid', int, ['optional']), - ], - - 'getLists': [], - - 'getAppUsers': [], - }, - - # notifications methods - 'notifications': { - 'get': [], - - 'send': [ - ('to_ids', list, []), - ('notification', str, []), - ('email', str, ['optional']), - ('type', str, ['optional']), - ], - - 'sendRequest': [ - ('to_ids', list, []), - ('type', str, []), - ('content', str, []), - ('image', str, []), - ('invite', bool, []), - ], - - 'sendEmail': [ - ('recipients', list, []), - ('subject', str, []), - ('text', str, ['optional']), - ('fbml', str, ['optional']), - ] - }, - - # profile methods - 'profile': { - 'setFBML': [ - ('markup', str, ['optional']), - ('uid', int, ['optional']), - ('profile', str, ['optional']), - ('profile_action', str, ['optional']), - ('mobile_fbml', str, ['optional']), - ('profile_main', str, ['optional']), - ], - - 'getFBML': [ - ('uid', int, ['optional']), - ('type', int, ['optional']), - ], - - 'setInfo': [ - ('title', str, []), - ('type', int, []), - ('info_fields', json, []), - ('uid', int, []), - ], - - 'getInfo': [ - ('uid', int, []), - ], - - 'setInfoOptions': [ - ('field', str, []), - ('options', json, []), - ], - - 'getInfoOptions': [ - ('field', str, []), - ], - }, - - # users methods - 'users': { - 'getInfo': [ - ('uids', list, []), - ('fields', list, [('default', ['name'])]), - ], - - 'getStandardInfo': [ - ('uids', list, []), - ('fields', list, [('default', ['uid'])]), - ], - - 'getLoggedInUser': [], - - 'isAppAdded': [], - - 'hasAppPermission': [ - ('ext_perm', str, []), - ('uid', int, ['optional']), - ], - - 'setStatus': [ - ('status', str, []), - ('clear', bool, []), - ('status_includes_verb', bool, ['optional']), - ('uid', int, ['optional']), - ], - }, - - # events methods - 'events': { - 'get': [ - ('uid', int, ['optional']), - ('eids', list, ['optional']), - ('start_time', int, ['optional']), - ('end_time', int, ['optional']), - ('rsvp_status', str, ['optional']), - ], - - 'getMembers': [ - ('eid', int, []), - ], - - 'create': [ - ('event_info', json, []), - ], - }, - - # update methods - 'update': { - 'decodeIDs': [ - ('ids', list, []), - ], - }, - - # groups methods - 'groups': { - 'get': [ - ('uid', int, ['optional']), - ('gids', list, ['optional']), - ], - - 'getMembers': [ - ('gid', int, []), - ], - }, - - # marketplace methods - 'marketplace': { - 'createListing': [ - ('listing_id', int, []), - ('show_on_profile', bool, []), - ('listing_attrs', str, []), - ], - - 'getCategories': [], - - 'getListings': [ - ('listing_ids', list, []), - ('uids', list, []), - ], - - 'getSubCategories': [ - ('category', str, []), - ], - - 'removeListing': [ - ('listing_id', int, []), - ('status', str, []), - ], - - 'search': [ - ('category', str, ['optional']), - ('subcategory', str, ['optional']), - ('query', str, ['optional']), - ], - }, - - # pages methods - 'pages': { - 'getInfo': [ - ('fields', list, [('default', ['page_id', 'name'])]), - ('page_ids', list, ['optional']), - ('uid', int, ['optional']), - ], - - 'isAdmin': [ - ('page_id', int, []), - ], - - 'isAppAdded': [ - ('page_id', int, []), - ], - - 'isFan': [ - ('page_id', int, []), - ('uid', int, []), - ], - }, - - # photos methods - 'photos': { - 'addTag': [ - ('pid', int, []), - ('tag_uid', int, [('default', 0)]), - ('tag_text', str, [('default', '')]), - ('x', float, [('default', 50)]), - ('y', float, [('default', 50)]), - ('tags', str, ['optional']), - ], - - 'createAlbum': [ - ('name', str, []), - ('location', str, ['optional']), - ('description', str, ['optional']), - ], - - 'get': [ - ('subj_id', int, ['optional']), - ('aid', int, ['optional']), - ('pids', list, ['optional']), - ], - - 'getAlbums': [ - ('uid', int, ['optional']), - ('aids', list, ['optional']), - ], - - 'getTags': [ - ('pids', list, []), - ], - }, - - # status methods - 'status': { - 'get': [ - ('uid', int, ['optional']), - ('limit', int, ['optional']), - ], - 'set': [ - ('status', str, ['optional']), - ('uid', int, ['optional']), - ], - }, - - # fbml methods - 'fbml': { - 'refreshImgSrc': [ - ('url', str, []), - ], - - 'refreshRefUrl': [ - ('url', str, []), - ], - - 'setRefHandle': [ - ('handle', str, []), - ('fbml', str, []), - ], - }, - - # SMS Methods - 'sms' : { - 'canSend' : [ - ('uid', int, []), - ], - - 'send' : [ - ('uid', int, []), - ('message', str, []), - ('session_id', int, []), - ('req_session', bool, []), - ], - }, - - 'data': { - 'getCookies': [ - ('uid', int, []), - ('string', str, ['optional']), - ], - - 'setCookie': [ - ('uid', int, []), - ('name', str, []), - ('value', str, []), - ('expires', int, ['optional']), - ('path', str, ['optional']), - ], - }, - - # connect methods - 'connect': { - 'registerUsers': [ - ('accounts', json, []), - ], - - 'unregisterUsers': [ - ('email_hashes', json, []), - ], - - 'getUnconnectedFriendsCount': [ - ], - }, - - #stream methods (beta) - 'stream' : { - 'addComment' : [ - ('post_id', int, []), - ('comment', str, []), - ('uid', int, ['optional']), - ], - - 'addLike': [ - ('uid', int, ['optional']), - ('post_id', int, ['optional']), - ], - - 'get' : [ - ('viewer_id', int, ['optional']), - ('source_ids', list, ['optional']), - ('start_time', int, ['optional']), - ('end_time', int, ['optional']), - ('limit', int, ['optional']), - ('filter_key', str, ['optional']), - ], - - 'getComments' : [ - ('post_id', int, []), - ], - - 'getFilters' : [ - ('uid', int, ['optional']), - ], - - 'publish' : [ - ('message', str, ['optional']), - ('attachment', json, ['optional']), - ('action_links', json, ['optional']), - ('target_id', str, ['optional']), - ('uid', str, ['optional']), - ], - - 'remove' : [ - ('post_id', int, []), - ('uid', int, ['optional']), - ], - - 'removeComment' : [ - ('comment_id', int, []), - ('uid', int, ['optional']), - ], - - 'removeLike' : [ - ('uid', int, ['optional']), - ('post_id', int, ['optional']), - ], - } -} - -class Proxy(object): - """Represents a "namespace" of Facebook API calls.""" - - def __init__(self, client, name): - self._client = client - self._name = name - - def __call__(self, method=None, args=None, add_session_args=True): - # for Django templates - if method is None: - return self - - if add_session_args: - self._client._add_session_args(args) - - return self._client('%s.%s' % (self._name, method), args) - - -# generate the Facebook proxies -def __generate_proxies(): - for namespace in METHODS: - methods = {} - - for method in METHODS[namespace]: - params = ['self'] - body = ['args = {}'] - - for param_name, param_type, param_options in METHODS[namespace][method]: - param = param_name - - for option in param_options: - if isinstance(option, tuple) and option[0] == 'default': - if param_type == list: - param = '%s=None' % param_name - body.append('if %s is None: %s = %s' % (param_name, param_name, repr(option[1]))) - else: - param = '%s=%s' % (param_name, repr(option[1])) - - if param_type == json: - # we only jsonify the argument if it's a list or a dict, for compatibility - body.append('if isinstance(%s, list) or isinstance(%s, dict): %s = simplejson.dumps(%s)' % ((param_name,) * 4)) - - if 'optional' in param_options: - param = '%s=None' % param_name - body.append('if %s is not None: args[\'%s\'] = %s' % (param_name, param_name, param_name)) - else: - body.append('args[\'%s\'] = %s' % (param_name, param_name)) - - params.append(param) - - # simple docstring to refer them to Facebook API docs - body.insert(0, '"""Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=%s.%s"""' % (namespace, method)) - - body.insert(0, 'def %s(%s):' % (method, ', '.join(params))) - - body.append('return self(\'%s\', args)' % method) - - exec('\n '.join(body)) - - methods[method] = eval(method) - - proxy = type('%sProxy' % namespace.title(), (Proxy, ), methods) - - globals()[proxy.__name__] = proxy - - -__generate_proxies() - - -class FacebookError(Exception): - """Exception class for errors received from Facebook.""" - - def __init__(self, code, msg, args=None): - self.code = code - self.msg = msg - self.args = args - - def __str__(self): - return 'Error %s: %s' % (self.code, self.msg) - - -class AuthProxy(AuthProxy): - """Special proxy for facebook.auth.""" - - def getSession(self): - """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=auth.getSession""" - args = {} - try: - args['auth_token'] = self._client.auth_token - except AttributeError: - raise RuntimeError('Client does not have auth_token set.') - result = self._client('%s.getSession' % self._name, args) - self._client.session_key = result['session_key'] - self._client.uid = result['uid'] - self._client.secret = result.get('secret') - self._client.session_key_expires = result['expires'] - return result - - def createToken(self): - """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=auth.createToken""" - token = self._client('%s.createToken' % self._name) - self._client.auth_token = token - return token - - -class FriendsProxy(FriendsProxy): - """Special proxy for facebook.friends.""" - - def get(self, **kwargs): - """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=friends.get""" - if not kwargs.get('flid') and self._client._friends: - return self._client._friends - return super(FriendsProxy, self).get(**kwargs) - - -class PhotosProxy(PhotosProxy): - """Special proxy for facebook.photos.""" - - def upload(self, image, aid=None, caption=None, size=(604, 1024), filename=None, callback=None): - """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=photos.upload - - size -- an optional size (width, height) to resize the image to before uploading. Resizes by default - to Facebook's maximum display width of 604. - """ - args = {} - - if aid is not None: - args['aid'] = aid - - if caption is not None: - args['caption'] = caption - - args = self._client._build_post_args('facebook.photos.upload', self._client._add_session_args(args)) - - try: - import cStringIO as StringIO - except ImportError: - import StringIO - - # check for a filename specified...if the user is passing binary data in - # image then a filename will be specified - if filename is None: - try: - import Image - except ImportError: - data = StringIO.StringIO(open(image, 'rb').read()) - else: - img = Image.open(image) - if size: - img.thumbnail(size, Image.ANTIALIAS) - data = StringIO.StringIO() - img.save(data, img.format) - else: - # there was a filename specified, which indicates that image was not - # the path to an image file but rather the binary data of a file - data = StringIO.StringIO(image) - image = filename - - content_type, body = self.__encode_multipart_formdata(list(args.iteritems()), [(image, data)]) - urlinfo = urlparse.urlsplit(self._client.facebook_url) - try: - content_length = len(body) - chunk_size = 4096 - - h = httplib.HTTPConnection(urlinfo[1]) - h.putrequest('POST', urlinfo[2]) - h.putheader('Content-Type', content_type) - h.putheader('Content-Length', str(content_length)) - h.putheader('MIME-Version', '1.0') - h.putheader('User-Agent', 'PyFacebook Client Library') - h.endheaders() - - if callback: - count = 0 - while len(body) > 0: - if len(body) < chunk_size: - data = body - body = '' - else: - data = body[0:chunk_size] - body = body[chunk_size:] - - h.send(data) - count += 1 - callback(count, chunk_size, content_length) - else: - h.send(body) - - response = h.getresponse() - - if response.status != 200: - raise Exception('Error uploading photo: Facebook returned HTTP %s (%s)' % (response.status, response.reason)) - response = response.read() - except: - # sending the photo failed, perhaps we are using GAE - try: - from google.appengine.api import urlfetch - - try: - response = urlread(url=self._client.facebook_url,data=body,headers={'POST':urlinfo[2],'Content-Type':content_type,'MIME-Version':'1.0'}) - except urllib2.URLError: - raise Exception('Error uploading photo: Facebook returned %s' % (response)) - except ImportError: - # could not import from google.appengine.api, so we are not running in GAE - raise Exception('Error uploading photo.') - - return self._client._parse_response(response, 'facebook.photos.upload') - - - def __encode_multipart_formdata(self, fields, files): - """Encodes a multipart/form-data message to upload an image.""" - boundary = '-------tHISiStheMulTIFoRMbOUNDaRY' - crlf = '\r\n' - l = [] - - for (key, value) in fields: - l.append('--' + boundary) - l.append('Content-Disposition: form-data; name="%s"' % str(key)) - l.append('') - l.append(str(value)) - for (filename, value) in files: - l.append('--' + boundary) - l.append('Content-Disposition: form-data; filename="%s"' % (str(filename), )) - l.append('Content-Type: %s' % self.__get_content_type(filename)) - l.append('') - l.append(value.getvalue()) - l.append('--' + boundary + '--') - l.append('') - body = crlf.join(l) - content_type = 'multipart/form-data; boundary=%s' % boundary - return content_type, body - - - def __get_content_type(self, filename): - """Returns a guess at the MIME type of the file from the filename.""" - return str(mimetypes.guess_type(filename)[0]) or 'application/octet-stream' - - -class Facebook(object): - """ - Provides access to the Facebook API. - - Instance Variables: - - added - True if the user has added this application. - - api_key - Your API key, as set in the constructor. - - app_name - Your application's name, i.e. the APP_NAME in http://apps.facebook.com/APP_NAME/ if - this is for an internal web application. Optional, but useful for automatic redirects - to canvas pages. - - auth_token - The auth token that Facebook gives you, either with facebook.auth.createToken, - or through a GET parameter. - - callback_path - The path of the callback set in the Facebook app settings. If your callback is set - to http://www.example.com/facebook/callback/, this should be '/facebook/callback/'. - Optional, but useful for automatic redirects back to the same page after login. - - desktop - True if this is a desktop app, False otherwise. Used for determining how to - authenticate. - - ext_perms - Any extended permissions that the user has granted to your application. - This parameter is set only if the user has granted any. - - facebook_url - The url to use for Facebook requests. - - facebook_secure_url - The url to use for secure Facebook requests. - - in_canvas - True if the current request is for a canvas page. - - in_iframe - True if the current request is for an HTML page to embed in Facebook inside an iframe. - - is_session_from_cookie - True if the current request session comes from a session cookie. - - in_profile_tab - True if the current request is for a user's tab for your application. - - internal - True if this Facebook object is for an internal application (one that can be added on Facebook) - - locale - The user's locale. Default: 'en_US' - - page_id - Set to the page_id of the current page (if any) - - profile_update_time - The time when this user's profile was last updated. This is a UNIX timestamp. Default: None if unknown. - - secret - Secret that is used after getSession for desktop apps. - - secret_key - Your application's secret key, as set in the constructor. - - session_key - The current session key. Set automatically by auth.getSession, but can be set - manually for doing infinite sessions. - - session_key_expires - The UNIX time of when this session key expires, or 0 if it never expires. - - uid - After a session is created, you can get the user's UID with this variable. Set - automatically by auth.getSession. - - ---------------------------------------------------------------------- - - """ - - def __init__(self, api_key, secret_key, auth_token=None, app_name=None, callback_path=None, internal=None, proxy=None, facebook_url=None, facebook_secure_url=None): - """ - Initializes a new Facebook object which provides wrappers for the Facebook API. - - If this is a desktop application, the next couple of steps you might want to take are: - - facebook.auth.createToken() # create an auth token - facebook.login() # show a browser window - wait_login() # somehow wait for the user to log in - facebook.auth.getSession() # get a session key - - For web apps, if you are passed an auth_token from Facebook, pass that in as a named parameter. - Then call: - - facebook.auth.getSession() - - """ - self.api_key = api_key - self.secret_key = secret_key - self.session_key = None - self.session_key_expires = None - self.auth_token = auth_token - self.secret = None - self.uid = None - self.page_id = None - self.in_canvas = False - self.in_iframe = False - self.is_session_from_cookie = False - self.in_profile_tab = False - self.added = False - self.app_name = app_name - self.callback_path = callback_path - self.internal = internal - self._friends = None - self.locale = 'en_US' - self.profile_update_time = None - self.ext_perms = None - self.proxy = proxy - if facebook_url is None: - self.facebook_url = FACEBOOK_URL - else: - self.facebook_url = facebook_url - if facebook_secure_url is None: - self.facebook_secure_url = FACEBOOK_SECURE_URL - else: - self.facebook_secure_url = facebook_secure_url - - for namespace in METHODS: - self.__dict__[namespace] = eval('%sProxy(self, \'%s\')' % (namespace.title(), 'facebook.%s' % namespace)) - - - def _hash_args(self, args, secret=None): - """Hashes arguments by joining key=value pairs, appending a secret, and then taking the MD5 hex digest.""" - # @author: houyr - # fix for UnicodeEncodeError - hasher = hashlib.md5(''.join(['%s=%s' % (isinstance(x, unicode) and x.encode("utf-8") or x, isinstance(args[x], unicode) and args[x].encode("utf-8") or args[x]) for x in sorted(args.keys())])) - if secret: - hasher.update(secret) - elif self.secret: - hasher.update(self.secret) - else: - hasher.update(self.secret_key) - return hasher.hexdigest() - - - def _parse_response_item(self, node): - """Parses an XML response node from Facebook.""" - if node.nodeType == node.DOCUMENT_NODE and \ - node.childNodes[0].hasAttributes() and \ - node.childNodes[0].hasAttribute('list') and \ - node.childNodes[0].getAttribute('list') == "true": - return {node.childNodes[0].nodeName: self._parse_response_list(node.childNodes[0])} - elif node.nodeType == node.ELEMENT_NODE and \ - node.hasAttributes() and \ - node.hasAttribute('list') and \ - node.getAttribute('list')=="true": - return self._parse_response_list(node) - elif len(filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes)) > 0: - return self._parse_response_dict(node) - else: - return ''.join(node.data for node in node.childNodes if node.nodeType == node.TEXT_NODE) - - - def _parse_response_dict(self, node): - """Parses an XML dictionary response node from Facebook.""" - result = {} - for item in filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes): - result[item.nodeName] = self._parse_response_item(item) - if node.nodeType == node.ELEMENT_NODE and node.hasAttributes(): - if node.hasAttribute('id'): - result['id'] = node.getAttribute('id') - return result - - - def _parse_response_list(self, node): - """Parses an XML list response node from Facebook.""" - result = [] - for item in filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes): - result.append(self._parse_response_item(item)) - return result - - - def _check_error(self, response): - """Checks if the given Facebook response is an error, and then raises the appropriate exception.""" - if type(response) is dict and response.has_key('error_code'): - raise FacebookError(response['error_code'], response['error_msg'], response['request_args']) - - - def _build_post_args(self, method, args=None): - """Adds to args parameters that are necessary for every call to the API.""" - if args is None: - args = {} - - for arg in args.items(): - if type(arg[1]) == list: - args[arg[0]] = ','.join(str(a) for a in arg[1]) - elif type(arg[1]) == unicode: - args[arg[0]] = arg[1].encode("UTF-8") - elif type(arg[1]) == bool: - args[arg[0]] = str(arg[1]).lower() - - args['method'] = method - args['api_key'] = self.api_key - args['v'] = '1.0' - args['format'] = RESPONSE_FORMAT - args['sig'] = self._hash_args(args) - - return args - - - def _add_session_args(self, args=None): - """Adds 'session_key' and 'call_id' to args, which are used for API calls that need sessions.""" - if args is None: - args = {} - - if not self.session_key: - return args - #some calls don't need a session anymore. this might be better done in the markup - #raise RuntimeError('Session key not set. Make sure auth.getSession has been called.') - - args['session_key'] = self.session_key - args['call_id'] = str(int(time.time() * 1000)) - - return args - - - def _parse_response(self, response, method, format=None): - """Parses the response according to the given (optional) format, which should be either 'JSON' or 'XML'.""" - if not format: - format = RESPONSE_FORMAT - - if format == 'JSON': - result = simplejson.loads(response) - - self._check_error(result) - elif format == 'XML': - dom = minidom.parseString(response) - result = self._parse_response_item(dom) - dom.unlink() - - if 'error_response' in result: - self._check_error(result['error_response']) - - result = result[method[9:].replace('.', '_') + '_response'] - else: - raise RuntimeError('Invalid format specified.') - - return result - - - def hash_email(self, email): - """ - Hash an email address in a format suitable for Facebook Connect. - - """ - email = email.lower().strip() - return "%s_%s" % ( - struct.unpack("I", struct.pack("i", binascii.crc32(email)))[0], - hashlib.md5(email).hexdigest(), - ) - - - def unicode_urlencode(self, params): - """ - @author: houyr - A unicode aware version of urllib.urlencode. - """ - if isinstance(params, dict): - params = params.items() - return urllib.urlencode([(k, isinstance(v, unicode) and v.encode('utf-8') or v) - for k, v in params]) - - - def __call__(self, method=None, args=None, secure=False): - """Make a call to Facebook's REST server.""" - # for Django templates, if this object is called without any arguments - # return the object itself - if method is None: - return self - - # __init__ hard-codes into en_US - if args is not None and not args.has_key('locale'): - args['locale'] = self.locale - - # @author: houyr - # fix for bug of UnicodeEncodeError - post_data = self.unicode_urlencode(self._build_post_args(method, args)) - - if self.proxy: - proxy_handler = urllib2.ProxyHandler(self.proxy) - opener = urllib2.build_opener(proxy_handler) - if secure: - response = opener.open(self.facebook_secure_url, post_data).read() - else: - response = opener.open(self.facebook_url, post_data).read() - else: - if secure: - response = urlread(self.facebook_secure_url, post_data) - else: - response = urlread(self.facebook_url, post_data) - - return self._parse_response(response, method) - - - # URL helpers - def get_url(self, page, **args): - """ - Returns one of the Facebook URLs (www.facebook.com/SOMEPAGE.php). - Named arguments are passed as GET query string parameters. - - """ - return 'http://www.facebook.com/%s.php?%s' % (page, urllib.urlencode(args)) - - - def get_app_url(self, path=''): - """ - Returns the URL for this app's canvas page, according to app_name. - - """ - return 'http://apps.facebook.com/%s/%s' % (self.app_name, path) - - - def get_add_url(self, next=None): - """ - Returns the URL that the user should be redirected to in order to add the application. - - """ - args = {'api_key': self.api_key, 'v': '1.0'} - - if next is not None: - args['next'] = next - - return self.get_url('install', **args) - - - def get_authorize_url(self, next=None, next_cancel=None): - """ - Returns the URL that the user should be redirected to in order to - authorize certain actions for application. - - """ - args = {'api_key': self.api_key, 'v': '1.0'} - - if next is not None: - args['next'] = next - - if next_cancel is not None: - args['next_cancel'] = next_cancel - - return self.get_url('authorize', **args) - - - def get_login_url(self, next=None, popup=False, canvas=True): - """ - Returns the URL that the user should be redirected to in order to login. - - next -- the URL that Facebook should redirect to after login - - """ - args = {'api_key': self.api_key, 'v': '1.0'} - - if next is not None: - args['next'] = next - - if canvas is True: - args['canvas'] = 1 - - if popup is True: - args['popup'] = 1 - - if self.auth_token is not None: - args['auth_token'] = self.auth_token - - return self.get_url('login', **args) - - - def login(self, popup=False): - """Open a web browser telling the user to login to Facebook.""" - import webbrowser - webbrowser.open(self.get_login_url(popup=popup)) - - - def get_ext_perm_url(self, ext_perm, next=None, popup=False): - """ - Returns the URL that the user should be redirected to in order to grant an extended permission. - - ext_perm -- the name of the extended permission to request - next -- the URL that Facebook should redirect to after login - - """ - args = {'ext_perm': ext_perm, 'api_key': self.api_key, 'v': '1.0'} - - if next is not None: - args['next'] = next - - if popup is True: - args['popup'] = 1 - - return self.get_url('authorize', **args) - - - def request_extended_permission(self, ext_perm, popup=False): - """Open a web browser telling the user to grant an extended permission.""" - import webbrowser - webbrowser.open(self.get_ext_perm_url(ext_perm, popup=popup)) - - - def check_session(self, request): - """ - Checks the given Django HttpRequest for Facebook parameters such as - POST variables or an auth token. If the session is valid, returns True - and this object can now be used to access the Facebook API. Otherwise, - it returns False, and the application should take the appropriate action - (either log the user in or have him add the application). - - """ - self.in_canvas = (request.POST.get('fb_sig_in_canvas') == '1') - - if self.session_key and (self.uid or self.page_id): - return True - - - if request.method == 'POST': - params = self.validate_signature(request.POST) - else: - if 'installed' in request.GET: - self.added = True - - if 'fb_page_id' in request.GET: - self.page_id = request.GET['fb_page_id'] - - if 'auth_token' in request.GET: - self.auth_token = request.GET['auth_token'] - - try: - self.auth.getSession() - except FacebookError, e: - self.auth_token = None - return False - - return True - - params = self.validate_signature(request.GET) - - if not params: - # first check if we are in django - to check cookies - if hasattr(request, 'COOKIES'): - params = self.validate_cookie_signature(request.COOKIES) - self.is_session_from_cookie = True - else: - # if not, then we might be on GoogleAppEngine, check their request object cookies - if hasattr(request,'cookies'): - params = self.validate_cookie_signature(request.cookies) - self.is_session_from_cookie = True - - if not params: - return False - - if params.get('in_canvas') == '1': - self.in_canvas = True - - if params.get('in_iframe') == '1': - self.in_iframe = True - - if params.get('in_profile_tab') == '1': - self.in_profile_tab = True - - if params.get('added') == '1': - self.added = True - - if params.get('expires'): - self.session_key_expires = int(params['expires']) - - if 'locale' in params: - self.locale = params['locale'] - - if 'profile_update_time' in params: - try: - self.profile_update_time = int(params['profile_update_time']) - except ValueError: - pass - - if 'ext_perms' in params: - self.ext_perms = params['ext_perms'] - - if 'friends' in params: - if params['friends']: - self._friends = params['friends'].split(',') - else: - self._friends = [] - - if 'session_key' in params: - self.session_key = params['session_key'] - if 'user' in params: - self.uid = params['user'] - elif 'page_id' in params: - self.page_id = params['page_id'] - else: - return False - elif 'profile_session_key' in params: - self.session_key = params['profile_session_key'] - if 'profile_user' in params: - self.uid = params['profile_user'] - else: - return False - elif 'canvas_user' in params: - self.uid = params['canvas_user'] - elif 'uninstall' in params: - self.uid = params['user'] - else: - return False - - return True - - - def validate_signature(self, post, prefix='fb_sig', timeout=None): - """ - Validate parameters passed to an internal Facebook app from Facebook. - - """ - args = post.copy() - - if prefix not in args: - return None - - del args[prefix] - - if timeout and '%s_time' % prefix in post and time.time() - float(post['%s_time' % prefix]) > timeout: - return None - - args = dict([(key[len(prefix + '_'):], value) for key, value in args.items() if key.startswith(prefix)]) - - hash = self._hash_args(args) - - if hash == post[prefix]: - return args - else: - return None - - def validate_cookie_signature(self, cookies): - """ - Validate parameters passed by cookies, namely facebookconnect or js api. - """ - - api_key = self.api_key - if api_key not in cookies: - return None - - prefix = api_key + "_" - - params = {} - vals = '' - for k in sorted(cookies): - if k.startswith(prefix): - key = k.replace(prefix,"") - value = cookies[k] - params[key] = value - vals += '%s=%s' % (key, value) - - hasher = hashlib.md5(vals) - - hasher.update(self.secret_key) - digest = hasher.hexdigest() - if digest == cookies[api_key]: - params['is_session_from_cookie'] = True - return params - else: - return False - - - - -if __name__ == '__main__': - # sample desktop application - - api_key = '' - secret_key = '' - - facebook = Facebook(api_key, secret_key) - - facebook.auth.createToken() - - # Show login window - # Set popup=True if you want login without navigational elements - facebook.login() - - # Login to the window, then press enter - print 'After logging in, press enter...' - raw_input() - - facebook.auth.getSession() - print 'Session Key: ', facebook.session_key - print 'Your UID: ', facebook.uid - - info = facebook.users.getInfo([facebook.uid], ['name', 'birthday', 'affiliations', 'sex'])[0] - - print 'Your Name: ', info['name'] - print 'Your Birthday: ', info['birthday'] - print 'Your Gender: ', info['sex'] - - friends = facebook.friends.get() - friends = facebook.users.getInfo(friends[0:5], ['name', 'birthday', 'relationship_status']) - - for friend in friends: - print friend['name'], 'has a birthday on', friend['birthday'], 'and is', friend['relationship_status'] - - arefriends = facebook.friends.areFriends([friends[0]['uid']], [friends[1]['uid']]) - - photos = facebook.photos.getAlbums(facebook.uid) - diff --git a/helios_auth/auth_systems/facebookclient/djangofb/__init__.py b/helios_auth/auth_systems/facebookclient/djangofb/__init__.py deleted file mode 100644 index 68b1b27c37d19e1372369837006947b4f60ef7c5..0000000000000000000000000000000000000000 --- a/helios_auth/auth_systems/facebookclient/djangofb/__init__.py +++ /dev/null @@ -1,248 +0,0 @@ -import re -import datetime -import facebook - -from django.http import HttpResponse, HttpResponseRedirect -from django.core.exceptions import ImproperlyConfigured -from django.conf import settings -from datetime import datetime - -try: - from threading import local -except ImportError: - from django.utils._threading_local import local - -__all__ = ['Facebook', 'FacebookMiddleware', 'get_facebook_client', 'require_login', 'require_add'] - -_thread_locals = local() - -class Facebook(facebook.Facebook): - def redirect(self, url): - """ - Helper for Django which redirects to another page. If inside a - canvas page, writes a <fb:redirect> instead to achieve the same effect. - - """ - if self.in_canvas: - return HttpResponse('<fb:redirect url="%s" />' % (url, )) - elif re.search("^https?:\/\/([^\/]*\.)?facebook\.com(:\d+)?", url.lower()): - return HttpResponse('<script type="text/javascript">\ntop.location.href = "%s";\n</script>' % url) - else: - return HttpResponseRedirect(url) - - -def get_facebook_client(): - """ - Get the current Facebook object for the calling thread. - - """ - try: - return _thread_locals.facebook - except AttributeError: - raise ImproperlyConfigured('Make sure you have the Facebook middleware installed.') - - -def require_login(next=None, internal=None): - """ - Decorator for Django views that requires the user to be logged in. - The FacebookMiddleware must be installed. - - Standard usage: - @require_login() - def some_view(request): - ... - - Redirecting after login: - To use the 'next' parameter to redirect to a specific page after login, a callable should - return a path relative to the Post-add URL. 'next' can also be an integer specifying how many - parts of request.path to strip to find the relative URL of the canvas page. If 'next' is None, - settings.callback_path and settings.app_name are checked to redirect to the same page after logging - in. (This is the default behavior.) - @require_login(next=some_callable) - def some_view(request): - ... - """ - def decorator(view): - def newview(request, *args, **kwargs): - next = newview.next - internal = newview.internal - - try: - fb = request.facebook - except: - raise ImproperlyConfigured('Make sure you have the Facebook middleware installed.') - - if internal is None: - internal = request.facebook.internal - - if callable(next): - next = next(request.path) - elif isinstance(next, int): - next = '/'.join(request.path.split('/')[next + 1:]) - elif next is None and fb.callback_path and request.path.startswith(fb.callback_path): - next = request.path[len(fb.callback_path):] - elif not isinstance(next, str): - next = '' - - if not fb.check_session(request): - #If user has never logged in before, the get_login_url will redirect to the TOS page - return fb.redirect(fb.get_login_url(next=next)) - - if internal and request.method == 'GET' and fb.app_name: - return fb.redirect('%s%s' % (fb.get_app_url(), next)) - - return view(request, *args, **kwargs) - newview.next = next - newview.internal = internal - return newview - return decorator - - -def require_add(next=None, internal=None, on_install=None): - """ - Decorator for Django views that requires application installation. - The FacebookMiddleware must be installed. - - Standard usage: - @require_add() - def some_view(request): - ... - - Redirecting after installation: - To use the 'next' parameter to redirect to a specific page after login, a callable should - return a path relative to the Post-add URL. 'next' can also be an integer specifying how many - parts of request.path to strip to find the relative URL of the canvas page. If 'next' is None, - settings.callback_path and settings.app_name are checked to redirect to the same page after logging - in. (This is the default behavior.) - @require_add(next=some_callable) - def some_view(request): - ... - - Post-install processing: - Set the on_install parameter to a callable in order to handle special post-install processing. - The callable should take a request object as the parameter. - @require_add(on_install=some_callable) - def some_view(request): - ... - """ - def decorator(view): - def newview(request, *args, **kwargs): - next = newview.next - internal = newview.internal - - try: - fb = request.facebook - except: - raise ImproperlyConfigured('Make sure you have the Facebook middleware installed.') - - if internal is None: - internal = request.facebook.internal - - if callable(next): - next = next(request.path) - elif isinstance(next, int): - next = '/'.join(request.path.split('/')[next + 1:]) - elif next is None and fb.callback_path and request.path.startswith(fb.callback_path): - next = request.path[len(fb.callback_path):] - else: - next = '' - - if not fb.check_session(request): - if fb.added: - if request.method == 'GET' and fb.app_name: - return fb.redirect('%s%s' % (fb.get_app_url(), next)) - return fb.redirect(fb.get_login_url(next=next)) - else: - return fb.redirect(fb.get_add_url(next=next)) - - if not fb.added: - return fb.redirect(fb.get_add_url(next=next)) - - if 'installed' in request.GET and callable(on_install): - on_install(request) - - if internal and request.method == 'GET' and fb.app_name: - return fb.redirect('%s%s' % (fb.get_app_url(), next)) - - return view(request, *args, **kwargs) - newview.next = next - newview.internal = internal - return newview - return decorator - -# try to preserve the argspecs -try: - import decorator -except ImportError: - pass -else: - def updater(f): - def updated(*args, **kwargs): - original = f(*args, **kwargs) - def newdecorator(view): - return decorator.new_wrapper(original(view), view) - return decorator.new_wrapper(newdecorator, original) - return decorator.new_wrapper(updated, f) - require_login = updater(require_login) - require_add = updater(require_add) - -class FacebookMiddleware(object): - """ - Middleware that attaches a Facebook object to every incoming request. - The Facebook object created can also be accessed from models for the - current thread by using get_facebook_client(). - - """ - - def __init__(self, api_key=None, secret_key=None, app_name=None, callback_path=None, internal=None): - self.api_key = api_key or settings.FACEBOOK_API_KEY - self.secret_key = secret_key or settings.FACEBOOK_SECRET_KEY - self.app_name = app_name or getattr(settings, 'FACEBOOK_APP_NAME', None) - self.callback_path = callback_path or getattr(settings, 'FACEBOOK_CALLBACK_PATH', None) - self.internal = internal or getattr(settings, 'FACEBOOK_INTERNAL', True) - self.proxy = None - if getattr(settings, 'USE_HTTP_PROXY', False): - self.proxy = settings.HTTP_PROXY - - def process_request(self, request): - _thread_locals.facebook = request.facebook = Facebook(self.api_key, self.secret_key, app_name=self.app_name, callback_path=self.callback_path, internal=self.internal, proxy=self.proxy) - if not self.internal: - if 'fb_sig_session_key' in request.GET and 'fb_sig_user' in request.GET: - request.facebook.session_key = request.session['facebook_session_key'] = request.GET['fb_sig_session_key'] - request.facebook.uid = request.session['fb_sig_user'] = request.GET['fb_sig_user'] - elif request.session.get('facebook_session_key', None) and request.session.get('facebook_user_id', None): - request.facebook.session_key = request.session['facebook_session_key'] - request.facebook.uid = request.session['facebook_user_id'] - - def process_response(self, request, response): - if not self.internal and request.facebook.session_key and request.facebook.uid: - request.session['facebook_session_key'] = request.facebook.session_key - request.session['facebook_user_id'] = request.facebook.uid - - if request.facebook.session_key_expires: - expiry = datetime.datetime.fromtimestamp(request.facebook.session_key_expires) - request.session.set_expiry(expiry) - - try: - fb = request.facebook - except: - return response - - if not fb.is_session_from_cookie: - # Make sure the browser accepts our session cookies inside an Iframe - response['P3P'] = 'CP="NOI DSP COR NID ADMa OPTa OUR NOR"' - fb_cookies = { - 'expires': fb.session_key_expires, - 'session_key': fb.session_key, - 'user': fb.uid, - } - - expire_time = None - if fb.session_key_expires: - expire_time = datetime.utcfromtimestamp(fb.session_key_expires) - - for k in fb_cookies: - response.set_cookie(self.api_key + '_' + k, fb_cookies[k], expires=expire_time) - response.set_cookie(self.api_key , fb._hash_args(fb_cookies), expires=expire_time) - - return response diff --git a/helios_auth/auth_systems/facebookclient/djangofb/context_processors.py b/helios_auth/auth_systems/facebookclient/djangofb/context_processors.py deleted file mode 100644 index 6f954397308f7af525d9fa600fb29e8cf6902c33..0000000000000000000000000000000000000000 --- a/helios_auth/auth_systems/facebookclient/djangofb/context_processors.py +++ /dev/null @@ -1,6 +0,0 @@ -def messages(request): - """Returns messages similar to ``django.core.context_processors.auth``.""" - if hasattr(request, 'facebook') and request.facebook.uid is not None: - from models import Message - messages = Message.objects.get_and_delete_all(uid=request.facebook.uid) - return {'messages': messages} \ No newline at end of file diff --git a/helios_auth/auth_systems/facebookclient/djangofb/default_app/__init__.py b/helios_auth/auth_systems/facebookclient/djangofb/default_app/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/helios_auth/auth_systems/facebookclient/djangofb/default_app/models.py b/helios_auth/auth_systems/facebookclient/djangofb/default_app/models.py deleted file mode 100644 index 666ccd3f39403df207fac99cee88c6ca00789b8f..0000000000000000000000000000000000000000 --- a/helios_auth/auth_systems/facebookclient/djangofb/default_app/models.py +++ /dev/null @@ -1,31 +0,0 @@ -from django.db import models - -# get_facebook_client lets us get the current Facebook object -# from outside of a view, which lets us have cleaner code -from facebook.djangofb import get_facebook_client - -class UserManager(models.Manager): - """Custom manager for a Facebook User.""" - - def get_current(self): - """Gets a User object for the logged-in Facebook user.""" - facebook = get_facebook_client() - user, created = self.get_or_create(id=int(facebook.uid)) - if created: - # we could do some custom actions for new users here... - pass - return user - -class User(models.Model): - """A simple User model for Facebook users.""" - - # We use the user's UID as the primary key in our database. - id = models.IntegerField(primary_key=True) - - # TODO: The data that you want to store for each user would go here. - # For this sample, we let users let people know their favorite progamming - # language, in the spirit of Extended Info. - language = models.CharField(maxlength=64, default='Python') - - # Add the custom manager - objects = UserManager() diff --git a/helios_auth/auth_systems/facebookclient/djangofb/default_app/templates/canvas.fbml b/helios_auth/auth_systems/facebookclient/djangofb/default_app/templates/canvas.fbml deleted file mode 100644 index 6734dd17caa138540fb10d0fcb750d70c8600d33..0000000000000000000000000000000000000000 --- a/helios_auth/auth_systems/facebookclient/djangofb/default_app/templates/canvas.fbml +++ /dev/null @@ -1,22 +0,0 @@ -<fb:header> - {% comment %} - We can use {{ fbuser }} to get at the current user. - {{ fbuser.id }} will be the user's UID, and {{ fbuser.language }} - is his/her favorite language (Python :-). - {% endcomment %} - Welcome, <fb:name uid="{{ fbuser.id }}" firstnameonly="true" useyou="false" />! -</fb:header> - -<div class="clearfix" style="float: left; border: 1px #d8dfea solid; padding: 10px 10px 10px 10px; margin-left: 30px; margin-bottom: 30px; width: 500px;"> - Your favorite language is {{ fbuser.language|escape }}. - <br /><br /> - - <div class="grayheader clearfix"> - <br /><br /> - - <form action="." method="POST"> - <input type="text" name="language" value="{{ fbuser.language|escape }}" /> - <input type="submit" value="Change" /> - </form> - </div> -</div> diff --git a/helios_auth/auth_systems/facebookclient/djangofb/default_app/urls.py b/helios_auth/auth_systems/facebookclient/djangofb/default_app/urls.py deleted file mode 100644 index 850184440c5fc153df12bf71e792cdccfe9baa57..0000000000000000000000000000000000000000 --- a/helios_auth/auth_systems/facebookclient/djangofb/default_app/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.conf.urls.defaults import * - -urlpatterns = patterns('{{ project }}.{{ app }}.views', - (r'^$', 'canvas'), - # Define other pages you want to create here -) - diff --git a/helios_auth/auth_systems/facebookclient/djangofb/default_app/views.py b/helios_auth/auth_systems/facebookclient/djangofb/default_app/views.py deleted file mode 100644 index 609314fe01b3bf546984841b9dded39756bfa0cb..0000000000000000000000000000000000000000 --- a/helios_auth/auth_systems/facebookclient/djangofb/default_app/views.py +++ /dev/null @@ -1,38 +0,0 @@ -from django.http import HttpResponse -# from django.views.generic.simple import direct_to_template -#uncomment the following two lines and the one below -#if you dont want to use a decorator instead of the middleware -#from django.utils.decorators import decorator_from_middleware -#from facebook.djangofb import FacebookMiddleware - -# Import the Django helpers -import facebook.djangofb as facebook - -# The User model defined in models.py -from models import User - -# We'll require login for our canvas page. This -# isn't necessarily a good idea, as we might want -# to let users see the page without granting our app -# access to their info. See the wiki for details on how -# to do this. -#@decorator_from_middleware(FacebookMiddleware) -@facebook.require_login() -def canvas(request): - # Get the User object for the currently logged in user - user = User.objects.get_current() - - # Check if we were POSTed the user's new language of choice - if 'language' in request.POST: - user.language = request.POST['language'][:64] - user.save() - - # User is guaranteed to be logged in, so pass canvas.fbml - # an extra 'fbuser' parameter that is the User object for - # the currently logged in user. - #return direct_to_template(request, 'canvas.fbml', extra_context={'fbuser': user}) - return None - -@facebook.require_login() -def ajax(request): - return HttpResponse('hello world') diff --git a/helios_auth/auth_systems/facebookclient/djangofb/models.py b/helios_auth/auth_systems/facebookclient/djangofb/models.py deleted file mode 100644 index b5d2c62221e9926f7ab4b57cb95fb71ab22be2da..0000000000000000000000000000000000000000 --- a/helios_auth/auth_systems/facebookclient/djangofb/models.py +++ /dev/null @@ -1,36 +0,0 @@ -from django.db import models -from django.utils.html import escape -from django.utils.safestring import mark_safe - -FB_MESSAGE_STATUS = ( - (0, 'Explanation'), - (1, 'Error'), - (2, 'Success'), -) - -class MessageManager(models.Manager): - def get_and_delete_all(self, uid): - messages = [] - for m in self.filter(uid=uid): - messages.append(m) - m.delete() - return messages - -class Message(models.Model): - """Represents a message for a Facebook user.""" - uid = models.CharField(max_length=25) - status = models.IntegerField(choices=FB_MESSAGE_STATUS) - message = models.CharField(max_length=300) - objects = MessageManager() - - def __unicode__(self): - return self.message - - def _fb_tag(self): - return self.get_status_display().lower() - - def as_fbml(self): - return mark_safe(u'<fb:%s message="%s" />' % ( - self._fb_tag(), - escape(self.message), - )) diff --git a/helios_auth/auth_systems/facebookclient/webappfb.py b/helios_auth/auth_systems/facebookclient/webappfb.py deleted file mode 100644 index 5fdf77af5c05ce29ccd56b6326ca4b8f64a08294..0000000000000000000000000000000000000000 --- a/helios_auth/auth_systems/facebookclient/webappfb.py +++ /dev/null @@ -1,170 +0,0 @@ -# -# webappfb - Facebook tools for Google's AppEngine "webapp" Framework -# -# Copyright (c) 2009, Max Battcher -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of the author nor the names of its contributors may -# be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS``AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from google.appengine.api import memcache -from google.appengine.ext.webapp import RequestHandler -from facebook import Facebook -import yaml - -""" -Facebook tools for Google AppEngine's object-oriented "webapp" framework. -""" - -# This global configuration dictionary is for configuration variables -# for Facebook requests such as the application's API key and secret -# key. Defaults to loading a 'facebook.yaml' YAML file. This should be -# useful and familiar for most AppEngine development. -FACEBOOK_CONFIG = yaml.load(file('facebook.yaml', 'r')) - -class FacebookRequestHandler(RequestHandler): - """ - Base class for request handlers for Facebook apps, providing useful - Facebook-related tools: a local - """ - - def _fbconfig_value(self, name, default=None): - """ - Checks the global config dictionary and then for a class/instance - variable, using a provided default if no value is found. - """ - if name in FACEBOOK_CONFIG: - default = FACEBOOK_CONFIG[name] - - return getattr(self, name, default) - - def initialize(self, request, response): - """ - Initialize's this request's Facebook client. - """ - super(FacebookRequestHandler, self).initialize(request, response) - - app_name = self._fbconfig_value('app_name', '') - api_key = self._fbconfig_value('api_key', None) - secret_key = self._fbconfig_value('secret_key', None) - - self.facebook = Facebook(api_key, secret_key, - app_name=app_name) - - require_app = self._fbconfig_value('require_app', False) - require_login = self._fbconfig_value('require_login', False) - need_session = self._fbconfig_value('need_session', False) - check_session = self._fbconfig_value('check_session', True) - - self._messages = None - self.redirecting = False - - if require_app or require_login: - if not self.facebook.check_session(request): - self.redirect(self.facebook.get_login_url(next=request.path)) - self.redirecting = True - return - elif check_session: - self.facebook.check_session(request) # ignore response - - # NOTE: require_app is deprecated according to modern Facebook login - # policies. Included for completeness, but unnecessary. - if require_app and not self.facebook.added: - self.redirect(self.facebook.get_add_url(next=request.path)) - self.redirecting = True - return - - if not (require_app or require_login) and need_session: - self.facebook.auth.getSession() - - def redirect(self, url, **kwargs): - """ - For Facebook canvas pages we should use <fb:redirect /> instead of - a normal redirect. - """ - if self.facebook.in_canvas: - self.response.clear() - self.response.out.write('<fb:redirect url="%s" />' % (url, )) - else: - super(FacebookRequestHandler, self).redirect(url, **kwargs) - - def add_user_message(self, kind, msg, detail='', time=15 * 60): - """ - Add a message to the current user to memcache. - """ - if self.facebook.uid: - key = 'messages:%s' % self.facebook.uid - self._messages = memcache.get(key) - message = { - 'kind': kind, - 'message': msg, - 'detail': detail, - } - if self._messages is not None: - self._messages.append(message) - else: - self._messages = [message] - memcache.set(key, self._messages, time=time) - - def get_and_delete_user_messages(self): - """ - Get all of the messages for the current user; removing them. - """ - if self.facebook.uid: - key = 'messages:%s' % self.facebook.uid - if not hasattr(self, '_messages') or self._messages is None: - self._messages = memcache.get(key) - memcache.delete(key) - return self._messages - return None - -class FacebookCanvasHandler(FacebookRequestHandler): - """ - Request handler for Facebook canvas (FBML application) requests. - """ - - def canvas(self, *args, **kwargs): - """ - This will be your handler to deal with Canvas requests. - """ - raise NotImplementedError() - - def get(self, *args): - """ - All valid canvas views are POSTS. - """ - # TODO: Attempt to auto-redirect to Facebook canvas? - self.error(404) - - def post(self, *args, **kwargs): - """ - Check a couple of simple safety checks and then call the canvas - handler. - """ - if self.redirecting: return - - if not self.facebook.in_canvas: - self.error(404) - return - - self.canvas(*args, **kwargs) - -# vim: ai et ts=4 sts=4 sw=4 diff --git a/helios_auth/auth_systems/facebookclient/wsgi.py b/helios_auth/auth_systems/facebookclient/wsgi.py deleted file mode 100644 index f6a790db14858d762159478a4aa51e7323144b5d..0000000000000000000000000000000000000000 --- a/helios_auth/auth_systems/facebookclient/wsgi.py +++ /dev/null @@ -1,129 +0,0 @@ -"""This is some simple helper code to bridge the Pylons / PyFacebook gap. - -There's some generic WSGI middleware, some Paste stuff, and some Pylons -stuff. Once you put FacebookWSGIMiddleware into your middleware stack, -you'll have access to ``environ["pyfacebook.facebook"]``, which is a -``facebook.Facebook`` object. If you're using Paste (which includes -Pylons users), you can also access this directly using the facebook -global in this module. - -""" - -# Be careful what you import. Don't expect everyone to have Pylons, -# Paste, etc. installed. Degrade gracefully. - -from facebook import Facebook - -__docformat__ = "restructuredtext" - - -# Setup Paste, if available. This needs to stay in the same module as -# FacebookWSGIMiddleware below. - -try: - from paste.registry import StackedObjectProxy - from webob.exc import _HTTPMove - from paste.util.quoting import strip_html, html_quote, no_quote -except ImportError: - pass -else: - facebook = StackedObjectProxy(name="PyFacebook Facebook Connection") - - - class CanvasRedirect(_HTTPMove): - - """This is for canvas redirects.""" - - title = "See Other" - code = 200 - template = '<fb:redirect url="%(location)s" />' - - def html(self, environ): - """ text/html representation of the exception """ - body = self.make_body(environ, self.template, html_quote, no_quote) - return body - -class FacebookWSGIMiddleware(object): - - """This is WSGI middleware for Facebook.""" - - def __init__(self, app, config, facebook_class=Facebook): - """Initialize the Facebook middleware. - - ``app`` - This is the WSGI application being wrapped. - - ``config`` - This is a dict containing the keys "pyfacebook.apikey" and - "pyfacebook.secret". - - ``facebook_class`` - If you want to subclass the Facebook class, you can pass in - your replacement here. Pylons users will want to use - PylonsFacebook. - - """ - self.app = app - self.config = config - self.facebook_class = facebook_class - - def __call__(self, environ, start_response): - config = self.config - real_facebook = self.facebook_class(config["pyfacebook.apikey"], - config["pyfacebook.secret"]) - registry = environ.get('paste.registry') - if registry: - registry.register(facebook, real_facebook) - environ['pyfacebook.facebook'] = real_facebook - return self.app(environ, start_response) - - -# The remainder is Pylons specific. - -try: - import pylons - from pylons.controllers.util import redirect_to as pylons_redirect_to - from routes import url_for -except ImportError: - pass -else: - - - class PylonsFacebook(Facebook): - - """Subclass Facebook to add Pylons goodies.""" - - def check_session(self, request=None): - """The request parameter is now optional.""" - if request is None: - request = pylons.request - return Facebook.check_session(self, request) - - # The Django request object is similar enough to the Paste - # request object that check_session and validate_signature - # should *just work*. - - def redirect_to(self, url): - """Wrap Pylons' redirect_to function so that it works in_canvas. - - By the way, this won't work until after you call - check_session(). - - """ - if self.in_canvas: - raise CanvasRedirect(url) - pylons_redirect_to(url) - - def apps_url_for(self, *args, **kargs): - """Like url_for, but starts with "http://apps.facebook.com".""" - return "http://apps.facebook.com" + url_for(*args, **kargs) - - - def create_pylons_facebook_middleware(app, config): - """This is a simple wrapper for FacebookWSGIMiddleware. - - It passes the correct facebook_class. - - """ - return FacebookWSGIMiddleware(app, config, - facebook_class=PylonsFacebook) diff --git a/helios_auth/auth_systems/google.py b/helios_auth/auth_systems/google.py index b6eb57c4390ab54bd747bc03971bc42ba5fbca1f..0b08b04c17c01225eaa6c227e74c55e46f168055 100644 --- a/helios_auth/auth_systems/google.py +++ b/helios_auth/auth_systems/google.py @@ -3,16 +3,13 @@ Google Authentication """ -from django.http import * -from django.core.mail import send_mail +import httplib2 from django.conf import settings - -import httplib2,json - -import sys, os, cgi, urllib, urllib2, re - +from django.core.mail import send_mail from oauth2client.client import OAuth2WebServerFlow +from helios_auth import utils + # some parameters to indicate that status updating is not possible STATUS_UPDATES = False @@ -34,7 +31,7 @@ def get_auth_url(request, redirect_url): def get_user_info_after_auth(request): flow = get_flow(request.session['google-redirect-url']) - if not request.GET.has_key('code'): + if 'code' not in request.GET: return None code = request.GET['code'] @@ -50,11 +47,11 @@ def get_user_info_after_auth(request): # get the nice name http = httplib2.Http(".cache") http = credentials.authorize(http) - (resp_headers, content) = http.request("https://www.googleapis.com/plus/v1/people/me", "GET") + (resp_headers, content) = http.request("https://people.googleapis.com/v1/people/me?personFields=names", "GET") - response = json.loads(content) + response = utils.from_json(content.decode('utf-8')) - name = response['displayName'] + name = response['names'][0]['displayName'] # watch out, response also contains email addresses, but not sure whether thsoe are verified or not # so for email address we will only look at the id_token diff --git a/helios_auth/auth_systems/linkedin.py b/helios_auth/auth_systems/linkedin.py index 32b0033c14fc0c3277671470055d91dbd1271556..e75e0786d46a508f9846bd6b9d1491be1bdbcbc5 100644 --- a/helios_auth/auth_systems/linkedin.py +++ b/helios_auth/auth_systems/linkedin.py @@ -2,18 +2,12 @@ LinkedIn Authentication """ -from oauthclient import client - -from django.core.urlresolvers import reverse -from django.http import HttpResponseRedirect - -from helios_auth import utils - from xml.etree import ElementTree -import logging - from django.conf import settings + +from .oauthclient import client + API_KEY = settings.LINKEDIN_API_KEY API_SECRET = settings.LINKEDIN_API_SECRET diff --git a/helios_auth/auth_systems/live.py b/helios_auth/auth_systems/live.py index 9f34a2783198003f09a74abb1671d9630cf3895a..348d02ff34a22d685b3d6dbe46aa5885ad162b4d 100644 --- a/helios_auth/auth_systems/live.py +++ b/helios_auth/auth_systems/live.py @@ -5,14 +5,14 @@ so much like Facebook # NOT WORKING YET because Windows Live documentation and status is unclear. Is it in beta? I think it is. """ -import logging +import urllib.parse +import urllib.request from django.conf import settings + APP_ID = settings.LIVE_APP_ID APP_SECRET = settings.LIVE_APP_SECRET -import urllib, urllib2, cgi - # some parameters to indicate that status updating is possible STATUS_UPDATES = False # STATUS_UPDATE_WORDING_TEMPLATE = "Send %s to your facebook status" @@ -21,17 +21,17 @@ from helios_auth import utils def live_url(url, params): if params: - return "https://graph.facebook.com%s?%s" % (url, urllib.urlencode(params)) + return "https://graph.facebook.com%s?%s" % (url, urllib.parse.urlencode(params)) else: return "https://graph.facebook.com%s" % url def live_get(url, params): full_url = live_url(url,params) - return urllib2.urlopen(full_url).read() + return urllib.request.urlopen(full_url).read() def live_post(url, params): full_url = live_url(url, None) - return urllib2.urlopen(full_url, urllib.urlencode(params)).read() + return urllib.request.urlopen(full_url, urllib.parse.urlencode(params)).read() def get_auth_url(request, redirect_url): request.session['live_redirect_uri'] = redirect_url @@ -41,16 +41,16 @@ def get_auth_url(request, redirect_url): 'scope': 'publish_stream'}) def get_user_info_after_auth(request): - args = facebook_get('/oauth/access_token', { + args = live_get('/oauth/access_token', { 'client_id' : APP_ID, 'redirect_uri' : request.session['fb_redirect_uri'], - 'client_secret' : API_SECRET, + 'client_secret' : APP_SECRET, 'code' : request.GET['code'] }) - access_token = cgi.parse_qs(args)['access_token'][0] + access_token = urllib.parse.parse_qs(args)['access_token'][0] - info = utils.from_json(facebook_get('/me', {'access_token':access_token})) + info = utils.from_json(live_get('/me', {'access_token':access_token})) return {'type': 'facebook', 'user_id' : info['id'], 'name': info['name'], 'info': info, 'token': {'access_token': access_token}} @@ -58,7 +58,7 @@ def update_status(user_id, user_info, token, message): """ post a message to the auth system's update stream, e.g. twitter stream """ - result = facebook_post('/me/feed', { + result = live_post('/me/feed', { 'access_token': token['access_token'], 'message': message }) diff --git a/helios_auth/auth_systems/oauthclient/client.py b/helios_auth/auth_systems/oauthclient/client.py index c9e6b829bb946694130cabcc6b336939864bcb3a..4980535e45ab58942cc632c00ecc50c0dd33d578 100644 --- a/helios_auth/auth_systems/oauthclient/client.py +++ b/helios_auth/auth_systems/oauthclient/client.py @@ -7,12 +7,11 @@ Used the SampleClient from the OAUTH.org example python client as basis. props to leahculver for making a very hard to use but in the end usable oauth lib. ''' -import httplib -import urllib, urllib2 -import time +import urllib.request import webbrowser -import oauth as oauth -from urlparse import urlparse + +from . import oauth as oauth + class LoginOAuthClient(oauth.OAuthClient): @@ -36,31 +35,35 @@ class LoginOAuthClient(oauth.OAuthClient): self.sha1_method = oauth.OAuthSignatureMethod_HMAC_SHA1() self.consumer = oauth.OAuthConsumer(consumer_key, consumer_secret) - if ((oauth_token != None) and (oauth_token_secret!=None)): + if (oauth_token is not None) and (oauth_token_secret is not None): self.token = oauth.OAuthConsumer(oauth_token, oauth_token_secret) else: self.token = None - def oauth_request(self,url, args = {}, method=None): - if (method==None): - if args=={}: + def oauth_request(self, url, args=None, method=None): + if args is None: + args = {} + if method is None: + if args == {}: method = "GET" else: method = "POST" req = oauth.OAuthRequest.from_consumer_and_token(self.consumer, self.token, method, url, args) req.sign_request(self.sha1_method, self.consumer,self.token) - if (method=="GET"): + if method== "GET": return self.http_wrapper(req.to_url()) - elif (method == "POST"): + elif method == "POST": return self.http_wrapper(req.get_normalized_http_url(),req.to_postdata()) #this is barely working. (i think. mostly it is that everyone else is using httplib) - def http_wrapper(self, url, postdata={}): + def http_wrapper(self, url, postdata=None): + if postdata is None: + postdata = {} try: - if (postdata != {}): - f = urllib.urlopen(url, postdata) + if postdata != {}: + f = urllib.request.urlopen(url, postdata) else: - f = urllib.urlopen(url) + f = urllib.request.urlopen(url) response = f.read() except: import traceback @@ -133,26 +136,26 @@ if __name__ == '__main__': consumer_key = '' consumer_secret = '' while not consumer_key: - consumer_key = raw_input('Please enter consumer key: ') + consumer_key = input('Please enter consumer key: ') while not consumer_secret: - consumer_secret = raw_input('Please enter consumer secret: ') + consumer_secret = input('Please enter consumer secret: ') auth_client = LoginOAuthClient(consumer_key,consumer_secret) tok = auth_client.get_request_token() token = tok['oauth_token'] token_secret = tok['oauth_token_secret'] url = auth_client.get_authorize_url(token) webbrowser.open(url) - print "Visit this URL to authorize your app: " + url - response_token = raw_input('What is the oauth_token from twitter: ') + print("Visit this URL to authorize your app: " + url) + response_token = input('What is the oauth_token from twitter: ') response_client = LoginOAuthClient(consumer_key, consumer_secret,token, token_secret, server_params={}) tok = response_client.get_access_token() - print "Making signed request" + print("Making signed request") #verify user access content = response_client.oauth_request('https://twitter.com/account/verify_credentials.json', method='POST') #make an update #content = response_client.oauth_request('https://twitter.com/statuses/update.xml', {'status':'Updated from a python oauth client. awesome.'}, method='POST') - print content + print(content) - print 'Done.' + print('Done.') diff --git a/helios_auth/auth_systems/oauthclient/oauth/__init__.py b/helios_auth/auth_systems/oauthclient/oauth/__init__.py index baf543ed4a8db09d92ee91b925d18bc6f5d09f93..f318a8af4421f92c9a5276d619fd8ef52dbe9644 100755 --- a/helios_auth/auth_systems/oauthclient/oauth/__init__.py +++ b/helios_auth/auth_systems/oauthclient/oauth/__init__.py @@ -1,10 +1,8 @@ -import cgi -import urllib -import time -import random -import urlparse -import hmac import binascii +import hmac +import random +import time +import urllib.parse VERSION = '1.0' # Hi Blaine! HTTP_METHOD = 'GET' @@ -22,7 +20,7 @@ def build_authenticate_header(realm=''): # url escape def escape(s): # escape '/' too - return urllib.quote(s, safe='~') + return urllib.parse.quote(s, safe='~') # util function: current timestamp # seconds since epoch (UTC) @@ -60,12 +58,12 @@ class OAuthToken(object): self.secret = secret def to_string(self): - return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) + return urllib.parse.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) # return a token from something like: # oauth_token_secret=digg&oauth_token=digg def from_string(s): - params = cgi.parse_qs(s, keep_blank_values=False) + params = urllib.parse.parse_qs(s, keep_blank_values=False) key = params['oauth_token'][0] secret = params['oauth_token_secret'][0] return OAuthToken(key, secret) @@ -112,7 +110,7 @@ class OAuthRequest(object): # get any non-oauth parameters def get_nonoauth_parameters(self): parameters = {} - for k, v in self.parameters.iteritems(): + for k, v in self.parameters.items(): # ignore oauth parameters if k.find('oauth_') < 0: parameters[k] = v @@ -123,14 +121,14 @@ class OAuthRequest(object): auth_header = 'OAuth realm="%s"' % realm # add the oauth parameters if self.parameters: - for k, v in self.parameters.iteritems(): + for k, v in self.parameters.items(): if k[:6] == 'oauth_': auth_header += ', %s="%s"' % (k, escape(str(v))) return {'Authorization': auth_header} # serialize as post data for a POST request def to_postdata(self): - return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems()]) + return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.items()]) # serialize as a url for a GET request def to_url(self): @@ -144,7 +142,7 @@ class OAuthRequest(object): del params['oauth_signature'] except: pass - key_values = params.items() + key_values = list(params.items()) # sort lexicographically, first after key, then after value key_values.sort() # combine key value pairs in string and escape @@ -156,7 +154,7 @@ class OAuthRequest(object): # parses the url and rebuilds it to be scheme://host/path def get_normalized_http_url(self): - parts = urlparse.urlparse(self.http_url) + parts = urllib.parse.urlparse(self.http_url) url_string = '%s://%s%s' % (parts[0], parts[1], parts[2]) # scheme, netloc, path return url_string @@ -194,7 +192,7 @@ class OAuthRequest(object): parameters.update(query_params) # URL parameters - param_str = urlparse.urlparse(http_url)[4] # query + param_str = urllib.parse.urlparse(http_url)[4] # query url_params = OAuthRequest._split_url_string(param_str) parameters.update(url_params) @@ -249,15 +247,15 @@ class OAuthRequest(object): # split key-value param_parts = param.split('=', 1) # remove quotes and unescape the value - params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) + params[param_parts[0]] = urllib.parse.unquote(param_parts[1].strip('\"')) return params _split_header = staticmethod(_split_header) # util function: turn url string into parameters, has to do some unescaping def _split_url_string(param_str): - parameters = cgi.parse_qs(param_str, keep_blank_values=False) - for k, v in parameters.iteritems(): - parameters[k] = urllib.unquote(v[0]) + parameters = urllib.parse.parse_qs(param_str, keep_blank_values=False) + for k, v in parameters.items(): + parameters[k] = urllib.parse.unquote(v[0]) return parameters _split_url_string = staticmethod(_split_url_string) @@ -273,7 +271,7 @@ class OAuthServer(object): self.signature_methods = signature_methods or {} def set_data_store(self, oauth_data_store): - self.data_store = data_store + self.data_store = oauth_data_store def get_data_store(self): return self.data_store @@ -351,7 +349,7 @@ class OAuthServer(object): # get the signature method object signature_method = self.signature_methods[signature_method] except: - signature_method_names = ', '.join(self.signature_methods.keys()) + signature_method_names = ', '.join(list(self.signature_methods.keys())) raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names)) return signature_method @@ -499,11 +497,11 @@ class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod): # hmac object try: - import hashlib # 2.5 - hashed = hmac.new(key, raw, hashlib.sha1) + from Crypto.Hash import SHA1 + hashed = hmac.new(key, raw, SHA1) except: - import sha # deprecated - hashed = hmac.new(key, raw, sha) + import hashlib + hashed = hmac.new(key, raw, hashlib.sha1) # calculate the digest base 64 return binascii.b2a_base64(hashed.digest())[:-1] diff --git a/helios_auth/auth_systems/openid/util.py b/helios_auth/auth_systems/openid/util.py index 277b92ca18f21b5c9f465701217fe82fdea94c28..2a2e12a429b0206f6700acbb929b5e01ffdfe11f 100644 --- a/helios_auth/auth_systems/openid/util.py +++ b/helios_auth/auth_systems/openid/util.py @@ -3,20 +3,15 @@ Utility code for the Django example consumer and server. """ -from urlparse import urljoin - -from django.db import connection -from django.template.context import RequestContext -from django.template import loader -from django import http -from django.core.exceptions import ImproperlyConfigured -from django.core.urlresolvers import reverse as reverseURL +from urllib.parse import urljoin from django.conf import settings - -from openid.store.filestore import FileOpenIDStore +from django.core.exceptions import ImproperlyConfigured +from django.db import connection +from django.urls import reverse as reverseURL from openid.store import sqlstore -from openid.yadis.constants import YADIS_CONTENT_TYPE +from openid.store.filestore import FileOpenIDStore + def getOpenIDStore(filestore_path, table_prefix): """ @@ -69,14 +64,13 @@ def getOpenIDStore(filestore_path, table_prefix): s = types[db_engine](connection.connection, **tablenames) except KeyError: - raise ImproperlyConfigured, \ - "Database engine %s not supported by OpenID library" % \ - (db_engine,) + raise ImproperlyConfigured("Database engine %s not supported by OpenID library" % \ + (db_engine,)) try: s.createTables() - except (SystemExit, KeyboardInterrupt, MemoryError), e: - raise + except (SystemExit, KeyboardInterrupt, MemoryError) as e: + raise e except: # XXX This is not the Right Way to do this, but because the # underlying database implementation might differ in behavior @@ -138,5 +132,5 @@ def normalDict(request_data): values are lists, because in OpenID, each key in the query arg set can have at most one value. """ - return dict((k, v[0]) for k, v in request_data.iteritems()) + return dict((k, v[0]) for k, v in request_data.items()) diff --git a/helios_auth/auth_systems/openid/view_helpers.py b/helios_auth/auth_systems/openid/view_helpers.py index 06eef8a287bbbf0d5a05d32d8ed0e13939761eac..de79b451a037800f974e33535e52569af754c1c6 100644 --- a/helios_auth/auth_systems/openid/view_helpers.py +++ b/helios_auth/auth_systems/openid/view_helpers.py @@ -1,14 +1,8 @@ - -from django import http -from django.http import HttpResponseRedirect - from openid.consumer import consumer from openid.consumer.discover import DiscoveryFailure from openid.extensions import ax, pape, sreg -from openid.yadis.constants import YADIS_HEADER_NAME, YADIS_CONTENT_TYPE -from openid.server.trustroot import RP_RETURN_TO_URL_TYPE -import util +from . import util PAPE_POLICIES = [ 'AUTH_PHISHING_RESISTANT', @@ -56,16 +50,12 @@ def start_openid(session, openid_url, trust_root, return_to): # Start OpenID authentication. c = get_consumer(session) - error = None try: auth_request = c.begin(openid_url) - except DiscoveryFailure, e: + except DiscoveryFailure as e: # Some other protocol-level failure occurred. - error = "OpenID discovery error: %s" % (str(e),) - - if error: - raise Exception("error in openid") + raise Exception("error in openid: OpenID discovery error") from e # Add Simple Registration request information. Some fields # are optional, some are required. It's possible that the @@ -80,7 +70,7 @@ def start_openid(session, openid_url, trust_root, return_to): # XXX - uses myOpenID-compatible schema values, which are # not those listed at axschema.org. - for k, v in AX_REQUIRED_FIELDS.iteritems(): + for k, v in AX_REQUIRED_FIELDS.items(): ax_request.add(ax.AttrInfo(v, required=True)) auth_request.addExtension(ax_request) @@ -123,12 +113,12 @@ def finish_openid(session, request_args, return_to): ax_response = ax.FetchResponse.fromSuccessResponse(response) if ax_response: - for k, v in AX_REQUIRED_FIELDS.iteritems(): + for k, v in AX_REQUIRED_FIELDS.items(): """ the values are the URIs, they are the key into the data the key is the shortname """ - if ax_response.data.has_key(v): + if v in ax_response.data: ax_items[k] = ax_response.get(v) # Map different consumer status codes to template contexts. @@ -141,7 +131,7 @@ def finish_openid(session, request_args, return_to): consumer.SUCCESS: {'url': response.getDisplayIdentifier(), - 'sreg': sreg_response and sreg_response.items(), + 'sreg': sreg_response and list(sreg_response.items()), 'ax': ax_items} } diff --git a/helios_auth/auth_systems/password.py b/helios_auth/auth_systems/password.py index f78d9f6a1fffe679570b5f4e3a83837b14e082a5..36f42ecffcc109f283d157e1ffe2a37fadb8b706 100644 --- a/helios_auth/auth_systems/password.py +++ b/helios_auth/auth_systems/password.py @@ -2,17 +2,21 @@ Username/Password Authentication """ -from django.core.urlresolvers import reverse +from django.urls import reverse from django import forms from django.core.mail import send_mail from django.conf import settings from django.http import HttpResponseRedirect +from django.conf.urls import url + +from helios_auth import url_names import logging # some parameters to indicate that status updating is possible STATUS_UPDATES = False - +PASSWORD_LOGIN_URL_NAME = "auth@password@login" +PASSWORD_FORGOTTEN_URL_NAME = "auth@password@forgotten" def create_user(username, password, name = None): from helios_auth.models import User @@ -48,7 +52,7 @@ def password_login_view(request): # set this in case we came here straight from the multi-login chooser # and thus did not have a chance to hit the "start/password" URL request.session['auth_system_name'] = 'password' - if request.POST.has_key('return_url'): + if 'return_url' in request.POST: request.session['auth_return_url'] = request.POST.get('return_url') if form.is_valid(): @@ -58,7 +62,7 @@ def password_login_view(request): user = User.get_by_type_and_id('password', username) if password_check(user, password): request.session['password_user_id'] = user.user_id - return HttpResponseRedirect(reverse(after)) + return HttpResponseRedirect(reverse(url_names.AUTH_AFTER)) except User.DoesNotExist: pass error = 'Bad Username or Password' @@ -101,7 +105,7 @@ Your password: %s return HttpResponseRedirect(return_url) def get_auth_url(request, redirect_url = None): - return reverse(password_login_view) + return reverse(PASSWORD_LOGIN_URL_NAME) def get_user_info_after_auth(request): from helios_auth.models import User @@ -125,3 +129,9 @@ def send_message(user_id, user_name, user_info, subject, body): def can_create_election(user_id, user_info): return True + + +urlpatterns = [ + url(r'^password/login', password_login_view, name=PASSWORD_LOGIN_URL_NAME), + url(r'^password/forgot', password_forgotten_view, name=PASSWORD_FORGOTTEN_URL_NAME) +] diff --git a/helios_auth/auth_systems/twitter.py b/helios_auth/auth_systems/twitter.py index 9963f9121d42b3c53edaac0c128f67499d98c761..541ac4df7cd874259a71be3566246fa3b2a48b57 100644 --- a/helios_auth/auth_systems/twitter.py +++ b/helios_auth/auth_systems/twitter.py @@ -2,9 +2,10 @@ Twitter Authentication """ -from oauthclient import client +from .oauthclient import client -from django.core.urlresolvers import reverse +from django.conf.urls import url +from django.urls import reverse from django.http import HttpResponseRedirect from helios_auth import utils @@ -21,6 +22,7 @@ DM_TOKEN = settings.TWITTER_DM_TOKEN # some parameters to indicate that status updating is possible STATUS_UPDATES = True STATUS_UPDATE_WORDING_TEMPLATE = "Tweet %s" +FOLLOW_VIEW_URL_NAME = "auth@twitter@follow" OAUTH_PARAMS = { 'root_url' : 'https://twitter.com', @@ -70,7 +72,7 @@ def user_needs_intervention(user_id, user_info, token): if friendship: return None - return HttpResponseRedirect(reverse(follow_view)) + return HttpResponseRedirect(reverse(FOLLOW_VIEW_URL_NAME)) def _get_client_by_request(request): access_token = request.session['access_token'] @@ -114,8 +116,8 @@ def follow_view(request): twitter_client = _get_client_by_token(user.token) result = twitter_client.oauth_request('http://api.twitter.com/1/friendships/create.json', args={'screen_name': USER_TO_FOLLOW}, method='POST') - from helios_auth.views import after_intervention - return HttpResponseRedirect(reverse(after_intervention)) + from helios_auth.url_names import AUTH_AFTER_INTERVENTION + return HttpResponseRedirect(reverse(AUTH_AFTER_INTERVENTION)) @@ -125,3 +127,6 @@ def follow_view(request): def can_create_election(user_id, user_info): return True + + +urlpatterns = [url(r'^twitter/follow', follow_view, name=FOLLOW_VIEW_URL_NAME)] \ No newline at end of file diff --git a/helios_auth/auth_systems/yahoo.py b/helios_auth/auth_systems/yahoo.py index 16bc0343cd40b78386a95fb0279c05c02b23f317..ccfdc12ffd8850dc17feb54bee08b4fa784a5c1e 100644 --- a/helios_auth/auth_systems/yahoo.py +++ b/helios_auth/auth_systems/yahoo.py @@ -3,14 +3,10 @@ Yahoo Authentication """ -from django.http import * -from django.core.mail import send_mail from django.conf import settings +from django.core.mail import send_mail -import sys, os, cgi, urllib, urllib2, re -from xml.etree import ElementTree - -from openid import view_helpers +from .openid import view_helpers # some parameters to indicate that status updating is not possible STATUS_UPDATES = False diff --git a/helios_auth/jsonfield.py b/helios_auth/jsonfield.py index 0104ce496758988f891bf4c29dce64661473507e..ab66d5d5d95f5e48860e36f88a4f70124cfee558 100644 --- a/helios_auth/jsonfield.py +++ b/helios_auth/jsonfield.py @@ -4,11 +4,13 @@ taken from http://www.djangosnippets.org/snippets/377/ """ -import datetime, json -from django.db import models -from django.db.models import signals -from django.conf import settings +import json + from django.core.serializers.json import DjangoJSONEncoder +from django.db import models + +from . import utils + class JSONField(models.TextField): """ @@ -18,9 +20,6 @@ class JSONField(models.TextField): deserialization_params added on 2011-01-09 to provide additional hints at deserialization time """ - # Used so to_python() is called - __metaclass__ = models.SubfieldBase - def __init__(self, json_type=None, deserialization_params=None, **kwargs): self.json_type = json_type self.deserialization_params = deserialization_params @@ -36,17 +35,17 @@ class JSONField(models.TextField): if isinstance(value, dict) or isinstance(value, list): return value - if value == "" or value == None: - return None + return self.from_db_value(value) - try: - parsed_value = json.loads(value) - except: - raise Exception("not JSON") + # noinspection PyUnusedLocal + def from_db_value(self, value, *args, **kwargs): + parsed_value = utils.from_json(value) + if parsed_value is None: + return None if self.json_type and parsed_value: parsed_value = self.json_type.fromJSONDict(parsed_value, **self.deserialization_params) - + return parsed_value # we should never look up by JSON field anyways. @@ -54,10 +53,10 @@ class JSONField(models.TextField): def get_prep_value(self, value): """Convert our JSON object to a string before we save""" - if isinstance(value, basestring): + if isinstance(value, str): return value - if value == None: + if value is None: return None if self.json_type and isinstance(value, self.json_type): @@ -70,5 +69,4 @@ class JSONField(models.TextField): def value_to_string(self, obj): value = self._get_val_from_obj(obj) - return self.get_db_prep_value(value) - + return self.get_db_prep_value(value, None) diff --git a/helios_auth/migrations/0001_initial.py b/helios_auth/migrations/0001_initial.py index fc54ff2aa58d29872fdd0453e87ffb9123974af8..002068a63be3355f5eb285660f1b0eb5b7514816 100644 --- a/helios_auth/migrations/0001_initial.py +++ b/helios_auth/migrations/0001_initial.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from django.db import models, migrations + import helios_auth.jsonfield diff --git a/helios_auth/models.py b/helios_auth/models.py index b9179958e5bbca1ad7d9eacc4c2264c020fecf4b..15fa0253c7e16acc1200f61d0d527f2d01aa76fb 100644 --- a/helios_auth/models.py +++ b/helios_auth/models.py @@ -6,13 +6,11 @@ GAE Ben Adida (ben@adida.net) """ - from django.db import models -from jsonfield import JSONField -import datetime, logging +from .auth_systems import can_check_constraint, AUTH_SYSTEMS +from .jsonfield import JSONField -from auth_systems import AUTH_SYSTEMS, can_check_constraint, can_list_categories # an exception to catch when a user is no longer authenticated class AuthenticationExpired(Exception): @@ -35,7 +33,8 @@ class User(models.Model): class Meta: unique_together = (('user_type', 'user_id'),) - + app_label = 'helios_auth' + @classmethod def _get_type_and_id(cls, user_type, user_id): return "%s:%s" % (user_type, user_id) @@ -54,7 +53,7 @@ class User(models.Model): if not created_p: # special case the password: don't replace it if it exists - if obj.info.has_key('password'): + if 'password' in obj.info: info['password'] = obj.info['password'] obj.info = info @@ -65,7 +64,7 @@ class User(models.Model): return obj def can_update_status(self): - if not AUTH_SYSTEMS.has_key(self.user_type): + if self.user_type not in AUTH_SYSTEMS: return False return AUTH_SYSTEMS[self.user_type].STATUS_UPDATES @@ -75,7 +74,7 @@ class User(models.Model): Certain auth systems can choose to limit election creation to certain users. """ - if not AUTH_SYSTEMS.has_key(self.user_type): + if self.user_type not in AUTH_SYSTEMS: return False return AUTH_SYSTEMS[self.user_type].can_create_election(self.user_id, self.info) @@ -87,16 +86,16 @@ class User(models.Model): return AUTH_SYSTEMS[self.user_type].STATUS_UPDATE_WORDING_TEMPLATE def update_status(self, status): - if AUTH_SYSTEMS.has_key(self.user_type): + if self.user_type in AUTH_SYSTEMS: AUTH_SYSTEMS[self.user_type].update_status(self.user_id, self.info, self.token, status) def send_message(self, subject, body): - if AUTH_SYSTEMS.has_key(self.user_type): + if self.user_type in AUTH_SYSTEMS: subject = subject.split("\n")[0] AUTH_SYSTEMS[self.user_type].send_message(self.user_id, self.name, self.info, subject, body) def send_notification(self, message): - if AUTH_SYSTEMS.has_key(self.user_type): + if self.user_type in AUTH_SYSTEMS: if hasattr(AUTH_SYSTEMS[self.user_type], 'send_notification'): AUTH_SYSTEMS[self.user_type].send_notification(self.user_id, self.info, message) @@ -111,17 +110,17 @@ class User(models.Model): return False # no constraint? Then eligible! - if not eligibility_case.has_key('constraint'): + if 'constraint' not in eligibility_case: return True # from here on we know we match the auth system, but do we match one of the constraints? - auth_system = AUTH_SYSTEMS[self.user_type] - # does the auth system allow for checking a constraint? - if not hasattr(auth_system, 'check_constraint'): + if not can_check_constraint(self.user_type): return False - + + auth_system = AUTH_SYSTEMS[self.user_type] + for constraint in eligibility_case['constraint']: # do we match on this constraint? if auth_system.check_constraint(constraint=constraint, user = self): @@ -142,14 +141,14 @@ class User(models.Model): if self.name: return self.name - if self.info.has_key('name'): + if 'name' in self.info: return self.info['name'] return self.user_id @property def public_url(self): - if AUTH_SYSTEMS.has_key(self.user_type): + if self.user_type in AUTH_SYSTEMS: if hasattr(AUTH_SYSTEMS[self.user_type], 'public_url'): return AUTH_SYSTEMS[self.user_type].public_url(self.user_id) diff --git a/helios_auth/security/__init__.py b/helios_auth/security/__init__.py index b036067d9b837e4156df46fd9a6efdc78516b98f..1fa30e1cf3d5edef11b7a445f4527235f0f567d8 100644 --- a/helios_auth/security/__init__.py +++ b/helios_auth/security/__init__.py @@ -4,18 +4,16 @@ Generic Security -- for the auth system Ben Adida (ben@adida.net) """ +import uuid +from django.conf import settings +from django.core.exceptions import PermissionDenied +from django.http import HttpResponseNotAllowed +from django.http import HttpResponseRedirect # nicely update the wrapper function from functools import update_wrapper -from django.http import HttpResponse, Http404, HttpResponseRedirect -from django.core.exceptions import * -from django.conf import settings - -import oauth - -import uuid - -from helios_auth.models import * +from . import oauth +from helios_auth.models import User FIELDS_TO_SAVE = 'FIELDS_TO_SAVE' @@ -95,10 +93,10 @@ def get_user(request): # request.session.set_expiry(settings.SESSION_COOKIE_AGE) # set up CSRF protection if needed - if not request.session.has_key('csrf_token') or (type(request.session['csrf_token']) != str and type(request.session['csrf_token']) != unicode): + if 'csrf_token' not in request.session or not isinstance(request.session['csrf_token'], str): request.session['csrf_token'] = str(uuid.uuid4()) - if request.session.has_key('user'): + if 'user' in request.session: user = request.session['user'] # find the user @@ -111,7 +109,7 @@ def check_csrf(request): if request.method != "POST": return HttpResponseNotAllowed("only a POST for this URL") - if (not request.POST.has_key('csrf_token')) or (request.POST['csrf_token'] != request.session['csrf_token']): + if ('csrf_token' not in request.POST) or (request.POST['csrf_token'] != request.session['csrf_token']): raise Exception("A CSRF problem was detected") def save_in_session_across_logouts(request, field_name, field_value): diff --git a/helios_auth/security/oauth.py b/helios_auth/security/oauth.py index 4addf22677a22f19bdc350497ab1fab3746689a1..568272bd835eaae3a3f9238b821fab8b870222e8 100644 --- a/helios_auth/security/oauth.py +++ b/helios_auth/security/oauth.py @@ -6,15 +6,12 @@ Hacked a bit by Ben Adida (ben@adida.net) so that: - access tokens are looked up with an extra param of consumer """ -import cgi -import urllib -import time -import random -import urlparse -import hmac import base64 +import hmac import logging -import hashlib +import random +import time +import urllib.parse VERSION = '1.0' # Hi Blaine! HTTP_METHOD = 'GET' @@ -32,7 +29,7 @@ def build_authenticate_header(realm=''): # url escape def escape(s): # escape '/' too - return urllib.quote(s, safe='~') + return urllib.parse.quote(s, safe='~') # util function: current timestamp # seconds since epoch (UTC) @@ -55,7 +52,7 @@ class OAuthConsumer(object): self.secret = secret # OAuthToken is a data type that represents an End User via either an access -# or request token. +# or request token. class OAuthToken(object): # access tokens and request tokens key = None @@ -70,13 +67,13 @@ class OAuthToken(object): self.secret = secret def to_string(self): - return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) + return urllib.parse.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) # return a token from something like: # oauth_token_secret=digg&oauth_token=digg - @staticmethod + @staticmethod def from_string(s): - params = cgi.parse_qs(s, keep_blank_values=False) + params = urllib.parse.parse_qs(s, keep_blank_values=False) key = params['oauth_token'][0] secret = params['oauth_token_secret'][0] return OAuthToken(key, secret) @@ -88,11 +85,11 @@ class OAuthToken(object): class OAuthRequest(object): ''' OAuth parameters: - - oauth_consumer_key + - oauth_consumer_key - oauth_token - oauth_signature_method - - oauth_signature - - oauth_timestamp + - oauth_signature + - oauth_timestamp - oauth_nonce - oauth_version ... any additional parameters, as defined by the Service Provider. @@ -101,7 +98,7 @@ class OAuthRequest(object): http_method = HTTP_METHOD http_url = None version = VERSION - + # added by Ben to filter out extra params from header OAUTH_PARAMS = ['oauth_consumer_key', 'oauth_token', 'oauth_signature_method', 'oauth_signature', 'oauth_timestamp', 'oauth_nonce', 'oauth_version'] @@ -125,7 +122,7 @@ class OAuthRequest(object): # get any non-oauth parameters def get_nonoauth_parameters(self): parameters = {} - for k, v in self.parameters.iteritems(): + for k, v in self.parameters.items(): # ignore oauth parameters if k.find('oauth_') < 0: parameters[k] = v @@ -136,7 +133,7 @@ class OAuthRequest(object): auth_header = 'OAuth realm="%s"' % realm # add the oauth parameters if self.parameters: - for k, v in self.parameters.iteritems(): + for k, v in self.parameters.items(): # only if it's a standard OAUTH param (Ben) if k in self.OAUTH_PARAMS: auth_header += ', %s="%s"' % (k, escape(str(v))) @@ -144,7 +141,7 @@ class OAuthRequest(object): # serialize as post data for a POST request def to_postdata(self): - return '&'.join('%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems()) + return '&'.join('%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.items()) # serialize as a url for a GET request def to_url(self): @@ -158,7 +155,7 @@ class OAuthRequest(object): del params['oauth_signature'] except: pass - key_values = params.items() + key_values = list(params.items()) # sort lexicographically, first after key, then after value key_values.sort() # combine key value pairs in string and escape @@ -170,10 +167,10 @@ class OAuthRequest(object): # parses the url and rebuilds it to be scheme://host/path def get_normalized_http_url(self): - parts = urlparse.urlparse(self.http_url) + parts = urllib.parse.urlparse(self.http_url) url_string = '%s://%s%s' % (parts[0], parts[1], parts[2]) # scheme, netloc, path return url_string - + # set the signature parameter to the result of build_signature def sign_request(self, signature_method, consumer, token): # set the signature method @@ -209,7 +206,7 @@ class OAuthRequest(object): parameters.update(query_params) # URL parameters - param_str = urlparse.urlparse(http_url)[4] # query + param_str = urllib.parse.urlparse(http_url)[4] # query url_params = OAuthRequest._split_url_string(param_str) parameters.update(url_params) @@ -264,15 +261,15 @@ class OAuthRequest(object): # split key-value param_parts = param.split('=', 1) # remove quotes and unescape the value - params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) + params[param_parts[0]] = urllib.parse.unquote(param_parts[1].strip('\"')) return params - + # util function: turn url string into parameters, has to do some unescaping @staticmethod def _split_url_string(param_str): - parameters = cgi.parse_qs(param_str, keep_blank_values=False) - for k, v in parameters.iteritems(): - parameters[k] = urllib.unquote(v[0]) + parameters = urllib.parse.parse_qs(param_str, keep_blank_values=False) + for k, v in parameters.items(): + parameters[k] = urllib.parse.unquote(v[0]) return parameters # OAuthServer is a worker to check a requests validity against a data store @@ -287,7 +284,7 @@ class OAuthServer(object): self.signature_methods = signature_methods or {} def set_data_store(self, oauth_data_store): - self.data_store = data_store + self.data_store = oauth_data_store def get_data_store(self): return self.data_store @@ -336,12 +333,12 @@ class OAuthServer(object): # authorize a request token def authorize_token(self, token, user): return self.data_store.authorize_request_token(token, user) - + # get the callback url def get_callback(self, oauth_request): return oauth_request.get_parameter('oauth_callback') - # optional support for the authenticate header + # optional support for the authenticate header def build_authenticate_header(self, realm=''): return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} @@ -365,7 +362,7 @@ class OAuthServer(object): # get the signature method object signature_method = self.signature_methods[signature_method] except: - signature_method_names = ', '.join(self.signature_methods.keys()) + signature_method_names = ', '.join(list(self.signature_methods.keys())) raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names)) return signature_method @@ -495,7 +492,7 @@ class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod): def get_name(self): return 'HMAC-SHA1' - + def build_signature_base_string(self, oauth_request, consumer, token): sig = ( escape(oauth_request.get_normalized_http_method()), @@ -514,7 +511,12 @@ class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod): key, raw = self.build_signature_base_string(oauth_request, consumer, token) # hmac object - hashed = hmac.new(key, raw, hashlib.sha1) + try: + from Crypto.Hash import SHA1 + hashed = hmac.new(key, raw, SHA1) + except: + import hashlib + hashed = hmac.new(key, raw, hashlib.sha1) # calculate the digest base 64 return base64.b64encode(hashed.digest()) diff --git a/helios_auth/south_migrations/0001_initial.py b/helios_auth/south_migrations/0001_initial.py deleted file mode 100644 index 4ba2a3a2b5cfe08fe055f0dd0d5534a798d43f1e..0000000000000000000000000000000000000000 --- a/helios_auth/south_migrations/0001_initial.py +++ /dev/null @@ -1,49 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Adding model 'User' - db.create_table('helios_auth_user', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('user_type', self.gf('django.db.models.fields.CharField')(max_length=50)), - ('user_id', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('name', self.gf('django.db.models.fields.CharField')(max_length=200, null=True)), - ('info', self.gf('helios_auth.jsonfield.JSONField')()), - ('token', self.gf('helios_auth.jsonfield.JSONField')(null=True)), - ('admin_p', self.gf('django.db.models.fields.BooleanField')(default=False)), - )) - db.send_create_signal('helios_auth', ['User']) - - # Adding unique constraint on 'User', fields ['user_type', 'user_id'] - db.create_unique('helios_auth_user', ['user_type', 'user_id']) - - - def backwards(self, orm): - - # Removing unique constraint on 'User', fields ['user_type', 'user_id'] - db.delete_unique('helios_auth_user', ['user_type', 'user_id']) - - # Deleting model 'User' - db.delete_table('helios_auth_user') - - - models = { - 'helios_auth.user': { - 'Meta': {'unique_together': "(('user_type', 'user_id'),)", 'object_name': 'User'}, - 'admin_p': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'info': ('helios_auth.jsonfield.JSONField', [], {}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), - 'token': ('helios_auth.jsonfield.JSONField', [], {'null': 'True'}), - 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'user_type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - } - } - - complete_apps = ['helios_auth'] diff --git a/helios_auth/south_migrations/__init__.py b/helios_auth/south_migrations/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/helios_auth/templates/index.html b/helios_auth/templates/index.html index f16a7b80cf80e6362c2b70b5ce264f278cc0934f..3c2e9f8d73595f83e9d61c42cd9cf37145735079 100644 --- a/helios_auth/templates/index.html +++ b/helios_auth/templates/index.html @@ -8,7 +8,7 @@ You are currently logged in as<br /><b>{{user.user_id}}</b> via <b>{{user.user_type}}</b>. </p> <p> - <a href="{% url "helios_auth.views.logout" %}">logout</a> + <a href="{% url "auth@logout" %}">logout</a> </p> {% else %} diff --git a/helios_auth/templates/login_box.html b/helios_auth/templates/login_box.html index 2e89e94e49fb8384d97fe81a8ffed94aa2204f85..27ca7da17aa80df0367db54adc7bf4d4336365f8 100644 --- a/helios_auth/templates/login_box.html +++ b/helios_auth/templates/login_box.html @@ -1,12 +1,12 @@ {% if default_auth_system %} <p> -<a class="small button" href="{% url "helios_auth.views.start" system_name=default_auth_system %}?return_url={{return_url}}">Log in</a></p> +<a class="small button" href="{% url "auth@start" system_name=default_auth_system %}?return_url={{return_url}}">Log in</a></p> {% else %} {% for auth_system in enabled_auth_systems %} {% ifequal auth_system "password" %} {% else %} <p> - <a href="{{SECURE_URL_HOST}}{% url "helios_auth.views.start" system_name=auth_system %}?return_url={{return_url}}" style="font-size: 1.4em;"> + <a href="{{SECURE_URL_HOST}}{% url "auth@start" system_name=auth_system %}?return_url={{return_url}}" style="font-size: 1.4em;"> <img style="height: 35px; border: 0px;" src="/static/auth/login-icons/{{auth_system}}.png" alt="{{auth_system}}" /> {{auth_system}} {% endifequal %} </a> diff --git a/helios_auth/templates/perms_why.html b/helios_auth/templates/perms_why.html index 179d103c27153c17bf45f59e39af2658532d2090..3f38fd18d0b87ac528ee27852730bddf42c4e2e9 100644 --- a/helios_auth/templates/perms_why.html +++ b/helios_auth/templates/perms_why.html @@ -13,7 +13,7 @@ this information, we unfortunately cannot help you vote. </p> <form method="POST" action=""> -<input type="hidden" value="{{csrf_token}}" /> +<input type="hidden" name="csrf_token" value="{{csrf_token}}" /> <input type="submit" value="OK, I understand, let's do this login thing." /><br /><br /> <a href="http://google.com">nope, get me out of here</a>. </form> diff --git a/helios_auth/tests.py b/helios_auth/tests.py index f07f309fb5176007ca67a25ce14c712efc33af35..934933258f49fb8a64752fc241340a1ad4489789 100644 --- a/helios_auth/tests.py +++ b/helios_auth/tests.py @@ -3,16 +3,15 @@ Unit Tests for Auth Systems """ import unittest -import models +from django.core import mail from django.db import IntegrityError, transaction - -from django.test.client import Client from django.test import TestCase +from django.urls import reverse -from django.core import mail +from . import models, views +from .auth_systems import AUTH_SYSTEMS, password as password_views -from auth_systems import AUTH_SYSTEMS class UserModelTests(unittest.TestCase): @@ -23,7 +22,7 @@ class UserModelTests(unittest.TestCase): """ there should not be two users with the same user_type and user_id """ - for auth_system, auth_system_module in AUTH_SYSTEMS.iteritems(): + for auth_system, auth_system_module in AUTH_SYSTEMS.items(): models.User.objects.create(user_type = auth_system, user_id = 'foobar', info={'name':'Foo Bar'}) def double_insert(): @@ -36,22 +35,22 @@ class UserModelTests(unittest.TestCase): """ shouldn't create two users, and should reset the password """ - for auth_system, auth_system_module in AUTH_SYSTEMS.iteritems(): + for auth_system, auth_system_module in AUTH_SYSTEMS.items(): u = models.User.update_or_create(user_type = auth_system, user_id = 'foobar_cou', info={'name':'Foo Bar'}) def double_update_or_create(): new_name = 'Foo2 Bar' u2 = models.User.update_or_create(user_type = auth_system, user_id = 'foobar_cou', info={'name': new_name}) - self.assertEquals(u.id, u2.id) - self.assertEquals(u2.info['name'], new_name) + self.assertEqual(u.id, u2.id) + self.assertEqual(u2.info['name'], new_name) def test_can_create_election(self): """ check that auth systems have the can_create_election call and that it's true for the common ones """ - for auth_system, auth_system_module in AUTH_SYSTEMS.iteritems(): + for auth_system, auth_system_module in AUTH_SYSTEMS.items(): assert(hasattr(auth_system_module, 'can_create_election')) if auth_system != 'clever': assert(auth_system_module.can_create_election('foobar', {})) @@ -62,13 +61,13 @@ class UserModelTests(unittest.TestCase): check that a user set up with status update ability reports it as such, and otherwise does not report it """ - for auth_system, auth_system_module in AUTH_SYSTEMS.iteritems(): + for auth_system, auth_system_module in AUTH_SYSTEMS.items(): u = models.User.update_or_create(user_type = auth_system, user_id = 'foobar_status_update', info={'name':'Foo Bar Status Update'}) if hasattr(auth_system_module, 'send_message'): - self.assertNotEquals(u.update_status_template, None) + self.assertNotEqual(u.update_status_template, None) else: - self.assertEquals(u.update_status_template, None) + self.assertEqual(u.update_status_template, None) def test_eligibility(self): """ @@ -76,22 +75,18 @@ class UserModelTests(unittest.TestCase): FIXME: also test constraints on eligibility """ - for auth_system, auth_system_module in AUTH_SYSTEMS.iteritems(): + for auth_system, auth_system_module in AUTH_SYSTEMS.items(): u = models.User.update_or_create(user_type = auth_system, user_id = 'foobar_status_update', info={'name':'Foo Bar Status Update'}) self.assertTrue(u.is_eligible_for({'auth_system': auth_system})) def test_eq(self): - for auth_system, auth_system_module in AUTH_SYSTEMS.iteritems(): + for auth_system, auth_system_module in AUTH_SYSTEMS.items(): u = models.User.update_or_create(user_type = auth_system, user_id = 'foobar_eq', info={'name':'Foo Bar Status Update'}) u2 = models.User.update_or_create(user_type = auth_system, user_id = 'foobar_eq', info={'name':'Foo Bar Status Update'}) - self.assertEquals(u, u2) - + self.assertEqual(u, u2) -import views -import auth_systems.password as password_views -from django.core.urlresolvers import reverse # FIXME: login CSRF should make these tests more complicated # and should be tested for @@ -116,7 +111,7 @@ class UserBlackboxTests(TestCase): # self.assertContains(response, "Foobar User") def test_logout(self): - response = self.client.post(reverse(views.logout), follow=True) + response = self.client.post(reverse("auth@logout"), follow=True) self.assertContains(response, "not logged in") self.assertNotContains(response, "Foobar User") @@ -125,6 +120,6 @@ class UserBlackboxTests(TestCase): """using the test email backend""" self.test_user.send_message("testing subject", "testing body") - self.assertEquals(len(mail.outbox), 1) - self.assertEquals(mail.outbox[0].subject, "testing subject") - self.assertEquals(mail.outbox[0].to[0], "\"Foobar User\" <foobar-test@adida.net>") + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].subject, "testing subject") + self.assertEqual(mail.outbox[0].to[0], "\"Foobar User\" <foobar-test@adida.net>") diff --git a/helios_auth/url_names.py b/helios_auth/url_names.py new file mode 100644 index 0000000000000000000000000000000000000000..29f7b228f8363f090f44cf21225a5f6e2595caf1 --- /dev/null +++ b/helios_auth/url_names.py @@ -0,0 +1,6 @@ +AUTH_INDEX="auth@index" +AUTH_LOGOUT="auth@logout" +AUTH_START="auth@start" +AUTH_AFTER="auth@after" +AUTH_WHY="auth@why" +AUTH_AFTER_INTERVENTION="auth@after-intervention" diff --git a/helios_auth/urls.py b/helios_auth/urls.py index e4dca398503d1e2f802fd4811330b66b9020a1a9..9fbc158f499f0df32cd3f31830990b537ed00f73 100644 --- a/helios_auth/urls.py +++ b/helios_auth/urls.py @@ -1,31 +1,32 @@ + """ Authentication URLs Ben Adida (ben@adida.net) """ -from django.conf.urls import * +from django.conf.urls import url -from views import * -from auth_systems.password import password_login_view, password_forgotten_view -from auth_systems.twitter import follow_view +from settings import AUTH_ENABLED_SYSTEMS +from . import views, url_names -urlpatterns = patterns('', +urlpatterns = [ # basic static stuff - (r'^$', index), - (r'^logout$', logout), - (r'^start/(?P<system_name>.*)$', start), + url(r'^$', views.index, name=url_names.AUTH_INDEX), + url(r'^logout$', views.logout, name=url_names.AUTH_LOGOUT), + url(r'^start/(?P<system_name>.*)$', views.start, name=url_names.AUTH_START), # weird facebook constraint for trailing slash - (r'^after/$', after), - (r'^why$', perms_why), - (r'^after_intervention$', after_intervention), - - ## should make the following modular + url(r'^after/$', views.after, name=url_names.AUTH_AFTER), + url(r'^why$', views.perms_why, name=url_names.AUTH_WHY), + url(r'^after_intervention$', views.after_intervention, name=url_names.AUTH_AFTER_INTERVENTION), +] - # password auth - (r'^password/login', password_login_view), - (r'^password/forgot', password_forgotten_view), +# password auth +if 'password' in AUTH_ENABLED_SYSTEMS: + from .auth_systems.password import urlpatterns as password_patterns + urlpatterns.extend(password_patterns) - # twitter - (r'^twitter/follow', follow_view), -) +# twitter +if 'twitter' in AUTH_ENABLED_SYSTEMS: + from .auth_systems.twitter import urlpatterns as twitter_patterns + urlpatterns.extend(twitter_patterns) diff --git a/helios_auth/utils.py b/helios_auth/utils.py index f57dedf6e961e8598963049dca55f6cdaf211f00..dc723adce77a3f08c19e1a0438a8ae20f611e882 100644 --- a/helios_auth/utils.py +++ b/helios_auth/utils.py @@ -7,23 +7,30 @@ Some basic utils import json + ## JSON def to_json(d): - return json.dumps(d, sort_keys=True) - -def from_json(json_str): - if not json_str: return None - return json.loads(json_str) - -def JSONtoDict(json): - x=json.loads(json) - return x - -def JSONFiletoDict(filename): - f = open(filename, 'r') - content = f.read() - f.close() - return JSONtoDict(content) - + return json.dumps(d, sort_keys=True) + + +def from_json(value): + if value == "" or value is None: + return None + if isinstance(value, str): + try: + return json.loads(value) + except Exception as e: + # import ast + # try: + # parsed_value = ast.literal_eval(parsed_value) + # except Exception as e1: + raise Exception("value is not JSON parseable, that's bad news") from e + return value + + +def JSONFiletoDict(filename): + with open(filename, 'r') as f: + content = f.read() + return from_json(content) diff --git a/helios_auth/view_utils.py b/helios_auth/view_utils.py index 1355ba450b75ff40c0122b42b860751852225479..48b90c7806167ee9031a3c5e55a82f03adf9af37 100644 --- a/helios_auth/view_utils.py +++ b/helios_auth/view_utils.py @@ -4,15 +4,13 @@ Utilities for all views Ben Adida (12-30-2008) """ -from django.template import Context, Template, loader -from django.http import HttpResponse, Http404 +from django.conf import settings +from django.http import HttpResponse from django.shortcuts import render_to_response - -from helios_auth.security import get_user +from django.template import loader import helios_auth - -from django.conf import settings +from helios_auth.security import get_user ## ## BASICS @@ -24,37 +22,33 @@ SUCCESS = HttpResponse("SUCCESS") ## template abstraction ## -def prepare_vars(request, vars): - vars_with_user = vars.copy() - +def prepare_vars(request, values): + vars_with_user = values.copy() + if request: vars_with_user['user'] = get_user(request) vars_with_user['csrf_token'] = request.session['csrf_token'] vars_with_user['SECURE_URL_HOST'] = settings.SECURE_URL_HOST - + vars_with_user['STATIC'] = '/static/auth' vars_with_user['MEDIA_URL'] = '/static/auth/' vars_with_user['TEMPLATE_BASE'] = helios_auth.TEMPLATE_BASE - + vars_with_user['settings'] = settings - + return vars_with_user - -def render_template(request, template_name, vars = {}): - t = loader.get_template(template_name + '.html') - - vars_with_user = prepare_vars(request, vars) - + + +def render_template(request, template_name, values=None): + vars_with_user = prepare_vars(request, values or {}) + return render_to_response('helios_auth/templates/%s.html' % template_name, vars_with_user) -def render_template_raw(request, template_name, vars={}): - t = loader.get_template(template_name + '.html') - - vars_with_user = prepare_vars(request, vars) - c = Context(vars_with_user) - return t.render(c) -def render_json(json_txt): - return HttpResponse(json_txt) +def render_template_raw(request, template_name, values=None): + t = loader.get_template(template_name + '.html') + values = values or {} + vars_with_user = prepare_vars(request, values) + return t.render(context=vars_with_user, request=request) diff --git a/helios_auth/views.py b/helios_auth/views.py index b85f82e797192295cc9118dff36000b8ed6010cf..73f23050ba2f9767a94da17a2e5b180578c4c6be 100644 --- a/helios_auth/views.py +++ b/helios_auth/views.py @@ -5,22 +5,20 @@ Ben Adida 2009-07-05 """ -from django.http import * -from django.core.urlresolvers import reverse +from urllib.parse import urlencode -from view_utils import * -from helios_auth.security import get_user - -import auth_systems -from auth_systems import AUTH_SYSTEMS -from auth_systems import password -import helios_auth +from django.http import HttpResponseRedirect, HttpResponse +from django.urls import reverse -import copy, urllib - -from models import User +import settings +from helios_auth import DEFAULT_AUTH_SYSTEM, ENABLED_AUTH_SYSTEMS +from helios_auth.security import get_user +from helios_auth.url_names import AUTH_INDEX, AUTH_START, AUTH_AFTER, AUTH_WHY, AUTH_AFTER_INTERVENTION +from .auth_systems import AUTH_SYSTEMS, password +from .models import User +from .security import FIELDS_TO_SAVE +from .view_utils import render_template, render_template_raw -from security import FIELDS_TO_SAVE def index(request): """ @@ -30,42 +28,42 @@ def index(request): user = get_user(request) # single auth system? - if len(helios_auth.ENABLED_AUTH_SYSTEMS) == 1 and not user: - return HttpResponseRedirect(reverse(start, args=[helios_auth.ENABLED_AUTH_SYSTEMS[0]])+ '?return_url=' + request.GET.get('return_url', '')) + if len(ENABLED_AUTH_SYSTEMS) == 1 and not user: + return HttpResponseRedirect(reverse(AUTH_START, args=[ENABLED_AUTH_SYSTEMS[0]])+ '?return_url=' + request.GET.get('return_url', '')) - #if helios_auth.DEFAULT_AUTH_SYSTEM and not user: - # return HttpResponseRedirect(reverse(start, args=[helios_auth.DEFAULT_AUTH_SYSTEM])+ '?return_url=' + request.GET.get('return_url', '')) + #if DEFAULT_AUTH_SYSTEM and not user: + # return HttpResponseRedirect(reverse(start, args=[DEFAULT_AUTH_SYSTEM])+ '?return_url=' + request.GET.get('return_url', '')) default_auth_system_obj = None - if helios_auth.DEFAULT_AUTH_SYSTEM: - default_auth_system_obj = AUTH_SYSTEMS[helios_auth.DEFAULT_AUTH_SYSTEM] + if DEFAULT_AUTH_SYSTEM: + default_auth_system_obj = AUTH_SYSTEMS[DEFAULT_AUTH_SYSTEM] #form = password.LoginForm() - return render_template(request,'index', {'return_url' : request.GET.get('return_url', '/'), - 'enabled_auth_systems' : helios_auth.ENABLED_AUTH_SYSTEMS, - 'default_auth_system': helios_auth.DEFAULT_AUTH_SYSTEM, - 'default_auth_system_obj': default_auth_system_obj}) + return render_template(request, 'index', {'return_url' : request.GET.get('return_url', '/'), + 'enabled_auth_systems' : ENABLED_AUTH_SYSTEMS, + 'default_auth_system': DEFAULT_AUTH_SYSTEM, + 'default_auth_system_obj': default_auth_system_obj}) def login_box_raw(request, return_url='/', auth_systems = None): """ a chunk of HTML that shows the various login options """ default_auth_system_obj = None - if helios_auth.DEFAULT_AUTH_SYSTEM: - default_auth_system_obj = AUTH_SYSTEMS[helios_auth.DEFAULT_AUTH_SYSTEM] + if DEFAULT_AUTH_SYSTEM: + default_auth_system_obj = AUTH_SYSTEMS[DEFAULT_AUTH_SYSTEM] # make sure that auth_systems includes only available and enabled auth systems - if auth_systems != None: - enabled_auth_systems = set(auth_systems).intersection(set(helios_auth.ENABLED_AUTH_SYSTEMS)).intersection(set(AUTH_SYSTEMS.keys())) + if auth_systems is not None: + enabled_auth_systems = set(auth_systems).intersection(set(ENABLED_AUTH_SYSTEMS)).intersection(set(AUTH_SYSTEMS.keys())) else: - enabled_auth_systems = set(helios_auth.ENABLED_AUTH_SYSTEMS).intersection(set(AUTH_SYSTEMS.keys())) + enabled_auth_systems = set(ENABLED_AUTH_SYSTEMS).intersection(set(AUTH_SYSTEMS.keys())) form = password.LoginForm() return render_template_raw(request, 'login_box', { 'enabled_auth_systems': enabled_auth_systems, 'return_url': return_url, - 'default_auth_system': helios_auth.DEFAULT_AUTH_SYSTEM, 'default_auth_system_obj': default_auth_system_obj, + 'default_auth_system': DEFAULT_AUTH_SYSTEM, 'default_auth_system_obj': default_auth_system_obj, 'form' : form}) def do_local_logout(request): @@ -76,7 +74,7 @@ def do_local_logout(request): user = None - if request.session.has_key('user'): + if 'user' in request.session: user = request.session['user'] # 2010-08-14 be much more aggressive here @@ -87,7 +85,7 @@ def do_local_logout(request): # let's clean up the self-referential issue: field_names_to_save = set(field_names_to_save) - field_names_to_save = field_names_to_save - set([FIELDS_TO_SAVE]) + field_names_to_save = field_names_to_save - {FIELDS_TO_SAVE} field_names_to_save = list(field_names_to_save) fields_to_save = dict([(name, request.session.get(name, None)) for name in field_names_to_save]) @@ -144,7 +142,7 @@ def _do_auth(request): system = AUTH_SYSTEMS[system_name] # where to send the user to? - redirect_url = "%s%s" % (settings.SECURE_URL_HOST,reverse(after)) + redirect_url = settings.SECURE_URL_HOST + reverse(AUTH_AFTER) auth_url = system.get_auth_url(request, redirect_url=redirect_url) if auth_url: @@ -153,8 +151,8 @@ def _do_auth(request): return HttpResponse("an error occurred trying to contact " + system_name +", try again later") def start(request, system_name): - if not (system_name in helios_auth.ENABLED_AUTH_SYSTEMS): - return HttpResponseRedirect(reverse(index)) + if not (system_name in ENABLED_AUTH_SYSTEMS): + return HttpResponseRedirect(reverse(AUTH_INDEX)) # why is this here? Let's try without it # request.session.save() @@ -175,7 +173,7 @@ def perms_why(request): def after(request): # which auth system were we using? - if not request.session.has_key('auth_system_name'): + if 'auth_system_name' not in request.session: do_local_logout(request) return HttpResponseRedirect("/") @@ -190,7 +188,7 @@ def after(request): request.session['user'] = user else: - return HttpResponseRedirect("%s?%s" % (reverse(perms_why), urllib.urlencode({'system_name' : request.session['auth_system_name']}))) + return HttpResponseRedirect("%s?%s" % (reverse(AUTH_WHY), urlencode({'system_name' : request.session['auth_system_name']}))) # does the auth system want to present an additional view? # this is, for example, to prompt the user to follow @heliosvoting @@ -201,12 +199,12 @@ def after(request): return intervention_response # go to the after intervention page. This is for modularity - return HttpResponseRedirect(reverse(after_intervention)) + return HttpResponseRedirect(reverse(AUTH_AFTER_INTERVENTION)) def after_intervention(request): return_url = "/" - if request.session.has_key('auth_return_url'): + if 'auth_return_url' in request.session: return_url = request.session['auth_return_url'] del request.session['auth_return_url'] - return HttpResponseRedirect("%s%s" % (settings.URL_HOST, return_url)) + return HttpResponseRedirect(settings.URL_HOST + return_url) diff --git a/heliosbooth/boothworker-single.js b/heliosbooth/boothworker-single.js index edb1debb5132006a7efcd8af99706ebd19b9d1bb..1c87986cad03eef9b61fd5320a0ff8659d6e8af3 100644 --- a/heliosbooth/boothworker-single.js +++ b/heliosbooth/boothworker-single.js @@ -49,12 +49,16 @@ function do_encrypt(message) { // receive either // a) an election and an integer position of the question // that this worker will be used to encrypt -// {'type': 'setup', 'question_num' : 2, 'election' : election_json} +// {'type': 'setup', 'election': election_json} // // b) an answer that needs encrypting -// {'type': 'encrypt', 'answer' : answer_json} +// {'type': 'encrypt', 'q_num': 2, 'id': id, 'answer': answer_json} // self.onmessage = function(event) { // dispatch to method - self['do_' + event.data.type](event.data); -} + if (event.data.type === "setup") { + do_setup(event.data); + } else if (event.data.type === "encrypt") { + do_encrypt(event.data); + } +}; diff --git a/heliosbooth/js/jscrypto/random.js b/heliosbooth/js/jscrypto/random.js index dd69f9d4fcb14add53e641eef94072b377b93844..8e363a6d0d561d3bbfd21a515b472b717d7cefde 100644 --- a/heliosbooth/js/jscrypto/random.js +++ b/heliosbooth/js/jscrypto/random.js @@ -26,10 +26,9 @@ Random.getRandomInteger = function(max) { var bit_length = max.bitLength(); Random.setupGenerator(); var random; - random = sjcl.random.randomWords(Math.ceil(bit_length / 32)+2, 0); + random = sjcl.random.randomWords(Math.ceil(bit_length / 32) + 2, 6); // we get a bit array instead of a BigInteger in this case var rand_bi = new BigInt(sjcl.codec.hex.fromBits(random), 16); return rand_bi.mod(max); - return BigInt._from_java_object(random).mod(max); }; diff --git a/heliosbooth/templates/seal.html b/heliosbooth/templates/seal.html index a17de4bca95485157853692e52cf0778212eee18..d16ac3b8bb4a058199a6c7c3ef8ea174d2e71c0e 100644 --- a/heliosbooth/templates/seal.html +++ b/heliosbooth/templates/seal.html @@ -29,7 +29,7 @@ You will then be guided to re-encrypt your choices for final casting. <div style="margin-left: 15px;">✓ {$T.choice}</div> {#/for} {#if $T.choices[$T.question$index].length < $T.question.max} -[you under-voted: you may select up to {$T.question.max}] +[you selected {$T.choices[$T.question$index].length} candidates; you may select from {$T.question.min} to {$T.question.max}] {#/if} [<a onclick="BOOTH.show_question({$T.question$index}); return false;" href="#">edit responses</a>] {#if !$T.question$last}<br><br>{#/if} diff --git a/heliosverifier/js/jscrypto/helios.js b/heliosverifier/js/jscrypto/helios.js index 93c0ed48c317d21fee609d30a4108157b1fddbfd..f46a31bbb2db866ddf6acd59b120213d770b1fb8 100644 --- a/heliosverifier/js/jscrypto/helios.js +++ b/heliosverifier/js/jscrypto/helios.js @@ -609,13 +609,14 @@ HELIOS.dejsonify_list_of_lists = function(lol, item_dejsonifier) { } HELIOS.Trustee = Class.extend({ - init: function(uuid, public_key, public_key_hash, pok, decryption_factors, decryption_proofs) { + init: function(uuid, public_key, public_key_hash, pok, decryption_factors, decryption_proofs, email) { this.uuid = uuid; this.public_key = public_key; this.public_key_hash = public_key_hash; this.pok = pok; this.decryption_factors = decryption_factors; this.decryption_proofs = decryption_proofs; + this.email = email; }, toJSONObject: function() { @@ -631,5 +632,7 @@ HELIOS.Trustee.fromJSONObject = function(d) { return new HELIOS.Trustee(d.uuid, ElGamal.PublicKey.fromJSONObject(d.public_key), d.public_key_hash, ElGamal.DLogProof.fromJSONObject(d.pok), HELIOS.dejsonify_list_of_lists(d.decryption_factors, BigInt.fromJSONObject), - HELIOS.dejsonify_list_of_lists(d.decryption_proofs, ElGamal.Proof.fromJSONObject)); -}; \ No newline at end of file + HELIOS.dejsonify_list_of_lists(d.decryption_proofs, ElGamal.Proof.fromJSONObject), + d.email + ); +}; diff --git a/requirements.txt b/requirements.txt index 73f70a1d76b5968248798e353170b3f924ff6c5d..0b88e889d1dfaa36250db009fc41e40e1bea00c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,26 +1,22 @@ -Django==1.8.18 -anyjson==0.3.3 -celery==3.1.18 -django-celery==3.1.16 -django-picklefield==0.3.0 -kombu==3.0.30 -html5lib==0.999 -psycopg2==2.7.3.2 -pyparsing==1.5.7 -python-dateutil>=1.5 -python-openid==2.2.5 -wsgiref==0.1.2 -gunicorn==19.9 -requests==2.7.0 -unicodecsv==0.9.0 -dj_database_url==0.3.0 -django-sslify==0.2.7 -django_webtest>=1.9 -webtest==2.0.18 -django-secure==1.0.1 -bleach==1.4.1 -boto==2.27.0 -django-ses==0.6.0 -validate_email==1.2 -oauth2client==1.2 -rollbar==0.12.1 +Django==1.11.28 +django_webtest>=1.9.7 + +gunicorn==20.0.4 + +dj_database_url==0.5.0 +psycopg2==2.8.4 + +celery==4.2.1 +django-picklefield==1.1.0 + +python-dateutil>=2.8 +unicodecsv==0.14.1 +bleach==3.1.1 +validate_email==1.3 +pycryptodome==3.8.2 + +python3-openid==3.0.10 +boto==2.49.0 +django-ses==0.8.14 +oauth2client==4.1.3 +rollbar==0.14.7 diff --git a/reset.sh b/reset.sh index f7ad853220fcc371459bc86beeff2efdbac7122d..8192723eefa08a4af0b4092f341e7bf3e6cd758e 100755 --- a/reset.sh +++ b/reset.sh @@ -1,6 +1,7 @@ #!/bin/bash +set -e # Exit immediately if a command exits with a non-zero status. dropdb helios createdb helios -python manage.py syncdb +python manage.py makemigrations python manage.py migrate echo "from helios_auth.models import User; User.objects.create(user_type='google',user_id='ben@adida.net', info={'name':'Ben Adida'})" | python manage.py shell \ No newline at end of file diff --git a/runtime.txt b/runtime.txt index f27f1cc5ca4d2e750411e5ee682f6eb6baa674c2..4252f10667b7c2fce89b70b74097960c86c7cf6c 100644 --- a/runtime.txt +++ b/runtime.txt @@ -1 +1 @@ -python-2.7.15 +python-3.6.12 diff --git a/server_ui/__init__.py b/server_ui/__init__.py index bb89b654f886d6c945b7212738cfa15717e54ea3..3f1966c404ae56875aebbb86b13e1d65e43788f6 100644 --- a/server_ui/__init__.py +++ b/server_ui/__init__.py @@ -1,5 +1,3 @@ """ This django app is meant only to connect the pieces of Helios and Auth to present a clean UI """ - -import glue diff --git a/server_ui/glue.py b/server_ui/glue.py index 9b3b5b37ba54c7de399c820d274e131374029184..12b64b1ec35bfc3a777d2fdb4f03b5ce2ec191ff 100644 --- a/server_ui/glue.py +++ b/server_ui/glue.py @@ -2,36 +2,35 @@ Glue some events together """ -from django.conf import settings -from django.core.urlresolvers import reverse -from django.conf import settings +import helios.signals +import helios.views from helios.view_utils import render_template_raw -import helios.views, helios.signals -import views def vote_cast_send_message(user, voter, election, cast_vote, **kwargs): - ## FIXME: this doesn't work for voters that are not also users - # prepare the message - subject_template = 'email/cast_vote_subject.txt' - body_template = 'email/cast_vote_body.txt' - - extra_vars = { - 'election' : election, - 'voter': voter, - 'cast_vote': cast_vote, - 'cast_vote_url': helios.views.get_castvote_url(cast_vote), - 'custom_subject' : "%s - vote cast" % election.name - } - subject = render_template_raw(None, subject_template, extra_vars) - body = render_template_raw(None, body_template, extra_vars) - - # send it via the notification system associated with the auth system - user.send_message(subject, body) - -helios.signals.vote_cast.connect(vote_cast_send_message) + ## FIXME: this doesn't work for voters that are not also users + # prepare the message + subject_template = 'email/cast_vote_subject.txt' + body_template = 'email/cast_vote_body.txt' + + extra_vars = { + 'election': election, + 'voter': voter, + 'cast_vote': cast_vote, + 'cast_vote_url': helios.views.get_castvote_url(cast_vote), + 'custom_subject': "%s - vote cast" % election.name + } + subject = render_template_raw(None, subject_template, extra_vars) + body = render_template_raw(None, body_template, extra_vars) + + # send it via the notification system associated with the auth system + user.send_message(subject, body) + def election_tallied(election, **kwargs): - pass + pass + -helios.signals.election_tallied.connect(election_tallied) +def glue(): + helios.signals.vote_cast.connect(vote_cast_send_message) + helios.signals.election_tallied.connect(election_tallied) diff --git a/server_ui/templates/base.html b/server_ui/templates/base.html index ac5f2d13cc11ccd1a60456482fe5199d985e7476..8357be2aaa31636509e32c51609ce92f8d0b3801 100644 --- a/server_ui/templates/base.html +++ b/server_ui/templates/base.html @@ -42,7 +42,7 @@ <!-- Right Nav Section --> <ul class="right"> {% if user and user.admin_p %} - <li><a href="{% url "helios.stats_views.home" %}">Admin</a></li> + <li><a href="{% url "stats@home" %}">Admin</a></li> <li class="divider"></li> {% endif %} {% if not settings.MASTER_HELIOS %} @@ -88,13 +88,13 @@ </span>--> {% if user %} logged in as <b>{{user.display_html_small|safe}}</b> - <a class="tiny button" href="{% url "helios_auth.views.logout" %}?return_url={{CURRENT_URL}}">logout</a><br /> + <a class="tiny button" href="{% url "auth@logout" %}?return_url={{CURRENT_URL}}">logout</a><br /> {% else %} {% if voter %} - You are signed in as voter <u>{% if voter.alias %}{{voter.alias}}{% else %}{{voter.name}}{% endif %}</u> in election <u>{{voter.election.name}}</u>. [<a href="{{settings.SECURE_URL_HOST}}{% url "helios_auth.views.logout" %}?return_url={{CURRENT_URL}}">sign out</a>] + You are signed in as voter <u>{% if voter.alias %}{{voter.alias}}{% else %}{{voter.name}}{% endif %}</u> in election <u>{{voter.election.name}}</u>. [<a href="{{settings.SECURE_URL_HOST}}{% url "auth@logout" %}?return_url={{CURRENT_URL}}">sign out</a>] {% else %} {% if settings.SHOW_LOGIN_OPTIONS %} - not logged in. <a class="tiny button" href="{{settings.SECURE_URL_HOST}}{% url "helios_auth.views.index" %}?return_url={{CURRENT_URL}}">log in</a> + not logged in. <a class="tiny button" href="{{settings.SECURE_URL_HOST}}{% url "auth@index" %}?return_url={{CURRENT_URL}}">log in</a> {% else %} powered by <a href="http://heliosvoting.org">Helios Voting</a>. {% endif %} diff --git a/server_ui/templates/confirm.html b/server_ui/templates/confirm.html index d98f65fef5c7e46b9e480cb206ea8dd6408a06b6..2f24a807a0ae1854a5f2b8207c037d3b8a6c74d0 100644 --- a/server_ui/templates/confirm.html +++ b/server_ui/templates/confirm.html @@ -38,7 +38,7 @@ function show_waiting() { </form> <p> - Forgot your password? <a href="{% url "helios_auth.auth_systems.password.password_forgotten_view" %}?return_url={% url "server_ui.views.cast_confirm" %}">Have it emailed to you</a>.<br />(don't worry, we won't forget your vote). + Forgot your password? <a href="{% url "auth@password@forgotten" %}?return_url={% url "election@cast-confirm" %}">Have it emailed to you</a>.<br />(don't worry, we won't forget your vote). </p> </div> diff --git a/server_ui/templates/done.html b/server_ui/templates/done.html index 47ff73fa6023c847ea8fb2425c5bfc56b01d36d2..d4281060f7f16cef56798f82a06788da99467ddb 100644 --- a/server_ui/templates/done.html +++ b/server_ui/templates/done.html @@ -11,7 +11,7 @@ </p> <p> - Visit the <a href="{% url "helios.views.one_election_view" election.uuid %}">election homepage</a>. + Visit the <a href="{% url "election@view" election.uuid %}">election homepage</a>. </p> {% endblock %} diff --git a/server_ui/templates/election_tallied.html b/server_ui/templates/election_tallied.html index 747e083fc3d4ed3433000fd8b1edf5f41bd7364d..42d44410f9b298eecc3c429b7eca9bcf98aaaf69 100644 --- a/server_ui/templates/election_tallied.html +++ b/server_ui/templates/election_tallied.html @@ -9,6 +9,6 @@ </p> <p> - <a href="{% url "helios.views.one_election_view" election.uuid %}">view the election</a> + <a href="{% url "election@view" election.uuid %}">view the election</a> </p> {% endblock %} \ No newline at end of file diff --git a/server_ui/templates/index.html b/server_ui/templates/index.html index 2b0a0e2c44ebf541b8fbcdc1309b9d526148e852..4b1fcec8f7cb672a02569e02a01ff4efefa99d90 100644 --- a/server_ui/templates/index.html +++ b/server_ui/templates/index.html @@ -24,11 +24,11 @@ Helios elections are: </ul> <p> -More than <b>100,000 votes</b> have been cast using Helios. +More than <b>2,000,000 votes</b> have been cast using Helios. </p> {% if create_p %} -<a class="button" href="{% url "helios.views.election_new" %}">create an election</a> +<a class="button" href="{% url "elections@new" %}">create an election</a> {% endif %} {% else %} @@ -40,7 +40,7 @@ More than <b>100,000 votes</b> have been cast using Helios. <p> {% for election in elections %} <div class="panel"> - <a style="font-size: 1.4em;" href="{% url "helios.views.election_shortcut" election.short_name %}">{{election.name}}</a>{% if settings.SHOW_USER_INFO %}<br /> by {{election.admin.display_html_small|safe}}{% endif %} + <a style="font-size: 1.4em;" href="{% url "shortcut@election" election.short_name %}">{{election.name}}</a>{% if settings.SHOW_USER_INFO %}<br /> by {{election.admin.display_html_small|safe}}{% endif %} </div> <br /> {% endfor %} @@ -58,19 +58,19 @@ More than <b>100,000 votes</b> have been cast using Helios. {% if user %} <!--<div class="row right">{{user.display_html_big|safe}}</div>--> {% if create_p %} -<a class="small button" href="{% url "helios.views.election_new" %}">create election</a> +<a class="small button" href="{% url "elections@new" %}">create election</a> <h5 class="subheader">Administration</h5> {% if elections_administered %} <ul> {% for election in elections_administered %} -<li> <a href="{% url "helios.views.one_election_view" election.uuid %}">{{election.name}}</a></li> +<li> <a href="{% url "election@view" election.uuid %}">{{election.name}}</a></li> {% endfor %} </ul> {% else %} <em>none yet</em> {% endif %} <div class="row right"> -<a class="tiny button" href="{% url "helios.views.elections_administered" %}">see all</a> +<a class="tiny button" href="{% url "elections@administered" %}">see all</a> </div> <div class="row"></div> {% endif %} @@ -79,13 +79,13 @@ More than <b>100,000 votes</b> have been cast using Helios. {% if elections_voted %} <ul> {% for election in elections_voted %} -<li><a href="{% url "helios.views.one_election_view" election.uuid %}">{{election.name}}</a></li> +<li><a href="{% url "election@view" election.uuid %}">{{election.name}}</a></li> {% endfor %} </ul> {% else %} <em>none yet</em> {% endif %} -<div class="row right"><a class="tiny button" href="{% url "helios.views.elections_voted" %}">see all</a></div> +<div class="row right"><a class="tiny button" href="{% url "elections@voted" %}">see all</a></div> <div class="row"></div> {% else %} {% if settings.SHOW_LOGIN_OPTIONS %} diff --git a/server_ui/urls.py b/server_ui/urls.py index d71e04dcac68008d2001ca99c7df7f055bd6404a..aad77654242dcfa268cc23b7103de800b68b9dfa 100644 --- a/server_ui/urls.py +++ b/server_ui/urls.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- -from django.conf.urls import * +from django.conf.urls import url -from views import * +from .views import home, about, docs, faq, privacy -urlpatterns = patterns('', - (r'^$', home), - (r'^about$', about), - (r'^docs$', docs), - (r'^faq$', faq), - (r'^privacy$', privacy), -) +urlpatterns = [ + url(r'^$', home), + url(r'^about$', about), + url(r'^docs$', docs), + url(r'^faq$', faq), + url(r'^privacy$', privacy), +] diff --git a/server_ui/view_utils.py b/server_ui/view_utils.py index e22fcd5bc6beae9bef9e10eb6accf402b3998b7b..f499603e108fdbb3db9a352df2e0fb6efb4ab2e9 100644 --- a/server_ui/view_utils.py +++ b/server_ui/view_utils.py @@ -4,27 +4,23 @@ Utilities for single election views Ben Adida (2009-07-18) """ -from django.template import Context, Template, loader -from django.http import HttpResponse, Http404 +from django.conf import settings from django.shortcuts import render_to_response from helios_auth.security import get_user -from django.conf import settings ## ## template abstraction ## -def render_template(request, template_name, vars = {}): - t = loader.get_template(template_name + '.html') - - vars_with_user = vars.copy() +def render_template(request, template_name, values = None): + vars_with_user = values.copy() if values is not None else {} vars_with_user['user'] = get_user(request) vars_with_user['settings'] = settings vars_with_user['CURRENT_URL'] = request.path # csrf protection - if request.session.has_key('csrf_token'): + if 'csrf_token' in request.session: vars_with_user['csrf_token'] = request.session['csrf_token'] return render_to_response('server_ui/templates/%s.html' % template_name, vars_with_user) diff --git a/server_ui/views.py b/server_ui/views.py index a4fe09513b9e07972fdf3ae286e463f130174580..5010dbc34f6b270af8ceb1aad8faab14686ebaaa 100644 --- a/server_ui/views.py +++ b/server_ui/views.py @@ -2,23 +2,19 @@ server_ui specific views """ -from helios.models import * -from helios_auth.security import * -from view_utils import * - -import helios.views -import helios -from helios.crypto import utils as cryptoutils -from helios_auth.security import * -from helios.security import can_create_election - -from django.core.urlresolvers import reverse -from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponseNotAllowed +import copy from django.conf import settings -import copy import helios_auth.views as auth_views +from helios.models import Election +from helios.security import can_create_election +from helios_auth.security import get_user +from . import glue +from .view_utils import render_template + +glue.glue() # actually apply glue helios.view <-> helios.signals + def get_election(): return None @@ -40,7 +36,7 @@ def home(request): else: elections_voted = None - auth_systems = copy.copy(settings.AUTH_ENABLED_AUTH_SYSTEMS) + auth_systems = copy.copy(settings.AUTH_ENABLED_SYSTEMS) try: auth_systems.remove('password') except: pass diff --git a/settings.py b/settings.py index b01eb015c959a24024ca72a5b866846da311dac2..f4dddc3bd18e1466f77c5ccb6b1eddaace743d6b 100644 --- a/settings.py +++ b/settings.py @@ -1,19 +1,20 @@ -import os, json - # a massive hack to see if we're testing, in which case we use different settings import sys + +import json +import os + TESTING = 'test' in sys.argv # go through environment variables and override them def get_from_env(var, default): - if not TESTING and os.environ.has_key(var): + if not TESTING and var in os.environ: return os.environ[var] else: return default DEBUG = (get_from_env('DEBUG', '1') == '1') -TEMPLATE_DEBUG = DEBUG # add admins of the form: # ('Ben Adida', 'ben@adida.net'), @@ -37,21 +38,16 @@ SHOW_USER_INFO = (get_from_env('SHOW_USER_INFO', '1') == '1') DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'helios' - } + 'NAME': 'helios', + 'CONN_MAX_AGE': 600, + }, } -SOUTH_DATABASE_ADAPTERS = {'default':'south.db.postgresql_psycopg2'} - # override if we have an env variable if get_from_env('DATABASE_URL', None): import dj_database_url - DATABASES['default'] = dj_database_url.config() + DATABASES['default'] = dj_database_url.config(conn_max_age=600, ssl_require=True) DATABASES['default']['ENGINE'] = 'django.db.backends.postgresql_psycopg2' - DATABASES['default']['CONN_MAX_AGE'] = 600 - - # require SSL - DATABASES['default']['OPTIONS'] = {'sslmode': 'require'} # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name @@ -94,7 +90,7 @@ SECRET_KEY = get_from_env('SECRET_KEY', 'replaceme') ALLOWED_HOSTS = get_from_env('ALLOWED_HOSTS', 'localhost').split(",") # Secure Stuff -if (get_from_env('SSL', '0') == '1'): +if get_from_env('SSL', '0') == '1': SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True @@ -105,7 +101,7 @@ SESSION_COOKIE_HTTPONLY = True # let's go with one year because that's the way to do it now STS = False -if (get_from_env('HSTS', '0') == '1'): +if get_from_env('HSTS', '0') == '1': STS = True # we're using our own custom middleware now # SECURE_HSTS_SECONDS = 31536000 @@ -115,45 +111,46 @@ if (get_from_env('HSTS', '0') == '1'): SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader' -) - -MIDDLEWARE_CLASSES = ( - # make all things SSL - #'sslify.middleware.SSLifyMiddleware', +SILENCED_SYSTEM_CHECKS = ['urls.W002'] +MIDDLEWARE = [ # secure a bunch of things - 'djangosecure.middleware.SecurityMiddleware', + 'django.middleware.security.SecurityMiddleware', 'helios.security.HSTSMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + # 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware' -) + 'django.contrib.auth.middleware.AuthenticationMiddleware', +] ROOT_URLCONF = 'urls' ROOT_PATH = os.path.dirname(__file__) -TEMPLATE_DIRS = ( - ROOT_PATH, - os.path.join(ROOT_PATH, 'templates') -) + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'APP_DIRS': True, + 'DIRS': [ + ROOT_PATH, + os.path.join(ROOT_PATH, 'templates'), + # os.path.join(ROOT_PATH, 'helios/templates'), # covered by APP_DIRS:True + # os.path.join(ROOT_PATH, 'helios_auth/templates'), # covered by APP_DIRS:True + # os.path.join(ROOT_PATH, 'server_ui/templates'), # covered by APP_DIRS:True + ], + 'OPTIONS': { + 'debug': DEBUG + } + }, +] INSTALLED_APPS = ( -# 'django.contrib.auth', -# 'django.contrib.contenttypes', - 'djangosecure', + 'django.contrib.auth', + 'django.contrib.contenttypes', 'django.contrib.sessions', - #'django.contrib.sites', - ## needed for queues - 'djcelery', - 'kombu.transport.django', - ## in Django 1.7 we now use built-in migrations, no more south - ## 'south', + 'django.contrib.sites', ## HELIOS stuff 'helios_auth', 'helios', @@ -211,9 +208,11 @@ HELIOS_VOTERS_EMAIL = True HELIOS_PRIVATE_DEFAULT = False # authentication systems enabled -#AUTH_ENABLED_AUTH_SYSTEMS = ['password','facebook','twitter', 'google', 'yahoo'] -AUTH_ENABLED_AUTH_SYSTEMS = get_from_env('AUTH_ENABLED_AUTH_SYSTEMS', 'google').split(",") -AUTH_DEFAULT_AUTH_SYSTEM = get_from_env('AUTH_DEFAULT_AUTH_SYSTEM', None) +# AUTH_ENABLED_SYSTEMS = ['password','facebook','twitter', 'google', 'yahoo'] +AUTH_ENABLED_SYSTEMS = get_from_env('AUTH_ENABLED_SYSTEMS', + get_from_env('AUTH_ENABLED_AUTH_SYSTEMS', 'password,google,facebook') + ).split(",") +AUTH_DEFAULT_SYSTEM = get_from_env('AUTH_DEFAULT_SYSTEM', get_from_env('AUTH_DEFAULT_AUTH_SYSTEM', None)) # google GOOGLE_CLIENT_ID = get_from_env('GOOGLE_CLIENT_ID', '') @@ -262,29 +261,23 @@ if get_from_env('EMAIL_USE_AWS', '0') == '1': # set up logging import logging + logging.basicConfig( - level = logging.DEBUG, - format = '%(asctime)s %(levelname)s %(message)s' + level=logging.DEBUG if TESTING else logging.INFO, + format='%(asctime)s %(levelname)s %(message)s' ) - -# set up django-celery -# BROKER_BACKEND = "kombu.transport.DatabaseTransport" -BROKER_URL = "django://" -CELERY_RESULT_DBURI = DATABASES['default'] -import djcelery -djcelery.setup_loader() - - -# for testing -TEST_RUNNER = 'djcelery.contrib.test_runner.CeleryTestSuiteRunner' -# this effectively does CELERY_ALWAYS_EAGER = True +# set up celery +CELERY_BROKER_URL = get_from_env('CELERY_BROKER_URL', 'amqp://localhost') +if TESTING: + CELERY_TASK_ALWAYS_EAGER = True +#database_url = DATABASES['default'] # Rollbar Error Logging ROLLBAR_ACCESS_TOKEN = get_from_env('ROLLBAR_ACCESS_TOKEN', None) if ROLLBAR_ACCESS_TOKEN: - print "setting up rollbar" - MIDDLEWARE_CLASSES += ('rollbar.contrib.django.middleware.RollbarNotifierMiddleware',) + print("setting up rollbar") + MIDDLEWARE += ['rollbar.contrib.django.middleware.RollbarNotifierMiddleware',] ROLLBAR = { 'access_token': ROLLBAR_ACCESS_TOKEN, 'environment': 'development' if DEBUG else 'production', diff --git a/urls.py b/urls.py index d4674991451a72c154ae6d91bfdf5a0dd0bee8a1..8c29e83ea1a7b309247f81caf3dc6a423d6e6cd7 100644 --- a/urls.py +++ b/urls.py @@ -1,21 +1,19 @@ # -*- coding: utf-8 -*- -from django.conf.urls import * -from django.contrib import admin from django.conf import settings +from django.conf.urls import url, include +from django.views.static import serve -urlpatterns = patterns( - '', - (r'^auth/', include('helios_auth.urls')), - (r'^helios/', include('helios.urls')), +urlpatterns = [ + url(r'^auth/', include('helios_auth.urls')), + url(r'^helios/', include('helios.urls')), # SHOULD BE REPLACED BY APACHE STATIC PATH - (r'booth/(?P<path>.*)$', 'django.views.static.serve', {'document_root' : settings.ROOT_PATH + '/heliosbooth'}), - (r'verifier/(?P<path>.*)$', 'django.views.static.serve', {'document_root' : settings.ROOT_PATH + '/heliosverifier'}), + url(r'booth/(?P<path>.*)$', serve, {'document_root' : settings.ROOT_PATH + '/heliosbooth'}), + url(r'verifier/(?P<path>.*)$', serve, {'document_root' : settings.ROOT_PATH + '/heliosverifier'}), - (r'static/auth/(?P<path>.*)$', 'django.views.static.serve', {'document_root' : settings.ROOT_PATH + '/helios_auth/media'}), - (r'static/helios/(?P<path>.*)$', 'django.views.static.serve', {'document_root' : settings.ROOT_PATH + '/helios/media'}), - (r'static/(?P<path>.*)$', 'django.views.static.serve', {'document_root' : settings.ROOT_PATH + '/server_ui/media'}), + url(r'static/auth/(?P<path>.*)$', serve, {'document_root' : settings.ROOT_PATH + '/helios_auth/media'}), + url(r'static/helios/(?P<path>.*)$', serve, {'document_root' : settings.ROOT_PATH + '/helios/media'}), + url(r'static/(?P<path>.*)$', serve, {'document_root' : settings.ROOT_PATH + '/server_ui/media'}), - (r'^', include('server_ui.urls')), - - ) + url(r'^', include('server_ui.urls')), +]