2 from urllib import urlencode
 
   5 from django.conf import settings
 
   6 from django.contrib.auth import login as auth_login, logout as auth_logout
 
   7 from django.core.urlresolvers import get_callable
 
   8 from django.http import HttpResponse, HttpResponseRedirect
 
   9 from django.shortcuts import render_to_response
 
  10 from django.template import RequestContext
 
  11 from cas_provider.attribute_formatters import NSMAP, CAS
 
  12 from cas_provider.models import ProxyGrantingTicket, ProxyTicket
 
  13 from forms import LoginForm
 
  14 from models import ServiceTicket, LoginTicket
 
  17 __all__ = ['login', 'validate', 'logout', 'service_validate']
 
  19 INVALID_TICKET = 'INVALID_TICKET'
 
  20 INVALID_SERVICE = 'INVALID_SERVICE'
 
  21 INVALID_REQUEST = 'INVALID_REQUEST'
 
  22 INTERNAL_ERROR = 'INTERNAL_ERROR'
 
  25     (INVALID_TICKET, u'The provided ticket is invalid.'),
 
  26     (INVALID_SERVICE, u'Service is invalid'),
 
  27     (INVALID_REQUEST, u'Not all required parameters were sent.'),
 
  28     (INTERNAL_ERROR, u'An internal error occurred during ticket validation'),
 
  32 def login(request, template_name='cas/login.html',\
 
  33           success_redirect=settings.LOGIN_REDIRECT_URL,
 
  34           warn_template_name='cas/warn.html',
 
  35           form_class=LoginForm):
 
  36     service = request.GET.get('service', None)
 
  37     if request.user.is_authenticated():
 
  38         if service is not None:
 
  39             if request.GET.get('warn', False):
 
  40                 return render_to_response(warn_template_name, {
 
  43                 }, context_instance=RequestContext(request))
 
  44             ticket = ServiceTicket.objects.create(service=service, user=request.user)
 
  45             return HttpResponseRedirect(ticket.get_redirect_url())
 
  47             return HttpResponseRedirect(success_redirect)
 
  48     if request.method == 'POST':
 
  49         form = form_class(data=request.POST, request=request)
 
  51             user = form.get_user()
 
  52             auth_login(request, user)
 
  53             service = form.cleaned_data.get('service')
 
  54             if service is not None:
 
  55                 ticket = ServiceTicket.objects.create(service=service, user=user)
 
  56                 success_redirect = ticket.get_redirect_url()
 
  57             return HttpResponseRedirect(success_redirect)
 
  59         form = form_class(request=request, initial={
 
  61             'lt': LoginTicket.objects.create()
 
  63     if hasattr(request, 'session') and hasattr(request.session, 'set_test_cookie'):
 
  64         request.session.set_test_cookie()
 
  65     return render_to_response(template_name, {
 
  67         'errors': form.get_errors() if hasattr(form, 'get_errors') else None,
 
  68         }, context_instance=RequestContext(request))
 
  71 def validate(request):
 
  72     """Validate ticket via CAS v.1 protocol"""
 
  73     service = request.GET.get('service', None)
 
  74     ticket_string = request.GET.get('ticket', None)
 
  75     if service is not None and ticket_string is not None:
 
  76         #renew = request.GET.get('renew', True)
 
  78         # TODO: check user SSO session
 
  80             ticket = ServiceTicket.objects.get(ticket=ticket_string)
 
  81             assert ticket.service == service
 
  82             username = ticket.user.username
 
  83             return HttpResponse("yes\n%s\n" % username)
 
  86     return HttpResponse("no\n\n")
 
  89 def logout(request, template_name='cas/logout.html',
 
  90            auto_redirect=settings.CAS_AUTO_REDIRECT_AFTER_LOGOUT):
 
  91     url = request.GET.get('url', None)
 
  92     if request.user.is_authenticated():
 
  93         for ticket in ServiceTicket.objects.filter(user=request.user):
 
  96         if url and auto_redirect:
 
  97             return HttpResponseRedirect(url)
 
  98     return render_to_response(template_name, {'url': url},
 
  99         context_instance=RequestContext(request))
 
 103     targetService = request.GET['targetService']
 
 104     pgtiou = request.GET['pgt']
 
 107         proxyGrantingTicket = ProxyGrantingTicket.objects.get(pgtiou=pgtiou)
 
 108     except ProxyGrantingTicket.DoesNotExist:
 
 109         return _cas2_error_response(INVALID_TICKET)
 
 111     if not proxyGrantingTicket.targetService == targetService:
 
 112         return _cas2_error_response(INVALID_SERVICE,
 
 113             "The PGT was issued for %(original)s but the PT was requested for %(but)s" % dict(
 
 114                 original=proxyGrantingTicket.targetService, but=targetService))
 
 116     pt = ProxyTicket.objects.create(proxyGrantingTicket=proxyGrantingTicket,
 
 117         user=proxyGrantingTicket.serviceTicket.user,
 
 118         service=targetService)
 
 119     return _cas2_proxy_success(pt.ticket)
 
 122 def ticket_validate(service, ticket_string, pgtUrl):
 
 123     if service is None or ticket_string is None:
 
 124         return _cas2_error_response(INVALID_REQUEST)
 
 127         if ticket_string.startswith('ST'):
 
 128             ticket = ServiceTicket.objects.get(ticket=ticket_string)
 
 129         elif ticket_string.startswith('PT'):
 
 130             ticket = ProxyTicket.objects.get(ticket=ticket_string)
 
 132             return _cas2_error_response(INVALID_TICKET,
 
 133                 '%(ticket)s is neither Service (ST-...) nor Proxy Ticket (PT-...)' % {
 
 134                     'ticket': ticket_string})
 
 135     except ServiceTicket.DoesNotExist:
 
 136         return _cas2_error_response(INVALID_TICKET)
 
 138     ticketUrl =  urlparse.urlparse(ticket.service)
 
 139     serviceUrl =  urlparse.urlparse(service)
 
 141     if not(ticketUrl.hostname == serviceUrl.hostname and ticketUrl.path == serviceUrl.path and ticketUrl.port == serviceUrl.port):
 
 142         return _cas2_error_response(INVALID_SERVICE)
 
 146     if pgtUrl is not None:
 
 147         pgt = generate_proxy_granting_ticket(pgtUrl, ticket)
 
 149             pgtIouId = pgt.pgtiou
 
 151     if hasattr(ticket, 'proxyticket'):
 
 152         pgt = ticket.proxyticket.proxyGrantingTicket
 
 153         # I am issued by this proxy granting ticket
 
 154         if hasattr(pgt.serviceTicket, 'proxyticket'):
 
 156                 if hasattr(pgt.serviceTicket, 'proxyticket'):
 
 157                     proxies += (pgt.serviceTicket.service,)
 
 158                     pgt = pgt.serviceTicket.proxyticket.proxyGrantingTicket
 
 163     return _cas2_sucess_response(user, pgtIouId, proxies)
 
 166 def service_validate(request):
 
 167     """Validate ticket via CAS v.2 protocol"""
 
 168     service = request.GET.get('service', None)
 
 169     ticket_string = request.GET.get('ticket', None)
 
 170     pgtUrl = request.GET.get('pgtUrl', None)
 
 171     if ticket_string.startswith('PT-'):
 
 172         return _cas2_error_response(INVALID_TICKET, "serviceValidate cannot verify proxy tickets")
 
 174         return ticket_validate(service, ticket_string, pgtUrl)
 
 177 def proxy_validate(request):
 
 178     """Validate ticket via CAS v.2 protocol"""
 
 179     service = request.GET.get('service', None)
 
 180     ticket_string = request.GET.get('ticket', None)
 
 181     pgtUrl = request.GET.get('pgtUrl', None)
 
 182     return ticket_validate(service, ticket_string, pgtUrl)
 
 185 def generate_proxy_granting_ticket(pgt_url, ticket):
 
 186     proxy_callback_good_status = (200, 202, 301, 302, 304)
 
 187     uri = list(urlparse.urlsplit(pgt_url))
 
 189     pgt = ProxyGrantingTicket()
 
 190     pgt.serviceTicket = ticket
 
 191     pgt.targetService = pgt_url
 
 193     if hasattr(ticket, 'proxyGrantingTicket'):
 
 194         # here we got a proxy ticket! tata!
 
 195         pgt.pgt = ticket.proxyGrantingTicket
 
 197     params = {'pgtId': pgt.ticket, 'pgtIou': pgt.pgtiou}
 
 199     query = dict(urlparse.parse_qsl(uri[4]))
 
 202     uri[3] = urlencode(query)
 
 205         response = urllib2.urlopen(urlparse.urlunsplit(uri))
 
 206     except urllib2.HTTPError, e:
 
 207         if not e.code in proxy_callback_good_status:
 
 209     except urllib2.URLError, e:
 
 216 def _cas2_proxy_success(pt):
 
 217     return HttpResponse(proxy_success(pt))
 
 220 def _cas2_sucess_response(user, pgt=None, proxies=None):
 
 221     return HttpResponse(auth_success_response(user, pgt, proxies), mimetype='text/xml')
 
 224 def _cas2_error_response(code, message=None):
 
 225     return HttpResponse(u'''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
 
 226             <cas:authenticationFailure code="%(code)s">
 
 228             </cas:authenticationFailure>
 
 229         </cas:serviceResponse>''' % {
 
 231         'message': message if message else dict(ERROR_MESSAGES).get(code)
 
 232     }, mimetype='text/xml')
 
 235 def proxy_success(pt):
 
 236     response = etree.Element(CAS + 'serviceResponse', nsmap=NSMAP)
 
 237     proxySuccess = etree.SubElement(response, CAS + 'proxySuccess')
 
 238     proxyTicket = etree.SubElement(proxySuccess, CAS + 'proxyTicket')
 
 239     proxyTicket.text = pt
 
 240     return unicode(etree.tostring(response, encoding='utf-8'), 'utf-8')
 
 243 def auth_success_response(user, pgt, proxies):
 
 244     response = etree.Element(CAS + 'serviceResponse', nsmap=NSMAP)
 
 245     auth_success = etree.SubElement(response, CAS + 'authenticationSuccess')
 
 246     username = etree.SubElement(auth_success, CAS + 'user')
 
 247     username.text = user.username
 
 249     if settings.CAS_CUSTOM_ATTRIBUTES_CALLBACK:
 
 250         callback = get_callable(settings.CAS_CUSTOM_ATTRIBUTES_CALLBACK)
 
 251         attrs = callback(user)
 
 253             formater = get_callable(settings.CAS_CUSTOM_ATTRIBUTES_FORMATER)
 
 254             formater(auth_success, attrs)
 
 257         pgtElement = etree.SubElement(auth_success, CAS + 'proxyGrantingTicket')
 
 258         pgtElement.text = pgt
 
 261         proxiesElement = etree.SubElement(auth_success, CAS + "proxies")
 
 262         for proxy in proxies:
 
 263             proxyElement = etree.SubElement(proxiesElement, CAS + "proxy")
 
 264             proxyElement.text = proxy
 
 266     return unicode(etree.tostring(response, encoding='utf-8'), 'utf-8')