models.py 17.2 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

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

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

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

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

Valentin Samir's avatar
PEP8  
Valentin Samir committed
36

Valentin Samir's avatar
Valentin Samir committed
37
class User(models.Model):
Valentin Samir's avatar
Valentin Samir committed
38
    """A user logged into the CAS"""
Valentin Samir's avatar
Valentin Samir committed
39
    class Meta:
40
        unique_together = ("username", "session_key")
41 42
        verbose_name = _("User")
        verbose_name_plural = _("Users")
43
    session_key = models.CharField(max_length=40, blank=True, null=True)
Valentin Samir's avatar
Valentin Samir committed
44
    username = models.CharField(max_length=30)
Valentin Samir's avatar
Valentin Samir committed
45
    date = models.DateTimeField(auto_now=True)
Valentin Samir's avatar
Valentin Samir committed
46

Valentin Samir's avatar
Valentin Samir committed
47 48
    @classmethod
    def clean_old_entries(cls):
49
        """Remove users inactive since more that SESSION_COOKIE_AGE"""
50 51 52
        users = cls.objects.filter(
            date__lt=(timezone.now() - timedelta(seconds=settings.SESSION_COOKIE_AGE))
        )
Valentin Samir's avatar
Valentin Samir committed
53 54 55 56
        for user in users:
            user.logout()
        users.delete()

57 58
    @classmethod
    def clean_deleted_sessions(cls):
59
        """Remove user where the session do not exists anymore"""
60 61 62 63 64
        for user in cls.objects.all():
            if not SessionStore(session_key=user.session_key).get('authenticated'):
                user.logout()
                user.delete()

65 66 67 68 69
    @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
70
    def __unicode__(self):
Valentin Samir's avatar
oops  
Valentin Samir committed
71
        return u"%s - %s" % (self.username, self.session_key)
Valentin Samir's avatar
Valentin Samir committed
72

Valentin Samir's avatar
Valentin Samir committed
73
    def logout(self, request=None):
Valentin Samir's avatar
Valentin Samir committed
74
        """Sending SLO request to all services the user logged in"""
Valentin Samir's avatar
Valentin Samir committed
75
        async_list = []
76 77 78
        session = FuturesSession(
            executor=ThreadPoolExecutor(max_workers=settings.CAS_SLO_MAX_PARALLEL_REQUESTS)
        )
79 80
        # first invalidate all Tickets
        ticket_classes = [ProxyGrantingTicket, ServiceTicket, ProxyTicket]
81
        for ticket_class in ticket_classes:
82 83
            queryset = ticket_class.objects.filter(user=self)
            for ticket in queryset:
84
                ticket.logout(request, session, async_list)
85
            queryset.delete()
Valentin Samir's avatar
Valentin Samir committed
86
        for future in async_list:
87 88 89 90
            if future:
                try:
                    future.result()
                except Exception as error:
Valentin Samir's avatar
Valentin Samir committed
91 92 93 94 95 96
                    logger.warning(
                        "Error during SLO for user %s: %s" % (
                            self.username,
                            error
                        )
                    )
Valentin Samir's avatar
Valentin Samir committed
97 98 99 100 101 102 103
                    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
104 105 106 107 108 109 110 111 112 113 114 115 116

    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
117
        service_attributs = {}
Valentin Samir's avatar
Valentin Samir committed
118
        for (key, value) in self.attributs.items():
Valentin Samir's avatar
Valentin Samir committed
119
            if key in attributs or '*' in attributs:
Valentin Samir's avatar
Valentin Samir committed
120 121
                if key in replacements:
                    value = re.sub(replacements[key][0], replacements[key][1], value)
Valentin Samir's avatar
Valentin Samir committed
122
                service_attributs[attributs.get(key, key)] = value
Valentin Samir's avatar
Valentin Samir committed
123 124 125 126 127
        ticket = ticket_class.objects.create(
            user=self,
            attributs=service_attributs,
            service=service,
            renew=renew,
128 129
            service_pattern=service_pattern,
            single_log_out=service_pattern.single_log_out
Valentin Samir's avatar
Valentin Samir committed
130
        )
