From 6185ec5216e0422624b682a16fdb178574a09c06 Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Fri, 5 Jun 2015 15:44:17 +0200 Subject: [PATCH] Add Login Ticket to prevent login replay + by ticket len options --- cas_server/default_settings.py | 22 +++++++++++++- cas_server/forms.py | 1 + cas_server/utils.py | 16 +++++++---- cas_server/views.py | 52 +++++++++++++++++++++++----------- 4 files changed, 67 insertions(+), 24 deletions(-) diff --git a/cas_server/default_settings.py b/cas_server/default_settings.py index 4add92d..561255a 100644 --- a/cas_server/default_settings.py +++ b/cas_server/default_settings.py @@ -21,7 +21,18 @@ setting_default('CAS_WARN_TEMPLATE', 'cas_server/warn.html') setting_default('CAS_LOGGED_TEMPLATE', 'cas_server/logged.html') setting_default('CAS_LOGOUT_TEMPLATE', 'cas_server/logout.html') setting_default('CAS_AUTH_CLASS', 'cas_server.auth.DjangoAuthUser') -setting_default('CAS_ST_LEN', 30) +# All CAS implementation MUST support ST and PT up to 32 chars, +# PGT and PGTIOU up to 64 chars and it is RECOMMENDED that all +# tickets up to 256 chars are supports so we use 64 for the default +# len. +setting_default('CAS_TICKET_LEN', 64) + +setting_default('CAS_LT_LEN', settings.CAS_TICKET_LEN) +setting_default('CAS_ST_LEN', settings.CAS_TICKET_LEN) +setting_default('CAS_PT_LEN', settings.CAS_TICKET_LEN) +setting_default('CAS_PGT_LEN', settings.CAS_TICKET_LEN) +setting_default('CAS_PGTIOU_LEN', settings.CAS_TICKET_LEN) + setting_default('CAS_TICKET_VALIDITY', 300) setting_default('CAS_TICKET_TIMEOUT', 24*3600) setting_default('CAS_PROXY_CA_CERTIFICATE_PATH', True) @@ -29,9 +40,18 @@ setting_default('CAS_REDIRECT_TO_LOGIN_AFTER_LOGOUT', False) setting_default('CAS_AUTH_SHARED_SECRET', '') +setting_default('CAS_LOGIN_TICKET_PREFIX', 'LT') +# Service tickets MUST begin with the characters ST so you should not change this +# Services MUST be able to accept service tickets of up to 32 characters in length setting_default('CAS_SERVICE_TICKET_PREFIX', 'ST') +# Proxy tickets SHOULD begin with the characters, PT. +# Back-end services MUST be able to accept proxy tickets of up to 32 characters. setting_default('CAS_PROXY_TICKET_PREFIX', 'PT') +# Proxy-granting tickets SHOULD begin with the characters PGT +# Services MUST be able to handle proxy-granting tickets of up to 64 setting_default('CAS_PROXY_GRANTING_TICKET_PREFIX', 'PGT') +# Proxy-granting ticket IOUs SHOULD begin with the characters, PGTIOU +# Services MUST be able to handle PGTIOUs of up to 64 characters in length. setting_default('CAS_PROXY_GRANTING_TICKET_IOU_PREFIX', 'PGTIOU') setting_default('CAS_SQL_HOST', 'localhost') diff --git a/cas_server/forms.py b/cas_server/forms.py index 2bca9f2..aed61cb 100644 --- a/cas_server/forms.py +++ b/cas_server/forms.py @@ -22,6 +22,7 @@ class UserCredential(forms.Form): username = forms.CharField(label=_('login')) service = forms.CharField(widget=forms.HiddenInput(), required=False) password = forms.CharField(label=_('password'), widget=forms.PasswordInput) + lt = forms.CharField(widget=forms.HiddenInput()) method = forms.CharField(widget=forms.HiddenInput(), required=False) warn = forms.BooleanField(label=_('warn'), required=False) diff --git a/cas_server/utils.py b/cas_server/utils.py index 25a98a8..8e76271 100644 --- a/cas_server/utils.py +++ b/cas_server/utils.py @@ -66,32 +66,36 @@ def unpack_nested_exception(error): return error -def _gen_ticket(prefix): +def _gen_ticket(prefix, lg=settings.CAS_TICKET_LEN): """Generate a ticket with prefix `prefix`""" return '%s-%s' % ( prefix, ''.join( random.choice( string.ascii_letters + string.digits - ) for _ in range(settings.CAS_ST_LEN) + ) for _ in range(lg - len(prefix) - 1) ) ) +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) + 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) + 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) + 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) + return _gen_ticket(settings.CAS_PROXY_GRANTING_TICKET_IOU_PREFIX, settings.CAS_PGTIOU_LEN) def gen_saml_id(): diff --git a/cas_server/views.py b/cas_server/views.py index 7819992..cec0ae2 100644 --- a/cas_server/views.py +++ b/cas_server/views.py @@ -123,15 +123,26 @@ class LoginView(View, LogoutMixin): self.gateway = request.POST.get('gateway') self.method = request.POST.get('method') - if not request.session.get("authenticated") or self.renew: - self.form = forms.UserCredential( - request.POST, - initial={ - 'service':self.service, - 'method':self.method, - 'warn':request.session.get("warn") - } + # save LT for later check + lt_valid = request.session.get('lt') + lt_send = request.POST.get('lt') + # generate a new LT + request.session['lt'] = utils.gen_lt() + + # check if send LT is valid + if lt_valid is None or lt_valid != lt_send: + messages.add_message( + self.request, + messages.ERROR, + _(u"Invalid login ticket") ) + values = request.POST.copy() + # if not set a new LT and fail + values['lt'] = request.session['lt'] + self.init_form(values) + + elif not request.session.get("authenticated") or self.renew: + self.init_form(request.POST) if self.form.is_valid(): self.user = models.User.objects.get(username=self.form.cleaned_data['username']) request.session.set_expiry(0) @@ -152,17 +163,24 @@ class LoginView(View, LogoutMixin): self.gateway = request.GET.get('gateway') self.method = request.GET.get('method') - if not request.session.get("authenticated") or self.renew: - self.form = forms.UserCredential( - initial={ - 'service':self.service, - 'method':self.method, - 'warn':request.session.get("warn") - } - ) + # generate a new LT + request.session['lt'] = utils.gen_lt() + if not request.session.get("authenticated") or self.renew: + self.init_form() return self.common() + def init_form(self, values=None): + self.form = forms.UserCredential( + values, + initial={ + 'service':self.service, + 'method':self.method, + 'warn':self.request.session.get("warn"), + 'lt':self.request.session['lt'] + } + ) + def service_login(self): """Perform login agains a service""" try: @@ -231,7 +249,7 @@ class LoginView(View, LogoutMixin): self.user = models.User.objects.get(username=self.request.session.get("username")) except models.User.DoesNotExist: self.logout() - return utils.redirect_params("cas_server:login", params=dict(self.request.GET)) + return utils.redirect_params("cas_server:login", params=self.request.GET) # if login agains a service is self.requestest if self.service: