diff --git a/auth/auth_systems/cas.py b/auth/auth_systems/cas.py index c246839d71c2f9c78dccbbf83b6b910e87345b35..d5b824e86acce5bd10436c71ba8d7b32033598f5 100644 --- a/auth/auth_systems/cas.py +++ b/auth/auth_systems/cas.py @@ -9,12 +9,13 @@ from django.http import * from django.core.mail import send_mail from django.conf import settings -import sys, os, cgi, urllib, urllib2, re +import sys, os, cgi, urllib, urllib2, re, uuid, datetime from xml.etree import ElementTree CAS_EMAIL_DOMAIN = "princeton.edu" CAS_URL= 'https://fed.princeton.edu/cas/' CAS_LOGOUT_URL = 'https://fed.princeton.edu/cas/logout?service=%s' +CAS_SAML_VALIDATE_URL = 'https://fed.princeton.edu/cas/samlValidate?TARGET=%s' # eligibility checking if hasattr(settings, 'CAS_USERNAME'): @@ -34,7 +35,7 @@ def _get_service_url(): from django.conf import settings from django.core.urlresolvers import reverse - return settings.URL_HOST + reverse(after) + return settings.SECURE_URL_HOST + reverse(after) def get_auth_url(request, redirect_url): request.session['cas_redirect_url'] = redirect_url @@ -52,6 +53,47 @@ def get_user_category(user_id): parsed_result = ElementTree.fromstring(result) return parsed_result.text +def get_saml_info(ticket): + """ + Using SAML, get all of the information needed + """ + + import logging + + saml_request = """<?xml version='1.0' encoding='UTF-8'?> + <soap-env:Envelope + xmlns:soap-env='http://schemas.xmlsoap.org/soap/envelope/'> + <soap-env:Header /> + <soap-env:Body> + <samlp:Request xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" + MajorVersion="1" MinorVersion="1" + RequestID="%s" + IssueInstant="%sZ"> + <samlp:AssertionArtifact>%s</samlp:AssertionArtifact> + </samlp:Request> + </soap-env:Body> + </soap-env:Envelope> +""" % (uuid.uuid1(), datetime.datetime.utcnow().isoformat(), ticket) + + url = CAS_SAML_VALIDATE_URL % urllib.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() + + logging.info("RESP:\n%s\n\n" % raw_response) + + response = ElementTree.fromstring(raw_response) + + # ugly path down the tree of attributes + attributes = response.findall('{http://schemas.xmlsoap.org/soap/envelope/}Body/{urn:oasis:names:tc:SAML:1.0:protocol}Response/{urn:oasis:names:tc:SAML:1.0:assertion}Assertion/{urn:oasis:names:tc:SAML:1.0:assertion}AttributeStatement/{urn:oasis:names:tc:SAML:1.0:assertion}Attribute') + + values = {} + for attribute in attributes: + values[str(attribute.attrib['AttributeName'])] = attribute.findtext('{urn:oasis:names:tc:SAML:1.0:assertion}AttributeValue') + + # parse response for netid, display name, and employee type (category) + return {'user_id': values.get('mail',None), 'name': values.get('displayName', None), 'category': values.get('employeeType',None)} def get_user_info(user_id): url = 'http://dsml.princeton.edu/' @@ -101,34 +143,49 @@ def get_user_info(user_id): else: return None -def get_user_info_after_auth(request): - ticket = request.GET.get('ticket', None) - - # if no ticket, this is a logout - if not ticket: - return None - +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 + r = urllib.urlopen(val_url).readlines() # returns 2 lines # success if len(r) == 2 and re.match("yes", r[0]) != None: netid = r[1].strip() category = get_user_category(netid) - user_info = get_user_info(netid) + + #try: + # user_info = get_user_info(netid) + #except: + # user_info = None + + # for now, no need to wait for this request to finish + user_info = None if user_info: info = {'name': user_info['name'], 'category': category} else: info = {'name': netid, 'category': category} - return {'type': 'cas', 'user_id': netid, 'name': info['name'], 'info': info, 'token': None} + return {'user_id': netid, 'name': info['name'], 'info': info, 'token': None} else: return None + +def get_user_info_after_auth(request): + ticket = request.GET.get('ticket', None) + + # if no ticket, this is a logout + if not ticket: + return None + + #user_info = get_saml_info(ticket) + user_info = get_user_info_special(ticket) + + user_info['type'] = 'cas' + + return user_info def do_logout(user): """ diff --git a/helios/forms.py b/helios/forms.py index e7973d67ce12f1abb1f1eeb21018b63018d9d3f4..d2c887087bcafcf5a3586fb68a4b81c06b2f40cb 100644 --- a/helios/forms.py +++ b/helios/forms.py @@ -33,8 +33,8 @@ class EmailVotersForm(forms.Form): 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')]) + body = forms.CharField(max_length=2000, widget=forms.Textarea, required=False) + send_to = forms.ChoiceField(label="Send To", choices= [('all', 'all voters'), ('voted', 'only voters who cast a ballot'), ('none', 'no one -- are you sure about this?')]) class VoterPasswordForm(forms.Form): voter_id = forms.CharField(max_length=50) diff --git a/helios/templates/list_trustees.html b/helios/templates/list_trustees.html index 15b2e60835481650392fdfdfecacb082e7848568..94d41580cd39ad92540aaa99af76d0d32e9a733d 100644 --- a/helios/templates/list_trustees.html +++ b/helios/templates/list_trustees.html @@ -27,7 +27,9 @@ {% for t in trustees %} <h3> Trustee #{{forloop.counter}}: {{t.name}} {% if admin_p %} -{% if not t.secret_key %} +{% 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 %} +{% 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>] diff --git a/helios/views.py b/helios/views.py index b9a8d8bd29b8480c00549224f429c201178c3b81..f33e17d0a536b83c312317213edd621faf4fc7c7 100644 --- a/helios/views.py +++ b/helios/views.py @@ -353,7 +353,7 @@ def new_trustee_helios(request, election): election.generate_trustee(ELGAMAL_PARAMS) return HttpResponseRedirect(reverse(list_trustees_view, args=[election.uuid])) -@election_admin() +@election_admin(frozen=False) def delete_trustee(request, election): trustee = Trustee.get_by_election_and_uuid(election, request.GET['uuid']) trustee.delete() @@ -538,7 +538,7 @@ def one_election_cast_confirm(request, election): # status update this vote if voter and user and user.can_update_status(): status_update_label = voter.user.update_status_template() % "your smart ballot tracker" - status_update_message = "I voted in %s, my smart tracker is %s.. -- %s" % (election.name, cast_vote.vote_hash[:10], get_election_url(election)) + status_update_message = "I voted in %s - my smart tracker is %s.. #heliosvoting" % (get_election_url(election),cast_vote.vote_hash[:10]) else: status_update_label = None status_update_message = None @@ -929,21 +929,24 @@ def combine_decryptions(request, election): 'election' : election } - # exclude those who have not voted - if email_form.cleaned_data['send_to'] == 'voted': - voter_constraints_exclude = {'vote_hash' : None} - else: - voter_constraints_exclude = {} + # if the user opted for notifying no one, then we skip this step + if email_form.cleaned_data['send_to'] != 'none': + # exclude those who have not voted + if email_form.cleaned_data['send_to'] == 'voted': + voter_constraints_exclude = {'vote_hash' : None} + else: + voter_constraints_exclude = {} - # full-length email - tasks.voters_email.delay(election_id = election.id, - subject_template = 'email/result_subject.txt', - body_template = 'email/result_body.txt', - extra_vars = extra_vars, - voter_constraints_exclude = voter_constraints_exclude) + # full-length email + tasks.voters_email.delay(election_id = election.id, + subject_template = 'email/result_subject.txt', + body_template = 'email/result_body.txt', + extra_vars = extra_vars, + voter_constraints_exclude = voter_constraints_exclude) # rapid short-message notification # this inherently only applies to those who have voted (for the most part) + # and this is not configurable, this is ALWAYS sent tasks.voters_notify.delay(election_id = election.id, notification_template = 'notification/result.txt', extra_vars = extra_vars)