Commit 39557d19 authored by Valentin Samir's avatar Valentin Samir


parent ba4af137
......@@ -14,8 +14,12 @@ from .models import ServiceTicket, ProxyTicket, ProxyGrantingTicket, User, Servi
from .models import Username, ReplaceAttributName, ReplaceAttributValue, FilterAttributValue
from .forms import TicketForm
tickets_readonly_fields=('validate', 'service', 'service_pattern', 'creation', 'renew', 'single_log_out', 'value')
tickets_fields = ('validate', 'service', 'service_pattern', 'creation', 'renew', 'single_log_out')
tickets_readonly_fields = ('validate', 'service', 'service_pattern',
'creation', 'renew', 'single_log_out', 'value')
tickets_fields = ('validate', 'service', 'service_pattern',
'creation', 'renew', 'single_log_out')
class ServiceTicketInline(admin.TabularInline):
"""`ServiceTicket` in admin interface"""
model = ServiceTicket
......@@ -23,6 +27,8 @@ class ServiceTicketInline(admin.TabularInline):
form = TicketForm
readonly_fields = tickets_readonly_fields
fields = tickets_fields
class ProxyTicketInline(admin.TabularInline):
"""`ProxyTicket` in admin interface"""
model = ProxyTicket
......@@ -30,6 +36,8 @@ class ProxyTicketInline(admin.TabularInline):
form = TicketForm
readonly_fields = tickets_readonly_fields
fields = tickets_fields
class ProxyGrantingInline(admin.TabularInline):
"""`ProxyGrantingTicket` in admin interface"""
model = ProxyGrantingTicket
......@@ -38,30 +46,39 @@ class ProxyGrantingInline(admin.TabularInline):
readonly_fields = tickets_readonly_fields
fields = tickets_fields[1:]
class UserAdmin(admin.ModelAdmin):
"""`User` in admin interface"""
inlines = (ServiceTicketInline, ProxyTicketInline, ProxyGrantingInline)
readonly_fields=('username', 'date', "session_key")
readonly_fields = ('username', 'date', "session_key")
fields = ('username', 'date', "session_key")
list_display = ('username', 'date', "session_key")
class UsernamesInline(admin.TabularInline):
"""`Username` in admin interface"""
model = Username
extra = 0
class ReplaceAttributNameInline(admin.TabularInline):
"""`ReplaceAttributName` in admin interface"""
model = ReplaceAttributName
extra = 0
class ReplaceAttributValueInline(admin.TabularInline):
"""`ReplaceAttributValue` in admin interface"""
model = ReplaceAttributValue
extra = 0
class FilterAttributValueInline(admin.TabularInline):
"""`FilterAttributValue` in admin interface"""
model = FilterAttributValue
extra = 0
class ServicePatternAdmin(admin.ModelAdmin):
"""`ServicePattern` in admin interface"""
inlines = (
......@@ -70,7 +87,8 @@ class ServicePatternAdmin(admin.ModelAdmin):
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'), UserAdmin)
......@@ -19,8 +19,10 @@ try:
except ImportError:
MySQLdb = None
class DummyAuthUser(object):
"""A Dummy authentication class"""
def __init__(self, username):
self.username = username
......@@ -36,6 +38,7 @@ class DummyAuthUser(object):
class TestAuthUser(DummyAuthUser):
"""A test authentication class with one user test having
alose test as password and some attributes"""
def __init__(self, username):
super(TestAuthUser, self).__init__(username)
......@@ -45,20 +48,21 @@ class TestAuthUser(DummyAuthUser):
def attributs(self):
"""return a dict of user attributes"""
return {'nom':'Nymous', 'prenom':'Ano', 'email':''}
return {'nom': 'Nymous', 'prenom': 'Ano', 'email': ''}
class MysqlAuthUser(DummyAuthUser):
"""A mysql auth class: authentication user agains a mysql database"""
user = None
def __init__(self, username):
mysql_config = {
"user": settings.CAS_SQL_USERNAME,
"passwd": settings.CAS_SQL_PASSWORD,
"db": settings.CAS_SQL_DBNAME,
"host": settings.CAS_SQL_HOST,
"charset": settings.CAS_SQL_DBCHARSET,
"cursorclass": MySQLdb.cursors.DictCursor
if not MySQLdb:
raise RuntimeError("Please install MySQLdb before using the MysqlAuthUser backend")
......@@ -92,9 +96,11 @@ class MysqlAuthUser(DummyAuthUser):
return self.user
class DjangoAuthUser(DummyAuthUser):
"""A django auth class: authenticate user agains django internal users"""
user = None
def __init__(self, username):
self.user = User.objects.get(username=username)
......@@ -102,7 +108,6 @@ class DjangoAuthUser(DummyAuthUser):
super(DjangoAuthUser, self).__init__(username)
def test_password(self, password):
"""test `password` agains the user"""
if not self.user:
......@@ -11,6 +11,7 @@
"""Default values for the app's settings"""
from django.conf import settings
def setting_default(name, default_value):
"""if the config `name` is not set, set it the `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_DBNAME', '')
setting_default('CAS_SQL_DBCHARSET', 'utf8')
setting_default('CAS_SQL_USER_QUERY', 'SELECT user AS usersame, pass AS ' \
'password, users.* FROM users WHERE user = %s')
setting_default('CAS_SQL_PASSWORD_CHECK', 'crypt') # crypt or plain
setting_default('CAS_SQL_USER_QUERY', 'SELECT user AS usersame, pass AS '
'password, users.* FROM users WHERE user = %s')
setting_default('CAS_SQL_PASSWORD_CHECK', 'crypt') # crypt or plain
......@@ -17,6 +17,7 @@ from django.utils.translation import ugettext_lazy as _
import utils
import models
class UserCredential(forms.Form):
"""Form used on the login page to retrive user credentials"""
username = forms.CharField(label=_('login'))
from import BaseCommand, CommandError
from import BaseCommand
from django.utils.translation import ugettext_lazy as _
from ... import models
class Command(BaseCommand):
args = ''
help = _(u"Clean deleted sessions")
from import BaseCommand, CommandError
from import BaseCommand
from django.utils.translation import ugettext_lazy as _
from ... import models
class Command(BaseCommand):
args = ''
help = _(u"Clean old trickets")
......@@ -31,6 +31,7 @@ import utils
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
class User(models.Model):
"""A user logged into the CAS"""
class Meta:
......@@ -123,24 +124,32 @@ class User(models.Model):
"""Return the url to which the user must be redirected to
after a Service Ticket has been generated"""
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
class ServicePatternException(Exception):
class BadUsername(ServicePatternException):
"""Exception raised then an non allowed username
try to get a ticket for a service"""
class BadFilter(ServicePatternException):
""""Exception raised then a user try
to get a ticket for a service and do not reach a condition"""
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"""
class ServicePattern(models.Model):
"""Allowed services pattern agains services are tested to"""
class Meta:
......@@ -196,11 +205,10 @@ class ServicePattern(models.Model):
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.")
def __unicode__(self):
return u"%s: %s" % (self.pos, self.pattern)
......@@ -226,7 +234,6 @@ class ServicePattern(models.Model):
raise UserFieldNotDefined()
return True
def validate(cls, service):
"""Check if a Service Patern match `service` and
......@@ -236,6 +243,7 @@ class ServicePattern(models.Model):
return service_pattern
raise cls.DoesNotExist()
class Username(models.Model):
"""A list of allowed usernames on a service pattern"""
value = models.CharField(
......@@ -248,6 +256,7 @@ class Username(models.Model):
def __unicode__(self):
return self.value
class ReplaceAttributName(models.Model):
"""A list of replacement of attributs name for a service pattern"""
class Meta:
......@@ -261,8 +270,8 @@ 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")
help_text=_(u"name under which the attribut will be show"
u"to the service. empty = default name of the attribut")
service_pattern = models.ForeignKey(ServicePattern, related_name="attributs")
......@@ -272,6 +281,7 @@ class ReplaceAttributName(models.Model):
return u"%s → %s" % (, self.replace)
class FilterAttributValue(models.Model):
"""A list of filter on attributs for a service pattern"""
attribut = models.CharField(
......@@ -289,6 +299,7 @@ class FilterAttributValue(models.Model):
def __unicode__(self):
return u"%s %s" % (self.attribut, self.pattern)
class ReplaceAttributValue(models.Model):
"""Replacement to apply on attributs values for a service pattern"""
attribut = models.CharField(
......@@ -338,10 +349,10 @@ class Ticket(models.Model):
# removing old validated ticket and non validated expired tickets
Q(creation__lt=( - timedelta(seconds=cls.VALIDITY)))
Q(single_log_out=False) & Q(validate=True)
) | (
& Q(creation__lt=( - timedelta(seconds=cls.VALIDITY)))
......@@ -373,18 +384,18 @@ class Ticket(models.Model):
<saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"></saml:NameID>
</samlp:LogoutRequest>""" % \
'id' : os.urandom(20).encode("hex"),
'datetime' :,
'ticket': self.value
'id': os.urandom(20).encode("hex"),
'ticket': self.value
if self.service_pattern.single_log_out_callback:
url = self.service_pattern.single_log_out_callback
url = self.service
url = self.service
data={'logoutRequest': xml.encode('utf-8')},
except Exception as error:
if request is not None:
......@@ -393,33 +404,40 @@ class Ticket(models.Model):
_(u'Error during service logout %(service)s:\n%(error)s') %
{'service': self.service, 'error':error}
{'service': self.service, 'error': error}
sys.stderr.write("%r\n" % error)
class ServiceTicket(Ticket):
"""A Service Ticket"""
value = models.CharField(max_length=255, default=utils.gen_st, unique=True)
def __unicode__(self):
return u"ServiceTicket-%s" %
class ProxyTicket(Ticket):
"""A Proxy Ticket"""
value = models.CharField(max_length=255, default=utils.gen_pt, unique=True)
def __unicode__(self):
return u"ProxyTicket-%s" %
class ProxyGrantingTicket(Ticket):
"""A Proxy Granting Ticket"""
value = models.CharField(max_length=255, default=utils.gen_pgt, unique=True)
def __unicode__(self):
return u"ProxyGrantingTicket-%s" %
class Proxy(models.Model):
"""A list of proxies on `ProxyTicket`"""
class Meta:
......@@ -429,4 +447,3 @@ class Proxy(models.Model):
def __unicode__(self):
return self.url
# 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
......@@ -21,12 +21,27 @@ urlpatterns = patterns(
url('^login$', views.LoginView.as_view(), name='login'),
url('^logout$', views.LogoutView.as_view(), name='logout'),
url('^validate$', views.Validate.as_view(), name='validate'),
url('^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('^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('^auth$', views.Auth.as_view(), name='auth'),
......@@ -21,6 +21,7 @@ import urllib
import random
import string
def import_attr(path):
"""transform a python module.attr path to the attr"""
if not isinstance(path, str):
......@@ -28,16 +29,18 @@ def import_attr(path):
module, attr = path.rsplit('.', 1)
return getattr(import_module(module), attr)
def redirect_params(url_name, params=None):
"""Redirect to `url_name` with `params` as querystring"""
url = reverse(url_name)
params = urllib.urlencode(params if params else {})
return HttpResponseRedirect(url + "?%s" % params)
def update_url(url, params):
"""update params in the `url` query string"""
if isinstance(url, unicode):
url = url.encode('utf-8')
url = url.encode('utf-8')
for key, value in params.items():
if isinstance(key, unicode):
del params[key]
......@@ -51,6 +54,7 @@ def update_url(url, params):
url_parts[4] = urllib.urlencode(query)
return urlparse.urlunparse(url_parts).decode('utf-8')
def unpack_nested_exception(error):
"""If exception are stacked, return the first one"""
i = 0
......@@ -77,22 +81,27 @@ def _gen_ticket(prefix, lg=settings.CAS_TICKET_LEN):
def gen_lt():
"""Generate a Service Ticket"""
return _gen_ticket(settings.CAS_LOGIN_TICKET_PREFIX, settings.CAS_LT_LEN)
def gen_st():
"""Generate a Service Ticket"""
return _gen_ticket(settings.CAS_SERVICE_TICKET_PREFIX, settings.CAS_ST_LEN)
def gen_pt():
"""Generate a Proxy Ticket"""
return _gen_ticket(settings.CAS_PROXY_TICKET_PREFIX, settings.CAS_PT_LEN)
def gen_pgt():
"""Generate a Proxy Granting Ticket"""
return _gen_ticket(settings.CAS_PROXY_GRANTING_TICKET_PREFIX, settings.CAS_PGT_LEN)
def gen_pgtiou():
"""Generate a Proxy Granting Ticket IOU"""
return _gen_ticket(settings.CAS_PROXY_GRANTING_TICKET_IOU_PREFIX, settings.CAS_PGTIOU_LEN)
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment