diff --git a/helios/election_urls.py b/helios/election_urls.py index 4b7687623f61d1c72147217ec32ed668f2d6b4dd..1b9b6aae872ff15effe6a9151c47db669c441850 100644 --- a/helios/election_urls.py +++ b/helios/election_urls.py @@ -60,6 +60,7 @@ urlpatterns = patterns('', # 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), # post audited ballot diff --git a/helios/forms.py b/helios/forms.py index c46c9af4fe252d6dc54a8926cb04766b068f347f..83f05d8aa016ac0db2d633e7a077620dd1e9cad9 100644 --- a/helios/forms.py +++ b/helios/forms.py @@ -34,3 +34,8 @@ class TallyNotificationEmailForm(forms.Form): subject = forms.CharField(max_length=80) body = forms.CharField(max_length=2000, widget=forms.Textarea) send_to = forms.ChoiceField(label="Send To", choices= [('all', 'all voters'), ('voted', 'only voters who cast a ballot')]) + +class VoterPasswordForm(forms.Form): + voter_id = forms.CharField(max_length=50) + password = forms.CharField(widget=forms.PasswordInput(), max_length=100) + diff --git a/helios/models.py b/helios/models.py index d34bdf569d5c1f0bbcc9e15ac97ee5550449c845..673dae28a81ac3ac13d4f7a46aec1dc9aaaf667a 100644 --- a/helios/models.py +++ b/helios/models.py @@ -669,6 +669,14 @@ class Voter(models.Model, electionalgs.Voter): else: return 'password' + @property + def display_html_big(self): + if self.user: + return self.user.display_html_big + else: + return """<img border="0" height="25" src="/static/auth/login-icons/password.png" alt="password" /> %s""" % self.name + + def store_vote(self, cast_vote): # only store the vote if it's cast later than the current one if self.cast_at and cast_vote.cast_at < self.cast_at: diff --git a/helios/templates/_castconfirm_docast.html b/helios/templates/_castconfirm_docast.html new file mode 100644 index 0000000000000000000000000000000000000000..18acbd0cc5d863e7c4a5f8754dca0b28f80e3d1c --- /dev/null +++ b/helios/templates/_castconfirm_docast.html @@ -0,0 +1,30 @@ +<div id="cast_form"> +<form method="post" action="" onsubmit="show_waiting()"> + <input type="hidden" name="csrf_token" value="{{csrf_token}}" /> + +{% if status_update_label %} +<div class="round" style="background: #eee; padding: 10px; border: 1px dashed #888;"> +<input type="checkbox" name="status_update" value="1" checked /> {{status_update_label}}<br /> +<blockquote style="font-size: 1.3em;"> +"{{status_update_message}}" +</blockquote> +<input type="hidden" name="status_update_message" value="{{status_update_message}}" /> +</div> +<br /> +{% endif %} + + <button type="submit" style="font-size: 1.5em; height: 50px;">I am <u>{{voter.display_html_big|safe}}</u>, cast this ballot</button> + <span style="font-size:0.8em;"><br />You can cast as many ballots as you want.<br />Only the last one counts.</span> +</form> + +<p> + <button style="font-size: 1.5em;" onclick="document.location='./view';">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> + +</div> +<div id="waiting_div"> + Verifying and Casting your ballot<br /> + <img src="/static/helios/loading.gif" /> +</div> diff --git a/helios/templates/_castconfirm_password.html b/helios/templates/_castconfirm_password.html new file mode 100644 index 0000000000000000000000000000000000000000..9e17c26ccf2938b90871a3114a2cddd9c856da44 --- /dev/null +++ b/helios/templates/_castconfirm_password.html @@ -0,0 +1,8 @@ +Please provide the username and password you received by email.<br /><br /> +<form method="post" action="{% url helios.views.password_voter_login election.uuid %}"> +<input type="hidden" name="csrf_token" value="{{csrf_token}}" /> +<table> + {{password_login_form.as_table}} +</table> +<input type="submit" value="check credentials" /> +</form> diff --git a/helios/templates/election_cast_confirm.html b/helios/templates/election_cast_confirm.html index fcadde7338608e7486a0358c6039936aeb6f7d44..a16a786b8f2267f7e129f83a84549d5f30e8e303 100644 --- a/helios/templates/election_cast_confirm.html +++ b/helios/templates/election_cast_confirm.html @@ -11,6 +11,28 @@ function show_waiting() { $('#cast_form').hide(); $('#waiting_div').show(); } + +// FIXME: set this to false once it's clear how to set it back to true +// so that it's not easy to forget to cast a ballot +var ready_to_unload = true; + +window.onbeforeunload = function(evt) { + if (ready_to_unload) + return; + + if (typeof evt == 'undefined') { + evt = window.event; + } + + var message = "You have not yet cast your ballot! Make sure to complete the voting process if you want your vote to count."; + + if (evt) { + evt.returnValue = message; + } + + return message; +}; + </script> <h1>{{election.name}} — Submit your Vote</h1> @@ -20,65 +42,33 @@ Your smart ballot tracker is:<br /><br /> <tt style="font-size:1.8em; font-weight: bold; padding-left: 20px;"> {{vote_fingerprint}}</tt> </p> -{% if user %} - +{% if password_only %} {% if voter %} -{% if past_votes %} -<!-- -<h3>Past Votes Cast for this Election</h3> -<ul> -{% for vote in past_votes %} -<li> <tt>{{vote.vote_hash}}</tt></li> -{% endfor %} -</ul> ---> +{% include "_castconfirm_docast.html" %} {% else %} -<!--<em>no vote cast yet</em>--> +{% include "_castconfirm_password.html" %} {% endif %} +{% else %} -{% if election.voting_has_started %} -{% if not election.voting_has_stopped %} -<br /> -<div id="cast_form"> -<form method="post" action="" onsubmit="show_waiting()"> - <input type="hidden" name="csrf_token" value="{{csrf_token}}" /> - -{% if status_update_label %} -<div class="round" style="background: #eee; padding: 10px; border: 1px dashed #888;"> -<input type="checkbox" name="status_update" value="1" checked /> {{status_update_label}}<br /> -<blockquote style="font-size: 1.3em;"> -"{{status_update_message}}" -</blockquote> -<input type="hidden" name="status_update_message" value="{{status_update_message}}" /> -</div> -<br /> -{% endif %} - <button type="submit" style="font-size: 1.5em; height: 50px;">I am <u>{{user.display_html_big|safe}}</u>, cast this ballot</button> - <span style="font-size:0.8em;"><br />You can cast as many ballots as you want.<br />Only the last one counts.</span> -</form> +{% if user %} -<p> - <button style="font-size: 1.5em;" onclick="document.location='./view';">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> +{% if voter %} -</div> -<div id="waiting_div"> - Verifying and Casting your ballot<br /> - <img src="/static/helios/loading.gif" /> -</div> -{% else %} +{% if election.voting_has_started %} + {% if not election.voting_has_stopped %} + {% include "_castconfirm_docast.html" %} + {% else %} <p style="font-size:1.4em;"> voting has stopped, sorry. </p> -{% endif %} + {% endif %} {% else %} <p style="font-size:1.4em;"> voting has not yet begun, sorry. </p> {% endif %} + {% else %} <p> {% if election.openreg %} @@ -96,31 +86,24 @@ Your smart ballot tracker is:<br /><br /> <p> Now, we need you to log in, so we can verify your eligibility.<br /><br /> {% if election.openreg %} + {% if election.eligibility %} -{% if password_only %} -This election is open only to designated participants who received credentials via email. -{% else %} -{% endif %} {% else %} -This election is open to <em>anyone</em>, so log in with your preferred account. + This election is open to <em>anyone</em>, so log in with your preferred account. {% endif %} -{% else %} -{% if password_only %} -Please log in with the username and password you received by email.<br /> + {% else %} This election is only open to <em>registered voters</em>, so log in with the same account you registered with. {% endif %} -{% endif %} </p> {{login_box|safe}} -<!-- -<form method="get" action="{% url auth.views.index %}"> - <input type="hidden" name="return_url" value="{% url helios.views.one_election_cast_confirm election.uuid %}"> - <input type="submit" class="pretty" style="font-size: 1.6em;" value="Log In" /> -</form>--> <br /> Don't worry, we'll remember your ballot while you log in. {% endif %} + +{# this closes the IF ELSE of this being password_only #} +{% endif %} + {% endblock %} diff --git a/helios/views.py b/helios/views.py index b0b490f835fe4ad684fb359461c24cdd2adc66ee..10698612f994a26adff98fa1f4e9ef2a2ddd5e2e 100644 --- a/helios/views.py +++ b/helios/views.py @@ -449,7 +449,24 @@ def one_election_cast(request, election): 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]))) + +@election_view(frozen=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) + if password_login_form.is_valid(): + try: + voter = election.voter_set.get(voter_login_id = password_login_form.cleaned_data['voter_id'], + voter_password = password_login_form.cleaned_data['password']) + + request.session['CURRENT_VOTER'] = voter + except Voter.DoesNotExist: + pass + return HttpResponseRedirect(reverse(one_election_cast_confirm, args = [election.uuid])) + @election_view(frozen=True) def one_election_cast_confirm(request, election): user = get_user(request) @@ -458,10 +475,15 @@ def one_election_cast_confirm(request, election): if not request.session.has_key('encrypted_vote'): return HttpResponseRedirect(settings.URL_HOST) - if user: - voter = Voter.get_by_election_and_user(election, user) - else: - voter = None + voter = None + if request.session.has_key('CURRENT_VOTER'): + voter = request.session['CURRENT_VOTER'] + if voter.election != election: + voter = None + + if not voter: + if user: + voter = Voter.get_by_election_and_user(election, user) # auto-register this person if the election is openreg if user and not voter and election.openreg: @@ -518,8 +540,10 @@ def one_election_cast_confirm(request, election): if auth_systems == ['password']: password_only = True + password_login_form = forms.VoterPasswordForm() else: password_only = False + password_login_form = None return_url = reverse(one_election_cast_confirm, args=[election.uuid]) login_box = auth_views.login_box_raw(request, return_url=return_url, auth_systems = auth_systems) @@ -528,7 +552,7 @@ def one_election_cast_confirm(request, election): 'login_box': login_box, 'election' : election, 'vote_fingerprint': vote_fingerprint, 'past_votes': past_votes, 'issues': issues, 'voter' : voter, 'status_update_label': status_update_label, 'status_update_message': status_update_message, - 'password_only': password_only}) + 'password_only': password_only, 'password_login_form': password_login_form}) if request.method == "POST": check_csrf(request) diff --git a/server_ui/glue.py b/server_ui/glue.py index 376087a34e266ebaaf3386e875d6b18626ae1010..7d87d63fb5bffd588edec250dc67a2f04480e2fb 100644 --- a/server_ui/glue.py +++ b/server_ui/glue.py @@ -10,6 +10,7 @@ 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 = "%s - vote cast" % election.name