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
This commit is contained in:
Gabriel Hurley 2011-11-08 12:20:30 -08:00
parent 193e59f8ff
commit 67a979ae99
8 changed files with 87 additions and 122 deletions

View File

@ -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)

View File

@ -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', [])

View File

@ -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,

View File

@ -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)

View File

@ -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')

View File

@ -138,4 +138,3 @@ if DEBUG:
'debug_toolbar.middleware.DebugToolbarMiddleware',)
except ImportError:
logging.info('Running in debug mode without debug_toolbar.')

View File

@ -15,7 +15,7 @@
<a id="drop_btn" href="#">&nbsp;</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 %}

View File

@ -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