Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2# This program is distributed in the hope that it will be useful, but WITHOUT
3# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
4# FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for
5# more details.
6#
7# You should have received a copy of the GNU General Public License version 3
8# along with this program; if not, write to the Free Software Foundation, Inc., 51
9# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
10#
11# (c) 2015-2016 Valentin Samir
12"""views for the app"""
13from .default_settings import settings, SessionStore
15from django.shortcuts import render, redirect
16from django.http import HttpResponse, HttpResponseRedirect
17from django.contrib import messages
18from django.utils.decorators import method_decorator
19from django.utils import timezone
20from django.views.decorators.csrf import csrf_exempt
21from django.middleware.csrf import CsrfViewMiddleware
22from django.views.generic import View
23try:
24 from django.utils.encoding import python_2_unicode_compatible
25 from django.utils.translation import ugettext as _
26except ImportError:
27 def python_2_unicode_compatible(func):
28 """
29 We use Django >= 3.0 with Python >= 3.4, we don't need Python 2 compatibility.
30 """
31 return func
32 from django.utils.translation import gettext as _
33from django.utils.safestring import mark_safe
34try:
35 from django.urls import reverse
36except ImportError:
37 from django.core.urlresolvers import reverse
39import re
40import logging
41import pprint
42import requests
43from lxml import etree
44from datetime import timedelta
46import cas_server.utils as utils
47import cas_server.forms as forms
48import cas_server.models as models
50from .utils import json_response
51from .models import Ticket, ServiceTicket, ProxyTicket, ProxyGrantingTicket
52from .models import ServicePattern, FederatedIendityProvider, FederatedUser
53from .federate import CASFederateValidateUser
55logger = logging.getLogger(__name__)
58class LogoutMixin(object):
59 """destroy CAS session utils"""
61 def logout(self, all_session=False):
62 """
63 effectively destroy a CAS session
65 :param boolean all_session: If ``True`` destroy all the user sessions, otherwise
66 destroy the current user session.
67 :return: The number of destroyed sessions
68 :rtype: int
69 """
70 # initialize the counter of the number of destroyed sesisons
71 session_nb = 0
72 # save the current user username before flushing the session
73 username = self.request.session.get("username")
74 if username:
75 if all_session:
76 logger.info("Logging out user %s from all sessions." % username)
77 else:
78 logger.info("Logging out user %s." % username)
79 users = []
80 # try to get the user from the current session
81 try:
82 users.append(
83 models.User.objects.get(
84 username=username,
85 session_key=self.request.session.session_key
86 )
87 )
88 except models.User.DoesNotExist:
89 # if user not found in database, flush the session anyway
90 self.request.session.flush()
92 # If all_session is set, search all of the user sessions
93 if all_session:
94 users.extend(
95 models.User.objects.filter(
96 username=username
97 ).exclude(
98 session_key=self.request.session.session_key
99 )
100 )
102 # Iterate over all user sessions that have to be logged out
103 for user in users:
104 # get the user session
105 session = SessionStore(session_key=user.session_key)
106 # flush the session
107 session.flush()
108 # send SLO requests
109 user.logout(self.request)
110 # delete the user
111 user.delete()
112 # increment the destroyed session counter
113 session_nb += 1
114 if username:
115 logger.info("User %s logged out" % username)
116 return session_nb
119class CsrfExemptView(View):
120 """base class for csrf exempt class views"""
122 @method_decorator(csrf_exempt) # csrf is disabled for allowing SLO requests reception
123 def dispatch(self, request, *args, **kwargs):
124 """
125 dispatch different http request to the methods of the same name
127 :param django.http.HttpRequest request: The current request object
128 """
129 return super(CsrfExemptView, self).dispatch(request, *args, **kwargs)
132class LogoutView(View, LogoutMixin):
133 """destroy CAS session (logout) view"""
135 #: current :class:`django.http.HttpRequest` object
136 request = None
137 #: service GET parameter
138 service = None
139 #: url GET paramet
140 url = None
141 #: ``True`` if the HTTP_X_AJAX http header is sent and ``settings.CAS_ENABLE_AJAX_AUTH``
142 #: is ``True``, ``False`` otherwise.
143 ajax = None
145 def init_get(self, request):
146 """
147 Initialize the :class:`LogoutView` attributes on GET request
149 :param django.http.HttpRequest request: The current request object
150 """
151 self.request = request
152 self.service = request.GET.get('service')
153 self.url = request.GET.get('url')
154 self.ajax = settings.CAS_ENABLE_AJAX_AUTH and 'HTTP_X_AJAX' in request.META
156 def get(self, request, *args, **kwargs):
157 """
158 method called on GET request on this view
160 :param django.http.HttpRequest request: The current request object
161 """
162 logger.info("logout requested")
163 # initialize the class attributes
164 self.init_get(request)
165 # if CAS federation mode is enable, bakup the provider before flushing the sessions
166 if settings.CAS_FEDERATE:
167 try:
168 user = FederatedUser.get_from_federated_username(
169 self.request.session.get("username")
170 )
171 auth = CASFederateValidateUser(user.provider, service_url="")
172 except FederatedUser.DoesNotExist:
173 auth = None
174 session_nb = self.logout(self.request.GET.get("all"))
175 # if CAS federation mode is enable, redirect to user CAS logout page, appending the
176 # current querystring
177 if settings.CAS_FEDERATE:
178 if auth is not None:
179 params = utils.copy_params(request.GET, ignore={"forget_provider"})
180 url = auth.get_logout_url()
181 response = HttpResponseRedirect(utils.update_url(url, params))
182 if request.GET.get("forget_provider"):
183 response.delete_cookie("remember_provider")
184 return response
185 # if service is set, redirect to service after logout
186 if self.service:
187 list(messages.get_messages(request)) # clean messages before leaving the django app
188 return HttpResponseRedirect(self.service)
189 # if service is not set but url is set, redirect to url after logout
190 elif self.url:
191 list(messages.get_messages(request)) # clean messages before leaving the django app
192 return HttpResponseRedirect(self.url)
193 else:
194 # build logout message depending of the number of sessions the user logs out
195 if session_nb == 1:
196 logout_msg = mark_safe(_(
197 "<h3>Logout successful</h3>"
198 "You have successfully logged out from the Central Authentication Service. "
199 "For security reasons, close your web browser."
200 ))
201 elif session_nb > 1:
202 logout_msg = mark_safe(_(
203 "<h3>Logout successful</h3>"
204 "You have successfully logged out from %d sessions of the Central "
205 "Authentication Service. "
206 "For security reasons, close your web browser."
207 ) % session_nb)
208 else:
209 logout_msg = mark_safe(_(
210 "<h3>Logout successful</h3>"
211 "You were already logged out from the Central Authentication Service. "
212 "For security reasons, close your web browser."
213 ))
215 # depending of settings, redirect to the login page with a logout message or display
216 # the logout page. The default is to display tge logout page.
217 if settings.CAS_REDIRECT_TO_LOGIN_AFTER_LOGOUT:
218 messages.add_message(request, messages.SUCCESS, logout_msg)
219 if self.ajax:
220 url = reverse("cas_server:login")
221 data = {
222 'status': 'success',
223 'detail': 'logout',
224 'url': url,
225 'session_nb': session_nb
226 }
227 return json_response(request, data)
228 else:
229 return redirect("cas_server:login")
230 else:
231 if self.ajax:
232 data = {'status': 'success', 'detail': 'logout', 'session_nb': session_nb}
233 return json_response(request, data)
234 else:
235 return render(
236 request,
237 settings.CAS_LOGOUT_TEMPLATE,
238 utils.context({'logout_msg': logout_msg})
239 )
242class FederateAuth(CsrfExemptView):
243 """
244 view to authenticated user against a backend CAS then CAS_FEDERATE is True
246 csrf is disabled for allowing SLO requests reception.
247 """
249 #: current URL used as service URL by the CAS client
250 service_url = None
252 def get_cas_client(self, request, provider, renew=False):
253 """
254 return a CAS client object matching provider
256 :param django.http.HttpRequest request: The current request object
257 :param cas_server.models.FederatedIendityProvider provider: the user identity provider
258 :return: The user CAS client object
259 :rtype: :class:`federate.CASFederateValidateUser
260 <cas_server.federate.CASFederateValidateUser>`
261 """
262 # compute the current url, ignoring ticket dans provider GET parameters
263 service_url = utils.get_current_url(request, {"ticket", "provider"})
264 self.service_url = service_url
265 return CASFederateValidateUser(provider, service_url, renew=renew)
267 def post(self, request, provider=None, *args, **kwargs):
268 """
269 method called on POST request
271 :param django.http.HttpRequest request: The current request object
272 :param unicode provider: Optional parameter. The user provider suffix.
273 """
274 # if settings.CAS_FEDERATE is not True redirect to the login page
275 if not settings.CAS_FEDERATE:
276 logger.warning("CAS_FEDERATE is False, set it to True to use federation")
277 return redirect("cas_server:login")
278 # POST with a provider suffix, this is probably an SLO request. csrf is disabled for
279 # allowing SLO requests reception
280 try:
281 provider = FederatedIendityProvider.objects.get(suffix=provider)
282 auth = self.get_cas_client(request, provider)
283 try:
284 auth.clean_sessions(request.POST['logoutRequest'])
285 except (KeyError, AttributeError):
286 pass
287 return HttpResponse("ok")
288 # else, a User is trying to log in using an identity provider
289 except FederatedIendityProvider.DoesNotExist:
290 # Manually checking for csrf to protect the code below
291 reason = CsrfViewMiddleware().process_view(request, None, (), {})
292 if reason is not None: # pragma: no cover (csrf checks are disabled during tests)
293 return reason # Failed the test, stop here.
294 form = forms.FederateSelect(request.POST)
295 if form.is_valid():
296 params = utils.copy_params(
297 request.POST,
298 ignore={"provider", "csrfmiddlewaretoken", "ticket", "lt"}
299 )
300 if params.get("renew") == "False":
301 del params["renew"]
302 url = utils.reverse_params(
303 "cas_server:federateAuth",
304 kwargs=dict(provider=form.cleaned_data["provider"].suffix),
305 params=params
306 )
307 return HttpResponseRedirect(url)
308 else:
309 return redirect("cas_server:login")
311 def get(self, request, provider=None):
312 """
313 method called on GET request
315 :param django.http.HttpRequestself. request: The current request object
316 :param unicode provider: Optional parameter. The user provider suffix.
317 """
318 # if settings.CAS_FEDERATE is not True redirect to the login page
319 if not settings.CAS_FEDERATE:
320 logger.warning("CAS_FEDERATE is False, set it to True to use federation")
321 return redirect("cas_server:login")
322 renew = bool(request.GET.get('renew') and request.GET['renew'] != "False")
323 # Is the user is already authenticated, no need to request authentication to the user
324 # identity provider.
325 if self.request.session.get("authenticated") and not renew:
326 logger.warning("User already authenticated, dropping federated authentication request")
327 return redirect("cas_server:login")
328 try:
329 # get the identity provider from its suffix
330 provider = FederatedIendityProvider.objects.get(suffix=provider)
331 # get a CAS client for the user identity provider
332 auth = self.get_cas_client(request, provider, renew)
333 # if no ticket submited, redirect to the identity provider CAS login page
334 if 'ticket' not in request.GET:
335 logger.info("Trying to authenticate %s again" % auth.provider.server_url)
336 return HttpResponseRedirect(auth.get_login_url())
337 else:
338 ticket = request.GET['ticket']
339 try:
340 # if the ticket validation succeed
341 if auth.verify_ticket(ticket):
342 logger.info(
343 "Got a valid ticket for %s from %s" % (
344 auth.username,
345 auth.provider.server_url
346 )
347 )
348 params = utils.copy_params(request.GET, ignore={"ticket", "remember"})
349 request.session["federate_username"] = auth.federated_username
350 request.session["federate_ticket"] = ticket
351 auth.register_slo(
352 auth.federated_username,
353 request.session.session_key,
354 ticket
355 )
356 # redirect to the the login page for the user to become authenticated
357 # thanks to the `federate_username` and `federate_ticket` session parameters
358 url = utils.reverse_params("cas_server:login", params)
359 response = HttpResponseRedirect(url)
360 # If the user has checked "remember my identity provider" store it in a
361 # cookie
362 if request.GET.get("remember"):
363 max_age = settings.CAS_FEDERATE_REMEMBER_TIMEOUT
364 utils.set_cookie(
365 response,
366 "remember_provider",
367 provider.suffix,
368 max_age
369 )
370 return response
371 # else redirect to the identity provider CAS login page
372 else:
373 logger.info(
374 (
375 "Got an invalid ticket %s from %s for service %s. "
376 "Retrying authentication"
377 ) % (
378 ticket,
379 auth.provider.server_url,
380 self.service_url
381 )
382 )
383 return HttpResponseRedirect(auth.get_login_url())
384 # both xml.etree.ElementTree and lxml.etree exceptions inherit from SyntaxError
385 except SyntaxError as error:
386 messages.add_message(
387 request,
388 messages.ERROR,
389 _(
390 u"Invalid response from your identity provider CAS upon "
391 u"ticket %(ticket)s validation: %(error)r"
392 ) % {'ticket': ticket, 'error': error}
393 )
394 response = redirect("cas_server:login")
395 response.delete_cookie("remember_provider")
396 return response
397 except FederatedIendityProvider.DoesNotExist:
398 logger.warning("Identity provider suffix %s not found" % provider)
399 # if the identity provider is not found, redirect to the login page
400 return redirect("cas_server:login")
403class LoginView(View, LogoutMixin):
404 """credential requestor / acceptor"""
406 # pylint: disable=too-many-instance-attributes
407 # Nine is reasonable in this case.
409 #: The current :class:`models.User<cas_server.models.User>` object
410 user = None
411 #: The form to display to the user
412 form = None
414 #: current :class:`django.http.HttpRequest` object
415 request = None
416 #: service GET/POST parameter
417 service = None
418 #: ``True`` if renew GET/POST parameter is present and not "False"
419 renew = None
420 #: the warn GET/POST parameter
421 warn = None
422 #: the gateway GET/POST parameter
423 gateway = None
424 #: the method GET/POST parameter
425 method = None
427 #: ``True`` if the HTTP_X_AJAX http header is sent and ``settings.CAS_ENABLE_AJAX_AUTH``
428 #: is ``True``, ``False`` otherwise.
429 ajax = None
431 #: ``True`` if the user has just authenticated
432 renewed = False
433 #: ``True`` if renew GET/POST parameter is present and not "False"
434 warned = False
436 #: The :class:`FederateAuth` transmited username (only used if ``settings.CAS_FEDERATE``
437 #: is ``True``)
438 username = None
439 #: The :class:`FederateAuth` transmited ticket (only used if ``settings.CAS_FEDERATE`` is
440 #: ``True``)
441 ticket = None
443 INVALID_LOGIN_TICKET = 1
444 USER_LOGIN_OK = 2
445 USER_LOGIN_FAILURE = 3
446 USER_ALREADY_LOGGED = 4
447 USER_AUTHENTICATED = 5
448 USER_NOT_AUTHENTICATED = 6
450 def init_post(self, request):
451 """
452 Initialize POST received parameters
454 :param django.http.HttpRequest request: The current request object
455 """
456 self.request = request
457 self.service = request.POST.get('service')
458 self.renew = bool(request.POST.get('renew') and request.POST['renew'] != "False")
459 self.gateway = request.POST.get('gateway')
460 self.method = request.POST.get('method')
461 self.ajax = settings.CAS_ENABLE_AJAX_AUTH and 'HTTP_X_AJAX' in request.META
462 if request.POST.get('warned') and request.POST['warned'] != "False":
463 self.warned = True
464 self.warn = request.POST.get('warn')
465 if settings.CAS_FEDERATE:
466 self.username = request.POST.get('username')
467 # in federated mode, the valdated indentity provider CAS ticket is used as password
468 self.ticket = request.POST.get('password')
470 def gen_lt(self):
471 """Generate a new LoginTicket and add it to the list of valid LT for the user"""
472 self.request.session['lt'] = self.request.session.get('lt', []) + [utils.gen_lt()]
473 if len(self.request.session['lt']) > 100:
474 self.request.session['lt'] = self.request.session['lt'][-100:]
476 def check_lt(self):
477 """
478 Check is the POSTed LoginTicket is valid, if yes invalide it
480 :return: ``True`` if the LoginTicket is valid, ``False`` otherwise
481 :rtype: bool
482 """
483 # save LT for later check
484 lt_valid = self.request.session.get('lt', [])
485 lt_send = self.request.POST.get('lt')
486 # generate a new LT (by posting the LT has been consumed)
487 self.gen_lt()
488 # check if send LT is valid
489 if lt_send not in lt_valid:
490 return False
491 else:
492 self.request.session['lt'].remove(lt_send)
493 # we need to redo the affectation for django to detect that the list has changed
494 # and for its new value to be store in the session
495 self.request.session['lt'] = self.request.session['lt']
496 return True
498 def post(self, request, *args, **kwargs):
499 """
500 method called on POST request on this view
502 :param django.http.HttpRequest request: The current request object
503 """
504 # initialize class parameters
505 self.init_post(request)
506 # process the POST request
507 ret = self.process_post()
508 if ret == self.INVALID_LOGIN_TICKET:
509 messages.add_message(
510 self.request,
511 messages.ERROR,
512 _(u"Invalid login ticket, please try to log in again")
513 )
514 elif ret == self.USER_LOGIN_OK:
515 # On successful login, update the :class:`models.User<cas_server.models.User>` ``date``
516 # attribute by saving it. (``auto_now=True``)
517 self.user = models.User.objects.get_or_create(
518 username=self.request.session['username'],
519 session_key=self.request.session.session_key
520 )[0]
521 self.user.last_login = timezone.now()
522 self.user.save()
523 elif ret == self.USER_LOGIN_FAILURE: # bad user login
524 if settings.CAS_FEDERATE:
525 self.ticket = None
526 self.username = None
527 self.init_form()
528 # preserve valid LoginTickets from session flush
529 lt = self.request.session.get('lt', [])
530 # On login failure, flush the session
531 self.logout()
532 # restore valid LoginTickets
533 self.request.session['lt'] = lt
534 elif ret == self.USER_ALREADY_LOGGED:
535 pass
536 else: # pragma: no cover (should no happen)
537 raise EnvironmentError("invalid output for LoginView.process_post")
538 # call the GET/POST common part
539 response = self.common()
540 if self.warn:
541 utils.set_cookie(
542 response,
543 "warn",
544 "on",
545 10 * 365 * 24 * 3600
546 )
547 else:
548 response.delete_cookie("warn")
549 return response
551 def process_post(self):
552 """
553 Analyse the POST request:
555 * check that the LoginTicket is valid
556 * check that the user sumited credentials are valid
558 :return:
559 * :attr:`INVALID_LOGIN_TICKET` if the POSTed LoginTicket is not valid
560 * :attr:`USER_ALREADY_LOGGED` if the user is already logged and do no request
561 reauthentication.
562 * :attr:`USER_LOGIN_FAILURE` if the user is not logged or request for
563 reauthentication and his credentials are not valid
564 * :attr:`USER_LOGIN_OK` if the user is not logged or request for
565 reauthentication and his credentials are valid
566 :rtype: int
567 """
568 if not self.check_lt():
569 self.init_form(self.request.POST)
570 logger.warning("Received an invalid login ticket")
571 return self.INVALID_LOGIN_TICKET
572 elif not self.request.session.get("authenticated") or self.renew:
573 # authentication request receive, initialize the form to use
574 self.init_form(self.request.POST)
575 if self.form.is_valid():
576 self.request.session.set_expiry(0)
577 self.request.session["username"] = self.form.cleaned_data['username']
578 self.request.session["warn"] = True if self.form.cleaned_data.get("warn") else False
579 self.request.session["authenticated"] = True
580 self.renewed = True
581 self.warned = True
582 logger.info("User %s successfully authenticated" % self.request.session["username"])
583 return self.USER_LOGIN_OK
584 else:
585 logger.warning("A login attempt failed")
586 return self.USER_LOGIN_FAILURE
587 else:
588 logger.warning("Received a login attempt for an already-active user")
589 return self.USER_ALREADY_LOGGED
591 def init_get(self, request):
592 """
593 Initialize GET received parameters
595 :param django.http.HttpRequest request: The current request object
596 """
597 self.request = request
598 self.service = request.GET.get('service')
599 self.renew = bool(request.GET.get('renew') and request.GET['renew'] != "False")
600 self.gateway = request.GET.get('gateway')
601 self.method = request.GET.get('method')
602 self.ajax = settings.CAS_ENABLE_AJAX_AUTH and 'HTTP_X_AJAX' in request.META
603 self.warn = request.GET.get('warn')
604 if settings.CAS_FEDERATE:
605 # here username and ticket are fetch from the session after a redirection from
606 # FederateAuth.get
607 self.username = request.session.get("federate_username")
608 self.ticket = request.session.get("federate_ticket")
609 if self.username:
610 del request.session["federate_username"]
611 if self.ticket:
612 del request.session["federate_ticket"]
614 def get(self, request, *args, **kwargs):
615 """
616 method called on GET request on this view
618 :param django.http.HttpRequest request: The current request object
619 """
620 # initialize class parameters
621 self.init_get(request)
622 # process the GET request
623 self.process_get()
624 # call the GET/POST common part
625 return self.common()
627 def process_get(self):
628 """
629 Analyse the GET request
631 :return:
632 * :attr:`USER_NOT_AUTHENTICATED` if the user is not authenticated or is requesting
633 for authentication renewal
634 * :attr:`USER_AUTHENTICATED` if the user is authenticated and is not requesting
635 for authentication renewal
636 :rtype: int
637 """
638 # generate a new LT
639 self.gen_lt()
640 if not self.request.session.get("authenticated") or self.renew:
641 # authentication will be needed, initialize the form to use
642 self.init_form()
643 return self.USER_NOT_AUTHENTICATED
644 return self.USER_AUTHENTICATED
646 def init_form(self, values=None):
647 """
648 Initialization of the good form depending of POST and GET parameters
650 :param django.http.QueryDict values: A POST or GET QueryDict
651 """
652 if values:
653 values = values.copy()
654 values['lt'] = self.request.session['lt'][-1]
655 form_initial = {
656 'service': self.service,
657 'method': self.method,
658 'warn': (
659 self.warn or self.request.session.get("warn") or self.request.COOKIES.get('warn')
660 ),
661 'lt': self.request.session['lt'][-1],
662 'renew': self.renew
663 }
664 if settings.CAS_FEDERATE:
665 if self.username and self.ticket:
666 form_initial['username'] = self.username
667 form_initial['password'] = self.ticket
668 form_initial['ticket'] = self.ticket
669 self.form = forms.FederateUserCredential(
670 values,
671 initial=form_initial
672 )
673 else:
674 self.form = forms.FederateSelect(values, initial=form_initial)
675 else:
676 self.form = forms.UserCredential(
677 values,
678 initial=form_initial
679 )
681 def service_login(self):
682 """
683 Perform login against a service
685 :return:
686 * The rendering of the ``settings.CAS_WARN_TEMPLATE`` if the user asked to be
687 warned before ticket emission and has not yep been warned.
688 * The redirection to the service URL with a ticket GET parameter
689 * The redirection to the service URL without a ticket if ticket generation failed
690 and the :attr:`gateway` attribute is set
691 * The rendering of the ``settings.CAS_LOGGED_TEMPLATE`` template with some error
692 messages if the ticket generation failed (e.g: user not allowed).
693 :rtype: django.http.HttpResponse
694 """
695 try:
696 # is the service allowed
697 service_pattern = ServicePattern.validate(self.service)
698 # is the current user allowed on this service
699 service_pattern.check_user(self.user)
700 # if the user has asked to be warned before any login to a service
701 if self.request.session.get("warn", True) and not self.warned:
702 messages.add_message(
703 self.request,
704 messages.WARNING,
705 _(u"Authentication has been required by service %(name)s (%(url)s)") %
706 {'name': service_pattern.name, 'url': self.service}
707 )
708 if self.ajax:
709 data = {"status": "error", "detail": "confirmation needed"}
710 return json_response(self.request, data)
711 else:
712 warn_form = forms.WarnForm(initial={
713 'service': self.service,
714 'renew': self.renew,
715 'gateway': self.gateway,
716 'method': self.method,
717 'warned': True,
718 'lt': self.request.session['lt'][-1]
719 })
720 return render(
721 self.request,
722 settings.CAS_WARN_TEMPLATE,
723 utils.context({'form': warn_form})
724 )
725 else:
726 # redirect, using method ?
727 list(messages.get_messages(self.request)) # clean messages before leaving django
728 redirect_url = self.user.get_service_url(
729 self.service,
730 service_pattern,
731 renew=self.renewed
732 )
733 if not self.ajax:
734 return HttpResponseRedirect(redirect_url)
735 else:
736 data = {"status": "success", "detail": "auth", "url": redirect_url}
737 return json_response(self.request, data)
738 except ServicePattern.DoesNotExist:
739 error = 1
740 messages.add_message(
741 self.request,
742 messages.ERROR,
743 _(u'Service %(url)s not allowed.') % {'url': self.service}
744 )
745 except models.BadUsername:
746 error = 2
747 messages.add_message(
748 self.request,
749 messages.ERROR,
750 _(u"Username not allowed")
751 )
752 except models.BadFilter:
753 error = 3
754 messages.add_message(
755 self.request,
756 messages.ERROR,
757 _(u"User characteristics not allowed")
758 )
759 except models.UserFieldNotDefined:
760 error = 4
761 messages.add_message(
762 self.request,
763 messages.ERROR,
764 _(u"The attribute %(field)s is needed to use"
765 u" that service") % {'field': service_pattern.user_field}
766 )
768 # if gateway is set and auth failed redirect to the service without authentication
769 if self.gateway and not self.ajax:
770 list(messages.get_messages(self.request)) # clean messages before leaving django
771 return HttpResponseRedirect(self.service)
773 if not self.ajax:
774 return render(
775 self.request,
776 settings.CAS_LOGGED_TEMPLATE,
777 utils.context({'session': self.request.session})
778 )
779 else:
780 data = {"status": "error", "detail": "auth", "code": error}
781 return json_response(self.request, data)
783 def authenticated(self):
784 """
785 Processing authenticated users
787 :return:
788 * The returned value of :meth:`service_login` if :attr:`service` is defined
789 * The rendering of ``settings.CAS_LOGGED_TEMPLATE`` otherwise
790 :rtype: django.http.HttpResponse
791 """
792 # Try to get the current :class:`models.User<cas_server.models.User>` object for the current
793 # session
794 try:
795 self.user = models.User.objects.get(
796 username=self.request.session.get("username"),
797 session_key=self.request.session.session_key
798 )
799 # if not found, flush the session and redirect to the login page
800 except models.User.DoesNotExist:
801 logger.warning(
802 "User %s seems authenticated but is not found in the database." % (
803 self.request.session.get("username"),
804 )
805 )
806 self.logout()
807 if self.ajax:
808 data = {
809 "status": "error",
810 "detail": "login required",
811 "url": utils.reverse_params("cas_server:login", params=self.request.GET)
812 }
813 return json_response(self.request, data)
814 else:
815 return utils.redirect_params("cas_server:login", params=self.request.GET)
817 # if login against a service
818 if self.service:
819 return self.service_login()
820 # else display the logged template
821 else:
822 if self.ajax:
823 data = {"status": "success", "detail": "logged"}
824 return json_response(self.request, data)
825 else:
826 return render(
827 self.request,
828 settings.CAS_LOGGED_TEMPLATE,
829 utils.context({'session': self.request.session})
830 )
832 def not_authenticated(self):
833 """
834 Processing non authenticated users
836 :return:
837 * The rendering of ``settings.CAS_LOGIN_TEMPLATE`` with various messages
838 depending of GET/POST parameters
839 * The redirection to :class:`FederateAuth` if ``settings.CAS_FEDERATE`` is ``True``
840 and the "remember my identity provider" cookie is found
841 :rtype: django.http.HttpResponse
842 """
843 if self.service:
844 try:
845 service_pattern = ServicePattern.validate(self.service)
846 if self.gateway and not self.ajax:
847 # clean messages before leaving django
848 list(messages.get_messages(self.request))
849 return HttpResponseRedirect(self.service)
851 if settings.CAS_SHOW_SERVICE_MESSAGES:
852 if self.request.session.get("authenticated") and self.renew:
853 messages.add_message(
854 self.request,
855 messages.WARNING,
856 _(u"Authentication renewal required by service %(name)s (%(url)s).") %
857 {'name': service_pattern.name, 'url': self.service}
858 )
859 else:
860 messages.add_message(
861 self.request,
862 messages.WARNING,
863 _(u"Authentication required by service %(name)s (%(url)s).") %
864 {'name': service_pattern.name, 'url': self.service}
865 )
866 except ServicePattern.DoesNotExist:
867 if settings.CAS_SHOW_SERVICE_MESSAGES:
868 messages.add_message(
869 self.request,
870 messages.ERROR,
871 _(u'Service %s not allowed') % self.service
872 )
873 if self.ajax:
874 data = {
875 "status": "error",
876 "detail": "login required",
877 "url": utils.reverse_params("cas_server:login", params=self.request.GET)
878 }
879 return json_response(self.request, data)
880 else:
881 if settings.CAS_FEDERATE:
882 if self.username and self.ticket:
883 return render(
884 self.request,
885 settings.CAS_LOGIN_TEMPLATE,
886 utils.context({
887 'form': self.form,
888 'auto_submit': True,
889 'post_url': reverse("cas_server:login")
890 })
891 )
892 else:
893 if (
894 self.request.COOKIES.get('remember_provider') and
895 FederatedIendityProvider.objects.filter(
896 suffix=self.request.COOKIES['remember_provider']
897 )
898 ):
899 params = utils.copy_params(self.request.GET)
900 url = utils.reverse_params(
901 "cas_server:federateAuth",
902 params=params,
903 kwargs=dict(provider=self.request.COOKIES['remember_provider'])
904 )
905 return HttpResponseRedirect(url)
906 else:
907 # if user is authenticated and auth renewal is requested, redirect directly
908 # to the user identity provider
909 if self.renew and self.request.session.get("authenticated"):
910 try:
911 user = FederatedUser.get_from_federated_username(
912 self.request.session.get("username")
913 )
914 params = utils.copy_params(self.request.GET)
915 url = utils.reverse_params(
916 "cas_server:federateAuth",
917 params=params,
918 kwargs=dict(provider=user.provider.suffix)
919 )
920 return HttpResponseRedirect(url)
921 # Should normally not happen: if the user is logged, it exists in the
922 # database.
923 except FederatedUser.DoesNotExist: # pragma: no cover
924 pass
925 return render(
926 self.request,
927 settings.CAS_LOGIN_TEMPLATE,
928 utils.context({
929 'form': self.form,
930 'post_url': reverse("cas_server:federateAuth")
931 })
932 )
933 else:
934 return render(
935 self.request,
936 settings.CAS_LOGIN_TEMPLATE,
937 utils.context({'form': self.form})
938 )
940 def common(self):
941 """
942 Common part execute uppon GET and POST request
944 :return:
945 * The returned value of :meth:`authenticated` if the user is authenticated and
946 not requesting for authentication or if the authentication has just been renewed
947 * The returned value of :meth:`not_authenticated` otherwise
948 :rtype: django.http.HttpResponse
949 """
950 # if authenticated and successfully renewed authentication if needed
951 if self.request.session.get("authenticated") and (not self.renew or self.renewed):
952 return self.authenticated()
953 else:
954 return self.not_authenticated()
957class Auth(CsrfExemptView):
958 """
959 A simple view to validate username/password/service tuple
961 csrf is disable as it is intended to be used by programs. Security is assured by a shared
962 secret between the programs dans django-cas-server.
963 """
965 @staticmethod
966 def post(request):
967 """
968 method called on POST request on this view
970 :param django.http.HttpRequest request: The current request object
971 :return: ``HttpResponse(u"yes\\n")`` if the POSTed tuple (username, password, service)
972 if valid (i.e. (username, password) is valid dans username is allowed on service).
973 ``HttpResponse(u"no\\n…")`` otherwise, with possibly an error message on the second
974 line.
975 :rtype: django.http.HttpResponse
976 """
977 username = request.POST.get('username')
978 password = request.POST.get('password')
979 service = request.POST.get('service')
980 secret = request.POST.get('secret')
982 if not settings.CAS_AUTH_SHARED_SECRET:
983 return HttpResponse(
984 "no\nplease set CAS_AUTH_SHARED_SECRET",
985 content_type="text/plain; charset=utf-8"
986 )
987 if secret != settings.CAS_AUTH_SHARED_SECRET:
988 return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
989 if not username or not password or not service:
990 return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
991 form = forms.UserCredential(
992 request.POST,
993 initial={
994 'service': service,
995 'method': 'POST',
996 'warn': False
997 }
998 )
999 if form.is_valid():
1000 try:
1001 user = models.User.objects.get_or_create(
1002 username=form.cleaned_data['username'],
1003 session_key=request.session.session_key
1004 )[0]
1005 user.save()
1006 # is the service allowed
1007 service_pattern = ServicePattern.validate(service)
1008 # is the current user allowed on this service
1009 service_pattern.check_user(user)
1010 if not request.session.get("authenticated"):
1011 user.delete()
1012 return HttpResponse(u"yes\n", content_type="text/plain; charset=utf-8")
1013 except (ServicePattern.DoesNotExist, models.ServicePatternException):
1014 return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
1015 else:
1016 return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
1019class Validate(View):
1020 """service ticket validation"""
1021 @staticmethod
1022 def get(request):
1023 """
1024 method called on GET request on this view
1026 :param django.http.HttpRequest request: The current request object
1027 :return:
1028 * ``HttpResponse("yes\\nusername")`` if submited (service, ticket) is valid
1029 * else ``HttpResponse("no\\n")``
1030 :rtype: django.http.HttpResponse
1031 """
1032 # store wanted GET parameters
1033 service = request.GET.get('service')
1034 ticket = request.GET.get('ticket')
1035 renew = True if request.GET.get('renew') else False
1036 # service and ticket parameters are mandatory
1037 if service and ticket:
1038 try:
1039 # search for the ticket, associated at service that is not yet validated but is
1040 # still valid
1041 ticket = ServiceTicket.get(ticket, renew, service)
1042 logger.info(
1043 "Validate: Service ticket %s validated, user %s authenticated on service %s" % (
1044 ticket.value,
1045 ticket.user.username,
1046 ticket.service
1047 )
1048 )
1049 return HttpResponse(
1050 u"yes\n%s\n" % ticket.username(),
1051 content_type="text/plain; charset=utf-8"
1052 )
1053 except ServiceTicket.DoesNotExist:
1054 logger.warning(
1055 (
1056 "Validate: Service ticket %s not found or "
1057 "already validated, auth to %s failed"
1058 ) % (
1059 ticket,
1060 service
1061 )
1062 )
1063 return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
1064 else:
1065 logger.warning("Validate: service or ticket missing")
1066 return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
1069@python_2_unicode_compatible
1070class ValidationBaseError(Exception):
1071 """Base class for both saml and cas validation error"""
1073 #: The error code
1074 code = None
1075 #: The error message
1076 msg = None
1078 def __init__(self, code, msg=""):
1079 self.code = code
1080 self.msg = msg
1081 super(ValidationBaseError, self).__init__(code)
1083 def __str__(self):
1084 return u"%s" % self.msg
1086 def render(self, request):
1087 """
1088 render the error template for the exception
1090 :param django.http.HttpRequest request: The current request object:
1091 :return: the rendered ``cas_server/serviceValidateError.xml`` template
1092 :rtype: django.http.HttpResponse
1093 """
1094 return render(
1095 request,
1096 self.template,
1097 self.context(), content_type="text/xml; charset=utf-8"
1098 )
1101class ValidateError(ValidationBaseError):
1102 """handle service validation error"""
1104 #: template to be render for the error
1105 template = "cas_server/serviceValidateError.xml"
1107 def context(self):
1108 """
1109 content to use to render :attr:`template`
1111 :return: A dictionary to contextualize :attr:`template`
1112 :rtype: dict
1113 """
1114 return {'code': self.code, 'msg': self.msg}
1117class ValidateService(View):
1118 """service ticket validation [CAS 2.0] and [CAS 3.0]"""
1119 #: Current :class:`django.http.HttpRequest` object
1120 request = None
1121 #: The service GET parameter
1122 service = None
1123 #: the ticket GET parameter
1124 ticket = None
1125 #: the pgtUrl GET parameter
1126 pgt_url = None
1127 #: the renew GET parameter
1128 renew = None
1129 #: specify if ProxyTicket are allowed by the view. Hence we user the same view for
1130 #: ``/serviceValidate`` and ``/proxyValidate`` juste changing the parameter.
1131 allow_proxy_ticket = False
1133 def get(self, request):
1134 """
1135 method called on GET request on this view
1137 :param django.http.HttpRequest request: The current request object:
1138 :return: The rendering of ``cas_server/serviceValidate.xml`` if no errors is raised,
1139 the rendering or ``cas_server/serviceValidateError.xml`` otherwise.
1140 :rtype: django.http.HttpResponse
1141 """
1142 # define the class parameters
1143 self.request = request
1144 self.service = request.GET.get('service')
1145 self.ticket = request.GET.get('ticket')
1146 self.pgt_url = request.GET.get('pgtUrl')
1147 self.renew = True if request.GET.get('renew') else False
1149 # service and ticket parameter are mandatory
1150 if not self.service or not self.ticket:
1151 logger.warning("ValidateService: missing ticket or service")
1152 return ValidateError(
1153 u'INVALID_REQUEST',
1154 u"you must specify a service and a ticket"
1155 ).render(request)
1156 else:
1157 try:
1158 # search the ticket in the database
1159 self.ticket, proxies = self.process_ticket()
1160 # prepare template rendering context
1161 params = {
1162 'username': self.ticket.username(),
1163 'attributes': self.ticket.attributs_flat(),
1164 'proxies': proxies,
1165 'auth_date': self.ticket.user.last_login.replace(microsecond=0).isoformat(),
1166 'is_new_login': 'true' if self.ticket.renew else 'false'
1167 }
1168 # if pgtUrl is set, require https or localhost
1169 if self.pgt_url and (
1170 self.pgt_url.startswith("https://") or
1171 re.match(r"^http://(127\.0\.0\.1|localhost)(:[0-9]+)?(/.*)?$", self.pgt_url)
1172 ):
1173 return self.process_pgturl(params)
1174 else:
1175 logger.info(
1176 "ValidateService: ticket %s validated for user %s on service %s." % (
1177 self.ticket.value,
1178 self.ticket.user.username,
1179 self.ticket.service
1180 )
1181 )
1182 logger.debug(
1183 "ValidateService: User attributs are:\n%s" % (
1184 pprint.pformat(self.ticket.attributs),
1185 )
1186 )
1187 return render(
1188 request,
1189 "cas_server/serviceValidate.xml",
1190 params,
1191 content_type="text/xml; charset=utf-8"
1192 )
1193 except ValidateError as error:
1194 logger.warning(
1195 "ValidateService: validation error: %s %s" % (error.code, error.msg)
1196 )
1197 return error.render(request)
1199 def process_ticket(self):
1200 """
1201 fetch the ticket against the database and check its validity
1203 :raises ValidateError: if the ticket is not found or not valid, potentially for that
1204 service
1205 :returns: A couple (ticket, proxies list)
1206 :rtype: :obj:`tuple`
1207 """
1208 try:
1209 proxies = []
1210 if self.allow_proxy_ticket:
1211 ticket = models.Ticket.get(self.ticket, self.renew)
1212 else:
1213 ticket = models.ServiceTicket.get(self.ticket, self.renew)
1214 try:
1215 for prox in ticket.proxies.all():
1216 proxies.append(prox.url)
1217 except AttributeError:
1218 pass
1219 if ticket.service != self.service:
1220 raise ValidateError(u'INVALID_SERVICE', self.service)
1221 return ticket, proxies
1222 except Ticket.DoesNotExist:
1223 raise ValidateError(u'INVALID_TICKET', self.ticket)
1224 except (ServiceTicket.DoesNotExist, ProxyTicket.DoesNotExist):
1225 raise ValidateError(u'INVALID_TICKET', 'ticket not found')
1227 def process_pgturl(self, params):
1228 """
1229 Handle PGT request
1231 :param dict params: A template context dict
1232 :raises ValidateError: if pgtUrl is invalid or if TLS validation of the pgtUrl fails
1233 :return: The rendering of ``cas_server/serviceValidate.xml``, using ``params``
1234 :rtype: django.http.HttpResponse
1235 """
1236 try:
1237 pattern = ServicePattern.validate(self.pgt_url)
1238 if pattern.proxy_callback:
1239 proxyid = utils.gen_pgtiou()
1240 pticket = ProxyGrantingTicket.objects.create(
1241 user=self.ticket.user,
1242 service=self.pgt_url,
1243 service_pattern=pattern,
1244 single_log_out=pattern.single_log_out
1245 )
1246 url = utils.update_url(self.pgt_url, {'pgtIou': proxyid, 'pgtId': pticket.value})
1247 try:
1248 ret = requests.get(url, verify=settings.CAS_PROXY_CA_CERTIFICATE_PATH)
1249 if ret.status_code == 200:
1250 params['proxyGrantingTicket'] = proxyid
1251 else:
1252 pticket.delete()
1253 logger.info(
1254 (
1255 "ValidateService: ticket %s validated for user %s on service %s. "
1256 "Proxy Granting Ticket transmited to %s."
1257 ) % (
1258 self.ticket.value,
1259 self.ticket.user.username,
1260 self.ticket.service,
1261 self.pgt_url
1262 )
1263 )
1264 logger.debug(
1265 "ValidateService: User attributs are:\n%s" % (
1266 pprint.pformat(self.ticket.attributs),
1267 )
1268 )
1269 return render(
1270 self.request,
1271 "cas_server/serviceValidate.xml",
1272 params,
1273 content_type="text/xml; charset=utf-8"
1274 )
1275 except requests.exceptions.RequestException as error:
1276 error = utils.unpack_nested_exception(error)
1277 raise ValidateError(
1278 u'INVALID_PROXY_CALLBACK',
1279 u"%s: %s" % (type(error), str(error))
1280 )
1281 else:
1282 raise ValidateError(
1283 u'INVALID_PROXY_CALLBACK',
1284 u"callback url not allowed by configuration"
1285 )
1286 except ServicePattern.DoesNotExist:
1287 raise ValidateError(
1288 u'INVALID_PROXY_CALLBACK',
1289 u'callback url not allowed by configuration'
1290 )
1293class Proxy(View):
1294 """proxy ticket service"""
1296 #: Current :class:`django.http.HttpRequest` object
1297 request = None
1298 #: A ProxyGrantingTicket from the pgt GET parameter
1299 pgt = None
1300 #: the targetService GET parameter
1301 target_service = None
1303 def get(self, request):
1304 """
1305 method called on GET request on this view
1307 :param django.http.HttpRequest request: The current request object:
1308 :return: The returned value of :meth:`process_proxy` if no error is raised,
1309 else the rendering of ``cas_server/serviceValidateError.xml``.
1310 :rtype: django.http.HttpResponse
1311 """
1312 self.request = request
1313 self.pgt = request.GET.get('pgt')
1314 self.target_service = request.GET.get('targetService')
1315 try:
1316 # pgt and targetService parameters are mandatory
1317 if self.pgt and self.target_service:
1318 return self.process_proxy()
1319 else:
1320 raise ValidateError(
1321 u'INVALID_REQUEST',
1322 u"you must specify and pgt and targetService"
1323 )
1324 except ValidateError as error:
1325 logger.warning("Proxy: validation error: %s %s" % (error.code, error.msg))
1326 return error.render(request)
1328 def process_proxy(self):
1329 """
1330 handle PT request
1332 :raises ValidateError: if the PGT is not found, or the target service not allowed or
1333 the user not allowed on the tardet service.
1334 :return: The rendering of ``cas_server/proxy.xml``
1335 :rtype: django.http.HttpResponse
1336 """
1337 try:
1338 # is the target service allowed
1339 pattern = ServicePattern.validate(self.target_service)
1340 # to get a proxy ticket require that the service allow it
1341 if not pattern.proxy:
1342 raise ValidateError(
1343 u'UNAUTHORIZED_SERVICE',
1344 u'the service %s does not allow proxy tickets' % self.target_service
1345 )
1346 # is the proxy granting ticket valid
1347 ticket = ProxyGrantingTicket.get(self.pgt)
1348 # is the pgt user allowed on the target service
1349 pattern.check_user(ticket.user)
1350 pticket = ticket.user.get_ticket(
1351 ProxyTicket,
1352 self.target_service,
1353 pattern,
1354 renew=False
1355 )
1356 models.Proxy.objects.create(proxy_ticket=pticket, url=ticket.service)
1357 logger.info(
1358 "Proxy ticket created for user %s on service %s." % (
1359 ticket.user.username,
1360 self.target_service
1361 )
1362 )
1363 return render(
1364 self.request,
1365 "cas_server/proxy.xml",
1366 {'ticket': pticket.value},
1367 content_type="text/xml; charset=utf-8"
1368 )
1369 except (Ticket.DoesNotExist, ProxyGrantingTicket.DoesNotExist):
1370 raise ValidateError(u'INVALID_TICKET', u'PGT %s not found' % self.pgt)
1371 except ServicePattern.DoesNotExist:
1372 raise ValidateError(u'UNAUTHORIZED_SERVICE', self.target_service)
1373 except (models.BadUsername, models.BadFilter, models.UserFieldNotDefined):
1374 raise ValidateError(
1375 u'UNAUTHORIZED_USER',
1376 u'User %s not allowed on %s' % (ticket.user.username, self.target_service)
1377 )
1380class SamlValidateError(ValidationBaseError):
1381 """handle saml validation error"""
1383 #: template to be render for the error
1384 template = "cas_server/samlValidateError.xml"
1386 def context(self):
1387 """
1388 :return: A dictionary to contextualize :attr:`template`
1389 :rtype: dict
1390 """
1391 return {
1392 'code': self.code,
1393 'msg': self.msg,
1394 'IssueInstant': timezone.now().isoformat(),
1395 'ResponseID': utils.gen_saml_id()
1396 }
1399class SamlValidate(CsrfExemptView):
1400 """SAML ticket validation"""
1401 request = None
1402 target = None
1403 ticket = None
1404 root = None
1406 def post(self, request, *args, **kwargs):
1407 """
1408 method called on POST request on this view
1410 :param django.http.HttpRequest request: The current request object
1411 :return: the rendering of ``cas_server/samlValidate.xml`` if no error is raised,
1412 else the rendering of ``cas_server/samlValidateError.xml``.
1413 :rtype: django.http.HttpResponse
1414 """
1415 self.request = request
1416 self.target = request.GET.get('TARGET')
1417 self.root = etree.fromstring(request.body)
1418 try:
1419 self.ticket = self.process_ticket()
1420 expire_instant = (self.ticket.creation +
1421 timedelta(seconds=self.ticket.VALIDITY)).isoformat()
1422 params = {
1423 'IssueInstant': timezone.now().isoformat(),
1424 'expireInstant': expire_instant,
1425 'Recipient': self.target,
1426 'ResponseID': utils.gen_saml_id(),
1427 'username': self.ticket.username(),
1428 'attributes': self.ticket.attributs_flat(),
1429 'auth_date': self.ticket.user.last_login.replace(microsecond=0).isoformat(),
1430 'is_new_login': 'true' if self.ticket.renew else 'false'
1432 }
1433 logger.info(
1434 "SamlValidate: ticket %s validated for user %s on service %s." % (
1435 self.ticket.value,
1436 self.ticket.user.username,
1437 self.ticket.service
1438 )
1439 )
1440 logger.debug(
1441 "SamlValidate: User attributes are:\n%s" % pprint.pformat(self.ticket.attributs)
1442 )
1444 return render(
1445 request,
1446 "cas_server/samlValidate.xml",
1447 params,
1448 content_type="text/xml; charset=utf-8"
1449 )
1450 except SamlValidateError as error:
1451 logger.warning("SamlValidate: validation error: %s %s" % (error.code, error.msg))
1452 return error.render(request)
1454 def process_ticket(self):
1455 """
1456 validate ticket from SAML XML body
1458 :raises: SamlValidateError: if the ticket is not found or not valid, or if we fail
1459 to parse the posted XML.
1460 :return: a ticket object
1461 :rtype: :class:`models.Ticket<cas_server.models.Ticket>`
1462 """
1463 try:
1464 auth_req = self.root.getchildren()[1].getchildren()[0]
1465 ticket = auth_req.getchildren()[0].text
1466 ticket = models.Ticket.get(ticket)
1467 if ticket.service != self.target:
1468 raise SamlValidateError(
1469 u'AuthnFailed',
1470 u'TARGET %s does not match ticket service' % self.target
1471 )
1472 return ticket
1473 except (IndexError, KeyError):
1474 raise SamlValidateError(u'VersionMismatch')
1475 except Ticket.DoesNotExist:
1476 raise SamlValidateError(
1477 u'AuthnFailed',
1478 u'ticket %s should begin with PT- or ST-' % ticket
1479 )
1480 except (ServiceTicket.DoesNotExist, ProxyTicket.DoesNotExist):
1481 raise SamlValidateError(u'AuthnFailed', u'ticket %s not found' % ticket)