views.py 60.1 KB
Newer Older
1
# -*- coding: utf-8 -*-
Valentin Samir's avatar
Valentin Samir committed
2 3 4 5 6 7 8 9 10
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for
# more details.
#
# You should have received a copy of the GNU General Public License version 3
# along with this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
Valentin Samir's avatar
Valentin Samir committed
11
# (c) 2015-2016 Valentin Samir
Valentin Samir's avatar
Valentin Samir committed
12
"""views for the app"""
13
from .default_settings import settings, SessionStore
14

Valentin Samir's avatar
Valentin Samir committed
15
from django.shortcuts import render, redirect
Valentin Samir's avatar
Valentin Samir committed
16
from django.http import HttpResponse, HttpResponseRedirect
Valentin Samir's avatar
Valentin Samir committed
17
from django.contrib import messages
18
from django.utils.decorators import method_decorator
Valentin Samir's avatar
Valentin Samir committed
19
from django.utils.translation import ugettext as _
Valentin Samir's avatar
Valentin Samir committed
20
from django.utils import timezone
21
from django.views.decorators.csrf import csrf_exempt
22
from django.middleware.csrf import CsrfViewMiddleware
23
from django.views.generic import View
24
from django.utils.encoding import python_2_unicode_compatible
Valentin Samir's avatar
Valentin Samir committed
25
from django.utils.safestring import mark_safe
26 27 28 29
try:
    from django.urls import reverse
except ImportError:
    from django.core.urlresolvers import reverse
Valentin Samir's avatar
Valentin Samir committed
30

31
import re
32 33
import logging
import pprint
Valentin Samir's avatar
Valentin Samir committed
34
import requests
Valentin Samir's avatar
Valentin Samir committed
35
from lxml import etree
Valentin Samir's avatar
Valentin Samir committed
36
from datetime import timedelta
Valentin Samir's avatar
Valentin Samir committed
37

Valentin Samir's avatar
Valentin Samir committed
38 39 40
import cas_server.utils as utils
import cas_server.forms as forms
import cas_server.models as models
Valentin Samir's avatar
Valentin Samir committed
41

Valentin Samir's avatar
Valentin Samir committed
42
from .utils import json_response
Valentin Samir's avatar
Valentin Samir committed
43
from .models import Ticket, ServiceTicket, ProxyTicket, ProxyGrantingTicket
44
from .models import ServicePattern, FederatedIendityProvider, FederatedUser
Valentin Samir's avatar
Valentin Samir committed
45
from .federate import CASFederateValidateUser
46

47 48
logger = logging.getLogger(__name__)

Valentin Samir's avatar
Valentin Samir committed
49

Valentin Samir's avatar
Valentin Samir committed
50
class LogoutMixin(object):
51
    """destroy CAS session utils"""
52

Valentin Samir's avatar
Valentin Samir committed
53
    def logout(self, all_session=False):
Valentin Samir's avatar
Valentin Samir committed
54 55 56 57 58 59 60 61 62
        """
            effectively destroy a CAS session

            :param boolean all_session: If ``True`` destroy all the user sessions, otherwise
                destroy the current user session.
            :return: The number of destroyed sessions
            :rtype: int
        """
        # initialize the counter of the number of destroyed sesisons
63
        session_nb = 0
Valentin Samir's avatar
Valentin Samir committed
64
        # save the current user username before flushing the session
65 66
        username = self.request.session.get("username")
        if username:
Valentin Samir's avatar
Valentin Samir committed
67
            if all_session:
68
                logger.info("Logging out user %s from all sessions." % username)
69 70
            else:
                logger.info("Logging out user %s." % username)
71 72
        users = []
        # try to get the user from the current session
73
        try:
74 75 76 77 78
            users.append(
                models.User.objects.get(
                    username=username,
                    session_key=self.request.session.session_key
                )
Valentin Samir's avatar
Valentin Samir committed
79
            )
80 81
        except models.User.DoesNotExist:
            # if user not found in database, flush the session anyway
82
            self.request.session.flush()
83 84 85

        # If all_session is set, search all of the user sessions
        if all_session:
86 87 88 89 90 91 92
            users.extend(
                models.User.objects.filter(
                    username=username
                ).exclude(
                    session_key=self.request.session.session_key
                )
            )
93 94 95 96 97 98 99

        # Iterate over all user sessions that have to be logged out
        for user in users:
            # get the user session
            session = SessionStore(session_key=user.session_key)
            # flush the session
            session.flush()
Valentin Samir's avatar
Valentin Samir committed
100
            # send SLO requests
101
            user.logout(self.request)
Valentin Samir's avatar
Valentin Samir committed
102
            # delete the user
103
            user.delete()
Valentin Samir's avatar
Valentin Samir committed
104
            # increment the destroyed session counter
105
            session_nb += 1
Valentin Samir's avatar
Valentin Samir committed
106 107
        if username:
            logger.info("User %s logged out" % username)
108
        return session_nb
109

Valentin Samir's avatar
Valentin Samir committed
110

111 112 113 114 115 116 117 118 119 120 121 122 123
class CsrfExemptView(View):
    """base class for csrf exempt class views"""

    @method_decorator(csrf_exempt)  # csrf is disabled for allowing SLO requests reception
    def dispatch(self, request, *args, **kwargs):
        """
            dispatch different http request to the methods of the same name

            :param django.http.HttpRequest request: The current request object
        """
        return super(CsrfExemptView, self).dispatch(request, *args, **kwargs)


Valentin Samir's avatar
Valentin Samir committed
124 125 126
class LogoutView(View, LogoutMixin):
    """destroy CAS session (logout) view"""

Valentin Samir's avatar
Valentin Samir committed
127
    #: current :class:`django.http.HttpRequest` object
Valentin Samir's avatar
Valentin Samir committed
128
    request = None
Valentin Samir's avatar
Valentin Samir committed
129
    #: service GET parameter
Valentin Samir's avatar
Valentin Samir committed
130
    service = None
Valentin Samir's avatar
Valentin Samir committed
131 132 133 134 135
    #: url GET paramet
    url = None
    #: ``True`` if the HTTP_X_AJAX http header is sent and ``settings.CAS_ENABLE_AJAX_AUTH``
    #: is ``True``, ``False`` otherwise.
    ajax = None
Valentin Samir's avatar
Valentin Samir committed
136

137
    def init_get(self, request):
Valentin Samir's avatar
Valentin Samir committed
138 139 140 141 142
        """
            Initialize the :class:`LogoutView` attributes on GET request

            :param django.http.HttpRequest request: The current request object
        """
143 144
        self.request = request
        self.service = request.GET.get('service')
145
        self.url = request.GET.get('url')
146
        self.ajax = settings.CAS_ENABLE_AJAX_AUTH and 'HTTP_X_AJAX' in request.META
147 148

    def get(self, request, *args, **kwargs):
Valentin Samir's avatar
Valentin Samir committed
149
        """
150
            method called on GET request on this view
Valentin Samir's avatar
Valentin Samir committed
151 152 153

            :param django.http.HttpRequest request: The current request object
        """
154
        logger.info("logout requested")
Valentin Samir's avatar
Valentin Samir committed
155
        # initialize the class attributes
156
        self.init_get(request)
Valentin Samir's avatar
Valentin Samir committed
157 158
        # if CAS federation mode is enable, bakup the provider before flushing the sessions
        if settings.CAS_FEDERATE:
159 160 161 162 163 164
            try:
                user = FederatedUser.get_from_federated_username(
                    self.request.session.get("username")
                )
                auth = CASFederateValidateUser(user.provider, service_url="")
            except FederatedUser.DoesNotExist:
165
                auth = None
166
        session_nb = self.logout(self.request.GET.get("all"))
Valentin Samir's avatar
Valentin Samir committed
167 168
        # if CAS federation mode is enable, redirect to user CAS logout page, appending the
        # current querystring
Valentin Samir's avatar
Valentin Samir committed
169
        if settings.CAS_FEDERATE:
170
            if auth is not None:
171
                params = utils.copy_params(request.GET, ignore={"forget_provider"})
172
                url = auth.get_logout_url()
173 174 175 176
                response = HttpResponseRedirect(utils.update_url(url, params))
                if request.GET.get("forget_provider"):
                    response.delete_cookie("remember_provider")
                return response
177 178
        # if service is set, redirect to service after logout
        if self.service:
Valentin Samir's avatar
Valentin Samir committed
179
            list(messages.get_messages(request))  # clean messages before leaving the django app
180
            return HttpResponseRedirect(self.service)
Valentin Samir's avatar
Valentin Samir committed
181
        # if service is not set but url is set, redirect to url after logout
182
        elif self.url:
Valentin Samir's avatar
Valentin Samir committed
183
            list(messages.get_messages(request))  # clean messages before leaving the django app
184
            return HttpResponseRedirect(self.url)
185
        else:
Valentin Samir's avatar
Valentin Samir committed
186
            # build logout message depending of the number of sessions the user logs out
187
            if session_nb == 1:
Valentin Samir's avatar
Valentin Samir committed
188
                logout_msg = mark_safe(_(
189 190
                    "<h3>Logout successful</h3>"
                    "You have successfully logged out from the Central Authentication Service. "
191
                    "For security reasons, close your web browser."
Valentin Samir's avatar
Valentin Samir committed
192
                ))
193
            elif session_nb > 1:
Valentin Samir's avatar
Valentin Samir committed
194
                logout_msg = mark_safe(_(
195
                    "<h3>Logout successful</h3>"
Valentin Samir's avatar
Valentin Samir committed
196
                    "You have successfully logged out from %d sessions of the Central "
197
                    "Authentication Service. "
198
                    "For security reasons, close your web browser."
Valentin Samir's avatar
Valentin Samir committed
199
                ) % session_nb)
200
            else:
Valentin Samir's avatar
Valentin Samir committed
201
                logout_msg = mark_safe(_(
202 203
                    "<h3>Logout successful</h3>"
                    "You were already logged out from the Central Authentication Service. "
204
                    "For security reasons, close your web browser."
Valentin Samir's avatar
Valentin Samir committed
205
                ))
206

Valentin Samir's avatar
Valentin Samir committed
207 208
            # depending of settings, redirect to the login page with a logout message or display
            # the logout page. The default is to display tge logout page.
209
            if settings.CAS_REDIRECT_TO_LOGIN_AFTER_LOGOUT:
210
                messages.add_message(request, messages.SUCCESS, logout_msg)
211 212
                if self.ajax:
                    url = reverse("cas_server:login")
213 214 215 216 217 218
                    data = {
                        'status': 'success',
                        'detail': 'logout',
                        'url': url,
                        'session_nb': session_nb
                    }
Valentin Samir's avatar
Valentin Samir committed
219
                    return json_response(request, data)
220 221
                else:
                    return redirect("cas_server:login")
222
            else:
223
                if self.ajax:
224
                    data = {'status': 'success', 'detail': 'logout', 'session_nb': session_nb}
Valentin Samir's avatar
Valentin Samir committed
225
                    return json_response(request, data)
226
                else:
227 228 229
                    return render(
                        request,
                        settings.CAS_LOGOUT_TEMPLATE,
230
                        utils.context({'logout_msg': logout_msg})
231
                    )
232

Valentin Samir's avatar
Valentin Samir committed
233

234 235
class FederateAuth(CsrfExemptView):
    """
236
        view to authenticated user against a backend CAS then CAS_FEDERATE is True
Valentin Samir's avatar
Valentin Samir committed
237

238 239
        csrf is disabled for allowing SLO requests reception.
    """
240

241 242 243
    #: current URL used as service URL by the CAS client
    service_url = None

244
    def get_cas_client(self, request, provider, renew=False):
Valentin Samir's avatar
Valentin Samir committed
245 246 247 248 249 250
        """
            return a CAS client object matching provider

            :param django.http.HttpRequest request: The current request object
            :param cas_server.models.FederatedIendityProvider provider: the user identity provider
            :return: The user CAS client object
Valentin Samir's avatar
Valentin Samir committed
251 252
            :rtype: :class:`federate.CASFederateValidateUser
                <cas_server.federate.CASFederateValidateUser>`
Valentin Samir's avatar
Valentin Samir committed
253 254
        """
        # compute the current url, ignoring ticket dans provider GET parameters
255
        service_url = utils.get_current_url(request, {"ticket", "provider"})
256
        self.service_url = service_url
257
        return CASFederateValidateUser(provider, service_url, renew=renew)
258

Valentin Samir's avatar
Valentin Samir committed
259
    def post(self, request, provider=None):
Valentin Samir's avatar
Valentin Samir committed
260 261 262 263 264 265 266
        """
            method called on POST request

            :param django.http.HttpRequest request: The current request object
            :param unicode provider: Optional parameter. The user provider suffix.
        """
        # if settings.CAS_FEDERATE is not True redirect to the login page
267
        if not settings.CAS_FEDERATE:
268
            logger.warning("CAS_FEDERATE is False, set it to True to use federation")
269
            return redirect("cas_server:login")
Valentin Samir's avatar
Valentin Samir committed
270 271
        # POST with a provider suffix, this is probably an SLO request. csrf is disabled for
        # allowing SLO requests reception
272 273
        try:
            provider = FederatedIendityProvider.objects.get(suffix=provider)
274 275 276
            auth = self.get_cas_client(request, provider)
            try:
                auth.clean_sessions(request.POST['logoutRequest'])
277
            except (KeyError, AttributeError):
278 279 280
                pass
            return HttpResponse("ok")
        # else, a User is trying to log in using an identity provider
281
        except FederatedIendityProvider.DoesNotExist:
282 283
            # Manually checking for csrf to protect the code below
            reason = CsrfViewMiddleware().process_view(request, None, (), {})
284
            if reason is not None:  # pragma: no cover (csrf checks are disabled during tests)
285 286 287 288 289
                return reason  # Failed the test, stop here.
            form = forms.FederateSelect(request.POST)
            if form.is_valid():
                params = utils.copy_params(
                    request.POST,
290
                    ignore={"provider", "csrfmiddlewaretoken", "ticket", "lt"}
291
                )
Valentin Samir's avatar
Valentin Samir committed
292 293
                if params.get("renew") == "False":
                    del params["renew"]
294 295
                url = utils.reverse_params(
                    "cas_server:federateAuth",
296
                    kwargs=dict(provider=form.cleaned_data["provider"].suffix),
297 298
                    params=params
                )
299
                return HttpResponseRedirect(url)
300 301
            else:
                return redirect("cas_server:login")
Valentin Samir's avatar
Valentin Samir committed
302 303

    def get(self, request, provider=None):
Valentin Samir's avatar
Valentin Samir committed
304 305 306
        """
            method called on GET request

307
            :param django.http.HttpRequestself. request: The current request object
Valentin Samir's avatar
Valentin Samir committed
308 309 310
            :param unicode provider: Optional parameter. The user provider suffix.
        """
        # if settings.CAS_FEDERATE is not True redirect to the login page
311
        if not settings.CAS_FEDERATE:
312
            logger.warning("CAS_FEDERATE is False, set it to True to use federation")
313
            return redirect("cas_server:login")
314
        renew = bool(request.GET.get('renew') and request.GET['renew'] != "False")
Valentin Samir's avatar
Valentin Samir committed
315 316
        # Is the user is already authenticated, no need to request authentication to the user
        # identity provider.
317
        if self.request.session.get("authenticated") and not renew:
318
            logger.warning("User already authenticated, dropping federated authentication request")
319
            return redirect("cas_server:login")
320
        try:
Valentin Samir's avatar
Valentin Samir committed
321
            # get the identity provider from its suffix
322
            provider = FederatedIendityProvider.objects.get(suffix=provider)
Valentin Samir's avatar
Valentin Samir committed
323
            # get a CAS client for the user identity provider
324
            auth = self.get_cas_client(request, provider, renew)
Valentin Samir's avatar
Valentin Samir committed
325
            # if no ticket submited, redirect to the identity provider CAS login page
326
            if 'ticket' not in request.GET:
327
                logger.info("Trying to authenticate %s again" % auth.provider.server_url)
Valentin Samir's avatar
Valentin Samir committed
328
                return HttpResponseRedirect(auth.get_login_url())
329 330
            else:
                ticket = request.GET['ticket']
331 332 333 334 335 336 337 338
                try:
                    # if the ticket validation succeed
                    if auth.verify_ticket(ticket):
                        logger.info(
                            "Got a valid ticket for %s from %s" % (
                                auth.username,
                                auth.provider.server_url
                            )
339
                        )
340
                        params = utils.copy_params(request.GET, ignore={"ticket", "remember"})
341 342 343 344 345 346
                        request.session["federate_username"] = auth.federated_username
                        request.session["federate_ticket"] = ticket
                        auth.register_slo(
                            auth.federated_username,
                            request.session.session_key,
                            ticket
347
                        )
348 349 350
                        # redirect to the the login page for the user to become authenticated
                        # thanks to the `federate_username` and `federate_ticket` session parameters
                        url = utils.reverse_params("cas_server:login", params)
351 352 353 354 355 356 357
                        response = HttpResponseRedirect(url)
                        # If the user has checked "remember my identity provider" store it in a
                        # cookie
                        if request.GET.get("remember"):
                            max_age = settings.CAS_FEDERATE_REMEMBER_TIMEOUT
                            utils.set_cookie(
                                response,
358
                                "remember_provider",
359 360 361 362
                                provider.suffix,
                                max_age
                            )
                        return response
363 364 365
                    # else redirect to the identity provider CAS login page
                    else:
                        logger.info(
366
                            (
367 368
                                "Got an invalid ticket %s from %s for service %s. "
                                "Retrying authentication"
369 370 371 372
                            ) % (
                                ticket,
                                auth.provider.server_url,
                                self.service_url
373 374 375 376 377 378 379 380 381 382
                            )
                        )
                        return HttpResponseRedirect(auth.get_login_url())
                # both xml.etree.ElementTree and lxml.etree exceptions inherit from SyntaxError
                except SyntaxError as error:
                    messages.add_message(
                        request,
                        messages.ERROR,
                        _(
                            u"Invalid response from your identity provider CAS upon "
383 384
                            u"ticket %(ticket)s validation: %(error)r"
                        ) % {'ticket': ticket, 'error': error}
385
                    )
386
                    response = redirect("cas_server:login")
387
                    response.delete_cookie("remember_provider")
388
                    return response
389
        except FederatedIendityProvider.DoesNotExist:
390
            logger.warning("Identity provider suffix %s not found" % provider)
Valentin Samir's avatar
Valentin Samir committed
391
            # if the identity provider is not found, redirect to the login page
392
            return redirect("cas_server:login")
Valentin Samir's avatar
Valentin Samir committed
393 394


Valentin Samir's avatar
Valentin Samir committed
395
class LoginView(View, LogoutMixin):
Valentin Samir's avatar
Valentin Samir committed
396
    """credential requestor / acceptor"""
397 398 399 400

    # pylint: disable=too-many-instance-attributes
    # Nine is reasonable in this case.

Valentin Samir's avatar
Valentin Samir committed
401
    #: The current :class:`models.User<cas_server.models.User>` object
Valentin Samir's avatar
Valentin Samir committed
402
    user = None
Valentin Samir's avatar
Valentin Samir committed
403
    #: The form to display to the user
Valentin Samir's avatar
Valentin Samir committed
404
    form = None
405

Valentin Samir's avatar
Valentin Samir committed
406
    #: current :class:`django.http.HttpRequest` object
407
    request = None
Valentin Samir's avatar
Valentin Samir committed
408
    #: service GET/POST parameter
409
    service = None
Valentin Samir's avatar
Valentin Samir committed
410
    #: ``True`` if renew GET/POST parameter is present and not "False"
411
    renew = None
Valentin Samir's avatar
Valentin Samir committed
412 413 414
    #: the warn GET/POST parameter
    warn = None
    #: the gateway GET/POST parameter
415
    gateway = None
Valentin Samir's avatar
Valentin Samir committed
416
    #: the method GET/POST parameter
417
    method = None
Valentin Samir's avatar
Valentin Samir committed
418 419 420

    #: ``True`` if the HTTP_X_AJAX http header is sent and ``settings.CAS_ENABLE_AJAX_AUTH``
    #: is ``True``, ``False`` otherwise.
421
    ajax = None
422

Valentin Samir's avatar
Valentin Samir committed
423
    #: ``True`` if the user has just authenticated
Valentin Samir's avatar
Valentin Samir committed
424
    renewed = False
Valentin Samir's avatar
Valentin Samir committed
425
    #: ``True`` if renew GET/POST parameter is present and not "False"
426
    warned = False
427

Valentin Samir's avatar
Valentin Samir committed
428 429
    #: The :class:`FederateAuth` transmited username (only used if ``settings.CAS_FEDERATE``
    #: is ``True``)
430
    username = None
Valentin Samir's avatar
Valentin Samir committed
431 432
    #: The :class:`FederateAuth` transmited ticket (only used if ``settings.CAS_FEDERATE`` is
    #: ``True``)
433
    ticket = None
434

435 436 437 438 439 440 441 442
    INVALID_LOGIN_TICKET = 1
    USER_LOGIN_OK = 2
    USER_LOGIN_FAILURE = 3
    USER_ALREADY_LOGGED = 4
    USER_AUTHENTICATED = 5
    USER_NOT_AUTHENTICATED = 6

    def init_post(self, request):
Valentin Samir's avatar
Valentin Samir committed
443 444 445 446 447
        """
            Initialize POST received parameters

            :param django.http.HttpRequest request: The current request object
        """
448 449
        self.request = request
        self.service = request.POST.get('service')
Valentin Samir's avatar
Valentin Samir committed
450
        self.renew = bool(request.POST.get('renew') and request.POST['renew'] != "False")
451 452
        self.gateway = request.POST.get('gateway')
        self.method = request.POST.get('method')
453
        self.ajax = settings.CAS_ENABLE_AJAX_AUTH and 'HTTP_X_AJAX' in request.META
454 455
        if request.POST.get('warned') and request.POST['warned'] != "False":
            self.warned = True
Valentin Samir's avatar
Valentin Samir committed
456 457 458
        self.warn = request.POST.get('warn')
        if settings.CAS_FEDERATE:
            self.username = request.POST.get('username')
Valentin Samir's avatar
Valentin Samir committed
459 460
            # in federated mode, the valdated indentity provider CAS ticket is used as password
            self.ticket = request.POST.get('password')
461

462 463 464 465 466 467
    def gen_lt(self):
        """Generate a new LoginTicket and add it to the list of valid LT for the user"""
        self.request.session['lt'] = self.request.session.get('lt', []) + [utils.gen_lt()]
        if len(self.request.session['lt']) > 100:
            self.request.session['lt'] = self.request.session['lt'][-100:]

468
    def check_lt(self):
Valentin Samir's avatar
Valentin Samir committed
469 470 471 472 473 474
        """
            Check is the POSTed LoginTicket is valid, if yes invalide it

            :return: ``True`` if the LoginTicket is valid, ``False`` otherwise
            :rtype: bool
        """
475
        # save LT for later check
476
        lt_valid = self.request.session.get('lt', [])
477
        lt_send = self.request.POST.get('lt')
478
        # generate a new LT (by posting the LT has been consumed)
479
        self.gen_lt()
480
        # check if send LT is valid
481
        if lt_send not in lt_valid:
482 483
            return False
        else:
484
            self.request.session['lt'].remove(lt_send)
Valentin Samir's avatar
Valentin Samir committed
485 486
            # we need to redo the affectation for django to detect that the list has changed
            # and for its new value to be store in the session
487
            self.request.session['lt'] = self.request.session['lt']
488 489 490
            return True

    def post(self, request, *args, **kwargs):
Valentin Samir's avatar
Valentin Samir committed
491
        """
492
            method called on POST request on this view
Valentin Samir's avatar
Valentin Samir committed
493 494 495 496

            :param django.http.HttpRequest request: The current request object
        """
        # initialize class parameters
497
        self.init_post(request)
Valentin Samir's avatar
Valentin Samir committed
498
        # process the POST request
499 500
        ret = self.process_post()
        if ret == self.INVALID_LOGIN_TICKET:
501 502 503
            messages.add_message(
                self.request,
                messages.ERROR,
504
                _(u"Invalid login ticket, please try to log in again")
Valentin Samir's avatar
Valentin Samir committed
505
            )
506
        elif ret == self.USER_LOGIN_OK:
Valentin Samir's avatar
Valentin Samir committed
507 508
            # On successful login, update the :class:`models.User<cas_server.models.User>` ``date``
            # attribute by saving it. (``auto_now=True``)
509 510 511 512
            self.user = models.User.objects.get_or_create(
                username=self.request.session['username'],
                session_key=self.request.session.session_key
            )[0]
513
            self.user.last_login = timezone.now()
514
            self.user.save()
515
        elif ret == self.USER_LOGIN_FAILURE:  # bad user login
516 517
            if settings.CAS_FEDERATE:
                self.ticket = None
518
                self.username = None
519
                self.init_form()
520 521
            # preserve valid LoginTickets from session flush
            lt = self.request.session.get('lt', [])
Valentin Samir's avatar
Valentin Samir committed
522
            # On login failure, flush the session
523
            self.logout()
524 525
            # restore valid LoginTickets
            self.request.session['lt'] = lt
526 527
        elif ret == self.USER_ALREADY_LOGGED:
            pass
Valentin Samir's avatar
Valentin Samir committed
528 529 530
        else:  # pragma: no cover (should no happen)
            raise EnvironmentError("invalid output for LoginView.process_post")
        # call the GET/POST common part
531 532 533 534 535 536 537 538 539 540 541
        response = self.common()
        if self.warn:
            utils.set_cookie(
                response,
                "warn",
                "on",
                10 * 365 * 24 * 3600
            )
        else:
            response.delete_cookie("warn")
        return response
542

Valentin Samir's avatar
Valentin Samir committed
543
    def process_post(self):
544 545
        """
            Analyse the POST request:
Valentin Samir's avatar
Valentin Samir committed
546

547 548
                * check that the LoginTicket is valid
                * check that the user sumited credentials are valid
Valentin Samir's avatar
Valentin Samir committed
549 550 551 552 553 554 555 556 557 558

            :return:
                * :attr:`INVALID_LOGIN_TICKET` if the POSTed LoginTicket is not valid
                * :attr:`USER_ALREADY_LOGGED` if the user is already logged and do no request
                  reauthentication.
                * :attr:`USER_LOGIN_FAILURE` if the user is not logged or request for
                  reauthentication and his credentials are not valid
                * :attr:`USER_LOGIN_OK` if the user is not logged or request for
                  reauthentication and his credentials are valid
            :rtype: int
559
        """
560
        if not self.check_lt():
561
            self.init_form(self.request.POST)
562
            logger.warning("Received an invalid login ticket")
563 564
            return self.INVALID_LOGIN_TICKET
        elif not self.request.session.get("authenticated") or self.renew:
Valentin Samir's avatar
Valentin Samir committed
565
            # authentication request receive, initialize the form to use
566 567 568 569 570 571
            self.init_form(self.request.POST)
            if self.form.is_valid():
                self.request.session.set_expiry(0)
                self.request.session["username"] = self.form.cleaned_data['username']
                self.request.session["warn"] = True if self.form.cleaned_data.get("warn") else False
                self.request.session["authenticated"] = True
572 573
                self.renewed = True
                self.warned = True
574
                logger.info("User %s successfully authenticated" % self.request.session["username"])
575
                return self.USER_LOGIN_OK
Valentin Samir's avatar
Valentin Samir committed
576
            else:
577
                logger.warning("A login attempt failed")
578 579
                return self.USER_LOGIN_FAILURE
        else:
580
            logger.warning("Received a login attempt for an already-active user")
581
            return self.USER_ALREADY_LOGGED
582

583
    def init_get(self, request):
Valentin Samir's avatar
Valentin Samir committed
584 585 586 587 588
        """
            Initialize GET received parameters

            :param django.http.HttpRequest request: The current request object
        """
589 590
        self.request = request
        self.service = request.GET.get('service')
Valentin Samir's avatar
Valentin Samir committed
591
        self.renew = bool(request.GET.get('renew') and request.GET['renew'] != "False")
592 593
        self.gateway = request.GET.get('gateway')
        self.method = request.GET.get('method')
594
        self.ajax = settings.CAS_ENABLE_AJAX_AUTH and 'HTTP_X_AJAX' in request.META
Valentin Samir's avatar
Valentin Samir committed
595 596
        self.warn = request.GET.get('warn')
        if settings.CAS_FEDERATE:
Valentin Samir's avatar
Valentin Samir committed
597 598
            # here username and ticket are fetch from the session after a redirection from
            # FederateAuth.get
599 600 601 602 603 604
            self.username = request.session.get("federate_username")
            self.ticket = request.session.get("federate_ticket")
            if self.username:
                del request.session["federate_username"]
            if self.ticket:
                del request.session["federate_ticket"]
605

606
    def get(self, request, *args, **kwargs):
Valentin Samir's avatar
Valentin Samir committed
607
        """
608
            method called on GET request on this view
Valentin Samir's avatar
Valentin Samir committed
609 610 611 612

            :param django.http.HttpRequest request: The current request object
        """
        # initialize class parameters
613
        self.init_get(request)
Valentin Samir's avatar
Valentin Samir committed
614
        # process the GET request
615
        self.process_get()
Valentin Samir's avatar
Valentin Samir committed
616
        # call the GET/POST common part
617 618 619
        return self.common()

    def process_get(self):
Valentin Samir's avatar
Valentin Samir committed
620 621 622 623 624 625 626 627 628 629
        """
            Analyse the GET request

            :return:
                * :attr:`USER_NOT_AUTHENTICATED` if the user is not authenticated or is requesting
                  for authentication renewal
                * :attr:`USER_AUTHENTICATED` if the user is authenticated and is not requesting
                  for authentication renewal
            :rtype: int
        """
630 631
        # generate a new LT
        self.gen_lt()
632
        if not self.request.session.get("authenticated") or self.renew:
Valentin Samir's avatar
Valentin Samir committed
633
            # authentication will be needed, initialize the form to use
634
            self.init_form()
635 636
            return self.USER_NOT_AUTHENTICATED
        return self.USER_AUTHENTICATED
Valentin Samir's avatar
Valentin Samir committed
637

638
    def init_form(self, values=None):
Valentin Samir's avatar
Valentin Samir committed
639 640 641 642 643
        """
            Initialization of the good form depending of POST and GET parameters

            :param django.http.QueryDict values: A POST or GET QueryDict
        """
644 645 646
        if values:
            values = values.copy()
            values['lt'] = self.request.session['lt'][-1]
Valentin Samir's avatar
Valentin Samir committed
647 648 649
        form_initial = {
            'service': self.service,
            'method': self.method,
650 651 652
            'warn': (
                self.warn or self.request.session.get("warn") or self.request.COOKIES.get('warn')
            ),
653 654
            'lt': self.request.session['lt'][-1],
            'renew': self.renew
Valentin Samir's avatar
Valentin Samir committed
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
        }
        if settings.CAS_FEDERATE:
            if self.username and self.ticket:
                form_initial['username'] = self.username
                form_initial['password'] = self.ticket
                form_initial['ticket'] = self.ticket
                self.form = forms.FederateUserCredential(
                    values,
                    initial=form_initial
                )
            else:
                self.form = forms.FederateSelect(values, initial=form_initial)
        else:
            self.form = forms.UserCredential(
                values,
                initial=form_initial
            )
672

673
    def service_login(self):
Valentin Samir's avatar
Valentin Samir committed
674
        """
675
            Perform login against a service
Valentin Samir's avatar
Valentin Samir committed
676 677 678 679 680 681 682 683 684 685 686

            :return:
                * The rendering of the ``settings.CAS_WARN_TEMPLATE`` if the user asked to be
                  warned before ticket emission and has not yep been warned.
                * The redirection to the service URL with a ticket GET parameter
                * The redirection to the service URL without a ticket if ticket generation failed
                  and the :attr:`gateway` attribute is set
                * The rendering of the ``settings.CAS_LOGGED_TEMPLATE`` template with some error
                  messages if the ticket generation failed (e.g: user not allowed).
            :rtype: django.http.HttpResponse
        """
687 688
        try:
            # is the service allowed
689
            service_pattern = ServicePattern.validate(self.service)
690 691 692 693
            # is the current user allowed on this service
            service_pattern.check_user(self.user)
            # if the user has asked to be warned before any login to a service
            if self.request.session.get("warn", True) and not self.warned:
Valentin Samir's avatar
Valentin Samir committed
694
                messages.add_message(
695 696
                    self.request,
                    messages.WARNING,
Valentin Samir's avatar
Valentin Samir committed
697 698
                    _(u"Authentication has been required by service %(name)s (%(url)s)") %
                    {'name': service_pattern.name, 'url': self.service}
Valentin Samir's avatar
Valentin Samir committed
699
                )
700 701
                if self.ajax:
                    data = {"status": "error", "detail": "confirmation needed"}
Valentin Samir's avatar
Valentin Samir committed
702
                    return json_response(self.request, data)
703
                else:
704 705 706 707 708 709 710 711
                    warn_form = forms.WarnForm(initial={
                        'service': self.service,
                        'renew': self.renew,
                        'gateway': self.gateway,
                        'method': self.method,
                        'warned': True,
                        'lt': self.request.session['lt'][-1]
                    })
712 713 714
                    return render(
                        self.request,
                        settings.CAS_WARN_TEMPLATE,
715
                        utils.context({'form': warn_form})
Valentin Samir's avatar
Valentin Samir committed
716
                    )
717 718
            else:
                # redirect, using method ?
Valentin Samir's avatar
Valentin Samir committed
719
                list(messages.get_messages(self.request))  # clean messages before leaving django
720 721 722
                redirect_url = self.user.get_service_url(
                    self.service,
                    service_pattern,
723
                    renew=self.renewed
Valentin Samir's avatar
Valentin Samir committed
724
                )
725 726 727 728
                if not self.ajax:
                    return HttpResponseRedirect(redirect_url)
                else:
                    data = {"status": "success", "detail": "auth", "url": redirect_url}
Valentin Samir's avatar
Valentin Samir committed
729
                    return json_response(self.request, data)
730
        except ServicePattern.DoesNotExist:
731
            error = 1
732 733 734
            messages.add_message(
                self.request,
                messages.ERROR,
735
                _(u'Service %(url)s not allowed.') % {'url': self.service}
736 737
            )
        except models.BadUsername:
738
            error = 2
739 740 741
            messages.add_message(
                self.request,
                messages.ERROR,
742
                _(u"Username not allowed")
743 744
            )
        except models.BadFilter:
745
            error = 3
746 747 748
            messages.add_message(
                self.request,
                messages.ERROR,
749
                _(u"User characteristics not allowed")
750 751
            )
        except models.UserFieldNotDefined:
752
            error = 4
753 754 755
            messages.add_message(
                self.request,
                messages.ERROR,
756
                _(u"The attribute %(field)s is needed to use"
Valentin Samir's avatar
Valentin Samir committed
757
                  u" that service") % {'field': service_pattern.user_field}
758 759 760
            )

        # if gateway is set and auth failed redirect to the service without authentication
761
        if self.gateway and not self.ajax:
Valentin Samir's avatar
Valentin Samir committed
762
            list(messages.get_messages(self.request))  # clean messages before leaving django
763
            return HttpResponseRedirect(self.service)
Valentin Samir's avatar
Valentin Samir committed
764

765 766 767 768
        if not self.ajax:
            return render(
                self.request,
                settings.CAS_LOGGED_TEMPLATE,
769
                utils.context({'session': self.request.session})
770 771 772
            )
        else:
            data = {"status": "error", "detail": "auth", "code": error}
Valentin Samir's avatar
Valentin Samir committed
773
            return json_response(self.request, data)
Valentin Samir's avatar
Valentin Samir committed
774

775
    def authenticated(self):
Valentin Samir's avatar
Valentin Samir committed
776 777 778 779 780 781 782 783 784 785
        """
            Processing authenticated users

            :return:
                * The returned value of :meth:`service_login` if :attr:`service` is defined
                * The rendering of ``settings.CAS_LOGGED_TEMPLATE`` otherwise
            :rtype: django.http.HttpResponse
        """
        # Try to get the current :class:`models.User<cas_server.models.User>` object for the current
        # session
786
        try:
Valentin Samir's avatar
Valentin Samir committed
787 788
            self.user = models.User.objects.get(
                username=self.request.session.get("username"),
Valentin Samir's avatar
Valentin Samir committed
789
                session_key=self.request.session.session_key
Valentin Samir's avatar
Valentin Samir committed
790
            )
Valentin Samir's avatar
Valentin Samir committed
791
        # if not found, flush the session and redirect to the login page
792
        except models.User.DoesNotExist:
793 794 795 796 797
            logger.warning(
                "User %s seems authenticated but is not found in the database." % (
                    self.request.session.get("username"),
                )
            )
798
            self.logout()
799 800 801 802 803 804
            if self.ajax:
                data = {
                    "status": "error",
                    "detail": "login required",
                    "url": utils.reverse_params("cas_server:login", params=self.request.GET)
                }
Valentin Samir's avatar
Valentin Samir committed
805
                return json_response(self.request, data)
806 807
            else:
                return utils.redirect_params("cas_server:login", params=self.request.GET)
808

809
        # if login against a service
810 811
        if self.service:
            return self.service_login()
Valentin Samir's avatar
Valentin Samir committed
812
        # else display the logged template
813
        else:
814 815
            if self.ajax:
                data = {"status": "success", "detail": "logged"}
Valentin Samir's avatar
Valentin Samir committed
816
                return json_response(self.request, data)
817 818 819 820
            else:
                return render(
                    self.request,
                    settings.CAS_LOGGED_TEMPLATE,
821
                    utils.context({'session': self.request.session})
822
                )
823 824

    def not_authenticated(self):
Valentin Samir's avatar
Valentin Samir committed
825 826 827 828 829 830 831 832 833 834
        """
            Processing non authenticated users

            :return:
                * The rendering of ``settings.CAS_LOGIN_TEMPLATE`` with various messages
                  depending of GET/POST parameters
                * The redirection to :class:`FederateAuth` if ``settings.CAS_FEDERATE`` is ``True``
                  and the "remember my identity provider" cookie is found
            :rtype: django.http.HttpResponse
        """
