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:
parent
193e59f8ff
commit
67a979ae99
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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', [])
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -138,4 +138,3 @@ if DEBUG:
|
|||
'debug_toolbar.middleware.DebugToolbarMiddleware',)
|
||||
except ImportError:
|
||||
logging.info('Running in debug mode without debug_toolbar.')
|
||||
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue