From 85f4c8b473cf550cf3b0df04ae26b8684801f0e8 Mon Sep 17 00:00:00 2001 From: Julie Pichon Date: Wed, 28 Aug 2013 15:30:44 +0100 Subject: [PATCH] Display a message on the login page In some cases, particularly when having to log the user out after performing some action (e.g. password change), we want to display a friendly message on the login screen to explain to the user why they have been redirected to the login page. This adds a function to do so, and uses it in a couple of places: - When updating one's own password using the Settings panel - Session time out - HTTP 401 Change-Id: Ie53c5552159304e1f1304ac6211b3accfd9aa623 Implements: blueprint messages-on-login-page --- horizon/middleware.py | 13 ++++++---- horizon/templates/auth/_login.html | 5 ++++ horizon/test/helpers.py | 4 ++-- horizon/utils/functions.py | 9 +++++++ .../dashboards/settings/password/forms.py | 8 ++++++- .../dashboards/settings/password/tests.py | 24 ++++++++++++++----- 6 files changed, 50 insertions(+), 13 deletions(-) diff --git a/horizon/middleware.py b/horizon/middleware.py index 3134a63371..6ed26ef232 100644 --- a/horizon/middleware.py +++ b/horizon/middleware.py @@ -34,9 +34,10 @@ from django.http import HttpResponseRedirect # noqa from django import shortcuts from django.utils.encoding import iri_to_uri # noqa from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ # noqa from horizon import exceptions - +from horizon.utils import functions as utils LOG = logging.getLogger(__name__) @@ -62,7 +63,10 @@ class HorizonMiddleware(object): timestamp = datetime.datetime.now() if last_activity and (timestamp - last_activity).seconds > timeout: request.session.pop('last_activity') - return HttpResponseRedirect(settings.LOGOUT_URL) + response = HttpResponseRedirect(settings.LOGOUT_URL) + reason = _("Session timed out.") + utils.add_logout_reason(request, response, reason) + return response request.session['last_activity'] = timestamp request.horizon = {'dashboard': None, @@ -86,12 +90,13 @@ class HorizonMiddleware(object): response = redirect_to_login(next_url, login_url=login_url, redirect_field_name=field_name) - # TODO(gabriel): Find a way to display an appropriate message to - # the user *on* the login form... if request.is_ajax(): response_401 = http.HttpResponse(status=401) response_401['X-Horizon-Location'] = response['location'] return response_401 + else: + utils.add_logout_reason(request, response, _("Unauthorized.")) + return response # If an internal "NotFound" error gets this far, return a real 404. diff --git a/horizon/templates/auth/_login.html b/horizon/templates/auth/_login.html index d244eb27a7..ebac8f44d4 100644 --- a/horizon/templates/auth/_login.html +++ b/horizon/templates/auth/_login.html @@ -18,6 +18,11 @@ {% trans "home page" %}

+ {% endif %} + {% if request.COOKIES.logout_reason %} +
+

{{ request.COOKIES.logout_reason }}