835
        if self.service:
Valentin Samir's avatar
Valentin Samir committed
836
            try:
837
                service_pattern = ServicePattern.validate(self.service)
838
                if self.gateway and not self.ajax:
Valentin Samir's avatar
Valentin Samir committed
839 840
                    # clean messages before leaving django
                    list(messages.get_messages(self.request))
841
                    return HttpResponseRedirect(self.service)
842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859

                if settings.CAS_SHOW_SERVICE_MESSAGES:
                    if self.request.session.get("authenticated") and self.renew:
                        messages.add_message(
                            self.request,
                            messages.WARNING,
                            _(u"Authentication renewal required by service %(name)s (%(url)s).") %
                            {'name': service_pattern.name, 'url': self.service}
                        )
                    else:
                        messages.add_message(
                            self.request,
                            messages.WARNING,
                            _(u"Authentication required by service %(name)s (%(url)s).") %
                            {'name': service_pattern.name, 'url': self.service}
                        )
            except ServicePattern.DoesNotExist:
                if settings.CAS_SHOW_SERVICE_MESSAGES:
Valentin Samir's avatar
Valentin Samir committed
860
                    messages.add_message(
861
                        self.request,
862 863
                        messages.ERROR,
                        _(u'Service %s not allowed') % self.service
Valentin Samir's avatar
Valentin Samir committed
864
                    )
865 866 867 868 869 870
        if self.ajax:
            data = {
                "status": "error",
                "detail": "login required",
                "url": utils.reverse_params("cas_server:login",  params=self.request.GET)
            }
Valentin Samir's avatar
Valentin Samir committed
871
            return json_response(self.request, data)
872
        else:
Valentin Samir's avatar
Valentin Samir committed
873 874 875 876 877
            if settings.CAS_FEDERATE:
                if self.username and self.ticket:
                    return render(
                        self.request,
                        settings.CAS_LOGIN_TEMPLATE,
878
                        utils.context({
Valentin Samir's avatar
Valentin Samir committed
879 880 881
                            'form': self.form,
                            'auto_submit': True,
                            'post_url': reverse("cas_server:login")
882
                        })
Valentin Samir's avatar
Valentin Samir committed
883 884 885
                    )
                else:
                    if (
886
                        self.request.COOKIES.get('remember_provider') and
887
                        FederatedIendityProvider.objects.filter(
888
                            suffix=self.request.COOKIES['remember_provider']
889
                        )
Valentin Samir's avatar
Valentin Samir committed
890 891 892 893 894
                    ):
                        params = utils.copy_params(self.request.GET)
                        url = utils.reverse_params(
                            "cas_server:federateAuth",
                            params=params,
895
                            kwargs=dict(provider=self.request.COOKIES['remember_provider'])
Valentin Samir's avatar
Valentin Samir committed
896 897 898
                        )
                        return HttpResponseRedirect(url)
                    else:
899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916
                        # if user is authenticated and auth renewal is requested, redirect directly
                        # to the user identity provider
                        if self.renew and self.request.session.get("authenticated"):
                            try:
                                user = FederatedUser.get_from_federated_username(
                                    self.request.session.get("username")
                                )
                                params = utils.copy_params(self.request.GET)
                                url = utils.reverse_params(
                                    "cas_server:federateAuth",
                                    params=params,
                                    kwargs=dict(provider=user.provider.suffix)
                                )
                                return HttpResponseRedirect(url)
                            # Should normally not happen: if the user is logged, it exists in the
                            # database.
                            except FederatedUser.DoesNotExist:  # pragma: no cover
                                pass
Valentin Samir's avatar
Valentin Samir committed
917 918
                        return render(
                            self.request,
919 920 921 922 923
                            settings.CAS_LOGIN_TEMPLATE,
                            utils.context({
                                'form': self.form,
                                'post_url': reverse("cas_server:federateAuth")
                            })
Valentin Samir's avatar
Valentin Samir committed
924 925
                        )
            else:
926 927 928 929 930
                return render(
                    self.request,
                    settings.CAS_LOGIN_TEMPLATE,
                    utils.context({'form': self.form})
                )
Valentin Samir's avatar
Valentin Samir committed
931

932
    def common(self):
Valentin Samir's avatar
Valentin Samir committed
933 934 935 936 937 938 939 940 941
        """
            Common part execute uppon GET and POST request

            :return:
                * The returned value of :meth:`authenticated` if the user is authenticated and
                  not requesting for authentication or if the authentication has just been renewed
                * The returned value of :meth:`not_authenticated` otherwise
            :rtype: django.http.HttpResponse
        """
942 943 944 945 946
        # if authenticated and successfully renewed authentication if needed
        if self.request.session.get("authenticated") and (not self.renew or self.renewed):
            return self.authenticated()
        else:
            return self.not_authenticated()
Valentin Samir's avatar
Valentin Samir committed
947

Valentin Samir's avatar
Valentin Samir committed
948

949 950 951
class Auth(CsrfExemptView):
    """
        A simple view to validate username/password/service tuple
Valentin Samir's avatar
Valentin Samir committed
952

953 954 955
        csrf is disable as it is intended to be used by programs. Security is assured by a shared
        secret between the programs dans django-cas-server.
    """
956 957 958

    @staticmethod
    def post(request):
Valentin Samir's avatar
Valentin Samir committed
959
        """
960
            method called on POST request on this view
Valentin Samir's avatar
Valentin Samir committed
961 962 963 964 965 966 967 968

            :param django.http.HttpRequest request: The current request object
            :return: ``HttpResponse(u"yes\\n")`` if the POSTed tuple (username, password, service)
                if valid (i.e. (username, password) is valid dans username is allowed on service).
                ``HttpResponse(u"no\\n…")`` otherwise, with possibly an error message on the second
                line.
            :rtype: django.http.HttpResponse
        """
969 970 971
        username = request.POST.get('username')
        password = request.POST.get('password')
        service = request.POST.get('service')
972
        secret = request.POST.get('secret')
973

974
        if not settings.CAS_AUTH_SHARED_SECRET:
975 976 977 978
            return HttpResponse(
                "no\nplease set CAS_AUTH_SHARED_SECRET",
                content_type="text/plain; charset=utf-8"
            )
979
        if secret != settings.CAS_AUTH_SHARED_SECRET:
980
            return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
981
        if not username or not password or not service:
982
            return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
983 984 985
        form = forms.UserCredential(
            request.POST,
            initial={
Valentin Samir's avatar
Valentin Samir committed
986 987 988
                'service': service,
                'method': 'POST',
                'warn': False
989 990 991 992
            }
        )
        if form.is_valid():
            try:
