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
This commit is contained in:
Jamie Lennox 2014-11-30 21:24:00 +10:00 committed by lin-hua-cheng
parent 5aeeaa09b0
commit 01e0abc17d
5 changed files with 281 additions and 217 deletions

View File

@ -69,35 +69,22 @@ class KeystoneBackend(object):
"""Authenticates a user via the Keystone Identity API.""" """Authenticates a user via the Keystone Identity API."""
LOG.debug('Beginning user authentication for user "%s".' % username) LOG.debug('Beginning user authentication for user "%s".' % username)
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) interface = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'public')
ca_cert = getattr(settings, "OPENSTACK_SSL_CACERT", None)
endpoint_type = getattr(
settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL')
if auth_url is None: if auth_url is None:
auth_url = settings.OPENSTACK_KEYSTONE_URL auth_url = settings.OPENSTACK_KEYSTONE_URL
# keystone client v3 does not support logging in on the v2 url any more session = utils.get_session()
if utils.get_keystone_version() >= 3: keystone_client_class = utils.get_keystone_client().Client
if utils.has_in_url_path(auth_url, "/v2.0"):
LOG.warning("The settings.py file points to a v2.0 keystone " auth_url = utils.fix_auth_url_version(auth_url)
"endpoint, but v3 is specified as the API version " unscoped_auth = utils.get_password_auth_plugin(auth_url,
"to use. Using v3 endpoint for authentication.") username,
auth_url = utils.url_path_replace(auth_url, "/v2.0", "/v3", 1) password,
user_domain_name)
keystone_client = utils.get_keystone_client()
try: try:
client = keystone_client.Client( unscoped_auth_ref = unscoped_auth.get_access(session)
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)
except (keystone_exceptions.Unauthorized, except (keystone_exceptions.Unauthorized,
keystone_exceptions.Forbidden, keystone_exceptions.Forbidden,
keystone_exceptions.NotFound) as exc: keystone_exceptions.NotFound) as exc:
@ -114,22 +101,16 @@ class KeystoneBackend(object):
# Check expiry for our unscoped auth ref. # Check expiry for our unscoped auth ref.
self.check_auth_expiry(unscoped_auth_ref) self.check_auth_expiry(unscoped_auth_ref)
# Check if token is automatically scoped to default_project unscoped_client = keystone_client_class(session=session,
# grab the project from this token, to use as a default auth=unscoped_auth)
# 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')
# We list all the user's projects # We list all the user's projects
try: try:
if utils.get_keystone_version() < 3: if utils.get_keystone_version() >= 3:
projects = client.tenants.list() projects = unscoped_client.projects.list(
else:
client.management_url = auth_url
projects = client.projects.list(
user=unscoped_auth_ref.user_id) user=unscoped_auth_ref.user_id)
else:
projects = unscoped_client.tenants.list()
except (keystone_exceptions.ClientException, except (keystone_exceptions.ClientException,
keystone_exceptions.AuthorizationFailure) as exc: keystone_exceptions.AuthorizationFailure) as exc:
msg = _('Unable to retrieve authorized projects.') 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 # the recent project id a user might have set in a cookie
recent_project = None recent_project = None
if request: 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', 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 # if a most recent project was found, try using it first
for pos, project in enumerate(projects): if recent_project:
if project.id == recent_project: for pos, project in enumerate(projects):
# move recent project to the beginning if project.id == recent_project:
projects.pop(pos) # move recent project to the beginning
projects.insert(0, project) projects.pop(pos)
break projects.insert(0, project)
break
for project in projects: 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: try:
client = keystone_client.Client( scoped_auth_ref = scoped_auth.get_access(session)
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
except (keystone_exceptions.ClientException, except (keystone_exceptions.ClientException,
keystone_exceptions.AuthorizationFailure): keystone_exceptions.AuthorizationFailure):
auth_ref = None pass
else:
if auth_ref is None: break
else:
msg = _("Unable to authenticate to any available projects.") msg = _("Unable to authenticate to any available projects.")
raise exceptions.KeystoneAuthException(msg) raise exceptions.KeystoneAuthException(msg)
# Check expiry for our new scoped token. # 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! # If we made it here we succeeded. Create our User!
user = auth_user.create_user_from_token( user = auth_user.create_user_from_token(
request, request,
auth_user.Token(auth_ref), auth_user.Token(scoped_auth_ref),
client.service_catalog.url_for(endpoint_type=endpoint_type)) scoped_auth_ref.service_catalog.url_for(endpoint_type=interface))
if request is not None: if request is not None:
request.session['unscoped_token'] = unscoped_token.id request.session['unscoped_token'] = unscoped_auth_ref.auth_token
request.user = user request.user = user
scoped_client = keystone_client_class(session=session,
auth=scoped_auth)
# Support client caching to save on auth calls. # 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) LOG.debug('Authentication completed for user "%s".' % username)
return user return user

View File

@ -15,7 +15,11 @@ from django.conf import settings
from django.contrib import auth from django.contrib import auth
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django import test 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 exceptions as keystone_exceptions
from keystoneclient import session
from keystoneclient.v2_0 import client as client_v2 from keystoneclient.v2_0 import client as client_v2
from keystoneclient.v3 import client as client_v3 from keystoneclient.v3 import client as client_v3
from mox3 import mox from mox3 import mox
@ -39,64 +43,44 @@ class OpenStackAuthTestsMixin(object):
('admin', {'interface': 'adminURL'}) ('admin', {'interface': 'adminURL'})
] ]
def tearDown(self):
self.mox.UnsetStubs()
self.mox.VerifyAll()
def _mock_unscoped_client(self, user): def _mock_unscoped_client(self, user):
self.mox.StubOutWithMock(self.ks_client_module, "Client") plugin = self._create_password_auth()
self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, plugin.get_access(mox.IsA(session.Session)). \
password=user.password, AndReturn(self.data.unscoped_access_info)
username=user.name, return self.ks_client_module.Client(session=mox.IsA(session.Session),
user_domain_name=DEFAULT_DOMAIN, auth=plugin)
insecure=False,
cacert=None,
debug=False)\
.AndReturn(self.keystone_client_unscoped)
def _mock_unscoped_client_with_token(self, user, unscoped): def _mock_unscoped_client_with_token(self, user, unscoped):
self.mox.StubOutWithMock(self.ks_client_module, "Client") plugin = token_endpoint.Token(settings.OPENSTACK_KEYSTONE_URL,
url = settings.OPENSTACK_KEYSTONE_URL unscoped.auth_token)
self.ks_client_module.Client(user_id=user.id, return self.ks_client_module.Client(session=mox.IsA(session.Session),
auth_url=url, auth=plugin)
token=unscoped.auth_token,
insecure=False,
cacert=None,
debug=False)\
.AndReturn(self.keystone_client_unscoped)
def _mock_client_token_auth_failure(self, unscoped, tenant_id): def _mock_client_token_auth_failure(self, unscoped, tenant_id):
exc = keystone_exceptions.AuthorizationFailure plugin = self._create_token_auth(tenant_id, unscoped.auth_token)
self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, plugin.get_access(mox.IsA(session.Session)). \
tenant_id=tenant_id, AndRaise(keystone_exceptions.AuthorizationFailure)
insecure=False,
cacert=None,
token=unscoped.auth_token,
debug=False) \
.AndRaise(exc)
def _mock_client_password_auth_failure(self, username, password, exc): def _mock_client_password_auth_failure(self, username, password, exc):
self.mox.StubOutWithMock(self.ks_client_module, "Client") plugin = self._create_password_auth(username=username,
self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, password=password)
password=password, plugin.get_access(mox.IsA(session.Session)).AndRaise(exc)
username=username,
user_domain_name=DEFAULT_DOMAIN,
insecure=False,
cacert=None,
debug=False).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: if url is None:
auth_url = settings.OPENSTACK_KEYSTONE_URL url = settings.OPENSTACK_KEYSTONE_URL
else:
auth_url = url plugin = self._create_token_auth(
self.ks_client_module.Client(auth_url=auth_url, tenant_id,
tenant_id=tenant_id, token=self.data.unscoped_access_info.auth_token,
insecure=False, url=url)
cacert=None,
token=auth_ref.auth_token, plugin.get_access(mox.IsA(session.Session)).AndReturn(auth_ref)
debug=False) \ if client:
.AndReturn(self.keystone_client_scoped) return self.ks_client_module.Client(
session=mox.IsA(session.Session),
auth=plugin)
def get_form_data(self, user): def get_form_data(self, user):
return {'region': settings.OPENSTACK_KEYSTONE_URL, return {'region': settings.OPENSTACK_KEYSTONE_URL,
@ -116,25 +100,67 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
self.addCleanup(override.disable) self.addCleanup(override.disable)
self.mox = mox.Mox() self.mox = mox.Mox()
self.addCleanup(self.mox.VerifyAll)
self.addCleanup(self.mox.UnsetStubs)
self.data = data_v2.generate_test_data() self.data = data_v2.generate_test_data()
self.ks_client_module = client_v2 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_API_VERSIONS['identity'] = 2.0
settings.OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v2.0" settings.OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v2.0"
def _mock_unscoped_list_tenants(self, tenants): self.mox.StubOutClassWithMocks(token_endpoint, 'Token')
self.mox.StubOutWithMock(self.keystone_client_unscoped.tenants, "list") self.mox.StubOutClassWithMocks(auth_v2, 'Token')
self.keystone_client_unscoped.tenants.list().AndReturn(tenants) 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): def _mock_unscoped_client_list_tenants(self, user, tenants):
self._mock_unscoped_client(user) client = self._mock_unscoped_client(user)
self._mock_unscoped_list_tenants(tenants) 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): def _login(self):
tenants = [self.data.tenant_one, self.data.tenant_two] tenants = [self.data.tenant_one, self.data.tenant_two]
@ -305,13 +331,16 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
scoped = self.data.scoped_access_info scoped = self.data.scoped_access_info
sc = self.data.service_catalog sc = self.data.service_catalog
et = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL') et = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL')
endpoint = sc.url_for(endpoint_type=et)
form_data = self.get_form_data(user) form_data = self.get_form_data(user)
self._mock_unscoped_client_list_tenants(user, tenants) 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(unscoped, self.data.tenant_one.id)
self._mock_scoped_client_for_tenant(scoped, tenant.id, self._mock_client_delete_token(user, unscoped.auth_token, endpoint)
url=sc.url_for(endpoint_type=et)) self._mock_scoped_client_for_tenant(scoped, tenant.id, url=endpoint,
client=False)
self.mox.ReplayAll() self.mox.ReplayAll()
url = reverse('login') url = reverse('login')
@ -346,13 +375,13 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
def test_switch_region(self, next=None): def test_switch_region(self, next=None):
tenants = [self.data.tenant_one, self.data.tenant_two] tenants = [self.data.tenant_one, self.data.tenant_two]
user = self.data.user user = self.data.user
unscoped = self.data.unscoped_access_info scoped = self.data.scoped_access_info
sc = self.data.service_catalog sc = self.data.service_catalog
form_data = self.get_form_data(user) form_data = self.get_form_data(user)
self._mock_unscoped_client_list_tenants(user, tenants) 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() self.mox.ReplayAll()
@ -395,18 +424,15 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
user = self.data.user user = self.data.user
unscoped = self.data.unscoped_access_info unscoped = self.data.unscoped_access_info
self._mock_unscoped_client_with_token(user, unscoped) client = self._mock_unscoped_client_with_token(user, unscoped)
self._mock_unscoped_list_tenants(tenants) self._mock_unscoped_list_tenants(client, tenants)
self.mox.ReplayAll() self.mox.ReplayAll()
tenant_list = utils.get_project_list( tenant_list = utils.get_project_list(
user_id=user.id, user_id=user.id,
auth_url=settings.OPENSTACK_KEYSTONE_URL, auth_url=settings.OPENSTACK_KEYSTONE_URL,
token=unscoped.auth_token, token=unscoped.auth_token)
insecure=False,
cacert=None,
debug=False)
self.assertEqual(tenant_list, expected_tenants) self.assertEqual(tenant_list, expected_tenants)
def test_tenant_list_caching(self): def test_tenant_list_caching(self):
@ -415,17 +441,14 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
user = self.data.user user = self.data.user
unscoped = self.data.unscoped_access_info unscoped = self.data.unscoped_access_info
self._mock_unscoped_list_tenants(tenants) client = self._mock_unscoped_client_with_token(user, unscoped)
self._mock_unscoped_client_with_token(user, unscoped) self._mock_unscoped_list_tenants(client, tenants)
self.mox.ReplayAll() self.mox.ReplayAll()
tenant_list = utils.get_project_list( tenant_list = utils.get_project_list(
user_id=user.id, user_id=user.id,
auth_url=settings.OPENSTACK_KEYSTONE_URL, auth_url=settings.OPENSTACK_KEYSTONE_URL,
token=unscoped.auth_token, token=unscoped.auth_token)
insecure=False,
cacert=None,
debug=False)
self.assertEqual(tenant_list, expected_tenants) self.assertEqual(tenant_list, expected_tenants)
# Test to validate that requesting the project list again results # 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( tenant_list = utils.get_project_list(
user_id=user.id, user_id=user.id,
auth_url=settings.OPENSTACK_KEYSTONE_URL, auth_url=settings.OPENSTACK_KEYSTONE_URL,
token=unscoped.auth_token, token=unscoped.auth_token)
insecure=False,
cacert=None,
debug=False)
self.assertEqual(tenant_list, expected_tenants) self.assertEqual(tenant_list, expected_tenants)
utils.remove_project_cache(unscoped.auth_token) utils.remove_project_cache(unscoped.auth_token)
@ -448,14 +468,39 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase): class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
def _mock_unscoped_client_list_projects(self, user, projects): def _mock_unscoped_client_list_projects(self, user, projects):
self._mock_unscoped_client(user) client = self._mock_unscoped_client(user)
self._mock_unscoped_list_projects(user, projects) self._mock_unscoped_list_projects(client, user, projects)
def _mock_unscoped_list_projects(self, user, projects): def _mock_unscoped_list_projects(self, client, user, projects):
self.mox.StubOutWithMock(self.keystone_client_unscoped.projects, client.projects = self.mox.CreateMockAnything()
"list") client.projects.list(user=user.id).AndReturn(projects)
self.keystone_client_unscoped.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): def setUp(self):
super(OpenStackAuthTestsV3, self).setUp() super(OpenStackAuthTestsV3, self).setUp()
@ -466,18 +511,20 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
self.addCleanup(override.disable) self.addCleanup(override.disable)
self.mox = mox.Mox() self.mox = mox.Mox()
self.addCleanup(self.mox.VerifyAll)
self.addCleanup(self.mox.UnsetStubs)
self.data = data_v3.generate_test_data() self.data = data_v3.generate_test_data()
self.ks_client_module = client_v3 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_API_VERSIONS['identity'] = 3
settings.OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v3" 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): def test_login(self):
projects = [self.data.project_one, self.data.project_two] projects = [self.data.project_one, self.data.project_two]
user = self.data.user user = self.data.user
@ -615,7 +662,6 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
project = self.data.project_two project = self.data.project_two
projects = [self.data.project_one, self.data.project_two] projects = [self.data.project_one, self.data.project_two]
user = self.data.user user = self.data.user
unscoped = self.data.unscoped_access_info
scoped = self.data.scoped_access_info scoped = self.data.scoped_access_info
sc = self.data.service_catalog sc = self.data.service_catalog
et = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL') et = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL')
@ -623,11 +669,12 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
form_data = self.get_form_data(user) form_data = self.get_form_data(user)
self._mock_unscoped_client_list_projects(user, projects) 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( self._mock_scoped_client_for_tenant(
unscoped, scoped,
project.id, project.id,
url=sc.url_for(endpoint_type=et)) url=sc.url_for(endpoint_type=et),
client=False)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -663,12 +710,12 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
def test_switch_region(self, next=None): def test_switch_region(self, next=None):
projects = [self.data.project_one, self.data.project_two] projects = [self.data.project_one, self.data.project_two]
user = self.data.user user = self.data.user
unscoped = self.data.unscoped_access_info scoped = self.data.unscoped_access_info
sc = self.data.service_catalog sc = self.data.service_catalog
form_data = self.get_form_data(user) form_data = self.get_form_data(user)
self._mock_unscoped_client_list_projects(user, projects) 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() self.mox.ReplayAll()
@ -710,17 +757,14 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
user = self.data.user user = self.data.user
unscoped = self.data.unscoped_access_info unscoped = self.data.unscoped_access_info
self._mock_unscoped_client_with_token(user, unscoped) client = self._mock_unscoped_client_with_token(user, unscoped)
self._mock_unscoped_list_projects(user, projects) self._mock_unscoped_list_projects(client, user, projects)
self.mox.ReplayAll() self.mox.ReplayAll()
project_list = utils.get_project_list( project_list = utils.get_project_list(
user_id=user.id, user_id=user.id,
auth_url=settings.OPENSTACK_KEYSTONE_URL, auth_url=settings.OPENSTACK_KEYSTONE_URL,
token=unscoped.auth_token, token=unscoped.auth_token)
insecure=False,
cacert=None,
debug=False)
self.assertEqual(project_list, expected_projects) self.assertEqual(project_list, expected_projects)
def test_tenant_list_caching(self): def test_tenant_list_caching(self):
@ -729,18 +773,15 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
user = self.data.user user = self.data.user
unscoped = self.data.unscoped_access_info unscoped = self.data.unscoped_access_info
self._mock_unscoped_client_with_token(user, unscoped) client = self._mock_unscoped_client_with_token(user, unscoped)
self._mock_unscoped_list_projects(user, projects) self._mock_unscoped_list_projects(client, user, projects)
self.mox.ReplayAll() self.mox.ReplayAll()
project_list = utils.get_project_list( project_list = utils.get_project_list(
user_id=user.id, user_id=user.id,
auth_url=settings.OPENSTACK_KEYSTONE_URL, auth_url=settings.OPENSTACK_KEYSTONE_URL,
token=unscoped.auth_token, token=unscoped.auth_token)
insecure=False,
cacert=None,
debug=False)
self.assertEqual(project_list, expected_projects) self.assertEqual(project_list, expected_projects)
# Test to validate that requesting the project list again results # 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( project_list = utils.get_project_list(
user_id=user.id, user_id=user.id,
auth_url=settings.OPENSTACK_KEYSTONE_URL, auth_url=settings.OPENSTACK_KEYSTONE_URL,
token=unscoped.auth_token, token=unscoped.auth_token)
insecure=False,
cacert=None,
debug=False)
self.assertEqual(project_list, expected_projects) self.assertEqual(project_list, expected_projects)
utils.remove_project_cache(unscoped.auth_token) utils.remove_project_cache(unscoped.auth_token)

