# Copyright 2012 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Copyright 2012 OpenStack Foundation # Copyright 2012 Nebula, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import importlib from django.conf import settings from django.contrib.auth.models import User from django.core.exceptions import ImproperlyConfigured from django.test.utils import override_settings from django import urls import horizon from horizon import base from horizon import conf from horizon.test import helpers as test from horizon.test.test_dashboards.cats.dashboard import Cats from horizon.test.test_dashboards.cats.kittens.panel import Kittens from horizon.test.test_dashboards.cats.tigers.panel import Tigers from horizon.test.test_dashboards.dogs.dashboard import Dogs from horizon.test.test_dashboards.dogs.puppies.panel import Puppies class MyDash(horizon.Dashboard): name = "My Dashboard" slug = "mydash" default_panel = "myslug" class MyOtherDash(horizon.Dashboard): name = "My Other Dashboard" slug = "mydash2" default_panel = "myslug2" class MyPanel(horizon.Panel): name = "My Panel" slug = "myslug" urls = 'horizon.test.test_dashboards.cats.kittens.urls' class RbacNoAccessPanel(horizon.Panel): name = "RBAC Panel No" slug = "rbac_panel_no" def allowed(self, context): return False class RbacYesAccessPanel(horizon.Panel): name = "RBAC Panel Yes" slug = "rbac_panel_yes" class BaseHorizonTests(test.TestCase): def setUp(self): super().setUp() # Adjust our horizon config and register our custom dashboards/panels. self.old_default_dash = settings.HORIZON_CONFIG['default_dashboard'] settings.HORIZON_CONFIG['default_dashboard'] = 'cats' self.old_dashboards = settings.HORIZON_CONFIG['dashboards'] settings.HORIZON_CONFIG['dashboards'] = ('cats', 'dogs') base.Horizon.register(Cats) base.Horizon.register(Dogs) Cats.register(Kittens) Cats.register(Tigers) Dogs.register(Puppies) # Trigger discovery, registration, and URLconf generation if it # hasn't happened yet. base.Horizon._urls() # Store our original dashboards self._discovered_dashboards = list(base.Horizon._registry) # Gather up and store our original panels for each dashboard self._discovered_panels = {} for dash in self._discovered_dashboards: panels = list(base.Horizon._registry[dash]._registry) self._discovered_panels[dash] = panels def tearDown(self): super().tearDown() # Restore our settings settings.HORIZON_CONFIG['default_dashboard'] = self.old_default_dash settings.HORIZON_CONFIG['dashboards'] = self.old_dashboards # Destroy our singleton and re-create it. base.HorizonSite._instance = None del base.Horizon base.Horizon = base.HorizonSite() # Reload the convenience references to Horizon stored in __init__ importlib.reload(importlib.import_module("horizon")) # Re-register our original dashboards and panels. # This is necessary because autodiscovery only works on the first # import, and calling reload introduces innumerable additional # problems. Manual re-registration is the only good way for testing. self._discovered_dashboards.remove(Cats) self._discovered_dashboards.remove(Dogs) for dash in self._discovered_dashboards: base.Horizon.register(dash) for panel in self._discovered_panels[dash]: dash.register(panel) def _reload_urls(self): """Clears out the URL caches, and reloads the root urls module. It re-triggers the autodiscovery mechanism for Horizon. Allows URLs to be re-calculated after registering new dashboards. Useful only for testing and should never be used on a live site. """ urls.clear_url_caches() importlib.reload(importlib.import_module(settings.ROOT_URLCONF)) base.Horizon._urls() class HorizonTests(BaseHorizonTests): def test_registry(self): """Verify registration and autodiscovery work correctly. Please note that this implicitly tests that autodiscovery works by virtue of the fact that the dashboards listed in ``settings.INSTALLED_APPS`` are loaded from the start. """ # Registration self.assertEqual(2, len(base.Horizon._registry)) horizon.register(MyDash) self.assertEqual(3, len(base.Horizon._registry)) with self.assertRaises(ValueError): horizon.register(MyPanel) with self.assertRaises(ValueError): horizon.register("MyPanel") # Retrieval my_dash_instance_by_name = horizon.get_dashboard("mydash") self.assertIsInstance(my_dash_instance_by_name, MyDash) my_dash_instance_by_class = horizon.get_dashboard(MyDash) self.assertEqual(my_dash_instance_by_name, my_dash_instance_by_class) with self.assertRaises(base.NotRegistered): horizon.get_dashboard("fake") self.assertQuerysetEqual(horizon.get_dashboards(), ['', '', ''], transform=repr) # Removal self.assertEqual(3, len(base.Horizon._registry)) horizon.unregister(MyDash) self.assertEqual(2, len(base.Horizon._registry)) with self.assertRaises(base.NotRegistered): horizon.get_dashboard(MyDash) def test_registry_two_dashboards(self): "Verify registration of 2 dashboards" # Registration self.assertEqual(2, len(base.Horizon._registry)) horizon.register(MyDash) horizon.register(MyOtherDash) self.assertEqual(4, len(base.Horizon._registry)) # Retrieval self.assertQuerysetEqual(horizon.get_dashboards(), ['', '', '', ''], transform=repr) # Removal self.assertEqual(4, len(base.Horizon._registry)) horizon.unregister(MyDash) horizon.unregister(MyOtherDash) self.assertEqual(2, len(base.Horizon._registry)) with self.assertRaises(base.NotRegistered): horizon.get_dashboard(MyDash) with self.assertRaises(base.NotRegistered): horizon.get_dashboard(MyOtherDash) def test_site(self): self.assertEqual("Horizon", str(base.Horizon)) self.assertEqual("", repr(base.Horizon)) dash = base.Horizon.get_dashboard('cats') self.assertEqual(dash, base.Horizon.get_default_dashboard()) test_user = User() self.assertEqual(dash.get_absolute_url(), base.Horizon.get_user_home(test_user)) def test_dashboard(self): cats = horizon.get_dashboard("cats") self.assertEqual(base.Horizon, cats._registered_with) self.assertQuerysetEqual(cats.get_panels(), ['', ''], transform=repr) self.assertEqual("/cats/", cats.get_absolute_url()) self.assertEqual("Cats", cats.name) # Test registering a module with a dashboard that defines panels # as a panel group. cats.register(MyPanel) self.assertQuerysetEqual(list(cats.get_panel_groups()['other']), [''], transform=repr) # Test that panels defined as a tuple still return a PanelGroup dogs = horizon.get_dashboard("dogs") self.assertQuerysetEqual(list(dogs.get_panel_groups().values()), [''], transform=repr) # Test registering a module with a dashboard that defines panels # as a tuple. dogs = horizon.get_dashboard("dogs") dogs.register(MyPanel) self.assertQuerysetEqual(dogs.get_panels(), ['', ''], transform=repr) cats.unregister(MyPanel) dogs.unregister(MyPanel) def test_panels(self): cats = horizon.get_dashboard("cats") tigers = cats.get_panel("tigers") self.assertEqual(cats, tigers._registered_with) self.assertEqual("/cats/tigers/", tigers.get_absolute_url()) def test_panel_without_slug_fails(self): class InvalidPanel(horizon.Panel): name = 'Invalid' self.assertRaises(ImproperlyConfigured, InvalidPanel) def test_registry_without_registerable_class_attr_fails(self): class InvalidRegistry(base.Registry): pass self.assertRaises(ImproperlyConfigured, InvalidRegistry) def test_index_url_name(self): cats = horizon.get_dashboard("cats") tigers = cats.get_panel("tigers") tigers.index_url_name = "does_not_exist" with self.assertRaises(urls.NoReverseMatch): tigers.get_absolute_url() tigers.index_url_name = "index" self.assertEqual("/cats/tigers/", tigers.get_absolute_url()) def test_lazy_urls(self): urlpatterns = horizon.urls[0] self.assertIsInstance(urlpatterns, base.LazyURLPattern) # The following two methods simply should not raise any exceptions iter(urlpatterns) reversed(urlpatterns) def test_horizon_test_isolation_1(self): """Isolation Test Part 1: sets a value.""" cats = horizon.get_dashboard("cats") cats.evil = True def test_horizon_test_isolation_2(self): """Isolation Test Part 2: The value set in part 1 should be gone.""" cats = horizon.get_dashboard("cats") self.assertFalse(hasattr(cats, "evil")) def test_public(self): dogs = horizon.get_dashboard("dogs") # Known to have no restrictions on it other than being logged in. puppies = dogs.get_panel("puppies") url = puppies.get_absolute_url() # Get a clean, logged out client instance. self.client.logout() resp = self.client.get(url) redirect_url = "?".join(['http://testserver' + settings.LOGIN_URL, "next=%s" % url]) self.assertRedirects(resp, redirect_url) # Simulate ajax call resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') # Response should be HTTP 401 with redirect header self.assertEqual(401, resp.status_code) self.assertEqual(redirect_url, resp["X-Horizon-Location"]) @override_settings(SESSION_REFRESH=False) def test_required_permissions(self): dash = horizon.get_dashboard("cats") panel = dash.get_panel('tigers') # Non-admin user self.assertQuerysetEqual(self.user.get_all_permissions(), []) resp = self.client.get(panel.get_absolute_url()) self.assertEqual(403, resp.status_code) resp = self.client.get(panel.get_absolute_url(), follow=False, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(403, resp.status_code) # Test insufficient permissions for logged-in user resp = self.client.get(panel.get_absolute_url(), follow=True) self.assertEqual(403, resp.status_code) self.assertTemplateUsed(resp, "not_authorized.html") # Set roles for admin user self.set_permissions(permissions=['test']) resp = self.client.get(panel.get_absolute_url()) self.assertEqual(200, resp.status_code) # Test modal form resp = self.client.get(panel.get_absolute_url(), follow=False, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(200, resp.status_code) def test_ssl_redirect_by_proxy(self): dogs = horizon.get_dashboard("dogs") puppies = dogs.get_panel("puppies") url = puppies.get_absolute_url() redirect_url = "?".join([settings.LOGIN_URL, "next=%s" % url]) self.client.logout() resp = self.client.get(url) self.assertRedirects(resp, settings.TESTSERVER + redirect_url) # Set SSL settings for test server settings.SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') resp = self.client.get(url, HTTP_X_FORWARDED_PROTOCOL="https") self.assertEqual(302, resp.status_code) self.assertEqual('https://testserver:80%s' % redirect_url, resp['location']) # Restore settings settings.SECURE_PROXY_SSL_HEADER = None class GetUserHomeTests(test.TestCase): """Test get_user_home parameters.""" def setUp(self): self.orig_user_home = settings.HORIZON_CONFIG['user_home'] super().setUp() self.original_username = "testname" self.test_user = User() self.test_user.username = self.original_username def tearDown(self): settings.HORIZON_CONFIG['user_home'] = self.orig_user_home conf.HORIZON_CONFIG._setup() super().tearDown() def test_using_callable(self): def themable_user_fnc(user): return user.username.upper() settings.HORIZON_CONFIG['user_home'] = themable_user_fnc conf.HORIZON_CONFIG._setup() self.assertEqual(self.test_user.username.upper(), base.Horizon.get_user_home(self.test_user)) def test_using_module_function(self): module_func = 'django.utils.encoding.force_str' settings.HORIZON_CONFIG['user_home'] = module_func conf.HORIZON_CONFIG._setup() self.test_user.username = 'testname' self.assertEqual(self.original_username, base.Horizon.get_user_home(self.test_user)) def test_using_url(self): fixed_url = "/url" settings.HORIZON_CONFIG['user_home'] = fixed_url conf.HORIZON_CONFIG._setup() self.assertEqual(fixed_url, base.Horizon.get_user_home(self.test_user)) class CustomPanelTests(BaseHorizonTests): """Test customization of dashboards and panels. This tests customization using 'customization_module' to HORIZON_CONFIG. """ def setUp(self): super().setUp() settings.HORIZON_CONFIG['customization_module'] = \ 'horizon.test.customization.cust_test1' # refresh config conf.HORIZON_CONFIG._setup() self._reload_urls() def tearDown(self): # Restore dash cats = horizon.get_dashboard("cats") cats.name = "Cats" horizon.register(Dogs) self._discovered_dashboards.append(Dogs) Dogs.register(Puppies) Cats.register(Tigers) super().tearDown() settings.HORIZON_CONFIG.pop('customization_module') # refresh config conf.HORIZON_CONFIG._setup() def test_customize_dashboard(self): cats = horizon.get_dashboard("cats") self.assertEqual("WildCats", cats.name) self.assertQuerysetEqual(cats.get_panels(), [''], transform=repr) with self.assertRaises(base.NotRegistered): horizon.get_dashboard("dogs") class CustomPermissionsTests(BaseHorizonTests): """Test customization of permissions on panels. This tests customization using 'customization_module' to HORIZON_CONFIG. """ def setUp(self): settings.HORIZON_CONFIG['customization_module'] = \ 'horizon.test.customization.cust_test2' # refresh config conf.HORIZON_CONFIG._setup() super().setUp() def tearDown(self): # Restore permissions dogs = horizon.get_dashboard("dogs") puppies = dogs.get_panel("puppies") puppies.permissions = tuple([]) super().tearDown() settings.HORIZON_CONFIG.pop('customization_module') # refresh config conf.HORIZON_CONFIG._setup() @override_settings(SESSION_REFRESH=False) def test_customized_permissions(self): dogs = horizon.get_dashboard("dogs") panel = dogs.get_panel('puppies') # Non-admin user self.assertQuerysetEqual(self.user.get_all_permissions(), []) resp = self.client.get(panel.get_absolute_url()) self.assertEqual(403, resp.status_code) resp = self.client.get(panel.get_absolute_url(), follow=False, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(403, resp.status_code) # Test customized permissions for logged-in user resp = self.client.get(panel.get_absolute_url(), follow=True) self.assertEqual(403, resp.status_code) self.assertTemplateUsed(resp, "not_authorized.html") # Set roles for admin user self.set_permissions(permissions=['test']) resp = self.client.get(panel.get_absolute_url()) self.assertEqual(200, resp.status_code) # Test modal form resp = self.client.get(panel.get_absolute_url(), follow=False, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(resp.status_code, 200) class RbacHorizonTests(test.TestCase): def setUp(self): super().setUp() # Adjust our horizon config and register our custom dashboards/panels. self.old_default_dash = settings.HORIZON_CONFIG['default_dashboard'] settings.HORIZON_CONFIG['default_dashboard'] = 'cats' self.old_dashboards = settings.HORIZON_CONFIG['dashboards'] settings.HORIZON_CONFIG['dashboards'] = ('cats', 'dogs') base.Horizon.register(Cats) base.Horizon.register(Dogs) Cats.register(RbacNoAccessPanel) Cats.default_panel = 'rbac_panel_no' Dogs.register(RbacYesAccessPanel) Dogs.default_panel = 'rbac_panel_yes' # Trigger discovery, registration, and URLconf generation if it # hasn't happened yet. base.Horizon._urls() # Store our original dashboards self._discovered_dashboards = list(base.Horizon._registry) # Gather up and store our original panels for each dashboard self._discovered_panels = {} for dash in self._discovered_dashboards: panels = list(base.Horizon._registry[dash]._registry) self._discovered_panels[dash] = panels def tearDown(self): super().tearDown() # Restore our settings settings.HORIZON_CONFIG['default_dashboard'] = self.old_default_dash settings.HORIZON_CONFIG['dashboards'] = self.old_dashboards # Destroy our singleton and re-create it. base.HorizonSite._instance = None del base.Horizon base.Horizon = base.HorizonSite() # Reload the convenience references to Horizon stored in __init__ importlib.reload(importlib.import_module("horizon")) # Reset Cats and Dogs default_panel to default values Cats.default_panel = 'kittens' Dogs.default_panel = 'puppies' # Re-register our original dashboards and panels. # This is necessary because autodiscovery only works on the first # import, and calling reload introduces innumerable additional # problems. Manual re-registration is the only good way for testing. self._discovered_dashboards.remove(Cats) self._discovered_dashboards.remove(Dogs) for dash in self._discovered_dashboards: base.Horizon.register(dash) for panel in self._discovered_panels[dash]: dash.register(panel) def test_rbac_panels(self): context = {'request': self.request} cats = horizon.get_dashboard("cats") self.assertEqual(cats._registered_with, base.Horizon) self.assertQuerysetEqual(cats.get_panels(), [''], transform=repr) self.assertFalse(cats.can_access(context)) dogs = horizon.get_dashboard("dogs") self.assertEqual(dogs._registered_with, base.Horizon) self.assertQuerysetEqual(dogs.get_panels(), [''], transform=repr) self.assertTrue(dogs.can_access(context))