From 78dc2c7ee1d8026adfed1e89cf9fdffe97e1fe1b Mon Sep 17 00:00:00 2001
From: Ben Adida <ben@adida.net>
Date: Thu, 15 May 2014 20:56:30 -0700
Subject: [PATCH] added step for release result

---
 helios/election_urls.py                   |  1 +
 helios/models.py                          |  9 ++++++
 helios/templates/combine_decryptions.html |  8 +++---
 helios/templates/election_view.html       | 34 ++++++++++++++---------
 helios/templates/release_result.html      | 16 +++++++++++
 helios/tests.py                           | 14 ++++++++--
 helios/views.py                           | 31 +++++++++++++++++++--
 7 files changed, 91 insertions(+), 22 deletions(-)
 create mode 100644 helios/templates/release_result.html

diff --git a/helios/election_urls.py b/helios/election_urls.py
index 459e131..9b0f874 100644
--- a/helios/election_urls.py
+++ b/helios/election_urls.py
@@ -63,6 +63,7 @@ urlpatterns = patterns('',
     # computing tally
     (r'^/compute_tally$', one_election_compute_tally),
     (r'^/combine_decryptions$', combine_decryptions),
+    (r'^/release_result$', release_result),
     
     # casting a ballot before we know who the voter is
     (r'^/cast$', one_election_cast),
diff --git a/helios/models.py b/helios/models.py
index e338bbd..d097b49 100644
--- a/helios/models.py
+++ b/helios/models.py
@@ -391,6 +391,15 @@ class Election(HeliosModel):
     
     return True
     
+  def release_result(self):
+    """
+    release the result that should already be computed
+    """
+    if not self.result:
+      return
+
+    self.result_released_at = datetime.datetime.utcnow()
+  
   def combine_decryptions(self):
     """
     combine all of the decryption results
diff --git a/helios/templates/combine_decryptions.html b/helios/templates/combine_decryptions.html
index e83e1f8..f5c9bea 100644
--- a/helios/templates/combine_decryptions.html
+++ b/helios/templates/combine_decryptions.html
@@ -1,16 +1,16 @@
 {% extends TEMPLATE_BASE %}
 
-{% block title %}Release Tally &mdash; {{election.name}}{% endblock %}
+{% block title %}Compute Tally &mdash; {{election.name}}{% endblock %}
 {% block content %}
-  <h2 class="title">{{election.name}} &mdash; Release 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}} &mdash; Compute Tally <span style="font-size:0.7em;">[<a href="{% url helios.views.one_election_view election.uuid %}">cancel</a>]</span></h2>
 
   <p>
-    You are about to release the tally for this election
+    You are about to compute the tally for this election. You only will then see the results.
   </p>
 
   <form method="POST" action="">
     <input type="hidden" name="csrf_token" value="{{csrf_token}}" />
-    <input type="submit" value="release the tally!" />
+    <input type="submit" value="compute the tally!" />
   </form>
 
 {% endblock %}
diff --git a/helios/templates/election_view.html b/helios/templates/election_view.html
index 5cbe997..3bd61e2 100644
--- a/helios/templates/election_view.html
+++ b/helios/templates/election_view.html
@@ -77,7 +77,7 @@ this {{election.election_type}} is <u>not</u> featured on the front page.
 {% endif %}
 <p>
 
-{% if election.result %}
+{% if election.result_released_at %}
 
 <!-- election complete, no next step -->
 
@@ -113,20 +113,25 @@ 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 />
+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 %}">
 {% if election.num_trustees == 1 %}
-release results
+compute results
 {% else %}
-combine trustee decryptions and release results
+combine trustee decryptions and compute results
 {% endif %}
 </a><br />
 {% if election.num_trustees == 1 %}
-The result is released and all voters are notified.
+The result will be computed and shown to you, the administrator, only.
 {% else %}
-The decryption shares from the trustees are combined and the tally is decrypted.<br />
-Once you do this, the tally will be immediately available for all to see, and
-all voters will be notified that the tally is ready.
+The decryption shares from the trustees will be combined and the tally computed.<br />
+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>
@@ -134,6 +139,8 @@ all voters will be notified that the tally is ready.
 
 {% endif %}
 
+{% endif %}
+
 {% endif %}
 </span>
 
@@ -143,14 +150,15 @@ all voters will be notified that the tally is ready.
 
 {% endif %}
 
-<br /><br />
+<br />
 
-{% if election.result %}
+{% if show_result %}
+{% if election.result_released_at %}
 <span class="highlight-box round">
     This election is complete.
-</span><br />
+</span><br /><br /><br />
+{% endif %}
 
-<br />
 <h3 class="highlight-box">Tally</h3>
 {% for question in election.pretty_result %}
 <b><span style="font-size:0.8em;">Question #{{forloop.counter}}</span><br />{{question.question}}</b><br />
@@ -165,8 +173,8 @@ all voters will be notified that the tally is ready.
 
 {% if election.voting_has_stopped %}
 <span class="highlight-box round">
-    Election closed. Tally will be computed soon.
-</span><br />
+    Election closed. Results will be released soon.
+</span><br /><br />
 {% else %}
 
 {% if election.voting_has_started %}
diff --git a/helios/templates/release_result.html b/helios/templates/release_result.html
new file mode 100644
index 0000000..5dc4276
--- /dev/null
+++ b/helios/templates/release_result.html
@@ -0,0 +1,16 @@
+{% extends TEMPLATE_BASE %}
+
+{% block title %}Release Result &mdash; {{election.name}}{% endblock %}
+{% block content %}
+  <h2 class="title">{{election.name}} &mdash; Release Result <span style="font-size:0.7em;">[<a href="{% url helios.views.one_election_view election.uuid %}">cancel</a>]</span></h2>
+
+  <p>
+    You are about to release the result for this election.
+  </p>
+
+  <form method="POST" action="">
+    <input type="hidden" name="csrf_token" value="{{csrf_token}}" />
+    <input type="submit" value="release result!" />
+  </form>
+
+{% endblock %}
diff --git a/helios/tests.py b/helios/tests.py
index eeb9cac..b39c3a8 100644
--- a/helios/tests.py
+++ b/helios/tests.py
@@ -721,9 +721,17 @@ class ElectionBlackboxTests(WebTest):
                 "csrf_token" : self.client.session['csrf_token'],
                 })
 
-        # after tallying, we now are supposed to see the email screen
-        # with the right template value of 'result'
-        self.assertRedirects(response, "/helios/elections/%s/voters/email?template=result" % election_id)
+        # after tallying, we now go back to election_view
+        self.assertRedirects(response, "/helios/elections/%s/view" % election_id)
+
+        # 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)
+
+        # release
+        response = self.client.post("/helios/elections/%s/release_result" % election_id, {
+                "csrf_token" : self.client.session['csrf_token'],
+                })
 
         # check that tally matches
         response = self.client.get("/helios/elections/%s/result" % election_id)
diff --git a/helios/views.py b/helios/views.py
index 65d8895..f6ed5cb 100644
--- a/helios/views.py
+++ b/helios/views.py
@@ -8,6 +8,7 @@ Ben Adida (ben@adida.net)
 from django.core.urlresolvers import reverse
 from django.core.mail import send_mail
 from django.core.paginator import Paginator
+from django.core.exceptions import PermissionDenied
 from django.http import *
 from django.db import transaction
 
@@ -333,11 +334,15 @@ def one_election_view(request, election):
 
   trustees = Trustee.get_by_election(election)
 
+  # should we show the result?
+  show_result = election.result_released_at or (election.result and admin_p)
+
   return render_template(request, 'election_view',
                          {'election' : election, 'trustees': trustees, 'admin_p': admin_p, 'user': user,
                           'voter': voter, 'votes': votes, 'notregistered': notregistered, 'eligible_p': eligible_p,
                           'can_feature_p': can_feature_p, 'election_url' : election_url, 
                           'vote_url': vote_url, 'election_badge_url' : election_badge_url,
+                          'show_result': show_result,
                           'test_cookie_url': test_cookie_url, 'socialbuttons_url' : socialbuttons_url})
 
 def test_cookie(request):
@@ -774,11 +779,15 @@ def one_election_cast_done(request, election):
 @election_view()
 @json
 def one_election_result(request, election):
+  if not election.result_released_at:
+    raise PermissionDenied
   return election.result
 
 @election_view()
 @json
 def one_election_result_proof(request, election):
+  if not election.result_released_at:
+    raise PermissionDenied
   return election.result_proof
   
 @election_view(frozen=True)
@@ -1040,7 +1049,25 @@ def trustee_upload_decryption(request, election, trustee_uuid):
     return SUCCESS
   else:
     return FAILURE
-  
+
+@election_admin(frozen=True)
+def release_result(request, election):
+  """
+  result is computed and now it's time to release the result
+  """
+  election_url = get_election_url(election)
+
+  if request.method == "POST":
+    check_csrf(request)
+
+    election.release_result()
+    election.save()
+
+    return HttpResponseRedirect("%s" % (settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])))
+
+  # if just viewing the form or the form is not valid
+  return render_template(request, 'release_result', {'election': election})
+
 @election_admin(frozen=True)
 def combine_decryptions(request, election):
   """
@@ -1055,7 +1082,7 @@ def combine_decryptions(request, election):
     election.combine_decryptions()
     election.save()
 
-    return HttpResponseRedirect("%s?%s" % (settings.SECURE_URL_HOST + reverse(voters_email, args=[election.uuid]), urllib.urlencode({'template': 'result'})))
+    return HttpResponseRedirect("%s" % (settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])))
 
   # if just viewing the form or the form is not valid
   return render_template(request, 'combine_decryptions', {'election': election})
-- 
GitLab