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')