Unverified Commit 4229f871 authored by Valentin Samir's avatar Valentin Samir Committed by GitHub

Merge pull request #34 from nitmir/dev

 Update version to 0.9.0

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
parents 85eae7d6 5811d643
Pipeline #619 failed with stage
......@@ -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
===================
......
......@@ -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:
......
......@@ -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'
......@@ -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(
......
......@@ -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`.
......
# 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 <EMAIL@ADDRESS>, 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 <robertomorati@gmail.com>\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 <valentin.samir@crans.org>\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 ""
"<h3>Log In Successful</h3>You have successfully logged into the Central Authentication "
"Service.<br/>For security reasons, please Log Out and Exit your web browser when you are "
"done accessing services that require authentication!"
msgstr ""
"<h3>Log In realizado com sucesso</h3>Você foi conectado com sucesso a Central de "
"Autenticação de Serviços.<br/>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 ""
"<h3>Logout successful</h3>You have successfully logged out from the Central "
"Authentication Service. For security reasons, close your web browser."
msgstr ""
"<h3>Logout realizado com sucesso</h3>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 ""
"<h3>Logout successful</h3>You have successfully logged out from %s sessions of the "
"Central Authentication Service. For security reasons, close your web browser."
msgstr ""
"<h3>Logout realizado com sucesso</h3>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 ""
"<h3>Logout successful</h3>You were already logged out from the Central Authentication "
"Service. For security reasons, close your web browser."
msgstr ""
"<h3>Logout realizado com sucesso</h3>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"
# -*- 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'),
),
]
# -*- 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),
),
]
......@@ -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
......
......@@ -58,7 +58,7 @@
class="alert alert-danger"
{% endif %}
{% endspaceless %}>
<p>{{message|safe}}</p>
<p>{{message}}</p>
</div>
{% endfor %}
{% if auto_submit %}</noscript>{% endif %}
......
......@@ -2,6 +2,6 @@
{% load staticfiles %}
{% load i18n %}
{% block content %}
<div class="alert alert-success" role="alert">{{logout_msg|safe}}</div>
<div class="alert alert-success" role="alert">{{logout_msg}}</div>
{% endblock %}
......@@ -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):
"""
......
......@@ -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(_(
"<h3>Logout successful</h3>"
"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(_(
"<h3>Logout successful</h3>"
"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(_(
"<h3>Logout successful</h3>"
"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(