This commit is contained in:
Valentin Samir 2015-06-12 18:10:52 +02:00
parent ba4af1372c
commit 39557d1942
11 changed files with 161 additions and 115 deletions

View file

@ -14,8 +14,12 @@ from .models import ServiceTicket, ProxyTicket, ProxyGrantingTicket, User, Servi
from .models import Username, ReplaceAttributName, ReplaceAttributValue, FilterAttributValue from .models import Username, ReplaceAttributName, ReplaceAttributValue, FilterAttributValue
from .forms import TicketForm from .forms import TicketForm
tickets_readonly_fields=('validate', 'service', 'service_pattern', 'creation', 'renew', 'single_log_out', 'value') tickets_readonly_fields = ('validate', 'service', 'service_pattern',
tickets_fields = ('validate', 'service', 'service_pattern', 'creation', 'renew', 'single_log_out') 'creation', 'renew', 'single_log_out', 'value')
tickets_fields = ('validate', 'service', 'service_pattern',
'creation', 'renew', 'single_log_out')
class ServiceTicketInline(admin.TabularInline): class ServiceTicketInline(admin.TabularInline):
"""`ServiceTicket` in admin interface""" """`ServiceTicket` in admin interface"""
model = ServiceTicket model = ServiceTicket
@ -23,6 +27,8 @@ class ServiceTicketInline(admin.TabularInline):
form = TicketForm form = TicketForm
readonly_fields = tickets_readonly_fields readonly_fields = tickets_readonly_fields
fields = tickets_fields fields = tickets_fields
class ProxyTicketInline(admin.TabularInline): class ProxyTicketInline(admin.TabularInline):
"""`ProxyTicket` in admin interface""" """`ProxyTicket` in admin interface"""
model = ProxyTicket model = ProxyTicket
@ -30,6 +36,8 @@ class ProxyTicketInline(admin.TabularInline):
form = TicketForm form = TicketForm
readonly_fields = tickets_readonly_fields readonly_fields = tickets_readonly_fields
fields = tickets_fields fields = tickets_fields
class ProxyGrantingInline(admin.TabularInline): class ProxyGrantingInline(admin.TabularInline):
"""`ProxyGrantingTicket` in admin interface""" """`ProxyGrantingTicket` in admin interface"""
model = ProxyGrantingTicket model = ProxyGrantingTicket
@ -38,30 +46,39 @@ class ProxyGrantingInline(admin.TabularInline):
readonly_fields = tickets_readonly_fields readonly_fields = tickets_readonly_fields
fields = tickets_fields[1:] fields = tickets_fields[1:]
class UserAdmin(admin.ModelAdmin): class UserAdmin(admin.ModelAdmin):
"""`User` in admin interface""" """`User` in admin interface"""
inlines = (ServiceTicketInline, ProxyTicketInline, ProxyGrantingInline) inlines = (ServiceTicketInline, ProxyTicketInline, ProxyGrantingInline)
readonly_fields=('username', 'date', "session_key") readonly_fields = ('username', 'date', "session_key")
fields = ('username', 'date', "session_key") fields = ('username', 'date', "session_key")
list_display = ('username', 'date', "session_key") list_display = ('username', 'date', "session_key")
class UsernamesInline(admin.TabularInline): class UsernamesInline(admin.TabularInline):
"""`Username` in admin interface""" """`Username` in admin interface"""
model = Username model = Username
extra = 0 extra = 0
class ReplaceAttributNameInline(admin.TabularInline): class ReplaceAttributNameInline(admin.TabularInline):
"""`ReplaceAttributName` in admin interface""" """`ReplaceAttributName` in admin interface"""
model = ReplaceAttributName model = ReplaceAttributName
extra = 0 extra = 0
class ReplaceAttributValueInline(admin.TabularInline): class ReplaceAttributValueInline(admin.TabularInline):
"""`ReplaceAttributValue` in admin interface""" """`ReplaceAttributValue` in admin interface"""
model = ReplaceAttributValue model = ReplaceAttributValue
extra = 0 extra = 0
class FilterAttributValueInline(admin.TabularInline): class FilterAttributValueInline(admin.TabularInline):
"""`FilterAttributValue` in admin interface""" """`FilterAttributValue` in admin interface"""
model = FilterAttributValue model = FilterAttributValue
extra = 0 extra = 0
class ServicePatternAdmin(admin.ModelAdmin): class ServicePatternAdmin(admin.ModelAdmin):
"""`ServicePattern` in admin interface""" """`ServicePattern` in admin interface"""
inlines = ( inlines = (
@ -70,7 +87,8 @@ class ServicePatternAdmin(admin.ModelAdmin):
ReplaceAttributValueInline, ReplaceAttributValueInline,
FilterAttributValueInline FilterAttributValueInline
) )
list_display = ('pos', 'name', 'pattern', 'proxy', 'single_log_out', 'proxy_callback', 'restrict_users') list_display = ('pos', 'name', 'pattern', 'proxy',
'single_log_out', 'proxy_callback', 'restrict_users')
admin.site.register(User, UserAdmin) admin.site.register(User, UserAdmin)

View file

