Add some docs using sphinx autodoc
This commit is contained in:
@ -10,7 +10,7 @@
|
||||
#
|
||||
# (c) 2015-2016 Valentin Samir
|
||||
"""models for the app"""
|
||||
from .default_settings import settings
|
||||
from .default_settings import settings, SessionStore
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
@ -23,36 +23,42 @@ from picklefield.fields import PickledObjectField
|
||||
import re
|
||||
import sys
|
||||
import logging
|
||||
from importlib import import_module
|
||||
from datetime import timedelta
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from requests_futures.sessions import FuturesSession
|
||||
|
||||
import cas_server.utils as utils
|
||||
|
||||
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
|
||||
|
||||
#: logger facility
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class FederatedIendityProvider(models.Model):
|
||||
"""
|
||||
Bases: :class:`django.db.models.Model`
|
||||
|
||||
An identity provider for the federated mode
|
||||
"""
|
||||
class Meta:
|
||||
verbose_name = _(u"identity provider")
|
||||
verbose_name_plural = _(u"identity providers")
|
||||
#: Suffix append to backend CAS returned username: ``returned_username`` @ ``suffix``.
|
||||
#: it must be unique.
|
||||
suffix = models.CharField(
|
||||
max_length=30,
|
||||
unique=True,
|
||||
verbose_name=_(u"suffix"),
|
||||
help_text=_(
|
||||
u"Suffix append to backend CAS returner "
|
||||
u"Suffix append to backend CAS returned "
|
||||
u"username: ``returned_username`` @ ``suffix``."
|
||||
)
|
||||
)
|
||||
#: URL to the root of the CAS server application. If login page is
|
||||
#: https://cas.example.net/cas/login then :attr:`server_url` should be
|
||||
#: https://cas.example.net/cas/
|
||||
server_url = models.CharField(max_length=255, verbose_name=_(u"server url"))
|
||||
#: Version of the CAS protocol to use when sending requests the the backend CAS.
|
||||
cas_protocol_version = models.CharField(
|
||||
max_length=30,
|
||||
choices=[
|
||||
@ -67,11 +73,14 @@ class FederatedIendityProvider(models.Model):
|
||||
),
|
||||
default="3"
|
||||
)
|
||||
#: Name for this identity provider displayed on the login page.
|
||||
verbose_name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_(u"verbose name"),
|
||||
help_text=_(u"Name for this identity provider displayed on the login page.")
|
||||
)
|
||||
#: Position of the identity provider on the login page. Identity provider are sorted using the
|
||||
#: (:attr:`pos`, :attr:`verbose_name`, :attr:`suffix`) attributes.
|
||||
pos = models.IntegerField(
|
||||
default=100,
|
||||
verbose_name=_(u"position"),
|
||||
@ -83,6 +92,9 @@ class FederatedIendityProvider(models.Model):
|
||||
)
|
||||
)
|
||||
)
|
||||
#: Display the provider on the login page. Beware that this do not disable the identity
|
||||
#: provider, it just hide it on the login page. User will always be able to log in using this
|
||||
#: provider by fetching ``/federate/suffix``.
|
||||
display = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_(u"display"),
|
||||
@ -99,23 +111,40 @@ class FederatedIendityProvider(models.Model):
|
||||
|
||||
:param unicode username: A CAS backend returned username
|
||||
:param unicode suffix: A suffix identifying the CAS backend
|
||||
:return: The federated username: ``username`` @ ``suffix``.
|
||||
:rtype: unicode
|
||||
"""
|
||||
return u'%s@%s' % (username, suffix)
|
||||
|
||||
def build_username(self, username):
|
||||
"""Transform backend username into federated username"""
|
||||
"""
|
||||
Transform backend username into federated username
|
||||
|
||||
:param unicode username: A CAS backend returned username
|
||||
:return: The federated username: ``username`` @ :attr:`suffix`.
|
||||
:rtype: unicode
|
||||
"""
|
||||
return u'%s@%s' % (username, self.suffix)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class FederatedUser(models.Model):
|
||||
"""A federated user as returner by a CAS provider (username and attributes)"""
|
||||
"""
|
||||
Bases: :class:`django.db.models.Model`
|
||||
|
||||
A federated user as returner by a CAS provider (username and attributes)
|
||||
"""
|
||||
class Meta:
|
||||
unique_together = ("username", "provider")
|
||||
#: The user username returned by the CAS backend on successful ticket validation
|
||||
username = models.CharField(max_length=124)
|
||||
#: A foreign key to :class:`FederatedIendityProvider`
|
||||
provider = models.ForeignKey(FederatedIendityProvider, on_delete=models.CASCADE)
|
||||
#: The user attributes returned by the CAS backend on successful ticket validation
|
||||
attributs = PickledObjectField()
|
||||
#: The last ticket used to authenticate :attr:`username` against :attr:`provider`
|
||||
ticket = models.CharField(max_length=255)
|
||||
#: Last update timespampt. Usually, the last time :attr:`ticket` has been set.
|
||||
last_update = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
@ -123,12 +152,15 @@ class FederatedUser(models.Model):
|
||||
|
||||
@property
|
||||
def federated_username(self):
|
||||
"""return the federated username with a suffix"""
|
||||
"""The federated username with a suffix for the current :class:`FederatedUser`."""
|
||||
return self.provider.build_username(self.username)
|
||||
|
||||
@classmethod
|
||||
def get_from_federated_username(cls, username):
|
||||
"""return a FederatedUser object from a federated username"""
|
||||
"""
|
||||
:return: A :class:`FederatedUser` object from a federated ``username``
|
||||
:rtype: :class:`FederatedUser`
|
||||
"""
|
||||
if username is None:
|
||||
raise cls.DoesNotExist()
|
||||
else:
|
||||
@ -143,7 +175,7 @@ class FederatedUser(models.Model):
|
||||
|
||||
@classmethod
|
||||
def clean_old_entries(cls):
|
||||
"""remove old unused federated users"""
|
||||
"""remove old unused :class:`FederatedUser`"""
|
||||
federated_users = cls.objects.filter(
|
||||
last_update__lt=(timezone.now() - timedelta(seconds=settings.CAS_TICKET_TIMEOUT))
|
||||
)
|
||||
@ -154,16 +186,23 @@ class FederatedUser(models.Model):
|
||||
|
||||
|
||||
class FederateSLO(models.Model):
|
||||
"""An association between a CAS provider ticket and a (username, session) for processing SLO"""
|
||||
"""
|
||||
Bases: :class:`django.db.models.Model`
|
||||
|
||||
An association between a CAS provider ticket and a (username, session) for processing SLO
|
||||
"""
|
||||
class Meta:
|
||||
unique_together = ("username", "session_key", "ticket")
|
||||
#: the federated username with the ``@``component
|
||||
username = models.CharField(max_length=30)
|
||||
#: the session key for the session :attr:`username` has been authenticated using :attr:`ticket`
|
||||
session_key = models.CharField(max_length=40, blank=True, null=True)
|
||||
#: The ticket used to authenticate :attr:`username`
|
||||
ticket = models.CharField(max_length=255, db_index=True)
|
||||
|
||||
@classmethod
|
||||
def clean_deleted_sessions(cls):
|
||||
"""remove old object for which the session do not exists anymore"""
|
||||
"""remove old :class:`FederateSLO` object for which the session do not exists anymore"""
|
||||
for federate_slo in cls.objects.all():
|
||||
if not SessionStore(session_key=federate_slo.session_key).get('authenticated'):
|
||||
federate_slo.delete()
|
||||
@ -171,17 +210,27 @@ class FederateSLO(models.Model):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class User(models.Model):
|
||||
"""A user logged into the CAS"""
|
||||
"""
|
||||
Bases: :class:`django.db.models.Model`
|
||||
|
||||
A user logged into the CAS
|
||||
"""
|
||||
class Meta:
|
||||
unique_together = ("username", "session_key")
|
||||
verbose_name = _("User")
|
||||
verbose_name_plural = _("Users")
|
||||
#: The session key of the current authenticated user
|
||||
session_key = models.CharField(max_length=40, blank=True, null=True)
|
||||
#: The username of the current authenticated user
|
||||
username = models.CharField(max_length=30)
|
||||
#: Last time the authenticated user has do something (auth, fetch ticket, etc…)
|
||||
date = models.DateTimeField(auto_now=True)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""remove the User"""
|
||||
"""
|
||||
Remove the current :class:`User`. If ``settings.CAS_FEDERATE`` is ``True``, also delete
|
||||
the corresponding :class:`FederateSLO` object.
|
||||
"""
|
||||
if settings.CAS_FEDERATE:
|
||||
FederateSLO.objects.filter(
|
||||
username=self.username,
|
||||
@ -191,7 +240,10 @@ class User(models.Model):
|
||||
|
||||
@classmethod
|
||||
def clean_old_entries(cls):
|
||||
"""Remove users inactive since more that SESSION_COOKIE_AGE"""
|
||||
"""
|
||||
Remove :class:`User` objects inactive since more that
|
||||
:django:setting:`SESSION_COOKIE_AGE` and send corresponding SingleLogOut requests.
|
||||
"""
|
||||
users = cls.objects.filter(
|
||||
date__lt=(timezone.now() - timedelta(seconds=settings.SESSION_COOKIE_AGE))
|
||||
)
|
||||
@ -201,7 +253,7 @@ class User(models.Model):
|
||||
|
||||
@classmethod
|
||||
def clean_deleted_sessions(cls):
|
||||
"""Remove user where the session do not exists anymore"""
|
||||
"""Remove :class:`User` objects where the corresponding session do not exists anymore."""
|
||||
for user in cls.objects.all():
|
||||
if not SessionStore(session_key=user.session_key).get('authenticated'):
|
||||
user.logout()
|
||||
@ -209,14 +261,22 @@ class User(models.Model):
|
||||
|
||||
@property
|
||||
def attributs(self):
|
||||
"""return a fresh dict for the user attributs"""
|
||||
"""
|
||||
Property.
|
||||
A fresh :class:`dict` for the user attributes, using ``settings.CAS_AUTH_CLASS``
|
||||
"""
|
||||
return utils.import_attr(settings.CAS_AUTH_CLASS)(self.username).attributs()
|
||||
|
||||
def __str__(self):
|
||||
return u"%s - %s" % (self.username, self.session_key)
|
||||
|
||||
def logout(self, request=None):
|
||||
"""Sending SLO request to all services the user logged in"""
|
||||
"""
|
||||
Send SLO requests to all services the user is logged in.
|
||||
|
||||
:param request: The current django HttpRequest to display possible failure to the user.
|
||||
:type request: :class:`django.http.HttpRequest` or :obj:`NoneType<types.NoneType>`
|
||||
"""
|
||||
async_list = []
|
||||
session = FuturesSession(
|
||||
executor=ThreadPoolExecutor(max_workers=settings.CAS_SLO_MAX_PARALLEL_REQUESTS)
|
||||
@ -249,9 +309,22 @@ class User(models.Model):
|
||||
|
||||
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`
|
||||
Generate a ticket using ``ticket_class`` for the service
|
||||
``service`` matching ``service_pattern`` and asking or not for
|
||||
authentication renewal with ``renew``
|
||||
|
||||
:param type ticket_class: :class:`ServiceTicket` or :class:`ProxyTicket` or
|
||||
:class:`ProxyGrantingTicket`.
|
||||
:param unicode service: The service url for which we want a ticket.
|
||||
:param ServicePattern service_pattern: The service pattern matching ``service``.
|
||||
Beware that ``service`` must match :attr:`ServicePattern.pattern` and the current
|
||||
:class:`User` must pass :meth:`ServicePattern.check_user`. These checks are not done
|
||||
here and you must perform them before calling this method.
|
||||
:param bool renew: Should be ``True`` if authentication has been renewed. Must be
|
||||
``False`` otherwise.
|
||||
:return: A :class:`Ticket` object.
|
||||
:rtype: :class:`ServiceTicket` or :class:`ProxyTicket` or
|
||||
:class:`ProxyGrantingTicket`.
|
||||
"""
|
||||
attributs = dict(
|
||||
(a.name, a.replace if a.replace else a.name) for a in service_pattern.attributs.all()
|
||||
@ -286,8 +359,20 @@ class User(models.Model):
|
||||
return ticket
|
||||
|
||||
def get_service_url(self, service, service_pattern, renew):
|
||||
"""Return the url to which the user must be redirected to
|
||||
after a Service Ticket has been generated"""
|
||||
"""
|
||||
Return the url to which the user must be redirected to
|
||||
after a Service Ticket has been generated
|
||||
|
||||
:param unicode service: The service url for which we want a ticket.
|
||||
:param ServicePattern service_pattern: The service pattern matching ``service``.
|
||||
Beware that ``service`` must match :attr:`ServicePattern.pattern` and the current
|
||||
:class:`User` must pass :meth:`ServicePattern.check_user`. These checks are not done
|
||||
here and you must perform them before calling this method.
|
||||
:param bool renew: Should be ``True`` if authentication has been renewed. Must be
|
||||
``False`` otherwise.
|
||||
:return unicode: The service url with the ticket GET param added.
|
||||
:rtype: unicode
|
||||
"""
|
||||
ticket = self.get_ticket(ServiceTicket, service, service_pattern, renew)
|
||||
url = utils.update_url(service, {'ticket': ticket.value})
|
||||
logger.info("Service ticket created for service %s by user %s." % (service, self.username))
|
||||
@ -295,41 +380,60 @@ class User(models.Model):
|
||||
|
||||
|
||||
class ServicePatternException(Exception):
|
||||
"""Base exception of exceptions raised in the ServicePattern model"""
|
||||
"""
|
||||
Bases: :class:`exceptions.Exception`
|
||||
|
||||
Base exception of exceptions raised in the ServicePattern model"""
|
||||
pass
|
||||
|
||||
|
||||
class BadUsername(ServicePatternException):
|
||||
"""Exception raised then an non allowed username
|
||||
try to get a ticket for a service"""
|
||||
"""
|
||||
Bases: :class:`ServicePatternException`
|
||||
|
||||
Exception raised then an non allowed username try to get a ticket for a service
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BadFilter(ServicePatternException):
|
||||
""""Exception raised then a user try
|
||||
to get a ticket for a service and do not reach a condition"""
|
||||
"""
|
||||
Bases: :class:`ServicePatternException`
|
||||
|
||||
Exception raised then a user try to get a ticket for a service and do not reach a condition
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class UserFieldNotDefined(ServicePatternException):
|
||||
"""Exception raised then a user try to get a ticket for a service
|
||||
using as username an attribut not present on this user"""
|
||||
"""
|
||||
Bases: :class:`ServicePatternException`
|
||||
|
||||
Exception raised then a user try to get a ticket for a service using as username
|
||||
an attribut not present on this user
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ServicePattern(models.Model):
|
||||
"""Allowed services pattern agains services are tested to"""
|
||||
"""
|
||||
Bases: :class:`django.db.models.Model`
|
||||
|
||||
Allowed services pattern agains services are tested to
|
||||
"""
|
||||
class Meta:
|
||||
ordering = ("pos", )
|
||||
verbose_name = _("Service pattern")
|
||||
verbose_name_plural = _("Services patterns")
|
||||
|
||||
#: service patterns are sorted using the :attr:`pos` attribute
|
||||
pos = models.IntegerField(
|
||||
default=100,
|
||||
verbose_name=_(u"position"),
|
||||
help_text=_(u"service patterns are sorted using the position attribute")
|
||||
)
|
||||
#: A name for the service (this can bedisplayed to the user on the login page)
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
unique=True,
|
||||
@ -338,6 +442,9 @@ class ServicePattern(models.Model):
|
||||
verbose_name=_(u"name"),
|
||||
help_text=_(u"A name for the service")
|
||||
)
|
||||
#: 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 '\\'.
|
||||
pattern = models.CharField(
|
||||
max_length=255,
|
||||
unique=True,
|
||||
@ -348,6 +455,7 @@ class ServicePattern(models.Model):
|
||||
"As it is a regular expression, special character must be escaped with a '\\'."
|
||||
)
|
||||
)
|
||||
#: Name of the attribut to transmit as username, if empty the user login is used
|
||||
user_field = models.CharField(
|
||||
max_length=255,
|
||||
default="",
|
||||
@ -355,27 +463,35 @@ class ServicePattern(models.Model):
|
||||
verbose_name=_(u"user field"),
|
||||
help_text=_("Name of the attribut to transmit as username, empty = login")
|
||||
)
|
||||
#: A boolean allowing to limit username allowed to connect to :attr:`usernames`.
|
||||
restrict_users = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_(u"restrict username"),
|
||||
help_text=_("Limit username allowed to connect to the list provided bellow")
|
||||
)
|
||||
#: A boolean allowing to deliver :class:`ProxyTicket` to the service.
|
||||
proxy = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_(u"proxy"),
|
||||
help_text=_("Proxy tickets can be delivered to the service")
|
||||
)
|
||||
#: A boolean allowing the service to be used as a proxy callback (via the pgtUrl GET param)
|
||||
#: to deliver :class:`ProxyGrantingTicket`.
|
||||
proxy_callback = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_(u"proxy callback"),
|
||||
help_text=_("can be used as a proxy callback to deliver PGT")
|
||||
)
|
||||
#: Enable SingleLogOut for the service. Old validaed tickets for the service will be kept
|
||||
#: until ``settings.CAS_TICKET_TIMEOUT`` after what a SLO request is send to the service and
|
||||
#: the ticket is purged from database. A SLO can be send earlier if the user log-out.
|
||||
single_log_out = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_(u"single log out"),
|
||||
help_text=_("Enable SLO for the service")
|
||||
)
|
||||
|
||||
#: An URL where the SLO request will be POST. If empty the service url will be used.
|
||||
#: This is usefull for non HTTP proxied services like smtp or imap.
|
||||
single_log_out_callback = models.CharField(
|
||||
max_length=255,
|
||||
default="",
|
||||
@ -393,7 +509,15 @@ class ServicePattern(models.Model):
|
||||
Check if ``user`` if allowed to use theses services. If ``user`` is not allowed,
|
||||
raises one of :class:`BadFilter`, :class:`UserFieldNotDefined`, :class:`BadUsername`
|
||||
|
||||
:param user: a :class:`User` object
|
||||
:param User user: a :class:`User` object
|
||||
:raises BadUsername: if :attr:`restrict_users` if ``True`` and :attr:`User.username`
|
||||
is not within :attr:`usernames`.
|
||||
:raises BadFilter: if a :class:`FilterAttributValue` condition of :attr:`filters`
|
||||
connot be verified.
|
||||
:raises UserFieldNotDefined: if :attr:`user_field` is defined and its value is not
|
||||
within :attr:`User.attributs`.
|
||||
:return: ``True``
|
||||
:rtype: bool
|
||||
"""
|
||||
if self.restrict_users and not self.usernames.filter(value=user.username):
|
||||
logger.warning("Username %s not allowed on service %s" % (user.username, self.name))
|
||||
@ -434,8 +558,15 @@ class ServicePattern(models.Model):
|
||||
|
||||
@classmethod
|
||||
def validate(cls, service):
|
||||
"""Check if a Service Patern match `service` and
|
||||
return it, else raise `ServicePattern.DoesNotExist`"""
|
||||
"""
|
||||
Get a :class:`ServicePattern` intance from a service url.
|
||||
|
||||
:param unicode service: A service url
|
||||
:return: A :class:`ServicePattern` instance matching ``service``.
|
||||
:rtype: :class:`ServicePattern`
|
||||
:raises ServicePattern.DoesNotExist: if no :class:`ServicePattern` is matching
|
||||
``service``.
|
||||
"""
|
||||
for service_pattern in cls.objects.all().order_by('pos'):
|
||||
if re.match(service_pattern.pattern, service):
|
||||
return service_pattern
|
||||
@ -445,12 +576,20 @@ class ServicePattern(models.Model):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Username(models.Model):
|
||||
"""A list of allowed usernames on a service pattern"""
|
||||
"""
|
||||
Bases: :class:`django.db.models.Model`
|
||||
|
||||
A list of allowed usernames on a :class:`ServicePattern`
|
||||
"""
|
||||
#: username allowed to connect to the service
|
||||
value = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_(u"username"),
|
||||
help_text=_(u"username allowed to connect to the service")
|
||||
)
|
||||
#: ForeignKey to a :class:`ServicePattern`. :class:`Username` instances for a
|
||||
#: :class:`ServicePattern` are accessible thought its :attr:`ServicePattern.usernames`
|
||||
#: attribute.
|
||||
service_pattern = models.ForeignKey(ServicePattern, related_name="usernames")
|
||||
|
||||
def __str__(self):
|
||||
@ -459,14 +598,23 @@ class Username(models.Model):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ReplaceAttributName(models.Model):
|
||||
"""A list of replacement of attributs name for a service pattern"""
|
||||
"""
|
||||
Bases: :class:`django.db.models.Model`
|
||||
|
||||
A replacement of an attribute name for a :class:`ServicePattern`. It also tell to transmit
|
||||
an attribute of :attr:`User.attributs` to the service. An empty :attr:`replace` mean
|
||||
to use the original attribute name.
|
||||
"""
|
||||
class Meta:
|
||||
unique_together = ('name', 'replace', 'service_pattern')
|
||||
#: Name the attribute: a key of :attr:`User.attributs`
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_(u"name"),
|
||||
help_text=_(u"name of an attribut to send to the service, use * for all attributes")
|
||||
)
|
||||
#: The name of the attribute to transmit to the service. If empty, the value of :attr:`name`
|
||||
#: is used.
|
||||
replace = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
@ -474,6 +622,9 @@ class ReplaceAttributName(models.Model):
|
||||
help_text=_(u"name under which the attribut will be show"
|
||||
u"to the service. empty = default name of the attribut")
|
||||
)
|
||||
#: ForeignKey to a :class:`ServicePattern`. :class:`ReplaceAttributName` instances for a
|
||||
#: :class:`ServicePattern` are accessible thought its :attr:`ServicePattern.attributs`
|
||||
#: attribute.
|
||||
service_pattern = models.ForeignKey(ServicePattern, related_name="attributs")
|
||||
|
||||
def __str__(self):
|
||||
@ -485,17 +636,29 @@ class ReplaceAttributName(models.Model):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class FilterAttributValue(models.Model):
|
||||
"""A list of filter on attributs for a service pattern"""
|
||||
"""
|
||||
Bases: :class:`django.db.models.Model`
|
||||
|
||||
A filter on :attr:`User.attributs` for a :class:`ServicePattern`. If a :class:`User` do not
|
||||
have an attribute :attr:`attribut` or its value do not match :attr:`pattern`, then
|
||||
:meth:`ServicePattern.check_user` will raises :class:`BadFilter` if called with that user.
|
||||
"""
|
||||
#: The name of a user attribute
|
||||
attribut = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_(u"attribut"),
|
||||
help_text=_(u"Name of the attribut which must verify pattern")
|
||||
)
|
||||
#: A regular expression the attribute :attr:`attribut` value must verify. If :attr:`attribut`
|
||||
#: if a list, only one of the list values needs to match.
|
||||
pattern = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_(u"pattern"),
|
||||
help_text=_(u"a regular expression")
|
||||
)
|
||||
#: ForeignKey to a :class:`ServicePattern`. :class:`FilterAttributValue` instances for a
|
||||
#: :class:`ServicePattern` are accessible thought its :attr:`ServicePattern.filters`
|
||||
#: attribute.
|
||||
service_pattern = models.ForeignKey(ServicePattern, related_name="filters")
|
||||
|
||||
def __str__(self):
|
||||
@ -504,23 +667,34 @@ class FilterAttributValue(models.Model):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ReplaceAttributValue(models.Model):
|
||||
"""Replacement to apply on attributs values for a service pattern"""
|
||||
"""
|
||||
Bases: :class:`django.db.models.Model`
|
||||
|
||||
A replacement (using a regular expression) of an attribute value for a
|
||||
:class:`ServicePattern`.
|
||||
"""
|
||||
#: Name the attribute: a key of :attr:`User.attributs`
|
||||
attribut = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_(u"attribut"),
|
||||
help_text=_(u"Name of the attribut for which the value must be replace")
|
||||
)
|
||||
#: A regular expression matching the part of the attribute value that need to be changed
|
||||
pattern = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_(u"pattern"),
|
||||
help_text=_(u"An regular expression maching whats need to be replaced")
|
||||
)
|
||||
#: The replacement to what is mached by :attr:`pattern`. groups are capture by \\1, \\2 …
|
||||
replace = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
verbose_name=_(u"replace"),
|
||||
help_text=_(u"replace expression, groups are capture by \\1, \\2 …")
|
||||
)
|
||||
#: ForeignKey to a :class:`ServicePattern`. :class:`ReplaceAttributValue` instances for a
|
||||
#: :class:`ServicePattern` are accessible thought its :attr:`ServicePattern.replacements`
|
||||
#: attribute.
|
||||
service_pattern = models.ForeignKey(ServicePattern, related_name="replacements")
|
||||
|
||||
def __str__(self):
|
||||
@ -529,19 +703,37 @@ class ReplaceAttributValue(models.Model):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Ticket(models.Model):
|
||||
"""Generic class for a Ticket"""
|
||||
"""
|
||||
Bases: :class:`django.db.models.Model`
|
||||
|
||||
Generic class for a Ticket
|
||||
"""
|
||||
class Meta:
|
||||
abstract = True
|
||||
#: ForeignKey to a :class:`User`.
|
||||
user = models.ForeignKey(User, related_name="%(class)s")
|
||||
#: The user attributes to be transmited to the service on successful validation
|
||||
attributs = PickledObjectField()
|
||||
#: A boolean. ``True`` if the ticket has been validated
|
||||
validate = models.BooleanField(default=False)
|
||||
#: The service url for the ticket
|
||||
service = models.TextField()
|
||||
#: ForeignKey to a :class:`ServicePattern`. The :class:`ServicePattern` corresponding to
|
||||
#: :attr:`service`. Use :meth:`ServicePattern.validate` to find it.
|
||||
service_pattern = models.ForeignKey(ServicePattern, related_name="%(class)s")
|
||||
#: Date of the ticket creation
|
||||
creation = models.DateTimeField(auto_now_add=True)
|
||||
#: A boolean. ``True`` if the user has just renew his authentication
|
||||
renew = models.BooleanField(default=False)
|
||||
#: A boolean. Set to :attr:`service_pattern` attribute
|
||||
#: :attr:`ServicePattern.single_log_out` value.
|
||||
single_log_out = models.BooleanField(default=False)
|
||||
|
||||
#: Max duration between ticket creation and its validation. Any validation attempt for the
|
||||
#: ticket after :attr:`creation` + VALIDITY will fail as if the ticket do not exists.
|
||||
VALIDITY = settings.CAS_TICKET_VALIDITY
|
||||
#: Time we keep ticket with :attr:`single_log_out` set to ``True`` before sending SingleLogOut
|
||||
#: requests.
|
||||
TIMEOUT = settings.CAS_TICKET_TIMEOUT
|
||||
|
||||
def __str__(self):
|
||||
@ -615,6 +807,14 @@ class Ticket(models.Model):
|
||||
|
||||
@staticmethod
|
||||
def get_class(ticket):
|
||||
"""
|
||||
Return the ticket class of ``ticket``
|
||||
|
||||
:param unicode ticket: A ticket
|
||||
:return: The class corresponding to ``ticket`` (:class:`ServiceTicket` or
|
||||
:class:`ProxyTicket` or :class:`ProxyGrantingTicket`) if found, ``None`` otherwise.
|
||||
:rtype: :obj:`type` or :obj:`NoneType<types.NoneType>`
|
||||
"""
|
||||
for ticket_class in [ServiceTicket, ProxyTicket, ProxyGrantingTicket]:
|
||||
if ticket.startswith(ticket_class.PREFIX):
|
||||
return ticket_class
|
||||
@ -622,8 +822,14 @@ class Ticket(models.Model):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ServiceTicket(Ticket):
|
||||
"""A Service Ticket"""
|
||||
"""
|
||||
Bases: :class:`Ticket`
|
||||
|
||||
A Service Ticket
|
||||
"""
|
||||
#: The ticket prefix used to differentiate it from other tickets types
|
||||
PREFIX = settings.CAS_SERVICE_TICKET_PREFIX
|
||||
#: The ticket value
|
||||
value = models.CharField(max_length=255, default=utils.gen_st, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
@ -632,8 +838,14 @@ class ServiceTicket(Ticket):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ProxyTicket(Ticket):
|
||||
"""A Proxy Ticket"""
|
||||
"""
|
||||
Bases: :class:`Ticket`
|
||||
|
||||
A Proxy Ticket
|
||||
"""
|
||||
#: The ticket prefix used to differentiate it from other tickets types
|
||||
PREFIX = settings.CAS_PROXY_TICKET_PREFIX
|
||||
#: The ticket value
|
||||
value = models.CharField(max_length=255, default=utils.gen_pt, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
@ -642,9 +854,17 @@ class ProxyTicket(Ticket):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ProxyGrantingTicket(Ticket):
|
||||
"""A Proxy Granting Ticket"""
|
||||
"""
|
||||
Bases: :class:`Ticket`
|
||||
|
||||
A Proxy Granting Ticket
|
||||
"""
|
||||
#: The ticket prefix used to differentiate it from other tickets types
|
||||
PREFIX = settings.CAS_PROXY_GRANTING_TICKET_PREFIX
|
||||
#: ProxyGranting ticket are never validated. However, they can be used during :attr:`VALIDITY`
|
||||
#: to get :class:`ProxyTicket` for :attr:`user`
|
||||
VALIDITY = settings.CAS_PGT_VALIDITY
|
||||
#: The ticket value
|
||||
value = models.CharField(max_length=255, default=utils.gen_pgt, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
@ -653,10 +873,18 @@ class ProxyGrantingTicket(Ticket):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Proxy(models.Model):
|
||||
"""A list of proxies on `ProxyTicket`"""
|
||||
"""
|
||||
Bases: :class:`django.db.models.Model`
|
||||
|
||||
A list of proxies on :class:`ProxyTicket`
|
||||
"""
|
||||
class Meta:
|
||||
ordering = ("-pk", )
|
||||
#: Service url of the PGT used for getting the associated :class:`ProxyTicket`
|
||||
url = models.CharField(max_length=255)
|
||||
#: ForeignKey to a :class:`ProxyTicket`. :class:`Proxy` instances for a
|
||||
#: :class:`ProxyTicket` are accessible thought its :attr:`ProxyTicket.proxies`
|
||||
#: attribute.
|
||||
proxy_ticket = models.ForeignKey(ProxyTicket, related_name="proxies")
|
||||
|
||||
def __str__(self):
|
||||
|
Reference in New Issue
Block a user