do some logging
[django-cas-provider.git] / cas_provider / views.py
1 import logging
2 from lxml import etree
3 from urllib import urlencode
4 import urllib2
5 import urlparse
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
16
17
18 __all__ = ['login', 'validate', 'logout', 'service_validate']
19
20 INVALID_TICKET = 'INVALID_TICKET'
21 INVALID_SERVICE = 'INVALID_SERVICE'
22 INVALID_REQUEST = 'INVALID_REQUEST'
23 INTERNAL_ERROR = 'INTERNAL_ERROR'
24
25 ERROR_MESSAGES = (
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'),
30     )
31
32
33 logger = logging.getLogger(__name__)
34
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, {
44                     'service': service,
45                     'warn': False
46                 }, context_instance=RequestContext(request))
47             ticket = ServiceTicket.objects.create(service=service, user=request.user)
48             return HttpResponseRedirect(ticket.get_redirect_url())
49         else:
50             return HttpResponseRedirect(success_redirect)
51     if request.method == 'POST':
52         form = form_class(data=request.POST, request=request)
53         if form.is_valid():
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)
61     else:
62         form = form_class(request=request, initial={
63             'service': service,
64             'lt': LoginTicket.objects.create()
65         })
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, {
69         'form': form,
70         'errors': form.get_errors() if hasattr(form, 'get_errors') else None,
71         }, context_instance=RequestContext(request))
72
73
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)
80         #if not renew:
81         # TODO: check user SSO session
82         try:
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)
87         except:
88             pass
89     return HttpResponse("no\n\n")
90
91
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):
97             ticket.delete()
98         auth_logout(request)
99         if url and auto_redirect:
100             return HttpResponseRedirect(url)
101     return render_to_response(template_name, {'url': url},
102         context_instance=RequestContext(request))
103
104
105 def proxy(request):
106     targetService = request.GET['targetService']
107     pgt_id = request.GET['pgt']
108
109     try:
110         proxyGrantingTicket = ProxyGrantingTicket.objects.get(ticket=pgt_id)
111     except ProxyGrantingTicket.DoesNotExist:
112         return _cas2_error_response(INVALID_TICKET)
113
114     pt = ProxyTicket.objects.create(proxyGrantingTicket=proxyGrantingTicket,
115         user=proxyGrantingTicket.serviceTicket.user,
116         service=targetService)
117     return _cas2_proxy_success(pt.ticket)
118
119
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)
123
124     try:
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)
129         else:
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)
135
136     ticketUrl =  urlparse.urlparse(ticket.service)
137     serviceUrl =  urlparse.urlparse(service)
138
139     if not(ticketUrl.hostname == serviceUrl.hostname and ticketUrl.path == serviceUrl.path and ticketUrl.port == serviceUrl.port):
140         return _cas2_error_response(INVALID_SERVICE)
141
142     pgtIouId = None
143     proxies = ()
144     if pgtUrl is not None:
145         pgt = generate_proxy_granting_ticket(pgtUrl, ticket)
146         if pgt:
147             pgtIouId = pgt.pgtiou
148
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'):
153             while pgt:
154                 if hasattr(pgt.serviceTicket, 'proxyticket'):
155                     proxies += (pgt.serviceTicket.service,)
156                     pgt = pgt.serviceTicket.proxyticket.proxyGrantingTicket
157                 else:
158                     pgt = None
159
160     user = ticket.user
161     return _cas2_sucess_response(user, pgtIouId, proxies)
162
163
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")
171     else:
172         return ticket_validate(service, ticket_string, pgtUrl)
173
174
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)
181
182
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))
186
187     pgt = ProxyGrantingTicket()
188     pgt.serviceTicket = ticket
189     pgt.targetService = pgt_url
190
191     if hasattr(ticket, 'proxyGrantingTicket'):
192         # here we got a proxy ticket! tata!
193         pgt.pgt = ticket.proxyGrantingTicket
194
195     params = {'pgtId': pgt.ticket, 'pgtIou': pgt.pgtiou}
196
197     query = dict(urlparse.parse_qsl(uri[4]))
198     query.update(params)
199
200     uri[3] = urlencode(query)
201
202     try:
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))
207             return
208     except urllib2.URLError, e:
209         logger.debug('Checking Proxy Callback URL {} raised URLError. Not issuing PGT.'.format(uri))
210         return
211
212     pgt.save()
213     return pgt
214
215
216 def _cas2_proxy_success(pt):
217     return HttpResponse(proxy_success(pt))
218
219
220 def _cas2_sucess_response(user, pgt=None, proxies=None):
221     return HttpResponse(auth_success_response(user, pgt, proxies), mimetype='text/xml')
222
223
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">
227                 %(message)s
228             </cas:authenticationFailure>
229         </cas:serviceResponse>''' % {
230         'code': code,
231         'message': message if message else dict(ERROR_MESSAGES).get(code)
232     }, mimetype='text/xml')
233
234
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')
241
242
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
248
249     if settings.CAS_CUSTOM_ATTRIBUTES_CALLBACK:
250         callback = get_callable(settings.CAS_CUSTOM_ATTRIBUTES_CALLBACK)
251         attrs = callback(user)
252         if len(attrs) > 0:
253             formater = get_callable(settings.CAS_CUSTOM_ATTRIBUTES_FORMATER)
254             formater(auth_success, attrs)
255
256     if pgt:
257         pgtElement = etree.SubElement(auth_success, CAS + 'proxyGrantingTicket')
258         pgtElement.text = pgt
259
260     if proxies:
261         proxiesElement = etree.SubElement(auth_success, CAS + "proxies")
262         for proxy in proxies:
263             proxyElement = etree.SubElement(proxiesElement, CAS + "proxy")
264             proxyElement.text = proxy
265
266     return unicode(etree.tostring(response, encoding='utf-8'), 'utf-8')