diff --git a/CHANGELOG.rst b/CHANGELOG.rst index beff5bd..9e60767 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,29 @@ All notable changes to this project will be documented in this file. .. contents:: Table of Contents :depth: 2 + +v0.9.0 - 2017-11-17 +=================== + +Added +----- +* Dutch translation +* Protuguese translation (brazilian variant) +* Support for ldap3 version 2 or more (changes in the API) + All exception are now in ldap3.core.exceptions, methodes for fetching attritutes and + dn are renamed. +* Possibility to disable service message boxes on the login pages + +Fixed +----- +* Then using the LDAP auth backend with ``bind`` method for password check, do not try to bind + if the user dn was not found. This was causing the exception + ``'NoneType' object has no attribute 'getitem'`` describe in #21 +* Increase the max size of usernames (30 chars to 250) +* Fix XSS js injection + + + v0.8.0 - 2017-03-08 =================== diff --git a/README.rst b/README.rst index 715bf3e..8bddb08 100644 --- a/README.rst +++ b/README.rst @@ -218,7 +218,8 @@ Template settings } if you omit some keys of the dictionnary, the default value for these keys is used. - +* ``CAS_SHOW_SERVICE_MESSAGES``: Messages displayed about the state of the service on the login page. + The default is ``True``. * ``CAS_INFO_MESSAGES``: Messages displayed in info-boxes on the html pages of the default templates. It is a dictionnary mapping message name to a message dict. A message dict has 3 keys: diff --git a/cas_server/__init__.py b/cas_server/__init__.py index 07db413..da8813c 100644 --- a/cas_server/__init__.py +++ b/cas_server/__init__.py @@ -11,7 +11,7 @@ """A django CAS server application""" #: version of the application -VERSION = '0.8.0' +VERSION = '0.9.0' #: path the the application configuration class default_app_config = 'cas_server.apps.CasAppConfig' diff --git a/cas_server/auth.py b/cas_server/auth.py index bcdce71..4a5b141 100644 --- a/cas_server/auth.py +++ b/cas_server/auth.py @@ -27,6 +27,7 @@ except ImportError: try: # pragma: no cover import ldap3 + import ldap3.core.exceptions except ImportError: ldap3 = None @@ -297,9 +298,19 @@ class LdapAuthUser(DBAuthUser): # pragma: no cover settings.CAS_LDAP_USER_QUERY % ldap3.utils.conv.escape_bytes(username), attributes=ldap3.ALL_ATTRIBUTES ) and len(conn.entries) == 1: - user = conn.entries[0].entry_get_attributes_dict() - # store the user dn - user["dn"] = conn.entries[0].entry_get_dn() + # try the new ldap3>=2 API + try: + user = conn.entries[0].entry_attributes_as_dict + # store the user dn + user["dn"] = conn.entries[0].entry_dn + # fallback to ldap3<2 API + except ( + ldap3.core.exceptions.LDAPKeyError, # ldap3<1 exception + ldap3.core.exceptions.LDAPAttributeError # ldap3<2 exception + ): + user = conn.entries[0].entry_get_attributes_dict() + # store the user dn + user["dn"] = conn.entries[0].entry_get_dn() if user.get(settings.CAS_LDAP_USERNAME_ATTR): self.user = user super(LdapAuthUser, self).__init__(user[settings.CAS_LDAP_USERNAME_ATTR][0]) @@ -308,7 +319,7 @@ class LdapAuthUser(DBAuthUser): # pragma: no cover else: super(LdapAuthUser, self).__init__(username) break - except ldap3.LDAPCommunicationError: + except ldap3.core.exceptions.LDAPCommunicationError: if retry_nb == 2: raise @@ -321,7 +332,7 @@ class LdapAuthUser(DBAuthUser): # pragma: no cover correct, ``False`` otherwise. :rtype: bool """ - if settings.CAS_LDAP_PASSWORD_CHECK == "bind": + if self.user and settings.CAS_LDAP_PASSWORD_CHECK == "bind": try: conn = ldap3.Connection( settings.CAS_LDAP_SERVER, @@ -336,8 +347,18 @@ class LdapAuthUser(DBAuthUser): # pragma: no cover settings.CAS_LDAP_USER_QUERY % ldap3.utils.conv.escape_bytes(self.username), attributes=ldap3.ALL_ATTRIBUTES ) and len(conn.entries) == 1: - attributes = conn.entries[0].entry_get_attributes_dict() - attributes["dn"] = conn.entries[0].entry_get_dn() + # try the ldap3>=2 API + try: + attributes = conn.entries[0].entry_attributes_as_dict + # store the user dn + attributes["dn"] = conn.entries[0].entry_dn + # fallback to ldap<2 API + except ( + ldap3.core.exceptions.LDAPKeyError, # ldap3<1 exception + ldap3.core.exceptions.LDAPAttributeError # ldap3<2 exception + ): + attributes = conn.entries[0].entry_get_attributes_dict() + attributes["dn"] = conn.entries[0].entry_get_dn() # cache the attributes locally as we wont have access to the user password # later. user = UserAttributes.objects.get_or_create(username=self.username)[0] @@ -346,7 +367,10 @@ class LdapAuthUser(DBAuthUser): # pragma: no cover finally: conn.unbind() return True - except (ldap3.LDAPBindError, ldap3.LDAPCommunicationError): + except ( + ldap3.core.exceptions.LDAPBindError, + ldap3.core.exceptions.LDAPCommunicationError + ): return False elif self.user and self.user.get(settings.CAS_LDAP_PASSWORD_ATTR): return check_password( diff --git a/cas_server/default_settings.py b/cas_server/default_settings.py index 737bb84..3cda3e3 100644 --- a/cas_server/default_settings.py +++ b/cas_server/default_settings.py @@ -185,6 +185,8 @@ CAS_NEW_VERSION_EMAIL_WARNING = True #: You should not change it. CAS_NEW_VERSION_JSON_URL = "https://pypi.python.org/pypi/django-cas-server/json" +#: If the service message should be displayed on the login page +CAS_SHOW_SERVICE_MESSAGES = True #: Messages displayed in a info-box on the html pages of the default templates. #: ``CAS_INFO_MESSAGES`` is a :class:`dict` mapping message name to a message :class:`dict`. diff --git a/cas_server/locale/pt_BR/django.mo b/cas_server/locale/pt_BR/django.mo new file mode 100644 index 0000000..e0cfa6b Binary files /dev/null and b/cas_server/locale/pt_BR/django.mo differ diff --git a/cas_server/locale/pt_BR/django.po b/cas_server/locale/pt_BR/django.po new file mode 100644 index 0000000..06c998a --- /dev/null +++ b/cas_server/locale/pt_BR/django.po @@ -0,0 +1,398 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-08-22 08:18-0300\n" +"PO-Revision-Date: 2017-08-29 18:09+0200\n" +"Language-Team: Roberto Morati \n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"Last-Translator: Valentin Samir \n" +"X-Generator: Poedit 1.8.11\n" + +#: cas_server/apps.py:25 cas_server/templates/cas_server/base.html:7 +#: cas_server/templates/cas_server/base.html:26 +msgid "Central Authentication Service" +msgstr "Central de Autenticação de Serviços" + +#: cas_server/default_settings.py:201 +msgid "" +"The Central Authentication Service grants you access to most of our websites by " +"authenticating only once, so you don't need to type your credentials again unless your " +"session expires or you logout." +msgstr "" +"A Central de Autenticação de Serviços garante seu acesso à maioria dos nossos sitespor " +"meio de uma única autenticação, então você não precisa digitar suas " +"credenciaisnovamente, ao menos que sua sessão expire ou seu logout." + +#: cas_server/forms.py:85 +msgid "Identity provider" +msgstr "Provedor de identidade" + +#: cas_server/forms.py:89 cas_server/forms.py:111 +msgid "Warn me before logging me into other sites." +msgstr "Avise-me antes de me registrar em outros sites" + +#: cas_server/forms.py:93 +msgid "Remember the identity provider" +msgstr "Relembrar o provedor de identidade" + +#: cas_server/forms.py:104 cas_server/models.py:638 +msgid "username" +msgstr "usuário" + +#: cas_server/forms.py:108 +msgid "password" +msgstr "senha" + +#: cas_server/forms.py:131 +msgid "The credentials you provided cannot be determined to be authentic." +msgstr "As credenciais que você forneceu não podem ser determinadas como autênticas." + +#: cas_server/forms.py:183 +msgid "User not found in the temporary database, please try to reconnect" +msgstr "Usuário não encontrado na base de dados temporária, por favor, tente se reconectar" + +#: cas_server/forms.py:197 +msgid "service" +msgstr "" + +#: cas_server/management/commands/cas_clean_federate.py:20 +msgid "Clean old federated users" +msgstr "" + +#: cas_server/management/commands/cas_clean_sessions.py:22 +msgid "Clean deleted sessions" +msgstr "" + +#: cas_server/management/commands/cas_clean_tickets.py:22 +msgid "Clean old tickets" +msgstr "" + +#: cas_server/models.py:71 +msgid "identity provider" +msgstr "provedor de identidade" + +#: cas_server/models.py:72 +msgid "identity providers" +msgstr "provedores de identidade" + +#: cas_server/models.py:78 +msgid "suffix" +msgstr "" + +#: cas_server/models.py:80 +msgid "Suffix append to backend CAS returned username: ``returned_username`` @ ``suffix``." +msgstr "" + +#: cas_server/models.py:87 +msgid "server url" +msgstr "" + +#: cas_server/models.py:97 +msgid "CAS protocol version" +msgstr "" + +#: cas_server/models.py:99 +msgid "Version of the CAS protocol to use when sending requests the the backend CAS." +msgstr "" + +#: cas_server/models.py:106 +msgid "verbose name" +msgstr "" + +#: cas_server/models.py:107 +msgid "Name for this identity provider displayed on the login page." +msgstr "Nome para exibir o provedor de identidade na página de login." + +#: cas_server/models.py:113 cas_server/models.py:490 +msgid "position" +msgstr "" + +#: cas_server/models.py:127 +msgid "display" +msgstr "" + +#: cas_server/models.py:128 +msgid "Display the provider on the login page." +msgstr "" + +#: cas_server/models.py:166 +msgid "Federated user" +msgstr "" + +#: cas_server/models.py:167 +msgid "Federated users" +msgstr "" + +#: cas_server/models.py:246 +msgid "User attributes cache" +msgstr "" + +#: cas_server/models.py:247 +msgid "User attributes caches" +msgstr "" + +#: cas_server/models.py:271 +msgid "User" +msgstr "" + +#: cas_server/models.py:272 +msgid "Users" +msgstr "" + +#: cas_server/models.py:364 +#, python-format +msgid "Error during service logout %s" +msgstr "" + +#: cas_server/models.py:484 +msgid "Service pattern" +msgstr "" + +#: cas_server/models.py:485 +msgid "Services patterns" +msgstr "" + +#: cas_server/models.py:491 +msgid "service patterns are sorted using the position attribute" +msgstr "" + +#: cas_server/models.py:499 cas_server/models.py:664 +msgid "name" +msgstr "" + +#: cas_server/models.py:500 +msgid "A name for the service" +msgstr "" + +#: cas_server/models.py:508 cas_server/models.py:707 cas_server/models.py:737 +msgid "pattern" +msgstr "" + +#: cas_server/models.py:510 +msgid "" +"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 " +"'\\'." +msgstr "" + +#: cas_server/models.py:521 +msgid "user field" +msgstr "" + +#: cas_server/models.py:522 +msgid "Name of the attribute to transmit as username, empty = login" +msgstr "" + +#: cas_server/models.py:527 +msgid "restrict username" +msgstr "" + +#: cas_server/models.py:528 +msgid "Limit username allowed to connect to the list provided bellow" +msgstr "" + +#: cas_server/models.py:533 +msgid "proxy" +msgstr "" + +#: cas_server/models.py:534 +msgid "Proxy tickets can be delivered to the service" +msgstr "" + +#: cas_server/models.py:540 +msgid "proxy callback" +msgstr "" + +#: cas_server/models.py:541 +msgid "can be used as a proxy callback to deliver PGT" +msgstr "" + +#: cas_server/models.py:548 +msgid "single log out" +msgstr "" + +#: cas_server/models.py:549 +msgid "Enable SLO for the service" +msgstr "" + +#: cas_server/models.py:558 +msgid "" +"URL where the SLO request will be POST. empty = service url\n" +"This is usefull for non HTTP proxied services." +msgstr "" + +#: cas_server/models.py:639 +msgid "username allowed to connect to the service" +msgstr "" + +#: cas_server/models.py:665 +msgid "name of an attribute to send to the service, use * for all attributes" +msgstr "" + +#: cas_server/models.py:672 cas_server/models.py:745 +msgid "replace" +msgstr "" + +#: cas_server/models.py:673 +msgid "" +"name under which the attribute will be show to the service. empty = default name of the " +"attribut" +msgstr "" + +#: cas_server/models.py:700 cas_server/models.py:731 +msgid "attribute" +msgstr "" + +#: cas_server/models.py:701 +msgid "Name of the attribute which must verify pattern" +msgstr "" + +#: cas_server/models.py:708 +msgid "a regular expression" +msgstr "" + +#: cas_server/models.py:732 +msgid "Name of the attribute for which the value must be replace" +msgstr "" + +#: cas_server/models.py:738 +msgid "An regular expression maching whats need to be replaced" +msgstr "" + +#: cas_server/models.py:746 +msgid "replace expression, groups are capture by \\1, \\2 …" +msgstr "" + +#: cas_server/templates/cas_server/base.html:43 +#, python-format +msgid "" +"A new version of the application is available. This instance runs %(VERSION)s and the " +"last version is %(LAST_VERSION)s. Please consider upgrading." +msgstr "" +"Uma nova versão da aplicação está disponível. Está instância usa a versão %(VERSION)s e " +"a última versão é %(LAST_VERSION)s. Por favor, considere a atualização." + +#: cas_server/templates/cas_server/logged.html:4 +msgid "" +"

Log In Successful

You have successfully logged into the Central Authentication " +"Service.
For security reasons, please Log Out and Exit your web browser when you are " +"done accessing services that require authentication!" +msgstr "" +"

Log In realizado com sucesso

Você foi conectado com sucesso a Central de " +"Autenticação de Serviços.
Por razões de segurança, faça o Log Out e saia do seu " +"navegador quando você terminar de acessar os serviços que exigem auntenticação!" + +#: cas_server/templates/cas_server/logged.html:8 +msgid "Log me out from all my sessions" +msgstr "Desconecte-me de todas as sessões" + +#: cas_server/templates/cas_server/logged.html:14 +msgid "Forget the identity provider" +msgstr "Esquecer o provedor de identidade" + +#: cas_server/templates/cas_server/logged.html:18 +msgid "Logout" +msgstr "" + +#: cas_server/templates/cas_server/login.html:6 +msgid "Please log in" +msgstr "Por favor, faça log in" + +#: cas_server/templates/cas_server/login.html:14 +msgid "Login" +msgstr "" + +#: cas_server/templates/cas_server/warn.html:9 +msgid "Connect to the service" +msgstr "" + +#: cas_server/utils.py:744 +#, python-format +msgid "\"%(value)s\" is not a valid regular expression" +msgstr "" + +#: cas_server/views.py:185 +msgid "" +"

