From 878c703fd006569219d3fc5be459f6ab76a48a15 Mon Sep 17 00:00:00 2001 From: Vlad Okhrimenko Date: Wed, 17 Dec 2014 16:47:16 +0200 Subject: [PATCH] Logout user if he has no valid tokens Before this patch, if user's rights were changed or revoked - there would be "Unauthorized" errors on every page since user had no rights to view them because he had no valid tokens in that case. Now user will be logged out if he has no valid tokens. Set `escalate` to True (for unauthorized-error) to always log user out. Also, now horizon.exceptions.NotAuthorized is a part of UNAUTHORIZED tuple in the exceptions.py, because this type of exception is re-raised after handling services unauthorized errors. Looks like it was missing. Now the horizon.exceptions.NotAuthorized is handled like all NotAuthorized exceptions. And horizon_middleware.py in process_exception now generates logout_reason for cases if user is not authorized. Closes-Bug: #1252341 Closes-Bug: #1407105 Co-Authored-By: Paul Karikh Change-Id: I417cad936ea80c0569c2f442fc87cbd58745757e --- horizon/exceptions.py | 7 +++-- horizon/middleware.py | 6 ++++ .../dashboards/project/instances/tests.py | 15 +++++++-- .../dashboards/project/overview/tests.py | 31 ++++++++++++++----- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/horizon/exceptions.py b/horizon/exceptions.py index 14b8c3b460..6eafe2d9a0 100644 --- a/horizon/exceptions.py +++ b/horizon/exceptions.py @@ -201,6 +201,7 @@ class HandledException(HorizonException): UNAUTHORIZED = tuple(HORIZON_CONFIG['exceptions']['unauthorized']) +UNAUTHORIZED += (NotAuthorized,) NOT_FOUND = tuple(HORIZON_CONFIG['exceptions']['not_found']) RECOVERABLE = (AlreadyExists, Conflict, NotAvailable, ServiceCatalogException) RECOVERABLE += tuple(HORIZON_CONFIG['exceptions']['recoverable']) @@ -279,7 +280,8 @@ def handle_recoverable(request, message, redirect, ignore, escalate, handled, HANDLE_EXC_METHODS = [ - {'exc': UNAUTHORIZED, 'handler': handle_unauthorized, 'set_wrap': False}, + {'exc': UNAUTHORIZED, 'handler': handle_unauthorized, + 'set_wrap': False, 'escalate': True}, {'exc': NOT_FOUND, 'handler': handle_notfound, 'set_wrap': True}, {'exc': RECOVERABLE, 'handler': handle_recoverable, 'set_wrap': True}, ] @@ -346,7 +348,8 @@ def handle(request, message=None, redirect=None, ignore=False, if exc_handler['set_wrap']: wrap = True handler = exc_handler['handler'] - ret = handler(request, message, redirect, ignore, escalate, + ret = handler(request, message, redirect, ignore, + exc_handler.get('escalate', escalate), handled, force_silence, force_log, log_method, log_entry, log_level) if ret: diff --git a/horizon/middleware.py b/horizon/middleware.py index 885489e5c5..d39f4da265 100644 --- a/horizon/middleware.py +++ b/horizon/middleware.py @@ -158,6 +158,12 @@ class HorizonMiddleware(object): login_url = request.build_absolute_uri(auth_url) response = redirect_to_login(next_url, login_url=login_url, redirect_field_name=field_name) + if isinstance(exception, exceptions.NotAuthorized): + logout_reason = _("Unauthorized. Please try logging in again.") + utils.add_logout_reason(request, response, logout_reason) + # delete messages, created in get_data() method + # since we are going to redirect user to the login page + response.delete_cookie('messages') if request.is_ajax(): response_401 = http.HttpResponse(status=401) diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py index 10c43fde7a..6dd984caed 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -17,10 +17,12 @@ # under the License. import json +import logging import sys import django from django.conf import settings +from django.contrib.auth import REDIRECT_FIELD_NAME # noqa from django.core.urlresolvers import reverse from django.forms import widgets from django import http @@ -889,9 +891,18 @@ class InstanceTests(helpers.TestCase): url = reverse('horizon:project:instances:detail', args=[server.id]) - res = self.client.get(url) - self.assertRedirectsNoFollow(res, INDEX_URL) + # Avoid the log message in the test + # when unauthorized exception will be logged + logging.disable(logging.ERROR) + res = self.client.get(url) + logging.disable(logging.NOTSET) + + self.assertEqual(302, res.status_code) + self.assertEqual(('Location', settings.TESTSERVER + + settings.LOGIN_URL + '?' + + REDIRECT_FIELD_NAME + '=' + url), + res._headers.get('location', None),) def test_instance_details_flavor_not_found(self): server = self.servers.first() diff --git a/openstack_dashboard/dashboards/project/overview/tests.py b/openstack_dashboard/dashboards/project/overview/tests.py index 97ae955de9..e382488008 100644 --- a/openstack_dashboard/dashboards/project/overview/tests.py +++ b/openstack_dashboard/dashboards/project/overview/tests.py @@ -17,7 +17,10 @@ # under the License. import datetime +import logging +from django.conf import settings +from django.contrib.auth import REDIRECT_FIELD_NAME # noqa from django.core.urlresolvers import reverse from django import http from django.utils import timezone @@ -140,18 +143,32 @@ class UsageViewTests(test.TestCase): self._common_assertions(nova_stu_enabled, maxTotalFloatingIps=10) + @test.create_stubs({api.nova: ('usage_get', + 'extension_supported')}) + def _stub_nova_api_calls_unauthorized(self, exception): + api.nova.extension_supported( + 'SimpleTenantUsage', IsA(http.HttpRequest)) \ + .AndReturn(True) + self._nova_stu_enabled(exception) + def test_unauthorized(self): - self._stub_nova_api_calls( - stu_exception=self.exceptions.nova_unauthorized) - self._stub_neutron_api_calls() - self._stub_cinder_api_calls() + self._stub_nova_api_calls_unauthorized( + self.exceptions.nova_unauthorized) self.mox.ReplayAll() url = reverse('horizon:project:overview:index') + + # Avoid the log message in the test + # when unauthorized exception will be logged + logging.disable(logging.ERROR) res = self.client.get(url) - self.assertTemplateUsed(res, 'project/overview/usage.html') - self.assertMessageCount(res, error=1) - self.assertContains(res, 'Unauthorized:') + logging.disable(logging.NOTSET) + + self.assertEqual(302, res.status_code) + self.assertEqual(('Location', settings.TESTSERVER + + settings.LOGIN_URL + '?' + + REDIRECT_FIELD_NAME + '=' + url), + res._headers.get('location', None),) def test_usage_csv(self): self._test_usage_csv(nova_stu_enabled=True)