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)