Logout successful

You have successfully logged out from the Central " +"Authentication Service. For security reasons, close your web browser." +msgstr "" +"

Logout realizado com sucesso

Você foi desconectado com sucesso da Central de " +"Autenticação de Serviços. Por razões de segurança, feche seu navegador." + +#: cas_server/views.py:191 +#, python-format +msgid "" +"

Logout successful

You have successfully logged out from %s sessions of the " +"Central Authentication Service. For security reasons, close your web browser." +msgstr "" +"

Logout realizado com sucesso

Você foi desconectado com sucesso da %s sessão da " +"Centralde Autenticação de Serviços. Por razões de segurança, feche seu navegador." + +#: cas_server/views.py:198 +msgid "" +"

Logout successful

You were already logged out from the Central Authentication " +"Service. For security reasons, close your web browser." +msgstr "" +"

Logout realizado com sucesso

Você já está desconectado da Central de " +"Autenticação de Serviços. Por razões de segurança, feche seu navegador." + +#: cas_server/views.py:378 +#, python-format +msgid "" +"Invalid response from your identity provider CAS upon ticket %(ticket)s validation: " +"%(error)r" +msgstr "" +"Resposta inválida do provedor de identidade CAS sobre o ticket %(ticket)svalidação: " +"%(error)r" + +#: cas_server/views.py:500 +msgid "Invalid login ticket, please try to log in again" +msgstr "Ticket de login inválido, por favor tente novamente" + +#: cas_server/views.py:693 +#, python-format +msgid "Authentication has been required by service %(name)s (%(url)s)" +msgstr "Autenticação requerida pelo serviço %(name)s (%(url)s)" + +#: cas_server/views.py:731 +#, python-format +msgid "Service %(url)s not allowed." +msgstr "Serviço %(url)s não permitido" + +#: cas_server/views.py:738 +msgid "Username not allowed" +msgstr "Usuário não permitido" + +#: cas_server/views.py:745 +msgid "User characteristics not allowed" +msgstr "Características de usuário não permitida" + +#: cas_server/views.py:752 +#, python-format +msgid "The attribute %(field)s is needed to use that service" +msgstr "O atributo %(field)s é necessário para usar o serviço" + +#: cas_server/views.py:842 +#, python-format +msgid "Authentication renewal required by service %(name)s (%(url)s)." +msgstr "Renovação da autenticação requerida pelo serviço %(name)s (%(url)s)." + +#: cas_server/views.py:849 +#, python-format +msgid "Authentication required by service %(name)s (%(url)s)." +msgstr "Autenticação requerida pelo serviço %(name)s (%(url)s)." + +#: cas_server/views.py:856 +#, python-format +msgid "Service %s not allowed" +msgstr "Serviço %s não permitido" diff --git a/cas_server/migrations/0012_auto_20170328_1610.py b/cas_server/migrations/0012_auto_20170328_1610.py new file mode 100644 index 0000000..207348e --- /dev/null +++ b/cas_server/migrations/0012_auto_20170328_1610.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-03-28 14:10 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cas_server', '0011_auto_20161007_1258'), + ] + + operations = [ + migrations.AlterField( + model_name='federatediendityprovider', + name='cas_protocol_version', + field=models.CharField(choices=[('1', 'CAS 1.0'), ('2', 'CAS 2.0'), ('3', 'CAS 3.0'), ('CAS_2_SAML_1_0', 'SAML 1.1')], default='3', help_text='Version of the CAS protocol to use when sending requests the the backend CAS.', max_length=30, verbose_name='CAS protocol version'), + ), + migrations.AlterField( + model_name='servicepattern', + name='single_log_out_callback', + field=models.CharField(blank=True, default='', help_text='URL where the SLO request will be POST. empty = service url\nThis is usefull for non HTTP proxied services.', max_length=255, verbose_name='single log out callback'), + ), + migrations.AlterField( + model_name='servicepattern', + name='user_field', + field=models.CharField(blank=True, default='', help_text='Name of the attribute to transmit as username, empty = login', max_length=255, verbose_name='user field'), + ), + ] diff --git a/cas_server/migrations/0013_auto_20170329_1748.py b/cas_server/migrations/0013_auto_20170329_1748.py new file mode 100644 index 0000000..a49f908 --- /dev/null +++ b/cas_server/migrations/0013_auto_20170329_1748.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-03-29 15:48 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cas_server', '0012_auto_20170328_1610'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='username', + field=models.CharField(max_length=250), + ), + ] diff --git a/cas_server/models.py b/cas_server/models.py index be871b6..4b6596f 100644 --- a/cas_server/models.py +++ b/cas_server/models.py @@ -273,7 +273,7 @@ class User(models.Model): #: 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) + username = models.CharField(max_length=250) #: Last time the authenticated user has do something (auth, fetch ticket, etc…) date = models.DateTimeField(auto_now=True) #: last time the user logged diff --git a/cas_server/templates/cas_server/base.html b/cas_server/templates/cas_server/base.html index a3dd3a7..8b16b5f 100644 --- a/cas_server/templates/cas_server/base.html +++ b/cas_server/templates/cas_server/base.html @@ -58,7 +58,7 @@ class="alert alert-danger" {% endif %} {% endspaceless %}> -

{{message|safe}}

+

{{message}}

{% endfor %} {% if auto_submit %}{% endif %} diff --git a/cas_server/templates/cas_server/logout.html b/cas_server/templates/cas_server/logout.html index 5c69dfc..d8ab1dc 100644 --- a/cas_server/templates/cas_server/logout.html +++ b/cas_server/templates/cas_server/logout.html @@ -2,6 +2,6 @@ {% load staticfiles %} {% load i18n %} {% block content %} - + {% endblock %} diff --git a/cas_server/tests/test_view.py b/cas_server/tests/test_view.py index c1ffe01..016db3e 100644 --- a/cas_server/tests/test_view.py +++ b/cas_server/tests/test_view.py @@ -295,6 +295,24 @@ class LoginTestCase(TestCase, BaseServicePattern, CanLogin): ) in response.content ) + @override_settings(CAS_SHOW_SERVICE_MESSAGES=False) + def test_view_login_get_allowed_service_no_message(self): + """Request a ticket for an allowed service by an unauthenticated client""" + # get a bare new http client + client = Client() + # we are not authenticated and are asking for a ticket for https://www.example.com + # which is a valid service matched by self.service_pattern + response = client.get("/login?service=https://www.example.com") + # the login page should be displayed + self.assertEqual(response.status_code, 200) + # we warn the user why it need to authenticated + self.assertFalse( + ( + b"Authentication required by service " + b"example (https://www.example.com)" + ) in response.content + ) + def test_view_login_get_denied_service(self): """Request a ticket for an denied service by an unauthenticated client""" # get a bare new http client @@ -306,6 +324,18 @@ class LoginTestCase(TestCase, BaseServicePattern, CanLogin): # we warn the user that https://www.example.net is not an allowed service url self.assertTrue(b"Service https://www.example.net not allowed" in response.content) + @override_settings(CAS_SHOW_SERVICE_MESSAGES=False) + def test_view_login_get_denied_service_no_message(self): + """Request a ticket for an denied service by an unauthenticated client""" + # get a bare new http client + client = Client() + # we are not authenticated and are asking for a ticket for https://www.example.net + # which is NOT a valid service + response = client.get("/login?service=https://www.example.net") + self.assertEqual(response.status_code, 200) + # we warn the user that https://www.example.net is not an allowed service url + self.assertFalse(b"Service https://www.example.net not allowed" in response.content) + def test_view_login_get_auth_allowed_service(self): """Request a ticket for an allowed service by an authenticated client""" # get a client that is already authenticated @@ -505,6 +535,40 @@ class LoginTestCase(TestCase, BaseServicePattern, CanLogin): # renewing authentication is done in the validate and serviceValidate views tests self.assertEqual(ticket.renew, True) + @override_settings(CAS_SHOW_SERVICE_MESSAGES=False) + def test_renew_message_disabled(self): + """test the authentication renewal request from a service""" + # use the default test service + service = "https://www.example.com" + # get a client that is already authenticated + client = get_auth_client() + # ask for a ticket for the service but aks for authentication renewal + response = client.get("/login", {'service': service, 'renew': 'on'}) + # we are ask to reauthenticate and tell the user why + self.assertEqual(response.status_code, 200) + self.assertFalse( + ( + b"Authentication renewal required by " + b"service example (https://www.example.com)" + ) in response.content + ) + # get the form default parameter + params = copy_form(response.context["form"]) + # set valid username/password + params["username"] = settings.CAS_TEST_USER + params["password"] = settings.CAS_TEST_PASSWORD + # the renew parameter from the form should be True + self.assertEqual(params["renew"], True) + # post the authentication request + response = client.post("/login", params) + # the request succed, a ticket is created and we are redirected to the service url + self.assertEqual(response.status_code, 302) + ticket_value = response['Location'].split('ticket=')[-1] + ticket = models.ServiceTicket.objects.get(value=ticket_value) + # the created ticket is marked has being gottent after a renew. Futher testing about + # renewing authentication is done in the validate and serviceValidate views tests + self.assertEqual(ticket.renew, True) + @override_settings(CAS_ENABLE_AJAX_AUTH=True) def test_ajax_login_required(self): """ diff --git a/cas_server/views.py b/cas_server/views.py index b3d3a1e..3db45c1 100644 --- a/cas_server/views.py +++ b/cas_server/views.py @@ -23,6 +23,7 @@ from django.views.decorators.csrf import csrf_exempt from django.middleware.csrf import CsrfViewMiddleware from django.views.generic import View from django.utils.encoding import python_2_unicode_compatible +from django.utils.safestring import mark_safe import re import logging @@ -181,24 +182,24 @@ class LogoutView(View, LogoutMixin): else: # build logout message depending of the number of sessions the user logs out if session_nb == 1: - logout_msg = _( + logout_msg = mark_safe(_( "

Logout successful

" "You have successfully logged out from the Central Authentication Service. " "For security reasons, close your web browser." - ) + )) elif session_nb > 1: - logout_msg = _( + logout_msg = mark_safe(_( "

Logout successful

" - "You have successfully logged out from %s sessions of the Central " + "You have successfully logged out from %d sessions of the Central " "Authentication Service. " "For security reasons, close your web browser." - ) % session_nb + ) % session_nb) else: - logout_msg = _( + logout_msg = mark_safe(_( "

Logout successful

" "You were already logged out from the Central Authentication Service. " "For security reasons, close your web browser." - ) + )) # depending of settings, redirect to the login page with a logout message or display # the logout page. The default is to display tge logout page. @@ -835,26 +836,29 @@ class LoginView(View, LogoutMixin): # clean messages before leaving django list(messages.get_messages(self.request)) return HttpResponseRedirect(self.service) - if self.request.session.get("authenticated") and self.renew: - messages.add_message( - self.request, - messages.WARNING, - _(u"Authentication renewal required by service %(name)s (%(url)s).") % - {'name': service_pattern.name, 'url': self.service} - ) - else: - messages.add_message( - self.request, - messages.WARNING, - _(u"Authentication required by service %(name)s (%(url)s).") % - {'name': service_pattern.name, 'url': self.service} - ) + + if settings.CAS_SHOW_SERVICE_MESSAGES: + if self.request.session.get("authenticated") and self.renew: + messages.add_message( + self.request, + messages.WARNING, + _(u"Authentication renewal required by service %(name)s (%(url)s).") % + {'name': service_pattern.name, 'url': self.service} + ) + else: + messages.add_message( + self.request, + messages.WARNING, + _(u"Authentication required by service %(name)s (%(url)s).") % + {'name': service_pattern.name, 'url': self.service} + ) except ServicePattern.DoesNotExist: - messages.add_message( - self.request, - messages.ERROR, - _(u'Service %s not allowed') % self.service - ) + if settings.CAS_SHOW_SERVICE_MESSAGES: + messages.add_message( + self.request, + messages.ERROR, + _(u'Service %s not allowed') % self.service + ) if self.ajax: data = { "status": "error",