From 4f4488531c7ffae1c0a76e8aa5c121d2784e62df Mon Sep 17 00:00:00 2001 From: Ben Adida <ben@adida.net> Date: Sun, 13 Mar 2011 15:29:43 -0700 Subject: [PATCH] adding private elections, not quite passing the tests yet. --- helios/__init__.py | 6 ----- helios/forms.py | 2 +- helios/security.py | 8 +++++++ helios/tests.py | 60 ++++++++++++++++++++++++++++++++++------------ helios/views.py | 14 ++++++++--- 5 files changed, 65 insertions(+), 25 deletions(-) diff --git a/helios/__init__.py b/helios/__init__.py index bf3c8dd..18371b7 100644 --- a/helios/__init__.py +++ b/helios/__init__.py @@ -12,9 +12,3 @@ VOTERS_UPLOAD = settings.HELIOS_VOTERS_UPLOAD # allow emailing of voters? VOTERS_EMAIL = settings.HELIOS_VOTERS_EMAIL -from django.core.urlresolvers import reverse - -# get the short path for the URL -def get_election_url(election): - from views import election_shortcut - return settings.URL_HOST + reverse(election_shortcut, args=[election.short_name]) diff --git a/helios/forms.py b/helios/forms.py index 13dc829..967525d 100644 --- a/helios/forms.py +++ b/helios/forms.py @@ -14,7 +14,7 @@ class ElectionForm(forms.Form): election_type = forms.ChoiceField(label="type", choices = Election.ELECTION_TYPES) use_voter_aliases = forms.BooleanField(required=False, initial=False, help_text='if selected, voter identities will be replaced with aliases, e.g. "V12", in the ballot tracking center') #use_advanced_audit_features = forms.BooleanField(required=False, initial=True, help_text='disable this only if you want a simple election with reduced security but a simpler user interface') - #private_p = forms.BooleanField(required=False, initial=False, label="Private?", help_text='a private election is only visible to registered/eligible voters') + private_p = forms.BooleanField(required=False, initial=False, label="Private?", help_text='a private election is only visible to registered/eligible voters', widget=forms.HiddenInput) class ElectionTimesForm(forms.Form): diff --git a/helios/security.py b/helios/security.py index a06c2de..73115e0 100644 --- a/helios/security.py +++ b/helios/security.py @@ -75,6 +75,14 @@ def election_view(**checks): # do checks do_election_checks(election, checks) + + # if private election, only logged in voters + if election.private_p and not checks.get('allow_logins',False): + from views import get_voter, get_user + user = get_user(request) + if not user_can_admin_election(user, election) and not get_voter(request, user, election): + # FIXME: should be a nice redirect + raise PermissionDenied() return func(request, election, *args, **kw) diff --git a/helios/tests.py b/helios/tests.py index 4456e4c..760c8de 100644 --- a/helios/tests.py +++ b/helios/tests.py @@ -21,6 +21,7 @@ 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 uuid @@ -402,30 +403,35 @@ class ElectionBlackboxTests(TestCase): new_election = models.Election.objects.get(uuid = self.election.uuid) self.assertEquals(new_election.short_name, self.election.short_name + "-2") - - def test_do_complete_election(self): + + def _setup_complete_election(self, election_params={}): + "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() # create the election - response = self.client.post("/helios/elections/new", { - "short_name" : "test-complete", - "name" : "Test Complete", - "description" : "A complete election test", - "election_type" : "referendum", - "use_voter_aliases": "0", - "use_advanced_audit_features": "1", - "private_p" : "0"}) + full_election_params = { + "short_name" : "test-complete", + "name" : "Test Complete", + "description" : "A complete election test", + "election_type" : "referendum", + "use_voter_aliases": "0", + "use_advanced_audit_features": "1", + "private_p" : "0"} + + # override with the given + full_election_params.update(election_params) + + response = self.client.post("/helios/elections/new", full_election_params) # 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) - # add helios as trustee - # no longer needed because automatic for all elections - #response = self.client.post("/helios/elections/%s/trustees/add-helios" % election_id) - #self.assertRedirects(response, "/helios/elections/%s/trustees/view" % election_id) + # helios is automatically added as a trustee # check that helios is indeed a trustee response = self.client.get("/helios/elections/%s/trustees/view" % election_id) @@ -483,6 +489,10 @@ class ElectionBlackboxTests(TestCase): self.clear_login() self.assertEquals(self.client.session.has_key('user'), False) + # return the voter username and password to vote + return election_id, username, password + + def _cast_ballot(self, election_id, username, password): # vote by preparing a ballot via the server-side encryption response = self.client.post("/helios/elections/%s/encrypt-ballot" % election_id, { 'answers_json': utils.to_json([[1]])}) @@ -512,7 +522,7 @@ class ElectionBlackboxTests(TestCase): # 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[num_messages_after] + email_message = mail.outbox[len(mail.outbox) - 1] url = re.search('http://[^/]+(/[^ \n]*)', email_message.body).group(1) # check that we can get at that URL @@ -524,6 +534,7 @@ class ElectionBlackboxTests(TestCase): response = self.client.get("/helios/elections/%s/cast_done" % election_id) assert not self.client.session.has_key('CURRENT_VOTER') + def _do_tally(self, election_id): # log back in as administrator self.setup_login() @@ -548,3 +559,22 @@ class ElectionBlackboxTests(TestCase): # check that tally matches response = self.client.get("/helios/elections/%s/result" % election_id) self.assertEquals(utils.from_json(response.content), [[0,1]]) + + def test_do_complete_election(self): + election_id, username, password = self._setup_complete_election() + self._cast_ballot(election_id, username, password) + self._do_tally(election_id) + + def test_do_complete_election_private(self): + # private election + election_id, username, password = self._setup_complete_election({'private_p' : "1"}) + + # log in + response = self.client.post("/helios/elections/%s/password_voter_login" % election_id, { + 'voter_id' : username, + 'password' : password + }) + self.assertRedirects(response, "/helios/elections/%s/view" % election_id) + + self._cast_ballot(election_id, username, password) + self._do_tally(election_id) diff --git a/helios/views.py b/helios/views.py index f8fd9af..e2b3776 100644 --- a/helios/views.py +++ b/helios/views.py @@ -499,12 +499,20 @@ def one_election_cast(request, election): return HttpResponseRedirect("%s%s" % (settings.SECURE_URL_HOST, reverse(one_election_cast_confirm, args=[election.uuid]))) -@election_view(frozen=True) +@election_view(frozen=True, allow_logins=True) def password_voter_login(request, election): """ This is used to log in as a voter for a particular election """ password_login_form = forms.VoterPasswordForm(request.POST) + + # redirect base 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: + redirect_base = reverse(one_election_view, args=[election.uuid]) + else: + redirect_base = reverse(one_election_cast_confirm, args=[election.uuid]) + if password_login_form.is_valid(): try: voter = election.voter_set.get(voter_login_id = password_login_form.cleaned_data['voter_id'], @@ -512,9 +520,9 @@ def password_voter_login(request, election): request.session['CURRENT_VOTER'] = voter except Voter.DoesNotExist: - return HttpResponseRedirect(reverse(one_election_cast_confirm, args = [election.uuid]) + "?bad_voter_login=1") + return HttpResponseRedirect(redirect_base + "?bad_voter_login=1") - return HttpResponseRedirect(reverse(one_election_cast_confirm, args = [election.uuid])) + return HttpResponseRedirect(redirect_base) @election_view(frozen=True) def one_election_cast_confirm(request, election): -- GitLab