diff options
author | Gabriel Hurley <gabriel@strikeawe.com> | 2011-11-08 12:20:30 -0800 |
---|---|---|
committer | Gabriel Hurley <gabriel@strikeawe.com> | 2011-11-08 14:22:50 -0800 |
commit | 67a979ae99fcba2b0f97789ae7a4dc5de8fa084e (patch) | |
tree | d1c911f27b9a830c7afc9fb9bd63db60784a987a | |
parent | 193e59f8ff041410901cd73f30edb74ca17ed1e3 (diff) | |
download | tuskar-ui-67a979ae99fcba2b0f97789ae7a4dc5de8fa084e.tar.gz |
Four modest bug fixes.
Sorry these are in one commit, but they all ran one into the other when I discovered them.
Fixed 887770 -- corrects an instance of "dash" -> "nova" renaming that was missed in openstack-dashboard/dashboard/views.py
Fixed 887768 -- removes duplicate code and adds docstrings to auth_views.py. These changes were previously made but somehow didn't get staged and committed.
Fixed 887767 -- the list of tenants in the tenant switcher no longer changes when viewing the Tenants or Users syspanel pages.
Fixed 887766 -- corrects a broken bit of if/else logic so that the tenant switch list isn't empty.
Change-Id: I0cb6fff83d7279b17233c50ef2d4af2586cca829
-rw-r--r-- | horizon/horizon/api/keystone.py | 32 | ||||
-rw-r--r-- | horizon/horizon/context_processors.py | 19 | ||||
-rw-r--r-- | horizon/horizon/test.py | 12 | ||||
-rw-r--r-- | horizon/horizon/tests/context_processor_tests.py | 18 | ||||
-rw-r--r-- | horizon/horizon/views/auth.py | 123 | ||||
-rw-r--r-- | openstack-dashboard/dashboard/settings.py | 1 | ||||
-rw-r--r-- | openstack-dashboard/dashboard/templates/_topbar.html | 2 | ||||
-rw-r--r-- | openstack-dashboard/dashboard/views.py | 2 |
8 files changed, 87 insertions, 122 deletions
diff --git a/horizon/horizon/api/keystone.py b/horizon/horizon/api/keystone.py index cdc45516..3c54123c 100644 --- a/horizon/horizon/api/keystone.py +++ b/horizon/horizon/api/keystone.py @@ -23,7 +23,9 @@ import logging from django.conf import settings from keystoneclient import exceptions as keystone_exceptions +from keystoneclient import service_catalog from keystoneclient.v2_0 import client as keystone_client +from keystoneclient.v2_0 import tokens from horizon.api.base import * from horizon.api.deprecated import admin_api @@ -59,7 +61,7 @@ class Services(APIResourceWrapper): def keystoneclient(request, username=None, password=None, tenant_id=None, - token_id=None, endpoint=None): + token_id=None, endpoint=None, endpoint_type=None): """Returns a client connected to the Keystone backend. Several forms of authentication are supported: @@ -82,8 +84,11 @@ def keystoneclient(request, username=None, password=None, tenant_id=None, user = request.user if hasattr(request, '_keystone') and \ request._keystone.auth_token == user.token: + LOG.debug("Using cached client for token: %s" % user.token) conn = request._keystone else: + LOG.debug("Creating a new client connection with endpoint: %s." + % endpoint) conn = keystone_client.Client(username=username or user.username, password=password, project_id=tenant_id or user.tenant_id, @@ -95,7 +100,10 @@ def keystoneclient(request, username=None, password=None, tenant_id=None, # Fetch the correct endpoint for the user type catalog = getattr(conn, 'service_catalog', None) if catalog and "serviceCatalog" in catalog.catalog.keys(): - if user.is_admin(): + if endpoint_type: + endpoint = catalog.url_for(service_type='identity', + endpoint_type=endpoint_type) + elif user.is_admin(): endpoint = catalog.url_for(service_type='identity', endpoint_type='adminURL') else: @@ -137,9 +145,11 @@ def tenant_delete(request, tenant_id): keystoneclient(request).tenants.delete(tenant_id) -def tenant_list_for_token(request, token): - c = keystoneclient(request, token_id=token, - endpoint=settings.OPENSTACK_KEYSTONE_URL) +def tenant_list_for_token(request, token, endpoint_type=None): + c = keystoneclient(request, + token_id=token, + endpoint=settings.OPENSTACK_KEYSTONE_URL, + endpoint_type=endpoint_type) return [Tenant(t) for t in c.tenants.list()] @@ -170,7 +180,17 @@ def token_create_scoped(request, tenant, token): del request._keystone c = keystoneclient(request, tenant_id=tenant, token_id=token, endpoint=settings.OPENSTACK_KEYSTONE_URL) - scoped_token = c.tokens.authenticate(tenant=tenant, token=token) + raw_token = c.tokens.authenticate(tenant=tenant, + token=token, + return_raw=True) + c.service_catalog = service_catalog.ServiceCatalog(raw_token) + if request.user.is_admin(): + c.management_url = c.service_catalog.url_for(service_type='identity', + endpoint_type='adminURL') + else: + c.management_url = c.service_catalog.url_for(service_type='identity', + endpoint_type='publicURL') + scoped_token = tokens.Token(tokens.TokenManager, raw_token) return Token(scoped_token) diff --git a/horizon/horizon/context_processors.py b/horizon/horizon/context_processors.py index 9f0a9878..eccb0452 100644 --- a/horizon/horizon/context_processors.py +++ b/horizon/horizon/context_processors.py @@ -21,12 +21,17 @@ Context processors used by Horizon. """ +import logging + from django.conf import settings from django.contrib import messages from horizon import api +LOG = logging.getLogger(__name__) + + def horizon(request): """ The main Horizon context processor. Required for Horizon to function. @@ -45,15 +50,19 @@ def horizon(request): context = {} # Auth/Keystone context + context.setdefault('authorized_tenants', []) if request.user.is_authenticated(): try: - tenants = api.tenant_list_for_token(request, request.user.token) - context['tenants'] = tenants + tenants = api.tenant_list_for_token(request, + request.user.token, + endpoint_type='internalURL') + context['authorized_tenants'] = tenants except Exception, e: if hasattr(request.user, 'message_set'): - messages.error(request, _("Unable to retrieve tenant list from\ - keystone: %s") % e.message) - context['tenants'] = [] + messages.error(request, _("Unable to retrieve tenant list: %s") + % e.message) + else: + LOG.exception('Could not retrieve tenant list.') # Object Store/Swift context catalog = getattr(request.user, 'service_catalog', []) diff --git a/horizon/horizon/test.py b/horizon/horizon/test.py index 97f512af..969718b9 100644 --- a/horizon/horizon/test.py +++ b/horizon/horizon/test.py @@ -66,6 +66,12 @@ class TestCase(django_test.TestCase): TEST_TOKEN = 'aToken' TEST_USER = 'test' TEST_ROLES = [{'name': 'admin', 'id': '1'}] + TEST_CONTEXT = {'authorized_tenants': [{'enabled': True, + 'name': 'aTenant', + 'id': '1', + 'description': "None"}], + 'object_store_configured': False, + 'network_configured': False} TEST_SERVICE_CATALOG = [ {"endpoints": [{ @@ -107,12 +113,8 @@ class TestCase(django_test.TestCase): def setUp(self): self.mox = mox.Mox() - context_dict = {'tenants': [], - 'object_store_configured': False, - 'network_configured': False} - self._real_horizon_context_processor = context_processors.horizon - context_processors.horizon = lambda request: context_dict + context_processors.horizon = lambda request: self.TEST_CONTEXT self._real_get_user_from_request = users.get_user_from_request self.setActiveUser(token=self.TEST_TOKEN, diff --git a/horizon/horizon/tests/context_processor_tests.py b/horizon/horizon/tests/context_processor_tests.py index b4e507d1..06b7e70f 100644 --- a/horizon/horizon/tests/context_processor_tests.py +++ b/horizon/horizon/tests/context_processor_tests.py @@ -18,8 +18,12 @@ # License for the specific language governing permissions and limitations # under the License. +from django import http +from mox import IsA + from horizon import context_processors from horizon import test +from horizon import api class ContextProcessorTests(test.TestCase): @@ -32,6 +36,20 @@ class ContextProcessorTests(test.TestCase): super(ContextProcessorTests, self).tearDown() self.request.user.service_catalog = self._prev_catalog + def test_authorized_tenants(self): + tenant_list = self.TEST_CONTEXT['authorized_tenants'] + self.mox.StubOutWithMock(api, 'tenant_list_for_token') + api.tenant_list_for_token(IsA(http.HttpRequest), + self.TEST_TOKEN, + endpoint_type='internalURL') \ + .AndReturn(tenant_list) + self.mox.ReplayAll() + + context = context_processors.horizon(self.request) + self.assertEqual(len(context['authorized_tenants']), 1) + tenant = context['authorized_tenants'].pop() + self.assertEqual(tenant['id'], self.TEST_TENANT) + def test_object_store(self): # Returns the object store service data when it's in the catalog context = context_processors.horizon(self.request) diff --git a/horizon/horizon/views/auth.py b/horizon/horizon/views/auth.py index 9322b049..6269e221 100644 --- a/horizon/horizon/views/auth.py +++ b/horizon/horizon/views/auth.py @@ -29,123 +29,36 @@ from openstackx.api import exceptions as api_exceptions from horizon import api from horizon import exceptions -from horizon import forms +from horizon import users +from horizon.base import Horizon +from horizon.views.auth_forms import Login, LoginWithTenant, _set_session_data LOG = logging.getLogger(__name__) -def _is_admin(token): - for role in token.user['roles']: - if role['name'].lower() == 'admin': - return True - return False - - -def _set_session_data(request, token): - request.session['admin'] = _is_admin(token) - request.session['serviceCatalog'] = token.serviceCatalog - request.session['tenant'] = token.tenant['name'] - request.session['tenant_id'] = token.tenant['id'] - request.session['token'] = token.id - request.session['user'] = token.user['name'] - request.session['roles'] = token.user['roles'] - - -class Login(forms.SelfHandlingForm): - username = forms.CharField(max_length="20", label=_("User Name")) - password = forms.CharField(max_length="20", label=_("Password"), - widget=forms.PasswordInput(render_value=False)) - - def handle(self, request, data): - try: - if data.get('tenant'): - token = api.token_create(request, - data.get('tenant'), - data['username'], - data['password']) - - tenants = api.tenant_list_for_token(request, token.id) - tenant = None - for t in tenants: - if t.id == data.get('tenant'): - tenant = t - else: - token = api.token_create(request, - '', - data['username'], - data['password']) - - # Unscoped token - request.session['unscoped_token'] = token.id - request.user.username = data['username'] - - # Get the tenant list, and log in using first tenant - # FIXME (anthony): add tenant chooser here? - tenants = api.tenant_list_for_token(request, token.id) - - # Abort if there are no valid tenants for this user - if not tenants: - messages.error(request, - _('No tenants present for user: %(user)s') % - {"user": data['username']}) - return - - # Create a token. - # NOTE(gabriel): Keystone can return tenants that you're - # authorized to administer but not to log into as a user, so in - # the case of an Unauthorized error we should iterate through - # the tenants until one succeeds or we've failed them all. - while tenants: - tenant = tenants.pop() - try: - token = api.token_create_scoped(request, - tenant.id, - token.id) - break - except exceptions.Unauthorized as e: - token = None - if token is None: - raise exceptions.Unauthorized( - _("You are not authorized for any available tenants.")) - - LOG.info('Login form for user "%s". Service Catalog data:\n%s' % - (data['username'], token.serviceCatalog)) - _set_session_data(request, token) - - return shortcuts.redirect('horizon:nova:overview:index') - - except api_exceptions.Unauthorized as e: - msg = _('Error authenticating: %s') % e.message - LOG.exception(msg) - messages.error(request, msg) - except api_exceptions.ApiException as e: - messages.error(request, - _('Error authenticating with keystone: %s') % - e.message) - - -class LoginWithTenant(Login): - username = forms.CharField(max_length="20", - widget=forms.TextInput(attrs={'readonly': 'readonly'})) - tenant = forms.CharField(widget=forms.HiddenInput()) - - def login(request): - if request.user and request.user.is_authenticated(): - if request.user.is_admin(): - return shortcuts.redirect('horizon:syspanel:overview:index') - else: - return shortcuts.redirect('horizon:nova:overview:index') + """ + Logs in a user and redirects them to the URL specified by + :func:`horizon.get_user_home`. + """ + if request.user.is_authenticated(): + user = users.User(users.get_user_from_request(request)) + return shortcuts.redirect(Horizon.get_user_home(user)) form, handled = Login.maybe_handle(request) if handled: return handled + # FIXME(gabriel): we don't ship a view named splash return shortcuts.render(request, 'splash.html', {'form': form}) def switch_tenants(request, tenant_id): + """ + Swaps a user from one tenant to another using the unscoped token from + Keystone to exchange scoped tokens for the new tenant. + """ form, handled = LoginWithTenant.maybe_handle( request, initial={'tenant': tenant_id, 'username': request.user.username}) @@ -159,10 +72,12 @@ def switch_tenants(request, tenant_id): tenant_id, unscoped_token) _set_session_data(request, token) - return shortcuts.redirect('horizon:nova:overview:index') + user = users.User(users.get_user_from_request(request)) + return shortcuts.redirect(Horizon.get_user_home(user)) except exceptions.Unauthorized as e: messages.error(_("You are not authorized for that tenant.")) + # FIXME(gabriel): we don't ship switch_tenants.html return shortcuts.render(request, 'switch_tenants.html', { 'to_tenant': tenant_id, @@ -170,5 +85,7 @@ def switch_tenants(request, tenant_id): def logout(request): + """ Clears the session and logs the current user out. """ request.session.clear() + # FIXME(gabriel): we don't ship a view named splash return shortcuts.redirect('splash') diff --git a/openstack-dashboard/dashboard/settings.py b/openstack-dashboard/dashboard/settings.py index 1b5100be..e61865f7 100644 --- a/openstack-dashboard/dashboard/settings.py +++ b/openstack-dashboard/dashboard/settings.py @@ -138,4 +138,3 @@ if DEBUG: 'debug_toolbar.middleware.DebugToolbarMiddleware',) except ImportError: logging.info('Running in debug mode without debug_toolbar.') - diff --git a/openstack-dashboard/dashboard/templates/_topbar.html b/openstack-dashboard/dashboard/templates/_topbar.html index b75d38a5..c246cb7e 100644 --- a/openstack-dashboard/dashboard/templates/_topbar.html +++ b/openstack-dashboard/dashboard/templates/_topbar.html @@ -15,7 +15,7 @@ <a id="drop_btn" href="#"> </a> <ul id="user_tenant_list"> <li class="title"><h4>{% trans "Available Tenants"%}</h4></li> - {% for tenant in tenants %} + {% for tenant in authorized_tenants %} {% if tenant.enabled %} <li><a href="{% url horizon:auth_switch tenant.id %}">{{tenant.name}}</a></li> {% endif %} diff --git a/openstack-dashboard/dashboard/views.py b/openstack-dashboard/dashboard/views.py index fb06c62d..cef66324 100644 --- a/openstack-dashboard/dashboard/views.py +++ b/openstack-dashboard/dashboard/views.py @@ -32,7 +32,7 @@ from horizon.views import auth as auth_views def user_home(user): if user.admin: return horizon.get_dashboard('syspanel').get_absolute_url() - return horizon.get_dashboard('dash').get_absolute_url() + return horizon.get_dashboard('nova').get_absolute_url() @vary.vary_on_cookie |