View File

@ -270,9 +270,6 @@ class User(models.AnonymousUser):
@property @property
def authorized_tenants(self): def authorized_tenants(self):
"""Returns a memoized list of tenants this user may access.""" """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: if self.is_authenticated() and self._authorized_tenants is None:
endpoint = self.endpoint endpoint = self.endpoint
token = self.token token = self.token
@ -280,10 +277,7 @@ class User(models.AnonymousUser):
self._authorized_tenants = utils.get_project_list( self._authorized_tenants = utils.get_project_list(
user_id=self.id, user_id=self.id,
auth_url=endpoint, auth_url=endpoint,
token=token.id, token=token.id)
insecure=insecure,
cacert=ca_cert,
debug=settings.DEBUG)
except (keystone_exceptions.ClientException, except (keystone_exceptions.ClientException,
keystone_exceptions.AuthorizationFailure): keystone_exceptions.AuthorizationFailure):
LOG.exception('Unable to retrieve project list.') LOG.exception('Unable to retrieve project list.')

View File

@ -21,6 +21,10 @@ from django.contrib.auth import middleware
from django.contrib.auth import models from django.contrib.auth import models
from django.utils import decorators from django.utils import decorators
from django.utils import timezone 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.v2_0 import client as client_v2
from keystoneclient.v3 import client as client_v3 from keystoneclient.v3 import client as client_v3
from six.moves.urllib import parse as urlparse 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) 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(): def get_keystone_client():
if get_keystone_version() < 3: if get_keystone_version() < 3:
return client_v2 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)) 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', )) @memoize_by_keyword_arg(_PROJECT_CACHE, ('token', ))
def get_project_list(*args, **kwargs): 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: 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() projects = client.tenants.list()
else: 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 = client.projects.list(user=kwargs.get('user_id'))
projects.sort(key=lambda project: project.name.lower()) projects.sort(key=lambda project: project.name.lower())

View File

@ -24,6 +24,7 @@ from django.utils import http
from django.views.decorators.cache import never_cache # noqa from django.views.decorators.cache import never_cache # noqa
from django.views.decorators.csrf import csrf_protect # noqa from django.views.decorators.csrf import csrf_protect # noqa
from django.views.decorators.debug import sensitive_post_parameters # 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 keystoneclient import exceptions as keystone_exceptions
from openstack_auth import forms from openstack_auth import forms
@ -136,23 +137,21 @@ def logout(request, login_url=None, **kwargs):
def delete_token(endpoint, token_id): def delete_token(endpoint, token_id):
"""Delete a token.""" """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) utils.remove_project_cache(token_id)
try: try:
if utils.get_keystone_version() >= 3: endpoint = utils.fix_auth_url_version(endpoint)
if not utils.has_in_url_path(endpoint, '/v3'):
endpoint = utils.url_path_replace(endpoint, '/v2.0', '/v3', 1) session = utils.get_session()
client = utils.get_keystone_client().Client( auth_plugin = token_endpoint.Token(endpoint=endpoint,
endpoint=endpoint, token=token_id)
token=token_id, client = utils.get_keystone_client().Client(session=session,
insecure=insecure, auth=auth_plugin)
cacert=ca_cert,
debug=settings.DEBUG)
if utils.get_keystone_version() >= 3: if utils.get_keystone_version() >= 3:
client.tokens.revoke_token(token=token_id) client.tokens.revoke_token(token=token_id)
else: else:
client.tokens.delete(token=token_id) client.tokens.delete(token=token_id)
LOG.info('Deleted token %s' % token_id) LOG.info('Deleted token %s' % token_id)
except keystone_exceptions.ClientException: except keystone_exceptions.ClientException:
LOG.info('Could not delete token') 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.""" """Switches an authenticated user from one project to another."""
LOG.debug('Switching to tenant %s for user "%s".' LOG.debug('Switching to tenant %s for user "%s".'
% (tenant_id, request.user.username)) % (tenant_id, request.user.username))
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
ca_cert = getattr(settings, "OPENSTACK_SSL_CACERT", None) endpoint = utils.fix_auth_url_version(request.user.endpoint)
endpoint = 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: try:
if utils.get_keystone_version() >= 3: auth_ref = auth.get_access(session)
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
msg = 'Project switch successful for user "%(username)s".' % \ msg = 'Project switch successful for user "%(username)s".' % \
{'username': request.user.username} {'username': request.user.username}
LOG.info(msg) LOG.info(msg)