models.py 18.9 KB
Newer Older
Valentin Samir's avatar
Valentin Samir committed
1
# ⁻*- coding: utf-8 -*-
Valentin Samir's avatar
Valentin Samir committed
2 3 4 5 6 7 8 9 10 11
# 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.
#
# (c) 2015 Valentin Samir
Valentin Samir's avatar
Valentin Samir committed
12
"""models for the app"""
13
from .default_settings import settings
14

Valentin Samir's avatar
Valentin Samir committed
15
from django.db import models
16
from django.db.models import Q
Valentin Samir's avatar
Valentin Samir committed
17
from django.contrib import messages
18
from django.utils.translation import ugettext_lazy as _
19
from django.utils import timezone
20
from picklefield.fields import PickledObjectField
Valentin Samir's avatar
Valentin Samir committed
21 22 23

import re
import os
24
import sys
Valentin Samir's avatar
Valentin Samir committed
25
import logging
26
from importlib import import_module
27
from datetime import timedelta
Valentin Samir's avatar
Valentin Samir committed
28 29
from concurrent.futures import ThreadPoolExecutor
from requests_futures.sessions import FuturesSession
Valentin Samir's avatar
Valentin Samir committed
30

Valentin Samir's avatar
Valentin Samir committed
31
import cas_server.utils as utils
Valentin Samir's avatar
Valentin Samir committed
32

33 34
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore

Valentin Samir's avatar
Valentin Samir committed
35 36
logger = logging.getLogger(__name__)

Valentin Samir's avatar
PEP8  
Valentin Samir committed
37

Valentin Samir's avatar
Valentin Samir committed
38 39 40 41 42 43 44 45 46
class FederatedUser(models.Model):
    class Meta:
        unique_together = ("username", "provider")
    username = models.CharField(max_length=124)
    provider = models.CharField(max_length=124)
    attributs = PickledObjectField()
    ticket = models.CharField(max_length=255)
    last_update = models.DateTimeField(auto_now=True)

47 48 49
    def __unicode__(self):
        return u"%s@%s" % (self.username, self.provider)

Valentin Samir's avatar
Valentin Samir committed
50

51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
class FederateSLO(models.Model):
    class Meta:
        unique_together = ("username", "session_key")
    username = models.CharField(max_length=30)
    session_key = models.CharField(max_length=40, blank=True, null=True)
    ticket = models.CharField(max_length=255)

    @property
    def provider(self):
        component = self.username.split("@")
        return component[-1]

    @classmethod
    def clean_deleted_sessions(cls):
        for federate_slo in cls.objects.all():
            if not SessionStore(session_key=federate_slo.session_key).get('authenticated'):
                federate_slo.delete()


Valentin Samir's avatar
Valentin Samir committed
70
class User(models.Model):
Valentin Samir's avatar
Valentin Samir committed
71
    """A user logged into the CAS"""
Valentin Samir's avatar
Valentin Samir committed
72
    class Meta:
73
        unique_together = ("username", "session_key")
74 75
        verbose_name = _("User")
        verbose_name_plural = _("Users")
76
    session_key = models.CharField(max_length=40, blank=True, null=True)
Valentin Samir's avatar
Valentin Samir committed
77
    username = models.CharField(max_length=30)
Valentin Samir's avatar
Valentin Samir committed
78
    date = models.DateTimeField(auto_now=True)
Valentin Samir's avatar
Valentin Samir committed
79

Valentin Samir's avatar
Valentin Samir committed
80 81
    @classmethod
    def clean_old_entries(cls):
82 83 84
        users = cls.objects.filter(
            date__lt=(timezone.now() - timedelta(seconds=settings.SESSION_COOKIE_AGE))
        )
Valentin Samir's avatar
Valentin Samir committed
85 86 87 88
        for user in users:
            user.logout()
        users.delete()

89 90 91 92 93 94 95
    @classmethod
    def clean_deleted_sessions(cls):
        for user in cls.objects.all():
            if not SessionStore(session_key=user.session_key).get('authenticated'):
                user.logout()
                user.delete()

96 97 98 99 100
    @property
    def attributs(self):
        """return a fresh dict for the user attributs"""
        return utils.import_attr(settings.CAS_AUTH_CLASS)(self.username).attributs()

Valentin Samir's avatar
Valentin Samir committed
101
    def __unicode__(self):
Valentin Samir's avatar
oops  
Valentin Samir committed
102
        return u"%s - %s" % (self.username, self.session_key)
Valentin Samir's avatar
Valentin Samir committed
103

Valentin Samir's avatar
Valentin Samir committed
104
    def logout(self, request=None):
Valentin Samir's avatar
Valentin Samir committed
105
        """Sending SLO request to all services the user logged in"""
Valentin Samir's avatar
Valentin Samir committed
106
        async_list = []
107 108 109
        session = FuturesSession(
            executor=ThreadPoolExecutor(max_workers=settings.CAS_SLO_MAX_PARALLEL_REQUESTS)
        )
110 111
        # first invalidate all Tickets
        ticket_classes = [ProxyGrantingTicket, ServiceTicket, ProxyTicket]
112
        for ticket_class in ticket_classes:
113 114
            queryset = ticket_class.objects.filter(user=self)
            for ticket in queryset:
115
                ticket.logout(request, session, async_list)
116
            queryset.delete()
Valentin Samir's avatar
Valentin Samir committed
117
        for future in async_list:
118 119 120 121
            if future:
                try:
                    future.result()
                except Exception as error:
Valentin Samir's avatar
Valentin Samir committed
122 123 124 125 126 127
                    logger.warning(
                        "Error during SLO for user %s: %s" % (
                            self.username,
                            error
                        )
                    )
Valentin Samir's avatar
Valentin Samir committed
128 129 130 131 132 133 134
                    if request is not None:
                        error = utils.unpack_nested_exception(error)
                        messages.add_message(
                            request,
                            messages.WARNING,
                            _(u'Error during service logout %s') % error
                        )
Valentin Samir's avatar
Valentin Samir committed
135 136 137 138 139 140 141 142 143 144 145 146 147

    def get_ticket(self, ticket_class, service, service_pattern, renew):
        """
           Generate a ticket using `ticket_class` for the service
           `service` matching `service_pattern` and asking or not for
           authentication renewal with `renew
        """
        attributs = dict(
            (a.name, a.replace if a.replace else a.name) for a in service_pattern.attributs.all()
        )
        replacements = dict(
            (a.name, (a.pattern, a.replace)) for a in service_pattern.replacements.all()
        )
Valentin Samir's avatar
Valentin Samir committed
148
        service_attributs = {}
Valentin Samir's avatar
Valentin Samir committed
149
        for (key, value) in self.attributs.items():
Valentin Samir's avatar
Valentin Samir committed
150
            if key in attributs or '*' in attributs:
Valentin Samir's avatar
Valentin Samir committed
151 152
                if key in replacements:
                    value = re.sub(replacements[key][0], replacements[key][1], value)
Valentin Samir's avatar
Valentin Samir committed
153
                service_attributs[attributs.get(key, key)] = value
Valentin Samir's avatar
Valentin Samir committed
154 155 156 157 158
        ticket = ticket_class.objects.create(
            user=self,
            attributs=service_attributs,
            service=service,
            renew=renew,
159 160
            service_pattern=service_pattern,
            single_log_out=service_pattern.single_log_out
Valentin Samir's avatar
Valentin Samir committed
161
        )
Valentin Samir's avatar
Valentin Samir committed
162
        ticket.save()
163
        self.save()
Valentin Samir's avatar
Valentin Samir committed
164 165 166
        return ticket

    def get_service_url(self, service, service_pattern, renew):
Valentin Samir's avatar
Valentin Samir committed
167 168
        """Return the url to which the user must be redirected to
        after a Service Ticket has been generated"""
Valentin Samir's avatar
Valentin Samir committed
169
        ticket = self.get_ticket(ServiceTicket, service, service_pattern, renew)
Valentin Samir's avatar
PEP8  
Valentin Samir committed
170
        url = utils.update_url(service, {'ticket': ticket.value})
Valentin Samir's avatar
Valentin Samir committed
171
        logger.info("Service ticket created for service %s by user %s." % (service, self.username))
Valentin Samir's avatar
Valentin Samir committed
172 173
        return url

Valentin Samir's avatar
PEP8  
Valentin Samir committed
174

175 176
class ServicePatternException(Exception):
    pass
Valentin Samir's avatar
PEP8  
Valentin Samir committed
177 178


179
class BadUsername(ServicePatternException):
Valentin Samir's avatar
Valentin Samir committed
180 181
    """Exception raised then an non allowed username
    try to get a ticket for a service"""
Valentin Samir's avatar
Valentin Samir committed
182
    pass
Valentin Samir's avatar
PEP8  
Valentin Samir committed
183 184


185
class BadFilter(ServicePatternException):
Valentin Samir's avatar
Valentin Samir committed
186 187
    """"Exception raised then a user try
    to get a ticket for a service and do not reach a condition"""
Valentin Samir's avatar
Valentin Samir committed
188
    pass
Valentin Samir's avatar
Valentin Samir committed
189

Valentin Samir's avatar
PEP8  
Valentin Samir committed
190

191
class UserFieldNotDefined(ServicePatternException):
Valentin Samir's avatar
Valentin Samir committed
192 193
    """Exception raised then a user try to get a ticket for a service
    using as username an attribut not present on this user"""
Valentin Samir's avatar
Valentin Samir committed
194
    pass
Valentin Samir's avatar
PEP8  
Valentin Samir committed
195 196


Valentin Samir's avatar
Valentin Samir committed
197
class ServicePattern(models.Model):
Valentin Samir's avatar
Valentin Samir committed
198
    """Allowed services pattern agains services are tested to"""
Valentin Samir's avatar
Valentin Samir committed
199 200
    class Meta:
        ordering = ("pos", )
201 202
        verbose_name = _("Service pattern")
        verbose_name_plural = _("Services patterns")
Valentin Samir's avatar
Valentin Samir committed
203

204 205 206 207
    pos = models.IntegerField(
        default=100,
        verbose_name=_(u"position")
    )
Valentin Samir's avatar
Valentin Samir committed
208 209 210 211 212
    name = models.CharField(
        max_length=255,
        unique=True,
        blank=True,
        null=True,
213 214 215 216 217 218
        verbose_name=_(u"name"),
        help_text=_(u"A name for the service")
    )
    pattern = models.CharField(
        max_length=255,
        unique=True,
219 220 221 222 223 224
        verbose_name=_(u"pattern"),
        help_text=_(
            "A regular expression matching services. "
            "Will usually looks like '^https://some\\.server\\.com/path/.*$'."
            "As it is a regular expression, special character must be escaped with a '\\'."
        )
Valentin Samir's avatar
Valentin Samir committed
225 226 227 228 229
    )
    user_field = models.CharField(
        max_length=255,
        default="",
        blank=True,
230 231
        verbose_name=_(u"user field"),
        help_text=_("Name of the attribut to transmit as username, empty = login")
Valentin Samir's avatar
Valentin Samir committed
232 233 234
    )
    restrict_users = models.BooleanField(
        default=False,
235 236
        verbose_name=_(u"restrict username"),
        help_text=_("Limit username allowed to connect to the list provided bellow")
Valentin Samir's avatar
Valentin Samir committed
237 238 239
    )
    proxy = models.BooleanField(
        default=False,
240
        verbose_name=_(u"proxy"),
241 242 243 244 245 246
        help_text=_("Proxy tickets can be delivered to the service")
    )
    proxy_callback = models.BooleanField(
        default=False,
        verbose_name=_(u"proxy callback"),
        help_text=_("can be used as a proxy callback to deliver PGT")
Valentin Samir's avatar
Valentin Samir committed
247
    )
Valentin Samir's avatar
Valentin Samir committed
248
    single_log_out = models.BooleanField(
Valentin Samir's avatar
Valentin Samir committed
249
        default=False,
Valentin Samir's avatar
Valentin Samir committed
250 251
        verbose_name=_(u"single log out"),
        help_text=_("Enable SLO for the service")
Valentin Samir's avatar
Valentin Samir committed
252
    )
Valentin Samir's avatar
Valentin Samir committed
253

254 255 256 257 258
    single_log_out_callback = models.CharField(
        max_length=255,
        default="",
        blank=True,
        verbose_name=_(u"single log out callback"),
Valentin Samir's avatar
PEP8  
Valentin Samir committed
259
        help_text=_(u"URL where the SLO request will be POST. empty = service url\n"
260 261 262
                    u"This is usefull for non HTTP proxied services.")
    )

Valentin Samir's avatar
Valentin Samir committed
263 264 265 266
    def __unicode__(self):
        return u"%s: %s" % (self.pos, self.pattern)

    def check_user(self, user):
Valentin Samir's avatar
Valentin Samir committed
267
        """Check if `user` if allowed to use theses services"""
Valentin Samir's avatar
Valentin Samir committed
268
        if self.restrict_users and not self.usernames.filter(value=user.username):
Valentin Samir's avatar
Valentin Samir committed
269
            logger.warning("Username %s not allowed on service %s" % (user.username, self.name))
Valentin Samir's avatar
Valentin Samir committed
270
            raise BadUsername()
Valentin Samir's avatar
Valentin Samir committed
271
        for filtre in self.filters.all():
272 273
            if isinstance(user.attributs.get(filtre.attribut, []), list):
                attrs = user.attributs.get(filtre.attribut, [])
Valentin Samir's avatar
Valentin Samir committed
274
            else:
Valentin Samir's avatar
Valentin Samir committed
275 276 277
                attrs = [user.attributs[filtre.attribut]]
            for value in attrs:
                if re.match(filtre.pattern, str(value)):
Valentin Samir's avatar
Valentin Samir committed
278 279
                    break
            else:
Valentin Samir's avatar
Valentin Samir committed
280 281 282 283 284 285 286 287 288
                logger.warning(
                    "User constraint failed for %s, service %s: %s do not match %s %s." % (
                        user.username,
                        self.name,
                        filtre.pattern,
                        filtre.attribut,
                        user.attributs.get(filtre.attribut)
                    )
                )
Valentin Samir's avatar
Valentin Samir committed
289 290 291
                raise BadFilter('%s do not match %s %s' % (
                    filtre.pattern,
                    filtre.attribut,
Valentin Samir's avatar
Valentin Samir committed
292
                    user.attributs.get(filtre.attribut)
Valentin Samir's avatar
Valentin Samir committed
293
                ))
Valentin Samir's avatar
Valentin Samir committed
294
        if self.user_field and not user.attributs.get(self.user_field):
Valentin Samir's avatar
Valentin Samir committed
295 296 297 298 299 300 301
            logger.warning(
                "Cannot use %s a loggin for user %s on service %s because it is absent" % (
                    self.user_field,
                    user.username,
                    self.name
                )
            )
Valentin Samir's avatar
Valentin Samir committed
302 303 304 305 306
            raise UserFieldNotDefined()
        return True

    @classmethod
    def validate(cls, service):
Valentin Samir's avatar
Valentin Samir committed
307 308 309 310 311
        """Check if a Service Patern match `service` and
        return it, else raise `ServicePattern.DoesNotExist`"""
        for service_pattern in cls.objects.all().order_by('pos'):
            if re.match(service_pattern.pattern, service):
                return service_pattern
Valentin Samir's avatar
Valentin Samir committed
312
        logger.warning("Service %s not allowed." % service)
Valentin Samir's avatar
Valentin Samir committed
313 314
        raise cls.DoesNotExist()

Valentin Samir's avatar
PEP8  
Valentin Samir committed
315

Valentin Samir's avatar
Valentin Samir committed
316 317
class Username(models.Model):
    """A list of allowed usernames on a service pattern"""
318 319 320 321 322
    value = models.CharField(
        max_length=255,
        verbose_name=_(u"username"),
        help_text=_(u"username allowed to connect to the service")
    )
Valentin Samir's avatar
Valentin Samir committed
323
    service_pattern = models.ForeignKey(ServicePattern, related_name="usernames")
Valentin Samir's avatar
Valentin Samir committed
324

Valentin Samir's avatar
Valentin Samir committed
325 326 327
    def __unicode__(self):
        return self.value

Valentin Samir's avatar
PEP8  
Valentin Samir committed
328

Valentin Samir's avatar
Valentin Samir committed
329
class ReplaceAttributName(models.Model):
Valentin Samir's avatar
Valentin Samir committed
330
    """A list of replacement of attributs name for a service pattern"""
Valentin Samir's avatar
Valentin Samir committed
331
    class Meta:
Valentin Samir's avatar
Valentin Samir committed
332
        unique_together = ('name', 'replace', 'service_pattern')
Valentin Samir's avatar
Valentin Samir committed
333 334
    name = models.CharField(
        max_length=255,
335
        verbose_name=_(u"name"),
Valentin Samir's avatar
Valentin Samir committed
336
        help_text=_(u"name of an attribut to send to the service, use * for all attributes")
Valentin Samir's avatar
Valentin Samir committed
337 338 339 340
    )
    replace = models.CharField(
        max_length=255,
        blank=True,
341
        verbose_name=_(u"replace"),
Valentin Samir's avatar
PEP8  
Valentin Samir committed
342 343
        help_text=_(u"name under which the attribut will be show"
                    u"to the service. empty = default name of the attribut")
Valentin Samir's avatar
Valentin Samir committed
344
    )
Valentin Samir's avatar
Valentin Samir committed
345 346 347 348 349 350 351 352
    service_pattern = models.ForeignKey(ServicePattern, related_name="attributs")

    def __unicode__(self):
        if not self.replace:
            return self.name
        else:
            return u"%s → %s" % (self.name, self.replace)

Valentin Samir's avatar
PEP8  
Valentin Samir committed
353

Valentin Samir's avatar
Valentin Samir committed
354
class FilterAttributValue(models.Model):
Valentin Samir's avatar
Valentin Samir committed
355 356 357
    """A list of filter on attributs for a service pattern"""
    attribut = models.CharField(
        max_length=255,
358 359
        verbose_name=_(u"attribut"),
        help_text=_(u"Name of the attribut which must verify pattern")
Valentin Samir's avatar
Valentin Samir committed
360 361 362
    )
    pattern = models.CharField(
        max_length=255,
363 364
        verbose_name=_(u"pattern"),
        help_text=_(u"a regular expression")
Valentin Samir's avatar
Valentin Samir committed
365
    )
Valentin Samir's avatar
Valentin Samir committed
366 367 368 369 370
    service_pattern = models.ForeignKey(ServicePattern, related_name="filters")

    def __unicode__(self):
        return u"%s %s" % (self.attribut, self.pattern)

Valentin Samir's avatar
PEP8  
Valentin Samir committed
371

Valentin Samir's avatar
Valentin Samir committed
372
class ReplaceAttributValue(models.Model):
Valentin Samir's avatar
Valentin Samir committed
373 374 375
    """Replacement to apply on attributs values for a service pattern"""
    attribut = models.CharField(
        max_length=255,
376 377
        verbose_name=_(u"attribut"),
        help_text=_(u"Name of the attribut for which the value must be replace")
Valentin Samir's avatar
Valentin Samir committed
378 379 380
    )
    pattern = models.CharField(
        max_length=255,
381 382
        verbose_name=_(u"pattern"),
        help_text=_(u"An regular expression maching whats need to be replaced")
Valentin Samir's avatar
Valentin Samir committed
383 384 385 386
    )
    replace = models.CharField(
        max_length=255,
        blank=True,
387 388
        verbose_name=_(u"replace"),
        help_text=_(u"replace expression, groups are capture by \\1, \\2 …")
Valentin Samir's avatar
Valentin Samir committed
389
    )
Valentin Samir's avatar
Valentin Samir committed
390 391 392 393
    service_pattern = models.ForeignKey(ServicePattern, related_name="replacements")

    def __unicode__(self):
        return u"%s %s %s" % (self.attribut, self.pattern, self.replace)
Valentin Samir's avatar
Valentin Samir committed
394 395


Valentin Samir's avatar
Valentin Samir committed
396
class Ticket(models.Model):
Valentin Samir's avatar
Valentin Samir committed
397
    """Generic class for a Ticket"""
Valentin Samir's avatar
Valentin Samir committed
398 399 400 401 402 403
    class Meta:
        abstract = True
    user = models.ForeignKey(User, related_name="%(class)s")
    attributs = PickledObjectField()
    validate = models.BooleanField(default=False)
    service = models.TextField()
Valentin Samir's avatar
Valentin Samir committed
404
    service_pattern = models.ForeignKey(ServicePattern, related_name="%(class)s")
Valentin Samir's avatar
Valentin Samir committed
405 406
    creation = models.DateTimeField(auto_now_add=True)
    renew = models.BooleanField(default=False)
407
    single_log_out = models.BooleanField(default=False)
Valentin Samir's avatar
Valentin Samir committed
408

Valentin Samir's avatar
Valentin Samir committed
409 410 411
    VALIDITY = settings.CAS_TICKET_VALIDITY
    TIMEOUT = settings.CAS_TICKET_TIMEOUT

Valentin Samir's avatar
Valentin Samir committed
412
    def __unicode__(self):
413
        return u"Ticket-%s" % self.pk
Valentin Samir's avatar
Valentin Samir committed
414

415
    @classmethod
Valentin Samir's avatar
Valentin Samir committed
416
    def clean_old_entries(cls):
417 418 419 420
        """Remove old ticket and send SLO to timed-out services"""
        # removing old validated ticket and non validated expired tickets
        cls.objects.filter(
            (
Valentin Samir's avatar
PEP8  
Valentin Samir committed
421 422
                Q(single_log_out=False) & Q(validate=True)
            ) | (
423 424
                Q(validate=False) &
                Q(creation__lt=(timezone.now() - timedelta(seconds=cls.VALIDITY)))
425 426 427 428
            )
        ).delete()

        # sending SLO to timed-out validated tickets
Valentin Samir's avatar
Valentin Samir committed
429
        if cls.TIMEOUT and cls.TIMEOUT > 0:
430
            async_list = []
431 432 433
            session = FuturesSession(
                executor=ThreadPoolExecutor(max_workers=settings.CAS_SLO_MAX_PARALLEL_REQUESTS)
            )
434
            queryset = cls.objects.filter(
Valentin Samir's avatar
Valentin Samir committed
435
                creation__lt=(timezone.now() - timedelta(seconds=cls.TIMEOUT))
436 437
            )
            for ticket in queryset:
438
                ticket.logout(None, session, async_list)
439 440 441 442 443 444
            queryset.delete()
            for future in async_list:
                if future:
                    try:
                        future.result()
                    except Exception as error:
Valentin Samir's avatar
Valentin Samir committed
445
                        logger.warning("Error durring SLO %s" % error)
446 447
                        sys.stderr.write("%r\n" % error)

448
    def logout(self, request, session, async_list=None):
Valentin Samir's avatar
Valentin Samir committed
449
        """Send a SLO request to the ticket service"""
450 451 452
        # On logout invalidate the Ticket
        self.validate = True
        self.save()
453
        if self.validate and self.single_log_out:
Valentin Samir's avatar
Valentin Samir committed
454 455 456 457 458 459
            logger.info(
                "Sending SLO requests to service %s for user %s" % (
                    self.service,
                    self.user.username
                )
            )
460 461
            try:
                xml = u"""<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
Valentin Samir's avatar
Valentin Samir committed
462 463 464
     ID="%(id)s" Version="2.0" IssueInstant="%(datetime)s">
    <saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"></saml:NameID>
    <samlp:SessionIndex>%(ticket)s</samlp:SessionIndex>
Valentin Samir's avatar
Valentin Samir committed
465
  </samlp:LogoutRequest>""" % \
Valentin Samir's avatar
PEP8  
Valentin Samir committed
466 467 468 469 470
                    {
                        'id': os.urandom(20).encode("hex"),
                        'datetime': timezone.now().isoformat(),
                        'ticket':  self.value
                    }
471 472 473
                if self.service_pattern.single_log_out_callback:
                    url = self.service_pattern.single_log_out_callback
                else:
Valentin Samir's avatar
PEP8  
Valentin Samir committed
474
                    url = self.service
475 476 477 478
                async_list.append(
                    session.post(
                        url.encode('utf-8'),
                        data={'logoutRequest': xml.encode('utf-8')},
Valentin Samir's avatar
Valentin Samir committed
479
                        timeout=settings.CAS_SLO_TIMEOUT
480
                    )
Valentin Samir's avatar
Valentin Samir committed
481 482
                )
            except Exception as error:
Valentin Samir's avatar
Valentin Samir committed
483 484 485 486 487 488 489 490
                error = utils.unpack_nested_exception(error)
                logger.warning(
                    "Error durring SLO for user %s on service %s: %s" % (
                        self.user.username,
                        self.service,
                        error
                    )
                )
491 492 493 494 495
                if request is not None:
                    messages.add_message(
                        request,
                        messages.WARNING,
                        _(u'Error during service logout %(service)s:\n%(error)s') %
Valentin Samir's avatar
PEP8  
Valentin Samir committed
496
                        {'service':  self.service, 'error': error}
497 498 499
                    )
                else:
                    sys.stderr.write("%r\n" % error)
Valentin Samir's avatar
Valentin Samir committed
500

Valentin Samir's avatar
PEP8  
Valentin Samir committed
501

Valentin Samir's avatar
Valentin Samir committed
502
class ServiceTicket(Ticket):
Valentin Samir's avatar
Valentin Samir committed
503
    """A Service Ticket"""
504
    PREFIX = settings.CAS_SERVICE_TICKET_PREFIX
505
    value = models.CharField(max_length=255, default=utils.gen_st, unique=True)
Valentin Samir's avatar
PEP8  
Valentin Samir committed
506

Valentin Samir's avatar
Valentin Samir committed
507
    def __unicode__(self):
508
        return u"ServiceTicket-%s" % self.pk
Valentin Samir's avatar
PEP8  
Valentin Samir committed
509 510


Valentin Samir's avatar
Valentin Samir committed
511
class ProxyTicket(Ticket):
Valentin Samir's avatar
Valentin Samir committed
512
    """A Proxy Ticket"""
513
    PREFIX = settings.CAS_PROXY_TICKET_PREFIX
514
    value = models.CharField(max_length=255, default=utils.gen_pt, unique=True)
Valentin Samir's avatar
PEP8  
Valentin Samir committed
515

Valentin Samir's avatar
Valentin Samir committed
516
    def __unicode__(self):
517
        return u"ProxyTicket-%s" % self.pk
Valentin Samir's avatar
PEP8  
Valentin Samir committed
518 519


Valentin Samir's avatar
Valentin Samir committed
520
class ProxyGrantingTicket(Ticket):
Valentin Samir's avatar
Valentin Samir committed
521
    """A Proxy Granting Ticket"""
522
    PREFIX = settings.CAS_PROXY_GRANTING_TICKET_PREFIX
Valentin Samir's avatar
Valentin Samir committed
523
    VALIDITY = settings.CAS_PGT_VALIDITY
524
    value = models.CharField(max_length=255, default=utils.gen_pgt, unique=True)
Valentin Samir's avatar
Valentin Samir committed
525

Valentin Samir's avatar
Valentin Samir committed
526
    def __unicode__(self):
527
        return u"ProxyGrantingTicket-%s" % self.pk
Valentin Samir's avatar
Valentin Samir committed
528

Valentin Samir's avatar
PEP8  
Valentin Samir committed
529

Valentin Samir's avatar
Valentin Samir committed
530
class Proxy(models.Model):
Valentin Samir's avatar
Valentin Samir committed
531
    """A list of proxies on `ProxyTicket`"""
Valentin Samir's avatar
Valentin Samir committed
532 533 534 535 536
    class Meta:
        ordering = ("-pk", )
    url = models.CharField(max_length=255)
    proxy_ticket = models.ForeignKey(ProxyTicket, related_name="proxies")

Valentin Samir's avatar
Valentin Samir committed
537 538
    def __unicode__(self):
        return self.url