summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2017-01-19 22:35:20 +0000
committerGerrit Code Review <review@openstack.org>2017-01-19 22:35:20 +0000
commitea208c774f6707d50b72a72c14d76a5ed0177bc9 (patch)
treeabf4752d54d161008fae0338f9287ebd9f89ab0f
parent75f274da9491f4dcb729640f46a3dabf26a18562 (diff)
parentf0c7f27af61c2d638b2b75e6e58e5f60554c64ff (diff)
downloaddjango_openstack_auth-ea208c774f6707d50b72a72c14d76a5ed0177bc9.tar.gz
Merge "Add K2K Auth Dropdown"3.1.0
-rw-r--r--openstack_auth/backend.py3
-rw-r--r--openstack_auth/plugin/__init__.py4
-rw-r--r--openstack_auth/plugin/k2k.py104
-rw-r--r--openstack_auth/tests/data_v3.py49
-rw-r--r--openstack_auth/tests/settings.py2
-rw-r--r--openstack_auth/tests/tests.py297
-rw-r--r--openstack_auth/urls.py5
-rw-r--r--openstack_auth/utils.py51
-rw-r--r--openstack_auth/views.py74
9 files changed, 544 insertions, 45 deletions
diff --git a/openstack_auth/backend.py b/openstack_auth/backend.py
index 9ada58d..c8acd21 100644
--- a/openstack_auth/backend.py
+++ b/openstack_auth/backend.py
@@ -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
diff --git a/openstack_auth/plugin/__init__.py b/openstack_auth/plugin/__init__.py
index 64caeac..c664bed 100644
--- a/openstack_auth/plugin/__init__.py
+++ b/openstack_auth/plugin/__init__.py
@@ -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']
diff --git a/openstack_auth/plugin/k2k.py b/openstack_auth/plugin/k2k.py
new file mode 100644
index 0000000..03a85f0
--- /dev/null
+++ b/openstack_auth/plugin/k2k.py
@@ -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
diff --git a/openstack_auth/tests/data_v3.py b/openstack_auth/tests/data_v3.py
index 7053091..1d2ef82 100644
--- a/openstack_auth/tests/data_v3.py
+++ b/openstack_auth/tests/data_v3.py
@@ -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
diff --git a/openstack_auth/tests/settings.py b/openstack_auth/tests/settings.py
index 17f59e6..9590d51 100644
--- a/openstack_auth/tests/settings.py
+++ b/openstack_auth/tests/settings.py
@@ -70,3 +70,5 @@ TEMPLATES = [
'APP_DIRS': True,
},
]
+
+AUTH_USER_MODEL = 'openstack_auth.User'
diff --git a/openstack_auth/tests/tests.py b/openstack_auth/tests/tests.py
index c58aacc..6016d73 100644
--- a/openstack_auth/tests/tests.py
+++ b/openstack_auth/tests/tests.py
@@ -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()
diff --git a/openstack_auth/urls.py b/openstack_auth/urls.py
index a7d9dac..12d7cfb 100644
--- a/openstack_auth/urls.py
+++ b/openstack_auth/urls.py
@@ -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():
diff --git a/openstack_auth/utils.py b/openstack_auth/utils.py
index c050726..0d5ee11 100644
--- a/openstack_auth/utils.py
+++ b/openstack_auth/utils.py
@@ -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
diff --git a/openstack_auth/views.py b/openstack_auth/views.py
index 8831403..ce51fa6 100644
--- a/openstack_auth/views.py
+++ b/openstack_auth/views.py
@@ -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