Valentin Samir's avatar
Valentin Samir committed
131
        ticket.save()
132
        self.save()
Valentin Samir's avatar
Valentin Samir committed
133 134 135
        return ticket

    def get_service_url(self, service, service_pattern, renew):
Valentin Samir's avatar
Valentin Samir committed
136 137
        """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
138
        ticket = self.get_ticket(ServiceTicket, service, service_pattern, renew)
Valentin Samir's avatar
PEP8  
Valentin Samir committed
139
        url = utils.update_url(service, {'ticket': ticket.value})
Valentin Samir's avatar
Valentin Samir committed
140
        logger.info("Service ticket created for service %s by user %s." % (service, self.username))
Valentin Samir's avatar
Valentin Samir committed
141 142
        return url

Valentin Samir's avatar
PEP8  
Valentin Samir committed
143

144
class ServicePatternException(Exception):
145
    """Base exception of exceptions raised in the ServicePattern model"""
146
    pass
Valentin Samir's avatar
PEP8  
Valentin Samir committed
147 148


149
class BadUsername(ServicePatternException):
Valentin Samir's avatar
Valentin Samir committed
150 151
    """Exception raised then an non allowed username
    try to get a ticket for a service"""
Valentin Samir's avatar
Valentin Samir committed
152
    pass
Valentin Samir's avatar
PEP8  
Valentin Samir committed
153 154


155
class BadFilter(ServicePatternException):
Valentin Samir's avatar
Valentin Samir committed
156 157
    """"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
158
    pass
Valentin Samir's avatar
Valentin Samir committed
159

Valentin Samir's avatar
PEP8  
Valentin Samir committed
160

161
class UserFieldNotDefined(ServicePatternException):
Valentin Samir's avatar
Valentin Samir committed
162 163
    """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
164
    pass
Valentin Samir's avatar
PEP8  
Valentin Samir committed
165 166


Valentin Samir's avatar
Valentin Samir committed
167
class ServicePattern(models.Model):
Valentin Samir's avatar
Valentin Samir committed
168
    """Allowed services pattern agains services are tested to"""
Valentin Samir's avatar
Valentin Samir committed
169 170
    class Meta:
        ordering = ("pos", )
171 172
        verbose_name = _("Service pattern")
        verbose_name_plural = _("Services patterns")
Valentin Samir's avatar
Valentin Samir committed
173

174 175 176 177
    pos = models.IntegerField(
        default=100,
        verbose_name=_(u"position")
    )
Valentin Samir's avatar
Valentin Samir committed
178 179 180 181 182
    name = models.CharField(
        max_length=255,
        unique=True,
        blank=True,
        null=True,
183 184 185 186 187 188
        verbose_name=_(u"name"),
        help_text=_(u"A name for the service")
    )
    pattern = models.CharField(
        max_length=255,
        unique=True,
189 190 191 192 193 194
        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
195 196 197 198 199
    )
    user_field = models.CharField(
        max_length=255,
        default="",
        blank=True,
200 201
        verbose_name=_(u"user field"),
        help_text=_("Name of the attribut to transmit as username, empty = login")
Valentin Samir's avatar
Valentin Samir committed
202 203 204
    )
    restrict_users = models.BooleanField(
        default=False,
205 206
        verbose_name=_(u"restrict username"),
        help_text=_("Limit username allowed to connect to the list provided bellow")
Valentin Samir's avatar
Valentin Samir committed
207 208 209
    )
    proxy = models.BooleanField(
        default=False,
210
        verbose_name=_(u"proxy"),
211 212 213 214 215 216
        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
217
    )
Valentin Samir's avatar
Valentin Samir committed
218
    single_log_out = models.BooleanField(
Valentin Samir's avatar
Valentin Samir committed
219
        default=False,
Valentin Samir's avatar
Valentin Samir committed
220 221
        verbose_name=_(u"single log out"),
        help_text=_("Enable SLO for the service")
Valentin Samir's avatar
Valentin Samir committed
222
    )
Valentin Samir's avatar
Valentin Samir committed
223

224 225 226 227 228
    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
229
        help_text=_(u"URL where the SLO request will be POST. empty = service url\n"
230 231 232
                    u"This is usefull for non HTTP proxied services.")
    )

Valentin Samir's avatar
Valentin Samir committed
233 234 235 236
    def __unicode__(self):
        return u"%s: %s" % (self.pos, self.pattern)

    def check_user(self, user):
Valentin Samir's avatar
Valentin Samir committed
237
        """Check if `user` if allowed to use theses services"""
Valentin Samir's avatar
Valentin Samir committed
238
        if self.restrict_users and not self.usernames.filter(value=user.username):
Valentin Samir's avatar
Valentin Samir committed
239
            logger.warning("Username %s not allowed on service %s" % (user.username, self.name))
Valentin Samir's avatar
Valentin Samir committed
240
            raise BadUsername()
Valentin Samir's avatar
Valentin Samir committed
241
        for filtre in self.filters.all():
242 243
            if isinstance(user.attributs.get(filtre.attribut, []), list):
                attrs = user.attributs.get(filtre.attribut, [])
Valentin Samir's avatar
Valentin Samir committed
244
            else:
Valentin Samir's avatar
Valentin Samir committed
245 246 247
                attrs = [user.attributs[filtre.attribut]]
            for value in attrs:
                if re.match(filtre.pattern, str(value)):
Valentin Samir's avatar
Valentin Samir committed
248 249
                    break
            else:
Valentin Samir's avatar
Valentin Samir committed
250 251 252 253 254 255 256 257 258
                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
259 260 261
                raise BadFilter('%s do not match %s %s' % (
                    filtre.pattern,
                    filtre.attribut,
Valentin Samir's avatar
Valentin Samir committed
262
                    user.attributs.get(filtre.attribut)
Valentin Samir's avatar
Valentin Samir committed
263
                ))
Valentin Samir's avatar
Valentin Samir committed
264
        if self.user_field and not user.attributs.get(self.user_field):
Valentin Samir's avatar
Valentin Samir committed
265 266 267 268 269 270 271
            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
272 273 274 275 276
            raise UserFieldNotDefined()
        return True

    @classmethod
    def validate(cls, service):
Valentin Samir's avatar
Valentin Samir committed
277 278 279 280 281
        """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
282
        logger.warning("Service %s not allowed." % service)
Valentin Samir's avatar
Valentin Samir committed
283 284
        raise cls.DoesNotExist()

Valentin Samir's avatar
PEP8  
Valentin Samir committed
285

Valentin Samir's avatar
Valentin Samir committed
286 287
class Username(models.Model):
    """A list of allowed usernames on a service pattern"""
288 289 290 291 292
    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
293
    service_pattern = models.ForeignKey(ServicePattern, related_name="usernames")
Valentin Samir's avatar
Valentin Samir committed
294

Valentin Samir's avatar
Valentin Samir committed
295 296 297
    def __unicode__(self):
        return self.value

Valentin Samir's avatar
PEP8  
Valentin Samir committed
298

Valentin Samir's avatar
Valentin Samir committed
299
class ReplaceAttributName(models.Model):
Valentin Samir's avatar
Valentin Samir committed
300
    """A list of replacement of attributs name for a service pattern"""
Valentin Samir's avatar
Valentin Samir committed
301
    class Meta:
Valentin Samir's avatar
Valentin Samir committed
302
        unique_together = ('name', 'replace', 'service_pattern')
Valentin Samir's avatar
Valentin Samir committed
303 304
    name = models.CharField(
        max_length=255,
305
        verbose_name=_(u"name"),
Valentin Samir's avatar
Valentin Samir committed
306
        help_text=_(u"name of an attribut to send to the service, use * for all attributes")
Valentin Samir's avatar
Valentin Samir committed
307 308 309 310
    )
    replace = models.CharField(
        max_length=255,
        blank=True,
311
        verbose_name=_(u"replace"),
Valentin Samir's avatar
PEP8  
Valentin Samir committed
312 313
        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
314
    )