@ -19,8 +19,10 @@ try:
except ImportError: except ImportError:
MySQLdb = None MySQLdb = None
class DummyAuthUser(object): class DummyAuthUser(object):
"""A Dummy authentication class""" """A Dummy authentication class"""
def __init__(self, username): def __init__(self, username):
self.username = username self.username = username
@ -36,6 +38,7 @@ class DummyAuthUser(object):
class TestAuthUser(DummyAuthUser): class TestAuthUser(DummyAuthUser):
"""A test authentication class with one user test having """A test authentication class with one user test having
alose test as password and some attributes""" alose test as password and some attributes"""
def __init__(self, username): def __init__(self, username):
super(TestAuthUser, self).__init__(username) super(TestAuthUser, self).__init__(username)
@ -45,20 +48,21 @@ class TestAuthUser(DummyAuthUser):
def attributs(self): def attributs(self):
"""return a dict of user attributes""" """return a dict of user attributes"""
return {'nom':'Nymous', 'prenom':'Ano', 'email':'anonymous@example.net'} return {'nom': 'Nymous', 'prenom': 'Ano', 'email': 'anonymous@example.net'}
class MysqlAuthUser(DummyAuthUser): class MysqlAuthUser(DummyAuthUser):
"""A mysql auth class: authentication user agains a mysql database""" """A mysql auth class: authentication user agains a mysql database"""
user = None user = None
def __init__(self, username): def __init__(self, username):
mysql_config = { mysql_config = {
"user": settings.CAS_SQL_USERNAME, "user": settings.CAS_SQL_USERNAME,
"passwd": settings.CAS_SQL_PASSWORD, "passwd": settings.CAS_SQL_PASSWORD,
"db": settings.CAS_SQL_DBNAME, "db": settings.CAS_SQL_DBNAME,
"host": settings.CAS_SQL_HOST, "host": settings.CAS_SQL_HOST,
"charset":settings.CAS_SQL_DBCHARSET, "charset": settings.CAS_SQL_DBCHARSET,
"cursorclass":MySQLdb.cursors.DictCursor "cursorclass": MySQLdb.cursors.DictCursor
} }
if not MySQLdb: if not MySQLdb:
raise RuntimeError("Please install MySQLdb before using the MysqlAuthUser backend") raise RuntimeError("Please install MySQLdb before using the MysqlAuthUser backend")
@ -92,9 +96,11 @@ class MysqlAuthUser(DummyAuthUser):
else: else:
return self.user return self.user
class DjangoAuthUser(DummyAuthUser): class DjangoAuthUser(DummyAuthUser):
"""A django auth class: authenticate user agains django internal users""" """A django auth class: authenticate user agains django internal users"""
user = None user = None
def __init__(self, username): def __init__(self, username):
try: try:
self.user = User.objects.get(username=username) self.user = User.objects.get(username=username)
@ -102,7 +108,6 @@ class DjangoAuthUser(DummyAuthUser):
pass pass
super(DjangoAuthUser, self).__init__(username) super(DjangoAuthUser, self).__init__(username)
def test_password(self, password): def test_password(self, password):
"""test `password` agains the user""" """test `password` agains the user"""
if not self.user: if not self.user:

View file

@ -11,6 +11,7 @@
"""Default values for the app's settings""" """Default values for the app's settings"""
from django.conf import settings from django.conf import settings
def setting_default(name, default_value): def setting_default(name, default_value):
"""if the config `name` is not set, set it the `default_value`""" """if the config `name` is not set, set it the `default_value`"""
value = getattr(settings, name, default_value) value = getattr(settings, name, default_value)
@ -60,7 +61,6 @@ setting_default('CAS_SQL_USERNAME', '')
setting_default('CAS_SQL_PASSWORD', '') setting_default('CAS_SQL_PASSWORD', '')
setting_default('CAS_SQL_DBNAME', '') setting_default('CAS_SQL_DBNAME', '')
setting_default('CAS_SQL_DBCHARSET', 'utf8') setting_default('CAS_SQL_DBCHARSET', 'utf8')
setting_default('CAS_SQL_USER_QUERY', 'SELECT user AS usersame, pass AS ' \ setting_default('CAS_SQL_USER_QUERY', 'SELECT user AS usersame, pass AS '
'password, users.* FROM users WHERE user = %s') 'password, users.* FROM users WHERE user = %s')
setting_default('CAS_SQL_PASSWORD_CHECK', 'crypt') # crypt or plain setting_default('CAS_SQL_PASSWORD_CHECK', 'crypt') # crypt or plain

View file

@ -17,6 +17,7 @@ from django.utils.translation import ugettext_lazy as _
import utils import utils
import models import models
class UserCredential(forms.Form): class UserCredential(forms.Form):
"""Form used on the login page to retrive user credentials""" """Form used on the login page to retrive user credentials"""
username = forms.CharField(label=_('login')) username = forms.CharField(label=_('login'))

View file

@ -1,8 +1,9 @@
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from ... import models from ... import models
class Command(BaseCommand): class Command(BaseCommand):
args = '' args = ''
help = _(u"Clean deleted sessions") help = _(u"Clean deleted sessions")

View file

