Merge "Add K2K Auth Dropdown"
This commit is contained in:
commit
ea208c774f
|
@ -190,6 +190,9 @@ class KeystoneBackend(object):
|
|||
services_region=region_name)
|
||||
|
||||
if request is not None:
|
||||
# if no k2k providers exist then the function returns quickly
|
||||
utils.store_initial_k2k_session(auth_url, request, scoped_auth_ref,
|
||||
unscoped_auth_ref)
|
||||
request.session['unscoped_token'] = unscoped_token
|
||||
if domain_auth_ref:
|
||||
# check django session engine, if using cookies, this will not
|
||||
|
|
|
@ -13,8 +13,10 @@
|
|||
from openstack_auth.plugin.base import * # noqa
|
||||
from openstack_auth.plugin.password import * # noqa
|
||||
from openstack_auth.plugin.token import * # noqa
|
||||
from openstack_auth.plugin.k2k import * # noqa
|
||||
|
||||
|
||||
__all__ = ['BasePlugin',
|
||||
'PasswordPlugin',
|
||||
'TokenPlugin']
|
||||
'TokenPlugin',
|
||||
'K2KAuthPlugin']
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
# 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 logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from keystoneauth1.identity import v3 as v3_auth
|
||||
|
||||
from openstack_auth import exceptions
|
||||
from openstack_auth.plugin import base
|
||||
from openstack_auth import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
__all__ = ['K2KAuthPlugin']
|
||||
|
||||
|
||||
class K2KAuthPlugin(base.BasePlugin):
|
||||
|
||||
def get_plugin(self, service_provider=None, auth_url=None, plugins=[],
|
||||
**kwargs):
|
||||
"""Authenticate using keystone to keystone federation.
|
||||
|
||||
This plugin uses other v3 plugins to authenticate a user to a
|
||||
identity provider in order to authenticate the user to a service
|
||||
provider
|
||||
|
||||
:param service_provider: service provider ID
|
||||
:param auth_url: Keystone auth url
|
||||
:param plugins: list of openstack_auth plugins to check
|
||||
:returns Keystone2Keystone keystone auth plugin
|
||||
"""
|
||||
|
||||
# service_provider being None prevents infinite recursion
|
||||
if utils.get_keystone_version() < 3 or not service_provider:
|
||||
return None
|
||||
|
||||
keystone_idp_id = getattr(settings, 'KEYSTONE_PROVIDER_IDP_ID',
|
||||
'localkeystone')
|
||||
if service_provider == keystone_idp_id:
|
||||
return None
|
||||
|
||||
for plugin in plugins:
|
||||
unscoped_idp_auth = plugin.get_plugin(plugins=plugins,
|
||||
auth_url=auth_url, **kwargs)
|
||||
if unscoped_idp_auth:
|
||||
break
|
||||
else:
|
||||
LOG.debug('Could not find base authentication backend for '
|
||||
'K2K plugin with the provided credentials.')
|
||||
return None
|
||||
|
||||
idp_exception = None
|
||||
scoped_idp_auth = None
|
||||
unscoped_auth_ref = base.BasePlugin.get_access_info(
|
||||
self, unscoped_idp_auth)
|
||||
try:
|
||||
scoped_idp_auth, __ = self.get_project_scoped_auth(
|
||||
unscoped_idp_auth, unscoped_auth_ref)
|
||||
except exceptions.KeystoneAuthException as idp_excp:
|
||||
idp_exception = idp_excp
|
||||
|
||||
if not scoped_idp_auth or idp_exception:
|
||||
msg = 'Identity provider authentication Failed.'
|
||||
raise exceptions.KeystoneAuthException(msg)
|
||||
|
||||
session = utils.get_session()
|
||||
|
||||
if scoped_idp_auth.get_sp_auth_url(session, service_provider) is None:
|
||||
msg = _('Could not find service provider ID on Keystone.')
|
||||
raise exceptions.KeystoneAuthException(msg)
|
||||
|
||||
unscoped_auth = v3_auth.Keystone2Keystone(
|
||||
base_plugin=scoped_idp_auth,
|
||||
service_provider=service_provider)
|
||||
return unscoped_auth
|
||||
|
||||
def get_access_info(self, unscoped_auth):
|
||||
"""Get the access info object
|
||||
|
||||
We attempt to get the auth ref. If it fails and if the K2K auth plugin
|
||||
was being used then we will prepend a message saying that the error was
|
||||
on the service provider side.
|
||||
:param: unscoped_auth: Keystone auth plugin for unscoped user
|
||||
:returns: keystoneclient.access.AccessInfo object
|
||||
"""
|
||||
try:
|
||||
unscoped_auth_ref = base.BasePlugin.get_access_info(
|
||||
self, unscoped_auth)
|
||||
except exceptions.KeystoneAuthException as excp:
|
||||
msg = _('Service provider authentication failed. %s')
|
||||
raise exceptions.KeystoneAuthException(msg % str(excp))
|
||||
return unscoped_auth_ref
|
|
@ -55,7 +55,8 @@ class TestResponse(requests.Response):
|
|||
return self._text
|
||||
|
||||
|
||||
def generate_test_data(pki=False):
|
||||
def generate_test_data(pki=False, service_providers=False,
|
||||
endpoint='localhost'):
|
||||
'''Builds a set of test_data data as returned by Keystone V2.'''
|
||||
test_data = TestDataContainer()
|
||||
|
||||
|
@ -64,19 +65,19 @@ def generate_test_data(pki=False):
|
|||
'id': uuid.uuid4().hex,
|
||||
'endpoints': [
|
||||
{
|
||||
'url': 'http://admin.localhost:35357/v3',
|
||||
'url': 'http://admin.%s:35357/v3' % endpoint,
|
||||
'region': 'RegionOne',
|
||||
'interface': 'admin',
|
||||
'id': uuid.uuid4().hex,
|
||||
},
|
||||
{
|
||||
'url': 'http://internal.localhost:5000/v3',
|
||||
'url': 'http://internal.%s:5000/v3' % endpoint,
|
||||
'region': 'RegionOne',
|
||||
'interface': 'internal',
|
||||
'id': uuid.uuid4().hex
|
||||
},
|
||||
{
|
||||
'url': 'http://public.localhost:5000/v3',
|
||||
'url': 'http://public.%s:5000/v3' % endpoint,
|
||||
'region': 'RegionOne',
|
||||
'interface': 'public',
|
||||
'id': uuid.uuid4().hex
|
||||
|
@ -131,43 +132,43 @@ def generate_test_data(pki=False):
|
|||
'id': uuid.uuid4().hex,
|
||||
'endpoints': [
|
||||
{
|
||||
'url': ('http://nova-admin.localhost:8774/v2.0/%s'
|
||||
% (project_dict_1['id'])),
|
||||
'url': ('http://nova-admin.%s:8774/v2.0/%s'
|
||||
% (endpoint, project_dict_1['id'])),
|
||||
'region': 'RegionOne',
|
||||
'interface': 'admin',
|
||||
'id': uuid.uuid4().hex,
|
||||
},
|
||||
{
|
||||
'url': ('http://nova-internal.localhost:8774/v2.0/%s'
|
||||
% (project_dict_1['id'])),
|
||||
'url': ('http://nova-internal.%s:8774/v2.0/%s'
|
||||
% (endpoint, project_dict_1['id'])),
|
||||
'region': 'RegionOne',
|
||||
'interface': 'internal',
|
||||
'id': uuid.uuid4().hex
|
||||
},
|
||||
{
|
||||
'url': ('http://nova-public.localhost:8774/v2.0/%s'
|
||||
% (project_dict_1['id'])),
|
||||
'url': ('http://nova-public.%s:8774/v2.0/%s'
|
||||
% (endpoint, project_dict_1['id'])),
|
||||
'region': 'RegionOne',
|
||||
'interface': 'public',
|
||||
'id': uuid.uuid4().hex
|
||||
},
|
||||
{
|
||||
'url': ('http://nova2-admin.localhost:8774/v2.0/%s'
|
||||
% (project_dict_1['id'])),
|
||||
'url': ('http://nova2-admin.%s:8774/v2.0/%s'
|
||||
% (endpoint, project_dict_1['id'])),
|
||||
'region': 'RegionTwo',
|
||||
'interface': 'admin',
|
||||
'id': uuid.uuid4().hex,
|
||||
},
|
||||
{
|
||||
'url': ('http://nova2-internal.localhost:8774/v2.0/%s'
|
||||
% (project_dict_1['id'])),
|
||||
'url': ('http://nova2-internal.%s:8774/v2.0/%s'
|
||||
% (endpoint, project_dict_1['id'])),
|
||||
'region': 'RegionTwo',
|
||||
'interface': 'internal',
|
||||
'id': uuid.uuid4().hex
|
||||
},
|
||||
{
|
||||
'url': ('http://nova2-public.localhost:8774/v2.0/%s'
|
||||
% (project_dict_1['id'])),
|
||||
'url': ('http://nova2-public.%s:8774/v2.0/%s'
|
||||
% (endpoint, project_dict_1['id'])),
|
||||
'region': 'RegionTwo',
|
||||
'interface': 'public',
|
||||
'id': uuid.uuid4().hex
|
||||
|
@ -218,6 +219,19 @@ def generate_test_data(pki=False):
|
|||
}
|
||||
}
|
||||
|
||||
sp_list = None
|
||||
if service_providers:
|
||||
test_data.sp_auth_url = 'http://service_provider_endp:5000/v3'
|
||||
test_data.service_provider_id = 'k2kserviceprovider'
|
||||
# The access info for the identity provider
|
||||
# should return a list of service providers
|
||||
sp_list = [
|
||||
{'auth_url': test_data.sp_auth_url,
|
||||
'id': test_data.service_provider_id,
|
||||
'sp_url': 'https://k2kserviceprovider/sp_url'}
|
||||
]
|
||||
scoped_token_dict['token']['service_providers'] = sp_list
|
||||
|
||||
test_data.scoped_access_info = access.create(
|
||||
resp=auth_response,
|
||||
body=scoped_token_dict
|
||||
|
@ -264,6 +278,9 @@ def generate_test_data(pki=False):
|
|||
}
|
||||
}
|
||||
|
||||
if service_providers:
|
||||
unscoped_token_dict['token']['service_providers'] = sp_list
|
||||
|
||||
test_data.unscoped_access_info = access.create(
|
||||
resp=auth_response,
|
||||
body=unscoped_token_dict
|
||||
|
|
|
@ -70,3 +70,5 @@ TEMPLATES = [
|
|||
'APP_DIRS': True,
|
||||
},
|
||||
]
|
||||
|
||||
AUTH_USER_MODEL = 'openstack_auth.User'
|
||||
|
|
|
@ -76,15 +76,18 @@ class OpenStackAuthTestsMixin(object):
|
|||
plugin.get_access(mox.IsA(session.Session)).AndRaise(exc)
|
||||
|
||||
def _mock_scoped_client_for_tenant(self, auth_ref, tenant_id, url=None,
|
||||
client=True):
|
||||
client=True, token=None):
|
||||
if url is None:
|
||||
url = settings.OPENSTACK_KEYSTONE_URL
|
||||
|
||||
if not token:
|
||||
token = self.data.unscoped_access_info.auth_token
|
||||
|
||||
plugin = self._create_token_auth(
|
||||
tenant_id,
|
||||
token=self.data.unscoped_access_info.auth_token,
|
||||
token=token,
|
||||
url=url)
|
||||
|
||||
self.scoped_token_auth = plugin
|
||||
plugin.get_access(mox.IsA(session.Session)).AndReturn(auth_ref)
|
||||
if client:
|
||||
return self.ks_client_module.Client(
|
||||
|
@ -98,6 +101,33 @@ class OpenStackAuthTestsMixin(object):
|
|||
'username': user.name}
|
||||
|
||||
|
||||
class OpenStackAuthFederatedTestsMixin(object):
|
||||
"""Common functions for federation"""
|
||||
def _mock_unscoped_federated_list_projects(self, client, projects):
|
||||
client.federation = self.mox.CreateMockAnything()
|
||||
client.federation.projects = self.mox.CreateMockAnything()
|
||||
client.federation.projects.list().AndReturn(projects)
|
||||
|
||||
def _mock_unscoped_token_client(self, unscoped, auth_url=None,
|
||||
client=True):
|
||||
if not auth_url:
|
||||
auth_url = settings.OPENSTACK_KEYSTONE_URL
|
||||
plugin = self._create_token_auth(
|
||||
None,
|
||||
token=unscoped.auth_token,
|
||||
url=auth_url)
|
||||
plugin.get_access(mox.IsA(session.Session)).AndReturn(unscoped)
|
||||
plugin.auth_url = auth_url
|
||||
if client:
|
||||
return self.ks_client_module.Client(
|
||||
session=mox.IsA(session.Session),
|
||||
auth=plugin)
|
||||
|
||||
def _mock_federated_client_list_projects(self, unscoped, projects):
|
||||
client = self._mock_unscoped_token_client(unscoped)
|
||||
self._mock_unscoped_federated_list_projects(client, projects)
|
||||
|
||||
|
||||
class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -431,7 +461,9 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
|
|||
self.assertEqual(tenant_list, expected_tenants)
|
||||
|
||||
|
||||
class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
|
||||
class OpenStackAuthTestsV3(OpenStackAuthTestsMixin,
|
||||
OpenStackAuthFederatedTestsMixin,
|
||||
test.TestCase):
|
||||
|
||||
def _mock_unscoped_client_list_projects(self, user, projects):
|
||||
client = self._mock_unscoped_client(user)
|
||||
|
@ -532,6 +564,7 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
|
|||
self.mox.StubOutClassWithMocks(v3_auth, 'Token')
|
||||
self.mox.StubOutClassWithMocks(v3_auth, 'Password')
|
||||
self.mox.StubOutClassWithMocks(client_v3, 'Client')
|
||||
self.mox.StubOutClassWithMocks(v3_auth, 'Keystone2Keystone')
|
||||
|
||||
def test_login(self):
|
||||
projects = [self.data.project_one, self.data.project_two]
|
||||
|
@ -774,6 +807,234 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
|
|||
def test_switch_region_with_next(self, next=None):
|
||||
self.test_switch_region(next='/next_url')
|
||||
|
||||
def test_switch_keystone_provider_remote_fail(self):
|
||||
auth_url = settings.OPENSTACK_KEYSTONE_URL
|
||||
target_provider = 'k2kserviceprovider'
|
||||
self.data = data_v3.generate_test_data(service_providers=True)
|
||||
self.sp_data = data_v3.generate_test_data(endpoint='http://sp2')
|
||||
projects = [self.data.project_one, self.data.project_two]
|
||||
user = self.data.user
|
||||
unscoped = self.data.unscoped_access_info
|
||||
form_data = self.get_form_data(user)
|
||||
|
||||
# mock authenticate
|
||||
self._mock_unscoped_and_domain_list_projects(user, projects)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id)
|
||||
|
||||
# mock switch
|
||||
plugin = v3_auth.Token(auth_url=auth_url,
|
||||
token=unscoped.auth_token,
|
||||
project_id=None,
|
||||
reauthenticate=False)
|
||||
plugin.get_access(mox.IsA(session.Session)
|
||||
).AndReturn(self.data.unscoped_access_info)
|
||||
plugin.auth_url = auth_url
|
||||
client = self.ks_client_module.Client(session=mox.IsA(session.Session),
|
||||
auth=plugin)
|
||||
|
||||
self._mock_unscoped_list_projects(client, user, projects)
|
||||
plugin = self._create_token_auth(
|
||||
self.data.project_one.id,
|
||||
token=self.data.unscoped_access_info.auth_token,
|
||||
url=settings.OPENSTACK_KEYSTONE_URL)
|
||||
plugin.get_access(mox.IsA(session.Session)).AndReturn(
|
||||
settings.OPENSTACK_KEYSTONE_URL)
|
||||
plugin.get_sp_auth_url(
|
||||
mox.IsA(session.Session), target_provider
|
||||
).AndReturn('https://k2kserviceprovider/sp_url')
|
||||
|
||||
# let the K2K plugin fail when logging in
|
||||
plugin = v3_auth.Keystone2Keystone(
|
||||
base_plugin=plugin, service_provider=target_provider)
|
||||
plugin.get_access(mox.IsA(session.Session)).AndRaise(
|
||||
keystone_exceptions.AuthorizationFailure)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# Log in
|
||||
url = reverse('login')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.post(url, form_data)
|
||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
# Switch
|
||||
url = reverse('switch_keystone_provider', args=[target_provider])
|
||||
form_data['keystone_provider'] = target_provider
|
||||
response = self.client.get(url, form_data, follow=True)
|
||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
# Assert that provider has not changed because of failure
|
||||
self.assertEqual(self.client.session['keystone_provider_id'],
|
||||
'localkeystone')
|
||||
# These should never change
|
||||
self.assertEqual(self.client.session['k2k_base_unscoped_token'],
|
||||
unscoped.auth_token)
|
||||
self.assertEqual(self.client.session['k2k_auth_url'], auth_url)
|
||||
|
||||
def test_switch_keystone_provider_remote(self):
|
||||
auth_url = settings.OPENSTACK_KEYSTONE_URL
|
||||
target_provider = 'k2kserviceprovider'
|
||||
self.data = data_v3.generate_test_data(service_providers=True)
|
||||
self.sp_data = data_v3.generate_test_data(endpoint='http://sp2')
|
||||
projects = [self.data.project_one, self.data.project_two]
|
||||
user = self.data.user
|
||||
unscoped = self.data.unscoped_access_info
|
||||
form_data = self.get_form_data(user)
|
||||
|
||||
# mock authenticate
|
||||
self._mock_unscoped_and_domain_list_projects(user, projects)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id)
|
||||
|
||||
# mock switch
|
||||
plugin = v3_auth.Token(auth_url=auth_url,
|
||||
token=unscoped.auth_token,
|
||||
project_id=None,
|
||||
reauthenticate=False)
|
||||
plugin.get_access(mox.IsA(session.Session)).AndReturn(
|
||||
self.data.unscoped_access_info)
|
||||
|
||||
plugin.auth_url = auth_url
|
||||
client = self.ks_client_module.Client(session=mox.IsA(session.Session),
|
||||
auth=plugin)
|
||||
|
||||
self._mock_unscoped_list_projects(client, user, projects)
|
||||
plugin = self._create_token_auth(
|
||||
self.data.project_one.id,
|
||||
token=self.data.unscoped_access_info.auth_token,
|
||||
url=settings.OPENSTACK_KEYSTONE_URL)
|
||||
plugin.get_access(mox.IsA(session.Session)).AndReturn(
|
||||
settings.OPENSTACK_KEYSTONE_URL)
|
||||
|
||||
plugin.get_sp_auth_url(
|
||||
mox.IsA(session.Session), target_provider
|
||||
).AndReturn('https://k2kserviceprovider/sp_url')
|
||||
plugin = v3_auth.Keystone2Keystone(base_plugin=plugin,
|
||||
service_provider=target_provider)
|
||||
plugin.get_access(mox.IsA(session.Session)). \
|
||||
AndReturn(self.sp_data.unscoped_access_info)
|
||||
plugin.auth_url = 'http://service_provider_endp:5000/v3'
|
||||
|
||||
# mock authenticate for service provider
|
||||
sp_projects = [self.sp_data.project_one, self.sp_data.project_two]
|
||||
sp_unscoped = self.sp_data.federated_unscoped_access_info
|
||||
client = self._mock_unscoped_token_client(sp_unscoped, plugin.auth_url)
|
||||
self._mock_unscoped_federated_list_projects(client, sp_projects)
|
||||
self._mock_scoped_client_for_tenant(sp_unscoped,
|
||||
self.sp_data.project_one.id,
|
||||
url=plugin.auth_url,
|
||||
token=sp_unscoped.auth_token)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# Log in
|
||||
url = reverse('login')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.post(url, form_data)
|
||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
# Switch
|
||||
url = reverse('switch_keystone_provider', args=[target_provider])
|
||||
form_data['keystone_provider'] = target_provider
|
||||
response = self.client.get(url, form_data, follow=True)
|
||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
# Assert keystone provider has changed
|
||||
self.assertEqual(self.client.session['keystone_provider_id'],
|
||||
target_provider)
|
||||
# These should not change
|
||||
self.assertEqual(self.client.session['k2k_base_unscoped_token'],
|
||||
unscoped.auth_token)
|
||||
self.assertEqual(self.client.session['k2k_auth_url'], auth_url)
|
||||
|
||||
def test_switch_keystone_provider_local(self):
|
||||
auth_url = settings.OPENSTACK_KEYSTONE_URL
|
||||
self.data = data_v3.generate_test_data(service_providers=True)
|
||||
keystone_provider = 'localkeystone'
|
||||
projects = [self.data.project_one, self.data.project_two]
|
||||
user = self.data.user
|
||||
unscoped = self.data.unscoped_access_info
|
||||
form_data = self.get_form_data(user)
|
||||
|
||||
# mock authenticate
|
||||
self._mock_unscoped_and_domain_list_projects(user, projects)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id)
|
||||
self._mock_unscoped_token_client(unscoped,
|
||||
auth_url=auth_url,
|
||||
client=False)
|
||||
client = self._mock_unscoped_token_client(unscoped, auth_url)
|
||||
self._mock_unscoped_list_projects(client, user, projects)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# Log in
|
||||
url = reverse('login')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.post(url, form_data)
|
||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
# Switch
|
||||
url = reverse('switch_keystone_provider', args=[keystone_provider])
|
||||
form_data['keystone_provider'] = keystone_provider
|
||||
response = self.client.get(url, form_data, follow=True)
|
||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
# Assert nothing has changed since we are going from local to local
|
||||
self.assertEqual(self.client.session['keystone_provider_id'],
|
||||
keystone_provider)
|
||||
self.assertEqual(self.client.session['k2k_base_unscoped_token'],
|
||||
unscoped.auth_token)
|
||||
self.assertEqual(self.client.session['k2k_auth_url'], auth_url)
|
||||
|
||||
def test_switch_keystone_provider_local_fail(self):
|
||||
auth_url = settings.OPENSTACK_KEYSTONE_URL
|
||||
self.data = data_v3.generate_test_data(service_providers=True)
|
||||
keystone_provider = 'localkeystone'
|
||||
projects = [self.data.project_one, self.data.project_two]
|
||||
user = self.data.user
|
||||
unscoped = self.data.unscoped_access_info
|
||||
form_data = self.get_form_data(user)
|
||||
|
||||
# mock authenticate
|
||||
self._mock_unscoped_and_domain_list_projects(user, projects)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id)
|
||||
|
||||
# Let using the base token for logging in fail
|
||||
plugin = v3_auth.Token(auth_url=auth_url,
|
||||
token=unscoped.auth_token,
|
||||
project_id=None,
|
||||
reauthenticate=False)
|
||||
plugin.get_access(mox.IsA(session.Session)). \
|
||||
AndRaise(keystone_exceptions.AuthorizationFailure)
|
||||
plugin.auth_url = auth_url
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# Log in
|
||||
url = reverse('login')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.post(url, form_data)
|
||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
# Switch
|
||||
url = reverse('switch_keystone_provider', args=[keystone_provider])
|
||||
form_data['keystone_provider'] = keystone_provider
|
||||
response = self.client.get(url, form_data, follow=True)
|
||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(self.client.session['keystone_provider_id'],
|
||||
keystone_provider)
|
||||
self.assertEqual(self.client.session['k2k_base_unscoped_token'],
|
||||
unscoped.auth_token)
|
||||
self.assertEqual(self.client.session['k2k_auth_url'], auth_url)
|
||||
|
||||
def test_tenant_sorting(self):
|
||||
projects = [self.data.project_two, self.data.project_one]
|
||||
expected_projects = [self.data.project_one, self.data.project_two]
|
||||
|
@ -791,7 +1052,9 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
|
|||
self.assertEqual(project_list, expected_projects)
|
||||
|
||||
|
||||
class OpenStackAuthTestsWebSSO(OpenStackAuthTestsMixin, test.TestCase):
|
||||
class OpenStackAuthTestsWebSSO(OpenStackAuthTestsMixin,
|
||||
OpenStackAuthFederatedTestsMixin,
|
||||
test.TestCase):
|
||||
|
||||
def _create_token_auth(self, project_id=None, token=None, url=None):
|
||||
if not token:
|
||||
|
@ -805,26 +1068,6 @@ class OpenStackAuthTestsWebSSO(OpenStackAuthTestsMixin, test.TestCase):
|
|||
project_id=project_id,
|
||||
reauthenticate=False)
|
||||
|
||||
def _mock_unscoped_client(self, unscoped):
|
||||
plugin = self._create_token_auth(
|
||||
None,
|
||||
token=unscoped.auth_token,
|
||||
url=settings.OPENSTACK_KEYSTONE_URL)
|
||||
plugin.get_access(mox.IsA(session.Session)).AndReturn(unscoped)
|
||||
plugin.auth_url = settings.OPENSTACK_KEYSTONE_URL
|
||||
|
||||
return self.ks_client_module.Client(session=mox.IsA(session.Session),
|
||||
auth=plugin)
|
||||
|
||||
def _mock_unscoped_federated_list_projects(self, client, projects):
|
||||
client.federation = self.mox.CreateMockAnything()
|
||||
client.federation.projects = self.mox.CreateMockAnything()
|
||||
client.federation.projects.list().AndReturn(projects)
|
||||
|
||||
def _mock_unscoped_client_list_projects(self, unscoped, projects):
|
||||
client = self._mock_unscoped_client(unscoped)
|
||||
self._mock_unscoped_federated_list_projects(client, projects)
|
||||
|
||||
def setUp(self):
|
||||
super(OpenStackAuthTestsWebSSO, self).setUp()
|
||||
|
||||
|
@ -908,7 +1151,7 @@ class OpenStackAuthTestsWebSSO(OpenStackAuthTestsMixin, test.TestCase):
|
|||
token = unscoped.auth_token
|
||||
|
||||
form_data = {'token': token}
|
||||
self._mock_unscoped_client_list_projects(unscoped, projects)
|
||||
self._mock_federated_client_list_projects(unscoped, projects)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
@ -927,7 +1170,7 @@ class OpenStackAuthTestsWebSSO(OpenStackAuthTestsMixin, test.TestCase):
|
|||
token = unscoped.auth_token
|
||||
|
||||
form_data = {'token': token}
|
||||
self._mock_unscoped_client_list_projects(unscoped, projects)
|
||||
self._mock_federated_client_list_projects(unscoped, projects)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
|
|
@ -26,7 +26,10 @@ urlpatterns = [
|
|||
name='switch_tenants'),
|
||||
url(r'^switch_services_region/(?P<region_name>[^/]+)/$',
|
||||
views.switch_region,
|
||||
name='switch_services_region')
|
||||
name='switch_services_region'),
|
||||
url(r'^switch_keystone_provider/(?P<keystone_provider>[^/]+)/$',
|
||||
views.switch_keystone_provider,
|
||||
name='switch_keystone_provider')
|
||||
]
|
||||
|
||||
if utils.is_websso_enabled():
|
||||
|
|
|
@ -498,3 +498,54 @@ def get_client_ip(request):
|
|||
request.META.get('REMOTE_ADDR')
|
||||
)
|
||||
return request.META.get('REMOTE_ADDR')
|
||||
|
||||
|
||||
def store_initial_k2k_session(auth_url, request, scoped_auth_ref,
|
||||
unscoped_auth_ref):
|
||||
"""Stores session variables if there are k2k service providers
|
||||
|
||||
This stores variables related to Keystone2Keystone federation. This
|
||||
function gets skipped if there are no Keystone service providers.
|
||||
An unscoped token to the identity provider keystone gets stored
|
||||
so that it can be used to do federated login into the service
|
||||
providers when switching keystone providers.
|
||||
The settings file can be configured to set the display name
|
||||
of the local (identity provider) keystone by setting
|
||||
KEYSTONE_PROVIDER_IDP_NAME. The KEYSTONE_PROVIDER_IDP_ID settings
|
||||
variable is used for comparison against the service providers.
|
||||
It should not conflict with any of the service provider ids.
|
||||
|
||||
:param auth_url: base token auth url
|
||||
:param request: Django http request object
|
||||
:param scoped_auth_ref: Scoped Keystone access info object
|
||||
:param unscoped_auth_ref: Unscoped Keystone access info object
|
||||
"""
|
||||
keystone_provider_id = request.session.get('keystone_provider_id', None)
|
||||
if keystone_provider_id:
|
||||
return None
|
||||
|
||||
providers = getattr(scoped_auth_ref, 'service_providers', None)
|
||||
if providers:
|
||||
providers = getattr(providers, '_service_providers', None)
|
||||
|
||||
if providers:
|
||||
keystone_idp_name = getattr(settings, 'KEYSTONE_PROVIDER_IDP_NAME',
|
||||
'Local Keystone')
|
||||
keystone_idp_id = getattr(
|
||||
settings, 'KEYSTONE_PROVIDER_IDP_ID', 'localkeystone')
|
||||
keystone_identity_provider = {'name': keystone_idp_name,
|
||||
'id': keystone_idp_id}
|
||||
# (edtubill) We will use the IDs as the display names
|
||||
# We may want to be able to set display names in the future.
|
||||
keystone_providers = [
|
||||
{'name': provider_id, 'id': provider_id}
|
||||
for provider_id in providers]
|
||||
|
||||
keystone_providers.append(keystone_identity_provider)
|
||||
|
||||
# We treat the Keystone idp ID as None
|
||||
request.session['keystone_provider_id'] = keystone_idp_id
|
||||
request.session['keystone_providers'] = keystone_providers
|
||||
request.session['k2k_base_unscoped_token'] =\
|
||||
unscoped_auth_ref.auth_token
|
||||
request.session['k2k_auth_url'] = auth_url
|
||||
|
|
|
@ -31,6 +31,8 @@ import six
|
|||
|
||||
from openstack_auth import exceptions
|
||||
from openstack_auth import forms
|
||||
from openstack_auth import plugin
|
||||
|
||||
# This is historic and is added back in to not break older versions of
|
||||
# Horizon, fix to Horizon to remove this requirement was committed in
|
||||
# Juno
|
||||
|
@ -241,3 +243,75 @@ def switch_region(request, region_name,
|
|||
utils.set_response_cookie(response, 'services_region',
|
||||
request.session['services_region'])
|
||||
return response
|
||||
|
||||
|
||||
@login_required
|
||||
def switch_keystone_provider(request, keystone_provider=None,
|
||||
redirect_field_name=auth.REDIRECT_FIELD_NAME):
|
||||
"""Switches the user's keystone provider using K2K Federation
|
||||
|
||||
If keystone_provider is given then we switch the user to
|
||||
the keystone provider using K2K federation. Otherwise if keystone_provider
|
||||
is None then we switch the user back to the Identity Provider Keystone
|
||||
which a non federated token auth will be used.
|
||||
"""
|
||||
base_token = request.session.get('k2k_base_unscoped_token', None)
|
||||
k2k_auth_url = request.session.get('k2k_auth_url', None)
|
||||
keystone_providers = request.session.get('keystone_providers', None)
|
||||
|
||||
if not base_token or not k2k_auth_url:
|
||||
msg = _('K2K Federation not setup for this session')
|
||||
raise exceptions.KeystoneAuthException(msg)
|
||||
|
||||
redirect_to = request.GET.get(redirect_field_name, '')
|
||||
if not is_safe_url(url=redirect_to, host=request.get_host()):
|
||||
redirect_to = settings.LOGIN_REDIRECT_URL
|
||||
|
||||
unscoped_auth_ref = None
|
||||
keystone_idp_id = getattr(
|
||||
settings, 'KEYSTONE_PROVIDER_IDP_ID', 'localkeystone')
|
||||
|
||||
if keystone_provider == keystone_idp_id:
|
||||
current_plugin = plugin.TokenPlugin()
|
||||
unscoped_auth = current_plugin.get_plugin(auth_url=k2k_auth_url,
|
||||
token=base_token)
|
||||
else:
|
||||
# Switch to service provider using K2K federation
|
||||
plugins = [plugin.TokenPlugin()]
|
||||
current_plugin = plugin.K2KAuthPlugin()
|
||||
|
||||
unscoped_auth = current_plugin.get_plugin(
|
||||
auth_url=k2k_auth_url, service_provider=keystone_provider,
|
||||
plugins=plugins, token=base_token)
|
||||
|
||||
try:
|
||||
# Switch to identity provider using token auth
|
||||
unscoped_auth_ref = current_plugin.get_access_info(unscoped_auth)
|
||||
except exceptions.KeystoneAuthException as exc:
|
||||
msg = 'Switching to Keystone Provider %s has failed. %s' \
|
||||
% (keystone_provider, (six.text_type(exc)))
|
||||
messages.error(request, msg)
|
||||
|
||||
if unscoped_auth_ref:
|
||||
try:
|
||||
request.user = auth.authenticate(
|
||||
request=request, auth_url=unscoped_auth.auth_url,
|
||||
token=unscoped_auth_ref.auth_token)
|
||||
except exceptions.KeystoneAuthException as exc:
|
||||
msg = 'Keystone provider switch failed: %s' % six.text_type(exc)
|
||||
res = django_http.HttpResponseRedirect(settings.LOGIN_URL)
|
||||
res.set_cookie('logout_reason', msg, max_age=10)
|
||||
return res
|
||||
auth.login(request, request.user)
|
||||
auth_user.set_session_from_user(request, request.user)
|
||||
request.session['keystone_provider_id'] = keystone_provider
|
||||
request.session['keystone_providers'] = keystone_providers
|
||||
request.session['k2k_base_unscoped_token'] = base_token
|
||||
request.session['k2k_auth_url'] = k2k_auth_url
|
||||
message = (
|
||||
_('Switch to Keystone Provider "%(keystone_provider)s"'
|
||||
'successful.') % {'keystone_provider': keystone_provider})
|
||||
messages.success(request, message)
|
||||
|
||||
response = shortcuts.redirect(redirect_to)
|
||||
return response
|
||||
|
|
Loading…
Reference in New Issue