+
{% endif %} {% if next %}{% endif %} {% include "horizon/common/_form_fields.html" %} diff --git a/horizon/test/helpers.py b/horizon/test/helpers.py index 1e5be2fd81..aff98d756e 100644 --- a/horizon/test/helpers.py +++ b/horizon/test/helpers.py @@ -60,14 +60,14 @@ class RequestFactoryWithMessages(RequestFactory): def get(self, *args, **kwargs): req = super(RequestFactoryWithMessages, self).get(*args, **kwargs) req.user = User() - req.session = [] + req.session = {} req._messages = default_storage(req) return req def post(self, *args, **kwargs): req = super(RequestFactoryWithMessages, self).post(*args, **kwargs) req.user = User() - req.session = [] + req.session = {} req._messages = default_storage(req) return req diff --git a/horizon/utils/functions.py b/horizon/utils/functions.py index c0c0875beb..1144d17e18 100644 --- a/horizon/utils/functions.py +++ b/horizon/utils/functions.py @@ -2,6 +2,7 @@ import math from django.utils.encoding import force_unicode # noqa from django.utils.functional import lazy # noqa +from django.utils import translation def _lazy_join(separator, strings): @@ -15,3 +16,11 @@ def bytes_to_gigabytes(bytes): # Converts the number of bytes to the next highest number of Gigabytes # For example 5000000 (5 Meg) would return '1' return int(math.ceil(float(bytes) / 1024 ** 3)) + + +def add_logout_reason(request, response, reason): + # Store the translated string in the cookie + lang = translation.get_language_from_request(request) + with translation.override(lang): + reason = unicode(reason).encode('utf-8') + response.set_cookie('logout_reason', reason, max_age=30) diff --git a/openstack_dashboard/dashboards/settings/password/forms.py b/openstack_dashboard/dashboards/settings/password/forms.py index 59bbbdf49f..5cc736161e 100644 --- a/openstack_dashboard/dashboards/settings/password/forms.py +++ b/openstack_dashboard/dashboards/settings/password/forms.py @@ -14,13 +14,16 @@ # License for the specific language governing permissions and limitations # under the License. +from django.conf import settings # noqa from django.forms import ValidationError # noqa +from django import http from django.utils.translation import ugettext_lazy as _ # noqa from django.views.decorators.debug import sensitive_variables # noqa from horizon import exceptions from horizon import forms from horizon import messages +from horizon.utils import functions as utils from horizon.utils import validators from openstack_dashboard import api @@ -56,7 +59,10 @@ class PasswordForm(forms.SelfHandlingForm): api.keystone.user_update_own_password(request, data['current_password'], data['new_password']) - messages.success(request, _('Password changed.')) + response = http.HttpResponseRedirect(settings.LOGOUT_URL) + msg = _("Password changed. Please log in again to continue.") + utils.add_logout_reason(request, response, msg) + return response except Exception: exceptions.handle(request, _('Unable to change password.')) diff --git a/openstack_dashboard/dashboards/settings/password/tests.py b/openstack_dashboard/dashboards/settings/password/tests.py index 6a553d9bb9..43126658ba 100644 --- a/openstack_dashboard/dashboards/settings/password/tests.py +++ b/openstack_dashboard/dashboards/settings/password/tests.py @@ -31,27 +31,39 @@ class ChangePasswordTests(test.TestCase): @test.create_stubs({api.keystone: ('user_update_own_password', )}) def test_change_password(self): api.keystone.user_update_own_password(IsA(http.HttpRequest), - 'oldpwd', - 'normalpwd',).AndReturn(None) - + 'oldpwd', + 'normalpwd',).AndReturn(None) self.mox.ReplayAll() formData = {'method': 'PasswordForm', 'current_password': 'oldpwd', 'new_password': 'normalpwd', 'confirm_password': 'normalpwd'} - res = self.client.post(INDEX_URL, formData) self.assertNoFormErrors(res) def test_change_validation_passwords_not_matching(self): - formData = {'method': 'PasswordForm', 'current_password': 'currpasswd', 'new_password': 'testpassword', 'confirm_password': 'doesnotmatch'} - res = self.client.post(INDEX_URL, formData) self.assertFormError(res, "form", None, ['Passwords do not match.']) + + @test.create_stubs({api.keystone: ('user_update_own_password', )}) + def test_change_password_shows_message_on_login_page(self): + api.keystone.user_update_own_password(IsA(http.HttpRequest), + 'oldpwd', + 'normalpwd').AndReturn(None) + self.mox.ReplayAll() + + formData = {'method': 'PasswordForm', + 'current_password': 'oldpwd', + 'new_password': 'normalpwd', + 'confirm_password': 'normalpwd'} + res = self.client.post(INDEX_URL, formData, follow=True) + + info_msg = "Password changed. Please log in again to continue." + self.assertContains(res, info_msg)