@ -1,8 +1,9 @@
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from ... import models from ... import models
class Command(BaseCommand): class Command(BaseCommand):
args = '' args = ''
help = _(u"Clean old trickets") help = _(u"Clean old trickets")

View file

@ -31,6 +31,7 @@ import utils
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
class User(models.Model): class User(models.Model):
"""A user logged into the CAS""" """A user logged into the CAS"""
class Meta: class Meta:
@ -123,24 +124,32 @@ class User(models.Model):
"""Return the url to which the user must be redirected to """Return the url to which the user must be redirected to
after a Service Ticket has been generated""" after a Service Ticket has been generated"""
ticket = self.get_ticket(ServiceTicket, service, service_pattern, renew) ticket = self.get_ticket(ServiceTicket, service, service_pattern, renew)
url = utils.update_url(service, {'ticket':ticket.value}) url = utils.update_url(service, {'ticket': ticket.value})
return url return url
class ServicePatternException(Exception): class ServicePatternException(Exception):
pass pass
class BadUsername(ServicePatternException): class BadUsername(ServicePatternException):
"""Exception raised then an non allowed username """Exception raised then an non allowed username
try to get a ticket for a service""" try to get a ticket for a service"""
pass pass
class BadFilter(ServicePatternException): class BadFilter(ServicePatternException):
""""Exception raised then a user try """"Exception raised then a user try
to get a ticket for a service and do not reach a condition""" to get a ticket for a service and do not reach a condition"""
pass pass
class UserFieldNotDefined(ServicePatternException): class UserFieldNotDefined(ServicePatternException):
"""Exception raised then a user try to get a ticket for a service """Exception raised then a user try to get a ticket for a service
using as username an attribut not present on this user""" using as username an attribut not present on this user"""
pass pass
class ServicePattern(models.Model): class ServicePattern(models.Model):
"""Allowed services pattern agains services are tested to""" """Allowed services pattern agains services are tested to"""
class Meta: class Meta:
@ -196,11 +205,10 @@ class ServicePattern(models.Model):
default="", default="",
blank=True, blank=True,
verbose_name=_(u"single log out callback"), verbose_name=_(u"single log out callback"),
help_text=_(u"URL where the SLO request will be POST. empty = service url\n" \ help_text=_(u"URL where the SLO request will be POST. empty = service url\n"
u"This is usefull for non HTTP proxied services.") u"This is usefull for non HTTP proxied services.")
) )
def __unicode__(self): def __unicode__(self):
return u"%s: %s" % (self.pos, self.pattern) return u"%s: %s" % (self.pos, self.pattern)
@ -226,7 +234,6 @@ class ServicePattern(models.Model):
raise UserFieldNotDefined() raise UserFieldNotDefined()
return True return True
@classmethod @classmethod
def validate(cls, service): def validate(cls, service):
"""Check if a Service Patern match `service` and """Check if a Service Patern match `service` and
@ -236,6 +243,7 @@ class ServicePattern(models.Model):
return service_pattern return service_pattern
raise cls.DoesNotExist() raise cls.DoesNotExist()
class Username(models.Model): class Username(models.Model):
"""A list of allowed usernames on a service pattern""" """A list of allowed usernames on a service pattern"""
value = models.CharField( value = models.CharField(
@ -248,6 +256,7 @@ class Username(models.Model):
def __unicode__(self): def __unicode__(self):
return self.value return self.value
class ReplaceAttributName(models.Model): class ReplaceAttributName(models.Model):
"""A list of replacement of attributs name for a service pattern""" """A list of replacement of attributs name for a service pattern"""
class Meta: class Meta:
@ -261,8 +270,8 @@ class ReplaceAttributName(models.Model):
max_length=255, max_length=255,
blank=True, blank=True,
verbose_name=_(u"replace"), verbose_name=_(u"replace"),
help_text=_(u"name under which the attribut will be show" \ help_text=_(u"name under which the attribut will be show"
u"to the service. empty = default name of the attribut") u"to the service. empty = default name of the attribut")
) )
service_pattern = models.ForeignKey(ServicePattern, related_name="attributs") service_pattern = models.ForeignKey(ServicePattern, related_name="attributs")
@ -272,6 +281,7 @@ class ReplaceAttributName(models.Model):
else: else:
return u"%s%s" % (self.name, self.replace) return u"%s%s" % (self.name, self.replace)
class FilterAttributValue(models.Model): class FilterAttributValue(models.Model):
"""A list of filter on attributs for a service pattern""" """A list of filter on attributs for a service pattern"""
attribut = models.CharField( attribut = models.CharField(
@ -289,6 +299,7 @@ class FilterAttributValue(models.Model):
def __unicode__(self): def __unicode__(self):
return u"%s %s" % (self.attribut, self.pattern) return u"%s %s" % (self.attribut, self.pattern)
class ReplaceAttributValue(models.Model): class ReplaceAttributValue(models.Model):
"""Replacement to apply on attributs values for a service pattern""" """Replacement to apply on attributs values for a service pattern"""
attribut = models.CharField( attribut = models.CharField(
@ -338,10 +349,10 @@ class Ticket(models.Model):
# removing old validated ticket and non validated expired tickets # removing old validated ticket and non validated expired tickets
cls.objects.filter( cls.objects.filter(
( (
Q(single_log_out=False)&Q(validate=True) Q(single_log_out=False) & Q(validate=True)
)|( ) | (
Q(validate=False)&\ Q(validate=False)
Q(creation__lt=(timezone.now() - timedelta(seconds=cls.VALIDITY))) & Q(creation__lt=(timezone.now() - timedelta(seconds=cls.VALIDITY)))
) )
).delete() ).delete()
@ -373,18 +384,18 @@ class Ticket(models.Model):
<saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"></saml:NameID> <saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"></saml:NameID>
<samlp:SessionIndex>%(ticket)s</samlp:SessionIndex> <samlp:SessionIndex>%(ticket)s</samlp:SessionIndex>
</samlp:LogoutRequest>""" % \ </samlp:LogoutRequest>""" % \
{ {
'id' : os.urandom(20).encode("hex"), 'id': os.urandom(20).encode("hex"),
'datetime' : timezone.now().isoformat(), 'datetime': timezone.now().isoformat(),
'ticket': self.value 'ticket': self.value
} }
if self.service_pattern.single_log_out_callback: if self.service_pattern.single_log_out_callback:
url = self.service_pattern.single_log_out_callback url = self.service_pattern.single_log_out_callback
else: else:
url = self.service url = self.service
return session.post( return session.post(
url.encode('utf-8'), url.encode('utf-8'),
data={'logoutRequest':xml.encode('utf-8')}, data={'logoutRequest': xml.encode('utf-8')},
) )
except Exception as error: except Exception as error:
if request is not None: if request is not None:
@ -393,33 +404,40 @@ class Ticket(models.Model):
request, request,
messages.WARNING, messages.WARNING,
_(u'Error during service logout %(service)s:\n%(error)s') % _(u'Error during service logout %(service)s:\n%(error)s') %
{'service': self.service, 'error':error} {'service': self.service, 'error': error}
) )
else: else:
sys.stderr.write("%r\n" % error) sys.stderr.write("%r\n" % error)
class ServiceTicket(Ticket): class ServiceTicket(Ticket):
"""A Service Ticket""" """A Service Ticket"""
PREFIX = settings.CAS_SERVICE_TICKET_PREFIX PREFIX = settings.CAS_SERVICE_TICKET_PREFIX
value = models.CharField(max_length=255, default=utils.gen_st, unique=True) value = models.CharField(max_length=255, default=utils.gen_st, unique=True)
def __unicode__(self): def __unicode__(self):
return u"ServiceTicket-%s" % self.pk return u"ServiceTicket-%s" % self.pk
class ProxyTicket(Ticket): class ProxyTicket(Ticket):
"""A Proxy Ticket""" """A Proxy Ticket"""
PREFIX = settings.CAS_PROXY_TICKET_PREFIX PREFIX = settings.CAS_PROXY_TICKET_PREFIX
value = models.CharField(max_length=255, default=utils.gen_pt, unique=True) value = models.CharField(max_length=255, default=utils.gen_pt, unique=True)
def __unicode__(self): def __unicode__(self):
return u"ProxyTicket-%s" % self.pk return u"ProxyTicket-%s" % self.pk
class ProxyGrantingTicket(Ticket): class ProxyGrantingTicket(Ticket):
"""A Proxy Granting Ticket""" """A Proxy Granting Ticket"""
PREFIX = settings.CAS_PROXY_GRANTING_TICKET_PREFIX PREFIX = settings.CAS_PROXY_GRANTING_TICKET_PREFIX
VALIDITY = settings.CAS_PGT_VALIDITY VALIDITY = settings.CAS_PGT_VALIDITY
value = models.CharField(max_length=255, default=utils.gen_pgt, unique=True) value = models.CharField(max_length=255, default=utils.gen_pgt, unique=True)
def __unicode__(self): def __unicode__(self):
return u"ProxyGrantingTicket-%s" % self.pk return u"ProxyGrantingTicket-%s" % self.pk
class Proxy(models.Model): class Proxy(models.Model):
"""A list of proxies on `ProxyTicket`""" """A list of proxies on `ProxyTicket`"""
class Meta: class Meta:
@ -429,4 +447,3 @@ class Proxy(models.Model):
def __unicode__(self): def __unicode__(self):
return self.url return self.url

View file

@ -1,12 +0,0 @@
# 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
from django.test import TestCase

View file

@ -21,12 +21,27 @@ urlpatterns = patterns(
url('^login$', views.LoginView.as_view(), name='login'), url('^login$', views.LoginView.as_view(), name='login'),
url('^logout$', views.LogoutView.as_view(), name='logout'), url('^logout$', views.LogoutView.as_view(), name='logout'),
url('^validate$', views.Validate.as_view(), name='validate'), url('^validate$', views.Validate.as_view(), name='validate'),
url('^serviceValidate$', views.ValidateService.as_view(allow_proxy_ticket=False), name='serviceValidate'), url(
url('^proxyValidate$', views.ValidateService.as_view(allow_proxy_ticket=True), name='proxyValidate'), '^serviceValidate$',
views.ValidateService.as_view(allow_proxy_ticket=False),
name='serviceValidate'
),
url(
'^proxyValidate$',
views.ValidateService.as_view(allow_proxy_ticket=True),
name='proxyValidate'
),
url('^proxy$', views.Proxy.as_view(), name='proxy'), url('^proxy$', views.Proxy.as_view(), name='proxy'),
url('^p3/serviceValidate$', views.ValidateService.as_view(allow_proxy_ticket=False), name='p3_serviceValidate'), url(
url('^p3/proxyValidate$', views.ValidateService.as_view(allow_proxy_ticket=True), name='p3_proxyValidate'), '^p3/serviceValidate$',
views.ValidateService.as_view(allow_proxy_ticket=False),
name='p3_serviceValidate'
),
url(
'^p3/proxyValidate$',
views.ValidateService.as_view(allow_proxy_ticket=True),
name='p3_proxyValidate'
),
url('^samlValidate$', views.SamlValidate.as_view(), name='samlValidate'), url('^samlValidate$', views.SamlValidate.as_view(), name='samlValidate'),
url('^auth$', views.Auth.as_view(), name='auth'), url('^auth$', views.Auth.as_view(), name='auth'),
) )

View file

@ -21,6 +21,7 @@ import urllib
import random import random
import string import string
def import_attr(path): def import_attr(path):
"""transform a python module.attr path to the attr""" """transform a python module.attr path to the attr"""
if not isinstance(path, str): if not isinstance(path, str):
@ -28,16 +29,18 @@ def import_attr(path):
module, attr = path.rsplit('.', 1) module, attr = path.rsplit('.', 1)
return getattr(import_module(module), attr) return getattr(import_module(module), attr)
def redirect_params(url_name, params=None): def redirect_params(url_name, params=None):
"""Redirect to `url_name` with `params` as querystring""" """Redirect to `url_name` with `params` as querystring"""
url = reverse(url_name) url = reverse(url_name)
params = urllib.urlencode(params if params else {}) params = urllib.urlencode(params if params else {})
return HttpResponseRedirect(url + "?%s" % params) return HttpResponseRedirect(url + "?%s" % params)
def update_url(url, params): def update_url(url, params):
"""update params in the `url` query string""" """update params in the `url` query string"""
if isinstance(url, unicode): if isinstance(url, unicode):
url = url.encode('utf-8') url = url.encode('utf-8')
for key, value in params.items(): for key, value in params.items():
if isinstance(key, unicode): if isinstance(key, unicode):
del params[key] del params[key]
@ -51,6 +54,7 @@ def update_url(url, params):
url_parts[4] = urllib.urlencode(query) url_parts[4] = urllib.urlencode(query)
return urlparse.urlunparse(url_parts).decode('utf-8') return urlparse.urlunparse(url_parts).decode('utf-8')
def unpack_nested_exception(error): def unpack_nested_exception(error):
"""If exception are stacked, return the first one""" """If exception are stacked, return the first one"""
i = 0 i = 0
@ -77,22 +81,27 @@ def _gen_ticket(prefix, lg=settings.CAS_TICKET_LEN):
) )
) )
def gen_lt(): def gen_lt():
"""Generate a Service Ticket""" """Generate a Service Ticket"""
return _gen_ticket(settings.CAS_LOGIN_TICKET_PREFIX, settings.CAS_LT_LEN) return _gen_ticket(settings.CAS_LOGIN_TICKET_PREFIX, settings.CAS_LT_LEN)
def gen_st(): def gen_st():
"""Generate a Service Ticket""" """Generate a Service Ticket"""
return _gen_ticket(settings.CAS_SERVICE_TICKET_PREFIX, settings.CAS_ST_LEN) return _gen_ticket(settings.CAS_SERVICE_TICKET_PREFIX, settings.CAS_ST_LEN)
def gen_pt(): def gen_pt():
"""Generate a Proxy Ticket""" """Generate a Proxy Ticket"""
return _gen_ticket(settings.CAS_PROXY_TICKET_PREFIX, settings.CAS_PT_LEN) return _gen_ticket(settings.CAS_PROXY_TICKET_PREFIX, settings.CAS_PT_LEN)
def gen_pgt(): def gen_pgt():
"""Generate a Proxy Granting Ticket""" """Generate a Proxy Granting Ticket"""
return _gen_ticket(settings.CAS_PROXY_GRANTING_TICKET_PREFIX, settings.CAS_PGT_LEN) return _gen_ticket(settings.CAS_PROXY_GRANTING_TICKET_PREFIX, settings.CAS_PGT_LEN)
def gen_pgtiou(): def gen_pgtiou():
"""Generate a Proxy Granting Ticket IOU""" """Generate a Proxy Granting Ticket IOU"""
return _gen_ticket(settings.CAS_PROXY_GRANTING_TICKET_IOU_PREFIX, settings.CAS_PGTIOU_LEN) return _gen_ticket(settings.CAS_PROXY_GRANTING_TICKET_IOU_PREFIX, settings.CAS_PGTIOU_LEN)

