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