993 994 995 996
                user = models.User.objects.get_or_create(
                    username=form.cleaned_data['username'],
                    session_key=request.session.session_key
                )[0]
997
                user.save()
998
                # is the service allowed
999
                service_pattern = ServicePattern.validate(service)
1000 1001
                # is the current user allowed on this service
                service_pattern.check_user(user)
Valentin Samir's avatar
Valentin Samir committed
1002 1003
                if not request.session.get("authenticated"):
                    user.delete()
1004
                return HttpResponse(u"yes\n", content_type="text/plain; charset=utf-8")
Valentin Samir's avatar
Valentin Samir committed
1005
            except (ServicePattern.DoesNotExist, models.ServicePatternException):
1006
                return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
1007
        else:
1008
            return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
1009

Valentin Samir's avatar
Valentin Samir committed
1010

1011
class Validate(View):
Valentin Samir's avatar
Valentin Samir committed
1012
    """service ticket validation"""
1013 1014
    @staticmethod
    def get(request):
Valentin Samir's avatar
Valentin Samir committed
1015
        """
1016
            method called on GET request on this view
Valentin Samir's avatar
Valentin Samir committed
1017 1018 1019 1020 1021 1022 1023 1024

            :param django.http.HttpRequest request: The current request object
            :return:
                * ``HttpResponse("yes\\nusername")`` if submited (service, ticket) is valid
                * else ``HttpResponse("no\\n")``
            :rtype: django.http.HttpResponse
        """
        # store wanted GET parameters
1025 1026 1027
        service = request.GET.get('service')
        ticket = request.GET.get('ticket')
        renew = True if request.GET.get('renew') else False
Valentin Samir's avatar
Valentin Samir committed
1028
        # service and ticket parameters are mandatory
1029 1030
        if service and ticket:
            try:
Valentin Samir's avatar
Valentin Samir committed
1031 1032 1033
                # search for the ticket, associated at service that is not yet validated but is
                # still valid
                ticket = ServiceTicket.get(ticket, renew, service)
1034 1035 1036 1037 1038 1039 1040
                logger.info(
                    "Validate: Service ticket %s validated, user %s authenticated on service %s" % (
                        ticket.value,
                        ticket.user.username,
                        ticket.service
                    )
                )
1041
                return HttpResponse(
Valentin Samir's avatar
Valentin Samir committed
1042
                    u"yes\n%s\n" % ticket.username(),
1043 1044
                    content_type="text/plain; charset=utf-8"
                )
1045
            except ServiceTicket.DoesNotExist:
1046 1047 1048 1049 1050 1051 1052 1053 1054
                logger.warning(
                    (
                        "Validate: Service ticket %s not found or "
                        "already validated, auth to %s failed"
                    ) % (
                        ticket,
                        service
                    )
                )
1055
                return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
1056
        else:
1057
            logger.warning("Validate: service or ticket missing")
1058
            return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
Valentin Samir's avatar
Valentin Samir committed
1059

Valentin Samir's avatar
Valentin Samir committed
1060

1061
@python_2_unicode_compatible
1062 1063 1064
class ValidationBaseError(Exception):
    """Base class for both saml and cas validation error"""

Valentin Samir's avatar
Valentin Samir committed
1065 1066 1067 1068
    #: The error code
    code = None
    #: The error message
    msg = None
Valentin Samir's avatar
Valentin Samir committed
1069

1070 1071 1072
    def __init__(self, code, msg=""):
        self.code = code
        self.msg = msg
1073
        super(ValidationBaseError, self).__init__(code)
1074

1075
    def __str__(self):
1076 1077 1078
        return u"%s" % self.msg

    def render(self, request):
Valentin Samir's avatar
Valentin Samir committed
1079 1080 1081 1082 1083 1084 1085
        """
            render the error template for the exception

            :param django.http.HttpRequest request: The current request object:
            :return: the rendered ``cas_server/serviceValidateError.xml`` template
            :rtype: django.http.HttpResponse
        """
1086 1087 1088 1089 1090
        return render(
            request,
            self.template,
            self.context(), content_type="text/xml; charset=utf-8"
        )
1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106


class ValidateError(ValidationBaseError):
    """handle service validation error"""

    #: template to be render for the error
    template = "cas_server/serviceValidateError.xml"

    def context(self):
        """
            content to use to render :attr:`template`

            :return: A dictionary to contextualize :attr:`template`
            :rtype: dict
        """
        return {'code': self.code, 'msg': self.msg}
1107

1108

Valentin Samir's avatar
Valentin Samir committed
1109
class ValidateService(View):
1110
    """service ticket validation [CAS 2.0] and [CAS 3.0]"""
Valentin Samir's avatar
Valentin Samir committed
1111
    #: Current :class:`django.http.HttpRequest` object
1112
    request = None
Valentin Samir's avatar
Valentin Samir committed
1113
    #: The service GET parameter
1114
    service = None
Valentin Samir's avatar
Valentin Samir committed
1115
    #: the ticket GET parameter
1116
    ticket = None
Valentin Samir's avatar
Valentin Samir committed
1117
    #: the pgtUrl GET parameter
1118
    pgt_url = None
Valentin Samir's avatar
Valentin Samir committed
1119
    #: the renew GET parameter
1120
    renew = None
Valentin Samir's avatar
Valentin Samir committed
1121 1122
    #: specify if ProxyTicket are allowed by the view. Hence we user the same view for
    #: ``/serviceValidate`` and ``/proxyValidate`` juste changing the parameter.
Valentin Samir's avatar
Valentin Samir committed
1123
    allow_proxy_ticket = False
1124

Valentin Samir's avatar
Valentin Samir committed
1125
    def get(self, request):
Valentin Samir's avatar
Valentin Samir committed
1126
        """
1127
            method called on GET request on this view
Valentin Samir's avatar
Valentin Samir committed
1128 1129 1130 1131 1132 1133 1134

            :param django.http.HttpRequest request: The current request object:
            :return: The rendering of ``cas_server/serviceValidate.xml`` if no errors is raised,
                the rendering or ``cas_server/serviceValidateError.xml`` otherwise.
            :rtype: django.http.HttpResponse
        """
        # define the class parameters
1135 1136 1137 1138 1139 1140
        self.request = request
        self.service = request.GET.get('service')
        self.ticket = request.GET.get('ticket')
        self.pgt_url = request.GET.get('pgtUrl')
        self.renew = True if request.GET.get('renew') else False