Valentin Samir's avatar
Valentin Samir committed
315 316 317 318 319 320 321 322
    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
323

Valentin Samir's avatar
Valentin Samir committed
324
class FilterAttributValue(models.Model):
Valentin Samir's avatar
Valentin Samir committed
325 326 327
    """A list of filter on attributs for a service pattern"""
    attribut = models.CharField(
        max_length=255,
328 329
        verbose_name=_(u"attribut"),
        help_text=_(u"Name of the attribut which must verify pattern")
Valentin Samir's avatar
Valentin Samir committed
330 331 332
    )
    pattern = models.CharField(
        max_length=255,
333 334
        verbose_name=_(u"pattern"),
        help_text=_(u"a regular expression")
Valentin Samir's avatar
Valentin Samir committed
335
    )
Valentin Samir's avatar
Valentin Samir committed
336 337 338 339 340
    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
341

Valentin Samir's avatar
Valentin Samir committed
342
class ReplaceAttributValue(models.Model):
Valentin Samir's avatar
Valentin Samir committed
343 344 345
    """Replacement to apply on attributs values for a service pattern"""
    attribut = models.CharField(
        max_length=255,
346 347
        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
348 349 350
    )
    pattern = models.CharField(
        max_length=255,
351 352
        verbose_name=_(u"pattern"),
        help_text=_(u"An regular expression maching whats need to be replaced")
Valentin Samir's avatar
Valentin Samir committed
353 354 355 356
    )
    replace = models.CharField(
        max_length=255,
        blank=True,
357 358
        verbose_name=_(u"replace"),
        help_text=_(u"replace expression, groups are capture by \\1, \\2 …")
Valentin Samir's avatar
Valentin Samir committed
359
    )
Valentin Samir's avatar
Valentin Samir committed
360 361 362 363
    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
364 365


Valentin Samir's avatar
Valentin Samir committed
366
class Ticket(models.Model):
Valentin Samir's avatar
Valentin Samir committed
367
    """Generic class for a Ticket"""
Valentin Samir's avatar
Valentin Samir committed
368 369 370 371 372 373
    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
374
    service_pattern = models.ForeignKey(ServicePattern, related_name="%(class)s")
Valentin Samir's avatar
Valentin Samir committed
375 376
    creation = models.DateTimeField(auto_now_add=True)
    renew = models.BooleanField(default=False)
377
    single_log_out = models.BooleanField(default=False)
Valentin Samir's avatar
Valentin Samir committed
378

Valentin Samir's avatar
Valentin Samir committed
379 380 381
    VALIDITY = settings.CAS_TICKET_VALIDITY
    TIMEOUT = settings.CAS_TICKET_TIMEOUT

Valentin Samir's avatar
Valentin Samir committed
382
    def __unicode__(self):
383
        return u"Ticket-%s" % self.pk
Valentin Samir's avatar
Valentin Samir committed
384

385
    @classmethod
Valentin Samir's avatar
Valentin Samir committed
386
    def clean_old_entries(cls):
387 388 389 390
        """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
391 392
                Q(single_log_out=False) & Q(validate=True)
            ) | (
393 394
                Q(validate=False) &
                Q(creation__lt=(timezone.now() - timedelta(seconds=cls.VALIDITY)))
395 396 397 398
            )
        ).delete()

        # sending SLO to timed-out validated tickets
Valentin Samir's avatar
Valentin Samir committed
399
        if cls.TIMEOUT and cls.TIMEOUT > 0:
400
            async_list = []
401 402 403
            session = FuturesSession(
                executor=ThreadPoolExecutor(max_workers=settings.CAS_SLO_MAX_PARALLEL_REQUESTS)
            )
404
            queryset = cls.objects.filter(
Valentin Samir's avatar
Valentin Samir committed
405
                creation__lt=(timezone.now() - timedelta(seconds=cls.TIMEOUT))
406 407
            )
            for ticket in queryset:
408
                ticket.logout(None, session, async_list)
409 410 411 412 413 414
            queryset.delete()
            for future in async_list:
                if future:
                    try:
                        future.result()
                    except Exception as error:
Valentin Samir's avatar
Valentin Samir committed
415
                        logger.warning("Error durring SLO %s" % error)
416 417
                        sys.stderr.write("%r\n" % error)

418
    def logout(self, request, session, async_list=None):
Valentin Samir's avatar
Valentin Samir committed
419
        """Send a SLO request to the ticket service"""
420 421 422
        # On logout invalidate the Ticket
        self.validate = True
        self.save()
423
        if self.validate and self.single_log_out:
Valentin Samir's avatar
Valentin Samir committed
424 425 426 427 428 429
            logger.info(
                "Sending SLO requests to service %s for user %s" % (
                    self.service,
                    self.user.username
                )
            )
Valentin Samir's avatar
Valentin Samir committed
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
            xml = u"""<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
 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>
</samlp:LogoutRequest>""" % \
                {
                    'id': utils.gen_saml_id(),
                    'datetime': timezone.now().isoformat(),
                    'ticket':  self.value
                }
            if self.service_pattern.single_log_out_callback:
                url = self.service_pattern.single_log_out_callback
            else:
                url = self.service
            async_list.append(
                session.post(
                    url.encode('utf-8'),
                    data={'logoutRequest': xml.encode('utf-8')},
                    timeout=settings.CAS_SLO_TIMEOUT
Valentin Samir's avatar
Valentin Samir committed
449
                )
Valentin Samir's avatar
Valentin Samir committed
450
            )
Valentin Samir's avatar
Valentin Samir committed
451

Valentin Samir's avatar
PEP8  
Valentin Samir committed
452

Valentin Samir's avatar
Valentin Samir committed
453
class ServiceTicket(Ticket):
Valentin Samir's avatar
Valentin Samir committed
454
    """A Service Ticket"""
455
    PREFIX = settings.CAS_SERVICE_TICKET_PREFIX
456
    value = models.CharField(max_length=255, default=utils.gen_st, unique=True)
Valentin Samir's avatar
PEP8  
Valentin Samir committed
457

Valentin Samir's avatar
Valentin Samir committed
458
    def __unicode__(self):
459
        return u"ServiceTicket-%s" % self.pk
Valentin Samir's avatar
PEP8  
Valentin Samir committed
460 461


Valentin Samir's avatar
Valentin Samir committed
462
class ProxyTicket(Ticket):
Valentin Samir's avatar
Valentin Samir committed
463
    """A Proxy Ticket"""
464
    PREFIX = settings.CAS_PROXY_TICKET_PREFIX
465
    value = models.CharField(max_length=255, default=utils.gen_pt, unique=True)
Valentin Samir's avatar
PEP8  
Valentin Samir committed
466

Valentin Samir's avatar
Valentin Samir committed
467
    def __unicode__(self):
468
        return u"ProxyTicket-%s" % self.pk
Valentin Samir's avatar
PEP8  
Valentin Samir committed
469 470


Valentin Samir's avatar
Valentin Samir committed
471
class ProxyGrantingTicket(Ticket):
Valentin Samir's avatar
Valentin Samir committed
472
    """A Proxy Granting Ticket"""
473
    PREFIX = settings.CAS_PROXY_GRANTING_TICKET_PREFIX
Valentin Samir's avatar
Valentin Samir committed
474
    VALIDITY = settings.CAS_PGT_VALIDITY
475
    value = models.CharField(max_length=255, default=utils.gen_pgt, unique=True)
Valentin Samir's avatar
Valentin Samir committed
476

Valentin Samir's avatar
Valentin Samir committed
477
    def __unicode__(self):
478
        return u"ProxyGrantingTicket-%s" % self.pk
Valentin Samir's avatar
Valentin Samir committed
479

Valentin Samir's avatar
PEP8  
Valentin Samir committed
480

Valentin Samir's avatar
Valentin Samir committed
481
class Proxy(models.Model):
Valentin Samir's avatar
Valentin Samir committed
482
    """A list of proxies on `ProxyTicket`"""
Valentin Samir's avatar
Valentin Samir committed
483 484 485 486 487
    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
488 489
    def __unicode__(self):
        return self.url