View file

@ -33,6 +33,7 @@ import models
from .models import ServiceTicket, ProxyTicket, ProxyGrantingTicket from .models import ServiceTicket, ProxyTicket, ProxyGrantingTicket
from .models import ServicePattern from .models import ServicePattern
class AttributesMixin(object): class AttributesMixin(object):
"""mixin for the attributs methode""" """mixin for the attributs methode"""
@ -49,6 +50,7 @@ class AttributesMixin(object):
attributes.append((key, value)) attributes.append((key, value))
return attributes return attributes
class LogoutMixin(object): class LogoutMixin(object):
"""destroy CAS session utils""" """destroy CAS session utils"""
def clean_session_variables(self): def clean_session_variables(self):
@ -80,6 +82,7 @@ class LogoutMixin(object):
finally: finally:
self.clean_session_variables() self.clean_session_variables()
class LogoutView(View, LogoutMixin): class LogoutView(View, LogoutMixin):
"""destroy CAS session (logout) view""" """destroy CAS session (logout) view"""
@ -94,10 +97,10 @@ class LogoutView(View, LogoutMixin):
self.logout() self.logout()
# if service is set, redirect to service after logout # if service is set, redirect to service after logout
if self.service: if self.service:
list(messages.get_messages(request)) # clean messages before leaving the django app list(messages.get_messages(request)) # clean messages before leaving the django app
return HttpResponseRedirect(self.service) return HttpResponseRedirect(self.service)
elif self.url: elif self.url:
list(messages.get_messages(request)) # clean messages before leaving the django app list(messages.get_messages(request)) # clean messages before leaving the django app
return HttpResponseRedirect(self.url) return HttpResponseRedirect(self.url)
# else redirect to login page # else redirect to login page
else: else:
@ -107,6 +110,7 @@ class LogoutView(View, LogoutMixin):
else: else:
return render(request, settings.CAS_LOGOUT_TEMPLATE) return render(request, settings.CAS_LOGOUT_TEMPLATE)
class LoginView(View, LogoutMixin): class LoginView(View, LogoutMixin):
"""credential requestor / acceptor""" """credential requestor / acceptor"""
@ -188,10 +192,10 @@ class LoginView(View, LogoutMixin):
self.request, self.request,
values, values,
initial={ initial={
'service':self.service, 'service': self.service,
'method':self.method, 'method': self.method,
'warn':self.request.session.get("warn"), 'warn': self.request.session.get("warn"),
'lt':self.request.session['lt'] 'lt': self.request.session['lt']
} }
) )
@ -207,13 +211,13 @@ class LoginView(View, LogoutMixin):
messages.add_message( messages.add_message(
self.request, self.request,
messages.WARNING, messages.WARNING,
_(u"Authentication has been required by service %(name)s (%(url)s)") % \ _(u"Authentication has been required by service %(name)s (%(url)s)") %
{'name':service_pattern.name, 'url':self.service} {'name': service_pattern.name, 'url': self.service}
) )
return render( return render(
self.request, self.request,
settings.CAS_WARN_TEMPLATE, settings.CAS_WARN_TEMPLATE,
{'service_ticket_url':self.user.get_service_url( {'service_ticket_url': self.user.get_service_url(
self.service, self.service,
service_pattern, service_pattern,
renew=self.renew renew=self.renew
@ -221,7 +225,7 @@ class LoginView(View, LogoutMixin):
) )
else: else:
# redirect, using method ? # redirect, using method ?
list(messages.get_messages(self.request)) # clean messages before leaving django list(messages.get_messages(self.request)) # clean messages before leaving django
return HttpResponseRedirect( return HttpResponseRedirect(
self.user.get_service_url(self.service, service_pattern, renew=self.renew) self.user.get_service_url(self.service, service_pattern, renew=self.renew)
) )
@ -229,7 +233,7 @@ class LoginView(View, LogoutMixin):
messages.add_message( messages.add_message(
self.request, self.request,
messages.ERROR, messages.ERROR,
_(u'Service %(url)s non allowed.') % {'url' : self.service} _(u'Service %(url)s non allowed.') % {'url': self.service}
) )
except models.BadUsername: except models.BadUsername:
messages.add_message( messages.add_message(
@ -247,16 +251,16 @@ class LoginView(View, LogoutMixin):
messages.add_message( messages.add_message(
self.request, self.request,
messages.ERROR, messages.ERROR,
_(u"The attribut %(field)s is needed to use" \ _(u"The attribut %(field)s is needed to use"
" that service") % {'field':service_pattern.user_field} u" that service") % {'field': service_pattern.user_field}
) )
# if gateway is set and auth failed redirect to the service without authentication # if gateway is set and auth failed redirect to the service without authentication
if self.gateway: if self.gateway:
list(messages.get_messages(self.request)) # clean messages before leaving django list(messages.get_messages(self.request)) # clean messages before leaving django
return HttpResponseRedirect(self.service) return HttpResponseRedirect(self.service)
return render(self.request, settings.CAS_LOGGED_TEMPLATE, {'session':self.request.session}) return render(self.request, settings.CAS_LOGGED_TEMPLATE, {'session': self.request.session})
def authenticated(self): def authenticated(self):
"""Processing authenticated users""" """Processing authenticated users"""
@ -276,7 +280,7 @@ class LoginView(View, LogoutMixin):
return render( return render(
self.request, self.request,
settings.CAS_LOGGED_TEMPLATE, settings.CAS_LOGGED_TEMPLATE,
{'session':self.request.session} {'session': self.request.session}
) )
def not_authenticated(self): def not_authenticated(self):
@ -285,21 +289,22 @@ class LoginView(View, LogoutMixin):
try: try:
service_pattern = ServicePattern.validate(self.service) service_pattern = ServicePattern.validate(self.service)
if self.gateway: if self.gateway:
list(messages.get_messages(self.request))# clean messages before leaving django # clean messages before leaving django
list(messages.get_messages(self.request))
return HttpResponseRedirect(self.service) return HttpResponseRedirect(self.service)
if self.request.session.get("authenticated") and self.renew: if self.request.session.get("authenticated") and self.renew:
messages.add_message( messages.add_message(
self.request, self.request,
messages.WARNING, messages.WARNING,
_(u"Authentication renewal required by service %(name)s (%(url)s).") % _(u"Authentication renewal required by service %(name)s (%(url)s).") %
{'name':service_pattern.name, 'url':self.service} {'name': service_pattern.name, 'url': self.service}
) )
else: else:
messages.add_message( messages.add_message(
self.request, self.request,
messages.WARNING, messages.WARNING,
_(u"Authentication required by service %(name)s (%(url)s).") % _(u"Authentication required by service %(name)s (%(url)s).") %
{'name':service_pattern.name, 'url':self.service} {'name': service_pattern.name, 'url': self.service}
) )
except ServicePattern.DoesNotExist: except ServicePattern.DoesNotExist:
messages.add_message( messages.add_message(
@ -307,7 +312,7 @@ class LoginView(View, LogoutMixin):
messages.ERROR, messages.ERROR,
_(u'Service %s non allowed') % self.service _(u'Service %s non allowed') % self.service
) )
return render(self.request, settings.CAS_LOGIN_TEMPLATE, {'form':self.form}) return render(self.request, settings.CAS_LOGIN_TEMPLATE, {'form': self.form})
def common(self): def common(self):
"""Part execute uppon GET and POST request""" """Part execute uppon GET and POST request"""
@ -317,6 +322,7 @@ class LoginView(View, LogoutMixin):
else: else:
return self.not_authenticated() return self.not_authenticated()
class Auth(View): class Auth(View):
"""A simple view to validate username/password/service tuple""" """A simple view to validate username/password/service tuple"""
@method_decorator(csrf_exempt) @method_decorator(csrf_exempt)
@ -342,16 +348,16 @@ class Auth(View):
request, request,
request.POST, request.POST,
initial={ initial={
'service':service, 'service': service,
'method':'POST', 'method': 'POST',
'warn':False 'warn': False
} }
) )
if form.is_valid(): if form.is_valid():
try: try:
user = models.User.objects.get( user = models.User.objects.get(
username=form.cleaned_data['username'], username=form.cleaned_data['username'],
session_key=self.request.session.session_key session_key=request.session.session_key
) )
# is the service allowed # is the service allowed
service_pattern = ServicePattern.validate(service) service_pattern = ServicePattern.validate(service)
@ -360,11 +366,12 @@ class Auth(View):
if not request.session.get("authenticated"): if not request.session.get("authenticated"):
user.delete() user.delete()
return HttpResponse("yes\n", content_type="text/plain") return HttpResponse("yes\n", content_type="text/plain")
except (ServicePattern.DoesNotExist, ServicePatternException) as error: except (ServicePattern.DoesNotExist, models.ServicePatternException):
return HttpResponse("no\n", content_type="text/plain") return HttpResponse("no\n", content_type="text/plain")
else: else:
return HttpResponse("no\n", content_type="text/plain") return HttpResponse("no\n", content_type="text/plain")
class Validate(View): class Validate(View):
"""service ticket validation""" """service ticket validation"""
@staticmethod @staticmethod
@ -406,7 +413,7 @@ class ValidateError(Exception):
return render( return render(
request, request,
"cas_server/serviceValidateError.xml", "cas_server/serviceValidateError.xml",
{'code':self.code, 'msg':self.msg}, {'code': self.code, 'msg': self.msg},
content_type="text/xml; charset=utf-8" content_type="text/xml; charset=utf-8"
) )
@ -437,12 +444,12 @@ class ValidateService(View, AttributesMixin):
try: try:
self.ticket, proxies = self.process_ticket() self.ticket, proxies = self.process_ticket()
params = { params = {
'username':self.ticket.user.username, 'username': self.ticket.user.username,
'attributes':self.attributes(), 'attributes': self.attributes(),
'proxies':proxies 'proxies': proxies
} }
if self.ticket.service_pattern.user_field and \ if (self.ticket.service_pattern.user_field and
self.ticket.user.attributs.get(self.ticket.service_pattern.user_field): self.ticket.user.attributs.get(self.ticket.service_pattern.user_field)):
params['username'] = self.ticket.user.attributs.get( params['username'] = self.ticket.user.attributs.get(
self.ticket.service_pattern.user_field self.ticket.service_pattern.user_field
) )
@ -458,7 +465,6 @@ class ValidateService(View, AttributesMixin):
except ValidateError as error: except ValidateError as error:
return error.render(request) return error.render(request)
def process_ticket(self): def process_ticket(self):
"""fetch the ticket angains the database and check its validity""" """fetch the ticket angains the database and check its validity"""
try: try:
@ -489,7 +495,6 @@ class ValidateService(View, AttributesMixin):
except (ServiceTicket.DoesNotExist, ProxyTicket.DoesNotExist): except (ServiceTicket.DoesNotExist, ProxyTicket.DoesNotExist):
raise ValidateError('INVALID_TICKET', 'ticket not found') raise ValidateError('INVALID_TICKET', 'ticket not found')
def process_pgturl(self, params): def process_pgturl(self, params):
"""Handle PGT request""" """Handle PGT request"""
try: try:
@ -502,7 +507,7 @@ class ValidateService(View, AttributesMixin):
service_pattern=pattern, service_pattern=pattern,
single_log_out=pattern.single_log_out single_log_out=pattern.single_log_out
) )
url = utils.update_url(self.pgt_url, {'pgtIou':proxyid, 'pgtId':pticket.value}) url = utils.update_url(self.pgt_url, {'pgtIou': proxyid, 'pgtId': pticket.value})
try: try:
ret = requests.get(url, verify=settings.CAS_PROXY_CA_CERTIFICATE_PATH) ret = requests.get(url, verify=settings.CAS_PROXY_CA_CERTIFICATE_PATH)
if ret.status_code == 200: if ret.status_code == 200:
@ -529,6 +534,7 @@ class ValidateService(View, AttributesMixin):
'callback url not allowed by configuration' 'callback url not allowed by configuration'
) )
class Proxy(View): class Proxy(View):
"""proxy ticket service""" """proxy ticket service"""
@ -552,7 +558,6 @@ class Proxy(View):
except ValidateError as error: except ValidateError as error:
return error.render(request) return error.render(request)
def process_proxy(self): def process_proxy(self):
"""handle PT request""" """handle PT request"""
try: try:
@ -579,7 +584,7 @@ class Proxy(View):
return render( return render(
self.request, self.request,
"cas_server/proxy.xml", "cas_server/proxy.xml",
{'ticket':pticket.value}, {'ticket': pticket.value},
content_type="text/xml; charset=utf-8" content_type="text/xml; charset=utf-8"
) )
except ProxyGrantingTicket.DoesNotExist: except ProxyGrantingTicket.DoesNotExist:
@ -593,7 +598,6 @@ class Proxy(View):
) )
class SamlValidateError(Exception): class SamlValidateError(Exception):
"""handle saml validation error""" """handle saml validation error"""
def __init__(self, code, msg=""): def __init__(self, code, msg=""):
@ -610,27 +614,14 @@ class SamlValidateError(Exception):
request, request,
"cas_server/samlValidateError.xml", "cas_server/samlValidateError.xml",
{ {
'code':self.code, 'code': self.code,
'msg':self.msg, 'msg': self.msg,
'IssueInstant':timezone.now().isoformat(), 'IssueInstant': timezone.now().isoformat(),
'ResponseID':utils.gen_saml_id() 'ResponseID': utils.gen_saml_id()
}, },
content_type="text/xml; charset=utf-8" content_type="text/xml; charset=utf-8"
) )
def _saml_validate_error(request, code, msg=""):
"""render the samlValidateError.xml templace using `code` and `msg`"""
return render(
request,
"cas_server/samlValidateError.xml",
{
'code':code,
'msg':msg,
'IssueInstant':timezone.now().isoformat(),
'ResponseID':utils.gen_saml_id()
},
content_type="text/xml; charset=utf-8"
)
class SamlValidate(View, AttributesMixin): class SamlValidate(View, AttributesMixin):
"""SAML ticket validation""" """SAML ticket validation"""
@ -651,19 +642,19 @@ class SamlValidate(View, AttributesMixin):
self.root = etree.fromstring(request.body) self.root = etree.fromstring(request.body)
try: try:
self.ticket = self.process_ticket() self.ticket = self.process_ticket()
expire_instant = (self.ticket.creation + \ expire_instant = (self.ticket.creation +
timedelta(seconds=self.ticket.VALIDITY)).isoformat() timedelta(seconds=self.ticket.VALIDITY)).isoformat()
attributes = self.attributes() attributes = self.attributes()
params = { params = {
'IssueInstant':timezone.now().isoformat(), 'IssueInstant': timezone.now().isoformat(),
'expireInstant':expire_instant, 'expireInstant': expire_instant,
'Recipient':self.target, 'Recipient': self.target,
'ResponseID':utils.gen_saml_id(), 'ResponseID': utils.gen_saml_id(),
'username':self.ticket.user.username, 'username': self.ticket.user.username,
'attributes':attributes 'attributes': attributes
} }
if self.ticket.service_pattern.user_field and \ if self.ticket.service_pattern.user_field and \
self.ticket.user.attributs.get(self.ticket.service_pattern.user_field): self.ticket.user.attributs.get(self.ticket.service_pattern.user_field):
params['username'] = self.ticket.user.attributs.get( params['username'] = self.ticket.user.attributs.get(
self.ticket.service_pattern.user_field self.ticket.service_pattern.user_field
) )