3 from urllib import urlencode
6 from django.conf import settings
7 from django.contrib.auth import login as auth_login, logout as auth_logout
8 from django.core.urlresolvers import get_callable
9 from django.http import HttpResponse, HttpResponseRedirect
10 from django.shortcuts import render_to_response
11 from django.template import RequestContext
12 from cas_provider.attribute_formatters import NSMAP, CAS
13 from cas_provider.models import ProxyGrantingTicket, ProxyTicket
14 from forms import LoginForm
15 from models import ServiceTicket, LoginTicket
18 __all__ = ['login', 'validate', 'logout', 'service_validate']
20 INVALID_TICKET = 'INVALID_TICKET'
21 INVALID_SERVICE = 'INVALID_SERVICE'
22 INVALID_REQUEST = 'INVALID_REQUEST'
23 INTERNAL_ERROR = 'INTERNAL_ERROR'
26 (INVALID_TICKET, u'The provided ticket is invalid.'),
27 (INVALID_SERVICE, u'Service is invalid'),
28 (INVALID_REQUEST, u'Not all required parameters were sent.'),
29 (INTERNAL_ERROR, u'An internal error occurred during ticket validation'),
33 logger = logging.getLogger(__name__)
35 def login(request, template_name='cas/login.html',\
36 success_redirect=settings.LOGIN_REDIRECT_URL,
37 warn_template_name='cas/warn.html',
38 form_class=LoginForm):
39 service = request.GET.get('service', None)
40 if request.user.is_authenticated():
41 if service is not None:
42 if request.GET.get('warn', False):
43 return render_to_response(warn_template_name, {
46 }, context_instance=RequestContext(request))
47 ticket = ServiceTicket.objects.create(service=service, user=request.user)
48 return HttpResponseRedirect(ticket.get_redirect_url())
50 return HttpResponseRedirect(success_redirect)
51 if request.method == 'POST':
52 form = form_class(data=request.POST, request=request)
54 user = form.get_user()
55 auth_login(request, user)
56 service = form.cleaned_data.get('service')
57 if service is not None:
58 ticket = ServiceTicket.objects.create(service=service, user=user)
59 success_redirect = ticket.get_redirect_url()
60 return HttpResponseRedirect(success_redirect)
62 form = form_class(request=request, initial={
64 'lt': LoginTicket.objects.create()
66 if hasattr(request, 'session') and hasattr(request.session, 'set_test_cookie'):
67 request.session.set_test_cookie()
68 return render_to_response(template_name, {
70 'errors': form.get_errors() if hasattr(form, 'get_errors') else None,
71 }, context_instance=RequestContext(request))
74 def validate(request):
75 """Validate ticket via CAS v.1 protocol"""
76 service = request.GET.get('service', None)
77 ticket_string = request.GET.get('ticket', None)
78 if service is not None and ticket_string is not None:
79 #renew = request.GET.get('renew', True)
81 # TODO: check user SSO session
83 ticket = ServiceTicket.objects.get(ticket=ticket_string)
84 assert ticket.service == service
85 username = ticket.user.username
86 return HttpResponse("yes\n%s\n" % username)
89 return HttpResponse("no\n\n")
92 def logout(request, template_name='cas/logout.html',
93 auto_redirect=settings.CAS_AUTO_REDIRECT_AFTER_LOGOUT):
94 url = request.GET.get('url', None)
95 if request.user.is_authenticated():
96 for ticket in ServiceTicket.objects.filter(user=request.user):
99 if url and auto_redirect:
100 return HttpResponseRedirect(url)
101 return render_to_response(template_name, {'url': url},
102 context_instance=RequestContext(request))
106 targetService = request.GET['targetService']
107 pgt_id = request.GET['pgt']
110 proxyGrantingTicket = ProxyGrantingTicket.objects.get(ticket=pgt_id)
111 except ProxyGrantingTicket.DoesNotExist:
112 return _cas2_error_response(INVALID_TICKET)
114 pt = ProxyTicket.objects.create(proxyGrantingTicket=proxyGrantingTicket,
115 user=proxyGrantingTicket.serviceTicket.user,
116 service=targetService)
117 return _cas2_proxy_success(pt.ticket)
120 def ticket_validate(service, ticket_string, pgtUrl):
121 if service is None or ticket_string is None:
122 return _cas2_error_response(INVALID_REQUEST)
125 if ticket_string.startswith('ST'):
126 ticket = ServiceTicket.objects.get(ticket=ticket_string)
127 elif ticket_string.startswith('PT'):
128 ticket = ProxyTicket.objects.get(ticket=ticket_string)
130 return _cas2_error_response(INVALID_TICKET,
131 '%(ticket)s is neither Service (ST-...) nor Proxy Ticket (PT-...)' % {
132 'ticket': ticket_string})
133 except ServiceTicket.DoesNotExist:
134 return _cas2_error_response(INVALID_TICKET)
136 ticketUrl = urlparse.urlparse(ticket.service)
137 serviceUrl = urlparse.urlparse(service)
139 if not(ticketUrl.hostname == serviceUrl.hostname and ticketUrl.path == serviceUrl.path and ticketUrl.port == serviceUrl.port):
140 return _cas2_error_response(INVALID_SERVICE)
144 if pgtUrl is not None:
145 pgt = generate_proxy_granting_ticket(pgtUrl, ticket)
147 pgtIouId = pgt.pgtiou
149 if hasattr(ticket, 'proxyticket'):
150 pgt = ticket.proxyticket.proxyGrantingTicket
151 # I am issued by this proxy granting ticket
152 if hasattr(pgt.serviceTicket, 'proxyticket'):
154 if hasattr(pgt.serviceTicket, 'proxyticket'):
155 proxies += (pgt.serviceTicket.service,)
156 pgt = pgt.serviceTicket.proxyticket.proxyGrantingTicket
161 return _cas2_sucess_response(user, pgtIouId, proxies)
164 def service_validate(request):
165 """Validate ticket via CAS v.2 protocol"""
166 service = request.GET.get('service', None)
167 ticket_string = request.GET.get('ticket', None)
168 pgtUrl = request.GET.get('pgtUrl', None)
169 if ticket_string.startswith('PT-'):
170 return _cas2_error_response(INVALID_TICKET, "serviceValidate cannot verify proxy tickets")
172 return ticket_validate(service, ticket_string, pgtUrl)
175 def proxy_validate(request):
176 """Validate ticket via CAS v.2 protocol"""
177 service = request.GET.get('service', None)
178 ticket_string = request.GET.get('ticket', None)
179 pgtUrl = request.GET.get('pgtUrl', None)
180 return ticket_validate(service, ticket_string, pgtUrl)
183 def generate_proxy_granting_ticket(pgt_url, ticket):
184 proxy_callback_good_status = (200, 202, 301, 302, 304)
185 uri = list(urlparse.urlsplit(pgt_url))
187 pgt = ProxyGrantingTicket()
188 pgt.serviceTicket = ticket
189 pgt.targetService = pgt_url
191 if hasattr(ticket, 'proxyGrantingTicket'):
192 # here we got a proxy ticket! tata!
193 pgt.pgt = ticket.proxyGrantingTicket
195 params = {'pgtId': pgt.ticket, 'pgtIou': pgt.pgtiou}
197 query = dict(urlparse.parse_qsl(uri[4]))
200 uri[3] = urlencode(query)
203 response = urllib2.urlopen(urlparse.urlunsplit(uri))
204 except urllib2.HTTPError, e:
205 if not e.code in proxy_callback_good_status:
206 logger.debug('Checking Proxy Callback URL {} returned {}. Not issuing PGT.'.format(uri, e.code))
208 except urllib2.URLError, e:
209 logger.debug('Checking Proxy Callback URL {} raised URLError. Not issuing PGT.'.format(uri))
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')