From 01e0abc17d5cf451e0e7556a90ad6fb2de2c78a5 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Sun, 30 Nov 2014 21:24:00 +1000 Subject: [PATCH] Use keystone auth plugins Convert the existing DOA to using authentication plugins keeping as close to the current code structure as possible. This will allow us to add additional authentication plugins later and to start changing horizon to use these plugins when talking to other services rather than hacking tokens into the clients. Change-Id: Idd9ad5044e998a6c514f6161f5159b44391a0849 --- openstack_auth/backend.py | 103 ++++++------- openstack_auth/tests/tests.py | 272 +++++++++++++++++++--------------- openstack_auth/user.py | 8 +- openstack_auth/utils.py | 72 +++++++-- openstack_auth/views.py | 43 +++--- 5 files changed, 281 insertions(+), 217 deletions(-) diff --git a/openstack_auth/backend.py b/openstack_auth/backend.py index 9ad57df5..f7ae5242 100644 --- a/openstack_auth/backend.py +++ b/openstack_auth/backend.py @@ -69,35 +69,22 @@ class KeystoneBackend(object): """Authenticates a user via the Keystone Identity API.""" LOG.debug('Beginning user authentication for user "%s".' % username) - insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) - ca_cert = getattr(settings, "OPENSTACK_SSL_CACERT", None) - endpoint_type = getattr( - settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL') + interface = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'public') if auth_url is None: auth_url = settings.OPENSTACK_KEYSTONE_URL - # keystone client v3 does not support logging in on the v2 url any more - if utils.get_keystone_version() >= 3: - if utils.has_in_url_path(auth_url, "/v2.0"): - LOG.warning("The settings.py file points to a v2.0 keystone " - "endpoint, but v3 is specified as the API version " - "to use. Using v3 endpoint for authentication.") - auth_url = utils.url_path_replace(auth_url, "/v2.0", "/v3", 1) + session = utils.get_session() + keystone_client_class = utils.get_keystone_client().Client + + auth_url = utils.fix_auth_url_version(auth_url) + unscoped_auth = utils.get_password_auth_plugin(auth_url, + username, + password, + user_domain_name) - keystone_client = utils.get_keystone_client() try: - client = keystone_client.Client( - user_domain_name=user_domain_name, - username=username, - password=password, - auth_url=auth_url, - insecure=insecure, - cacert=ca_cert, - debug=settings.DEBUG) - - unscoped_auth_ref = client.auth_ref - unscoped_token = auth_user.Token(auth_ref=unscoped_auth_ref) + unscoped_auth_ref = unscoped_auth.get_access(session) except (keystone_exceptions.Unauthorized, keystone_exceptions.Forbidden, keystone_exceptions.NotFound) as exc: @@ -114,22 +101,16 @@ class KeystoneBackend(object): # Check expiry for our unscoped auth ref. self.check_auth_expiry(unscoped_auth_ref) - # Check if token is automatically scoped to default_project - # grab the project from this token, to use as a default - # if no recent_project is found in the cookie - token_proj_id = None - if unscoped_auth_ref.project_scoped: - token_proj_id = unscoped_auth_ref.get('project', - {}).get('id') + unscoped_client = keystone_client_class(session=session, + auth=unscoped_auth) # We list all the user's projects try: - if utils.get_keystone_version() < 3: - projects = client.tenants.list() - else: - client.management_url = auth_url - projects = client.projects.list( + if utils.get_keystone_version() >= 3: + projects = unscoped_client.projects.list( user=unscoped_auth_ref.user_id) + else: + projects = unscoped_client.tenants.list() except (keystone_exceptions.ClientException, keystone_exceptions.AuthorizationFailure) as exc: msg = _('Unable to retrieve authorized projects.') @@ -143,51 +124,55 @@ class KeystoneBackend(object): # the recent project id a user might have set in a cookie recent_project = None if request: + # Check if token is automatically scoped to default_project + # grab the project from this token, to use as a default + # if no recent_project is found in the cookie recent_project = request.COOKIES.get('recent_project', - token_proj_id) + unscoped_auth_ref.project_id) # if a most recent project was found, try using it first - for pos, project in enumerate(projects): - if project.id == recent_project: - # move recent project to the beginning - projects.pop(pos) - projects.insert(0, project) - break + if recent_project: + for pos, project in enumerate(projects): + if project.id == recent_project: + # move recent project to the beginning + projects.pop(pos) + projects.insert(0, project) + break for project in projects: + token = unscoped_auth_ref.auth_token + scoped_auth = utils.get_token_auth_plugin(auth_url, + token=token, + project_id=project.id) + try: - client = keystone_client.Client( - tenant_id=project.id, - token=unscoped_auth_ref.auth_token, - auth_url=auth_url, - insecure=insecure, - cacert=ca_cert, - debug=settings.DEBUG) - auth_ref = client.auth_ref - break + scoped_auth_ref = scoped_auth.get_access(session) except (keystone_exceptions.ClientException, keystone_exceptions.AuthorizationFailure): - auth_ref = None - - if auth_ref is None: + pass + else: + break + else: msg = _("Unable to authenticate to any available projects.") raise exceptions.KeystoneAuthException(msg) # Check expiry for our new scoped token. - self.check_auth_expiry(auth_ref) + self.check_auth_expiry(scoped_auth_ref) # If we made it here we succeeded. Create our User! user = auth_user.create_user_from_token( request, - auth_user.Token(auth_ref), - client.service_catalog.url_for(endpoint_type=endpoint_type)) + auth_user.Token(scoped_auth_ref), + scoped_auth_ref.service_catalog.url_for(endpoint_type=interface)) if request is not None: - request.session['unscoped_token'] = unscoped_token.id + request.session['unscoped_token'] = unscoped_auth_ref.auth_token request.user = user + scoped_client = keystone_client_class(session=session, + auth=scoped_auth) # Support client caching to save on auth calls. - setattr(request, KEYSTONE_CLIENT_ATTR, client) + setattr(request, KEYSTONE_CLIENT_ATTR, scoped_client) LOG.debug('Authentication completed for user "%s".' % username) return user diff --git a/openstack_auth/tests/tests.py b/openstack_auth/tests/tests.py index 1d6d0bab..4d5ce71d 100644 --- a/openstack_auth/tests/tests.py +++ b/openstack_auth/tests/tests.py @@ -15,7 +15,11 @@ from django.conf import settings from django.contrib import auth from django.core.urlresolvers import reverse from django import test +from keystoneclient.auth.identity import v2 as auth_v2 +from keystoneclient.auth.identity import v3 as auth_v3 +from keystoneclient.auth import token_endpoint from keystoneclient import exceptions as keystone_exceptions +from keystoneclient import session from keystoneclient.v2_0 import client as client_v2 from keystoneclient.v3 import client as client_v3 from mox3 import mox @@ -39,64 +43,44 @@ class OpenStackAuthTestsMixin(object): ('admin', {'interface': 'adminURL'}) ] - def tearDown(self): - self.mox.UnsetStubs() - self.mox.VerifyAll() - def _mock_unscoped_client(self, user): - self.mox.StubOutWithMock(self.ks_client_module, "Client") - self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, - password=user.password, - username=user.name, - user_domain_name=DEFAULT_DOMAIN, - insecure=False, - cacert=None, - debug=False)\ - .AndReturn(self.keystone_client_unscoped) + plugin = self._create_password_auth() + plugin.get_access(mox.IsA(session.Session)). \ + AndReturn(self.data.unscoped_access_info) + return self.ks_client_module.Client(session=mox.IsA(session.Session), + auth=plugin) def _mock_unscoped_client_with_token(self, user, unscoped): - self.mox.StubOutWithMock(self.ks_client_module, "Client") - url = settings.OPENSTACK_KEYSTONE_URL - self.ks_client_module.Client(user_id=user.id, - auth_url=url, - token=unscoped.auth_token, - insecure=False, - cacert=None, - debug=False)\ - .AndReturn(self.keystone_client_unscoped) + plugin = token_endpoint.Token(settings.OPENSTACK_KEYSTONE_URL, + unscoped.auth_token) + return self.ks_client_module.Client(session=mox.IsA(session.Session), + auth=plugin) def _mock_client_token_auth_failure(self, unscoped, tenant_id): - exc = keystone_exceptions.AuthorizationFailure - self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, - tenant_id=tenant_id, - insecure=False, - cacert=None, - token=unscoped.auth_token, - debug=False) \ - .AndRaise(exc) + plugin = self._create_token_auth(tenant_id, unscoped.auth_token) + plugin.get_access(mox.IsA(session.Session)). \ + AndRaise(keystone_exceptions.AuthorizationFailure) def _mock_client_password_auth_failure(self, username, password, exc): - self.mox.StubOutWithMock(self.ks_client_module, "Client") - self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, - password=password, - username=username, - user_domain_name=DEFAULT_DOMAIN, - insecure=False, - cacert=None, - debug=False).AndRaise(exc) + plugin = self._create_password_auth(username=username, + password=password) + plugin.get_access(mox.IsA(session.Session)).AndRaise(exc) - def _mock_scoped_client_for_tenant(self, auth_ref, tenant_id, url=None): + def _mock_scoped_client_for_tenant(self, auth_ref, tenant_id, url=None, + client=True): if url is None: - auth_url = settings.OPENSTACK_KEYSTONE_URL - else: - auth_url = url - self.ks_client_module.Client(auth_url=auth_url, - tenant_id=tenant_id, - insecure=False, - cacert=None, - token=auth_ref.auth_token, - debug=False) \ - .AndReturn(self.keystone_client_scoped) + url = settings.OPENSTACK_KEYSTONE_URL + + plugin = self._create_token_auth( + tenant_id, + token=self.data.unscoped_access_info.auth_token, + url=url) + + plugin.get_access(mox.IsA(session.Session)).AndReturn(auth_ref) + if client: + return self.ks_client_module.Client( + session=mox.IsA(session.Session), + auth=plugin) def get_form_data(self, user): return {'region': settings.OPENSTACK_KEYSTONE_URL, @@ -116,25 +100,67 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase): self.addCleanup(override.disable) self.mox = mox.Mox() + self.addCleanup(self.mox.VerifyAll) + self.addCleanup(self.mox.UnsetStubs) + self.data = data_v2.generate_test_data() self.ks_client_module = client_v2 - endpoint = settings.OPENSTACK_KEYSTONE_URL - self.keystone_client_unscoped = self.ks_client_module.Client( - endpoint=endpoint, - auth_ref=self.data.unscoped_access_info) - self.keystone_client_scoped = self.ks_client_module.Client( - endpoint=endpoint, - auth_ref=self.data.scoped_access_info) + settings.OPENSTACK_API_VERSIONS['identity'] = 2.0 settings.OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v2.0" - def _mock_unscoped_list_tenants(self, tenants): - self.mox.StubOutWithMock(self.keystone_client_unscoped.tenants, "list") - self.keystone_client_unscoped.tenants.list().AndReturn(tenants) + self.mox.StubOutClassWithMocks(token_endpoint, 'Token') + self.mox.StubOutClassWithMocks(auth_v2, 'Token') + self.mox.StubOutClassWithMocks(auth_v2, 'Password') + self.mox.StubOutClassWithMocks(client_v2, 'Client') + + def _mock_unscoped_list_tenants(self, client, tenants): + client.tenants = self.mox.CreateMockAnything() + client.tenants.list().AndReturn(tenants) def _mock_unscoped_client_list_tenants(self, user, tenants): - self._mock_unscoped_client(user) - self._mock_unscoped_list_tenants(tenants) + client = self._mock_unscoped_client(user) + self._mock_unscoped_list_tenants(client, tenants) + + def _mock_client_delete_token(self, user, token, url=None): + if not url: + url = settings.OPENSTACK_KEYSTONE_URL + + plugin = token_endpoint.Token( + endpoint=url, + token=self.data.unscoped_access_info.auth_token) + + client = self.ks_client_module.Client(session=mox.IsA(session.Session), + auth=plugin) + client.tokens = self.mox.CreateMockAnything() + client.tokens.delete(token=token) + return client + + def _create_password_auth(self, username=None, password=None, url=None): + if not username: + username = self.data.user.name + + if not password: + password = self.data.user.password + + if not url: + url = settings.OPENSTACK_KEYSTONE_URL + + return auth_v2.Password(auth_url=url, + password=password, + username=username) + + def _create_token_auth(self, project_id, token=None, url=None): + if not token: + token = self.data.unscoped_access_info.auth_token + + if not url: + url = settings.OPENSTACK_KEYSTONE_URL + + return auth_v2.Token(auth_url=url, + token=token, + tenant_id=project_id, + reauthenticate=False) def _login(self): tenants = [self.data.tenant_one, self.data.tenant_two] @@ -305,13 +331,16 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase): scoped = self.data.scoped_access_info sc = self.data.service_catalog et = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL') + endpoint = sc.url_for(endpoint_type=et) form_data = self.get_form_data(user) self._mock_unscoped_client_list_tenants(user, tenants) self._mock_scoped_client_for_tenant(unscoped, self.data.tenant_one.id) - self._mock_scoped_client_for_tenant(scoped, tenant.id, - url=sc.url_for(endpoint_type=et)) + self._mock_client_delete_token(user, unscoped.auth_token, endpoint) + self._mock_scoped_client_for_tenant(scoped, tenant.id, url=endpoint, + client=False) + self.mox.ReplayAll() url = reverse('login') @@ -346,13 +375,13 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase): def test_switch_region(self, next=None): tenants = [self.data.tenant_one, self.data.tenant_two] user = self.data.user - unscoped = self.data.unscoped_access_info + scoped = self.data.scoped_access_info sc = self.data.service_catalog form_data = self.get_form_data(user) self._mock_unscoped_client_list_tenants(user, tenants) - self._mock_scoped_client_for_tenant(unscoped, self.data.tenant_one.id) + self._mock_scoped_client_for_tenant(scoped, self.data.tenant_one.id) self.mox.ReplayAll() @@ -395,18 +424,15 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase): user = self.data.user unscoped = self.data.unscoped_access_info - self._mock_unscoped_client_with_token(user, unscoped) - self._mock_unscoped_list_tenants(tenants) + client = self._mock_unscoped_client_with_token(user, unscoped) + self._mock_unscoped_list_tenants(client, tenants) self.mox.ReplayAll() tenant_list = utils.get_project_list( user_id=user.id, auth_url=settings.OPENSTACK_KEYSTONE_URL, - token=unscoped.auth_token, - insecure=False, - cacert=None, - debug=False) + token=unscoped.auth_token) self.assertEqual(tenant_list, expected_tenants) def test_tenant_list_caching(self): @@ -415,17 +441,14 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase): user = self.data.user unscoped = self.data.unscoped_access_info - self._mock_unscoped_list_tenants(tenants) - self._mock_unscoped_client_with_token(user, unscoped) + client = self._mock_unscoped_client_with_token(user, unscoped) + self._mock_unscoped_list_tenants(client, tenants) self.mox.ReplayAll() tenant_list = utils.get_project_list( user_id=user.id, auth_url=settings.OPENSTACK_KEYSTONE_URL, - token=unscoped.auth_token, - insecure=False, - cacert=None, - debug=False) + token=unscoped.auth_token) self.assertEqual(tenant_list, expected_tenants) # Test to validate that requesting the project list again results @@ -435,10 +458,7 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase): tenant_list = utils.get_project_list( user_id=user.id, auth_url=settings.OPENSTACK_KEYSTONE_URL, - token=unscoped.auth_token, - insecure=False, - cacert=None, - debug=False) + token=unscoped.auth_token) self.assertEqual(tenant_list, expected_tenants) utils.remove_project_cache(unscoped.auth_token) @@ -448,14 +468,39 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase): class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase): def _mock_unscoped_client_list_projects(self, user, projects): - self._mock_unscoped_client(user) - self._mock_unscoped_list_projects(user, projects) + client = self._mock_unscoped_client(user) + self._mock_unscoped_list_projects(client, user, projects) - def _mock_unscoped_list_projects(self, user, projects): - self.mox.StubOutWithMock(self.keystone_client_unscoped.projects, - "list") - self.keystone_client_unscoped.projects.list(user=user.id) \ - .AndReturn(projects) + def _mock_unscoped_list_projects(self, client, user, projects): + client.projects = self.mox.CreateMockAnything() + client.projects.list(user=user.id).AndReturn(projects) + + def _create_password_auth(self, username=None, password=None, url=None): + if not username: + username = self.data.user.name + + if not password: + password = self.data.user.password + + if not url: + url = settings.OPENSTACK_KEYSTONE_URL + + return auth_v3.Password(auth_url=url, + password=password, + username=username, + user_domain_name=DEFAULT_DOMAIN) + + def _create_token_auth(self, project_id, token=None, url=None): + if not token: + token = self.data.unscoped_access_info.auth_token + + if not url: + url = settings.OPENSTACK_KEYSTONE_URL + + return auth_v3.Token(auth_url=url, + token=token, + project_id=project_id, + reauthenticate=False) def setUp(self): super(OpenStackAuthTestsV3, self).setUp() @@ -466,18 +511,20 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase): self.addCleanup(override.disable) self.mox = mox.Mox() + self.addCleanup(self.mox.VerifyAll) + self.addCleanup(self.mox.UnsetStubs) + self.data = data_v3.generate_test_data() self.ks_client_module = client_v3 - endpoint = settings.OPENSTACK_KEYSTONE_URL - self.keystone_client_unscoped = self.ks_client_module.Client( - endpoint=endpoint, - auth_ref=self.data.unscoped_access_info) - self.keystone_client_scoped = self.ks_client_module.Client( - endpoint=endpoint, - auth_ref=self.data.scoped_access_info) + settings.OPENSTACK_API_VERSIONS['identity'] = 3 settings.OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v3" + self.mox.StubOutClassWithMocks(token_endpoint, 'Token') + self.mox.StubOutClassWithMocks(auth_v3, 'Token') + self.mox.StubOutClassWithMocks(auth_v3, 'Password') + self.mox.StubOutClassWithMocks(client_v3, 'Client') + def test_login(self): projects = [self.data.project_one, self.data.project_two] user = self.data.user @@ -615,7 +662,6 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase): project = self.data.project_two projects = [self.data.project_one, self.data.project_two] user = self.data.user - unscoped = self.data.unscoped_access_info scoped = self.data.scoped_access_info sc = self.data.service_catalog et = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL') @@ -623,11 +669,12 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase): form_data = self.get_form_data(user) self._mock_unscoped_client_list_projects(user, projects) - self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id) + self._mock_scoped_client_for_tenant(scoped, self.data.project_one.id) self._mock_scoped_client_for_tenant( - unscoped, + scoped, project.id, - url=sc.url_for(endpoint_type=et)) + url=sc.url_for(endpoint_type=et), + client=False) self.mox.ReplayAll() @@ -663,12 +710,12 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase): def test_switch_region(self, next=None): projects = [self.data.project_one, self.data.project_two] user = self.data.user - unscoped = self.data.unscoped_access_info + scoped = self.data.unscoped_access_info sc = self.data.service_catalog form_data = self.get_form_data(user) self._mock_unscoped_client_list_projects(user, projects) - self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id) + self._mock_scoped_client_for_tenant(scoped, self.data.project_one.id) self.mox.ReplayAll() @@ -710,17 +757,14 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase): user = self.data.user unscoped = self.data.unscoped_access_info - self._mock_unscoped_client_with_token(user, unscoped) - self._mock_unscoped_list_projects(user, projects) + client = self._mock_unscoped_client_with_token(user, unscoped) + self._mock_unscoped_list_projects(client, user, projects) self.mox.ReplayAll() project_list = utils.get_project_list( user_id=user.id, auth_url=settings.OPENSTACK_KEYSTONE_URL, - token=unscoped.auth_token, - insecure=False, - cacert=None, - debug=False) + token=unscoped.auth_token) self.assertEqual(project_list, expected_projects) def test_tenant_list_caching(self): @@ -729,18 +773,15 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase): user = self.data.user unscoped = self.data.unscoped_access_info - self._mock_unscoped_client_with_token(user, unscoped) - self._mock_unscoped_list_projects(user, projects) + client = self._mock_unscoped_client_with_token(user, unscoped) + self._mock_unscoped_list_projects(client, user, projects) self.mox.ReplayAll() project_list = utils.get_project_list( user_id=user.id, auth_url=settings.OPENSTACK_KEYSTONE_URL, - token=unscoped.auth_token, - insecure=False, - cacert=None, - debug=False) + token=unscoped.auth_token) self.assertEqual(project_list, expected_projects) # Test to validate that requesting the project list again results @@ -750,10 +791,7 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase): project_list = utils.get_project_list( user_id=user.id, auth_url=settings.OPENSTACK_KEYSTONE_URL, - token=unscoped.auth_token, - insecure=False, - cacert=None, - debug=False) + token=unscoped.auth_token) self.assertEqual(project_list, expected_projects) utils.remove_project_cache(unscoped.auth_token) diff --git a/openstack_auth/user.py b/openstack_auth/user.py index 42b1071f..1bc5ace3 100644 --- a/openstack_auth/user.py +++ b/openstack_auth/user.py @@ -270,9 +270,6 @@ class User(models.AnonymousUser): @property def authorized_tenants(self): """Returns a memoized list of tenants this user may access.""" - insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) - ca_cert = getattr(settings, "OPENSTACK_SSL_CACERT", None) - if self.is_authenticated() and self._authorized_tenants is None: endpoint = self.endpoint token = self.token @@ -280,10 +277,7 @@ class User(models.AnonymousUser): self._authorized_tenants = utils.get_project_list( user_id=self.id, auth_url=endpoint, - token=token.id, - insecure=insecure, - cacert=ca_cert, - debug=settings.DEBUG) + token=token.id) except (keystone_exceptions.ClientException, keystone_exceptions.AuthorizationFailure): LOG.exception('Unable to retrieve project list.') diff --git a/openstack_auth/utils.py b/openstack_auth/utils.py index f8263358..2e16f608 100644 --- a/openstack_auth/utils.py +++ b/openstack_auth/utils.py @@ -21,6 +21,10 @@ from django.contrib.auth import middleware from django.contrib.auth import models from django.utils import decorators from django.utils import timezone +from keystoneclient.auth.identity import v2 as v2_auth +from keystoneclient.auth.identity import v3 as v3_auth +from keystoneclient.auth import token_endpoint +from keystoneclient import session from keystoneclient.v2_0 import client as client_v2 from keystoneclient.v3 import client as client_v3 from six.moves.urllib import parse as urlparse @@ -152,6 +156,16 @@ def get_keystone_version(): return getattr(settings, 'OPENSTACK_API_VERSIONS', {}).get('identity', 2.0) +def get_session(): + insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) + verify = getattr(settings, 'OPENSTACK_SSL_CACERT', True) + + if insecure: + verify = False + + return session.Session(verify=verify) + + def get_keystone_client(): if get_keystone_version() < 3: return client_v2 @@ -180,20 +194,60 @@ def url_path_replace(url, old, new, count=None): scheme, netloc, path.replace(old, new, *args), query, fragment)) +def fix_auth_url_version(auth_url): + """Fix up the auth url if an invalid version prefix was given. + + People still give a v2 auth_url even when they specify that they want v3 + authentication. Fix the URL to say v3. This should be smarter and take the + base, unversioned URL and discovery. + """ + if get_keystone_version() >= 3: + if has_in_url_path(auth_url, "/v2.0"): + LOG.warning("The settings.py file points to a v2.0 keystone " + "endpoint, but v3 is specified as the API version " + "to use. Using v3 endpoint for authentication.") + auth_url = url_path_replace(auth_url, "/v2.0", "/v3", 1) + + return auth_url + + +def get_password_auth_plugin(auth_url, username, password, user_domain_name): + if get_keystone_version() >= 3: + return v3_auth.Password(auth_url=auth_url, + username=username, + password=password, + user_domain_name=user_domain_name) + + else: + return v2_auth.Password(auth_url=auth_url, + username=username, + password=password) + + +def get_token_auth_plugin(auth_url, token, project_id): + if get_keystone_version() >= 3: + return v3_auth.Token(auth_url=auth_url, + token=token, + project_id=project_id, + reauthenticate=False) + + else: + return v2_auth.Token(auth_url=auth_url, + token=token, + tenant_id=project_id, + reauthenticate=False) + + @memoize_by_keyword_arg(_PROJECT_CACHE, ('token', )) def get_project_list(*args, **kwargs): + sess = kwargs.get('session') or get_session() + auth_url = fix_auth_url_version(kwargs['auth_url']) + auth = token_endpoint.Token(auth_url, kwargs['token']) + client = get_keystone_client().Client(session=sess, auth=auth) + if get_keystone_version() < 3: - auth_url = url_path_replace( - kwargs.get('auth_url', ''), '/v3', '/v2.0', 1) - kwargs['auth_url'] = auth_url - client = get_keystone_client().Client(*args, **kwargs) projects = client.tenants.list() else: - auth_url = url_path_replace( - kwargs.get('auth_url', ''), '/v2.0', '/v3', 1) - kwargs['auth_url'] = auth_url - client = get_keystone_client().Client(*args, **kwargs) - client.management_url = auth_url projects = client.projects.list(user=kwargs.get('user_id')) projects.sort(key=lambda project: project.name.lower()) diff --git a/openstack_auth/views.py b/openstack_auth/views.py index c7d68d09..314afd72 100644 --- a/openstack_auth/views.py +++ b/openstack_auth/views.py @@ -24,6 +24,7 @@ from django.utils import http from django.views.decorators.cache import never_cache # noqa from django.views.decorators.csrf import csrf_protect # noqa from django.views.decorators.debug import sensitive_post_parameters # noqa +from keystoneclient.auth import token_endpoint from keystoneclient import exceptions as keystone_exceptions from openstack_auth import forms @@ -136,23 +137,21 @@ def logout(request, login_url=None, **kwargs): def delete_token(endpoint, token_id): """Delete a token.""" - insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) - ca_cert = getattr(settings, "OPENSTACK_SSL_CACERT", None) utils.remove_project_cache(token_id) + try: - if utils.get_keystone_version() >= 3: - if not utils.has_in_url_path(endpoint, '/v3'): - endpoint = utils.url_path_replace(endpoint, '/v2.0', '/v3', 1) - client = utils.get_keystone_client().Client( - endpoint=endpoint, - token=token_id, - insecure=insecure, - cacert=ca_cert, - debug=settings.DEBUG) + endpoint = utils.fix_auth_url_version(endpoint) + + session = utils.get_session() + auth_plugin = token_endpoint.Token(endpoint=endpoint, + token=token_id) + client = utils.get_keystone_client().Client(session=session, + auth=auth_plugin) if utils.get_keystone_version() >= 3: client.tokens.revoke_token(token=token_id) else: client.tokens.delete(token=token_id) + LOG.info('Deleted token %s' % token_id) except keystone_exceptions.ClientException: LOG.info('Could not delete token') @@ -163,21 +162,15 @@ def switch(request, tenant_id, redirect_field_name=auth.REDIRECT_FIELD_NAME): """Switches an authenticated user from one project to another.""" LOG.debug('Switching to tenant %s for user "%s".' % (tenant_id, request.user.username)) - insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) - ca_cert = getattr(settings, "OPENSTACK_SSL_CACERT", None) - endpoint = request.user.endpoint + + endpoint = utils.fix_auth_url_version(request.user.endpoint) + session = utils.get_session() + auth = utils.get_token_auth_plugin(auth_url=endpoint, + token=request.user.token.id, + project_id=tenant_id) + try: - if utils.get_keystone_version() >= 3: - if not utils.has_in_url_path(endpoint, '/v3'): - endpoint = utils.url_path_replace(endpoint, '/v2.0', '/v3', 1) - client = utils.get_keystone_client().Client( - tenant_id=tenant_id, - token=request.user.token.id, - auth_url=endpoint, - insecure=insecure, - cacert=ca_cert, - debug=settings.DEBUG) - auth_ref = client.auth_ref + auth_ref = auth.get_access(session) msg = 'Project switch successful for user "%(username)s".' % \ {'username': request.user.username} LOG.info(msg)