Merge "Add domain scoped token to session in multidomain"

This commit is contained in:
Jenkins
2015-11-23 22:13:45 +00:00
committed by Gerrit Code Review
5 changed files with 152 additions and 29 deletions

View File

@@ -129,15 +129,47 @@ class KeystoneBackend(object):
# Check expiry for our unscoped auth ref.
self.check_auth_expiry(unscoped_auth_ref)
# domain support can require domain scoped tokens to perform
# identity operations depending on the policy files being used
# for keystone.
domain_auth = None
domain_auth_ref = None
if utils.get_keystone_version() >= 3 and 'user_domain_name' in kwargs:
try:
token = unscoped_auth_ref.auth_token
domain_auth = utils.get_token_auth_plugin(
auth_url,
token,
domain_name=kwargs['user_domain_name'])
domain_auth_ref = domain_auth.get_access(session)
except Exception:
LOG.debug('Error getting domain scoped token.', exc_info=True)
projects = plugin.list_projects(session,
unscoped_auth,
unscoped_auth_ref)
# Attempt to scope only to enabled projects
projects = [project for project in projects if project.enabled]
# Abort if there are no projects for this user
if not projects:
# Abort if there are no projects for this user and a valid domain
# token has not been obtained
#
# The valid use cases for a user login are:
# Keystone v2: user must have a role on a project and be able
# to obtain a project scoped token
# Keystone v3: 1) user can obtain a domain scoped token (user
# has a role on the domain they authenticated to),
# only, no roles on a project
# 2) user can obtain a domain scoped token and has
# a role on a project in the domain they
# authenticated to (and can obtain a project scoped
# token)
# 3) user cannot obtain a domain scoped token, but can
# obtain a project scoped token
if not projects and not domain_auth_ref:
msg = _('You are not authorized for any projects.')
if utils.get_keystone_version() >= 3:
msg = _('You are not authorized for any projects or domains.')
raise exceptions.KeystoneAuthException(msg)
# the recent project id a user might have set in a cookie
@@ -172,8 +204,15 @@ class KeystoneBackend(object):
else:
break
else:
msg = _("Unable to authenticate to any available projects.")
raise exceptions.KeystoneAuthException(msg)
# if the user can't obtain a project scoped token, set the scoped
# token to be the domain token, if valid
if domain_auth_ref:
scoped_auth = domain_auth
scoped_auth_ref = domain_auth_ref
else:
# if no domain or project token for user, abort
msg = _("Unable to authenticate to any available projects.")
raise exceptions.KeystoneAuthException(msg)
# Check expiry for our new scoped token.
self.check_auth_expiry(scoped_auth_ref)
@@ -189,6 +228,19 @@ class KeystoneBackend(object):
if request is not None:
request.session['unscoped_token'] = unscoped_token
if domain_auth_ref:
# check django session engine, if using cookies, this will not
# work, as it will overflow the cookie so don't add domain
# scoped token to the session and put error in the log
if utils.using_cookie_backed_sessions():
LOG.error('Using signed cookies as SESSION_ENGINE with '
'OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT is '
'enabled. This disables the ability to '
'perform identity operations due to cookie size '
'constraints.')
else:
request.session['domain_token'] = domain_auth_ref
request.user = user
timeout = getattr(settings, "SESSION_TIMEOUT", 3600)
token_life = user.token.expires - datetime.datetime.now(pytz.utc)

View File

@@ -216,6 +216,31 @@ def generate_test_data():
body=scoped_token_dict
)
domain_token_dict = {
'token': {
'methods': ['password'],
'expires_at': expiration,
'domain': {
'id': domain_dict['id'],
'name': domain_dict['name'],
},
'user': {
'id': user_dict['id'],
'name': user_dict['name'],
'domain': {
'id': domain_dict['id'],
'name': domain_dict['name']
}
},
'roles': [role_dict],
'catalog': [keystone_service, nova_service]
}
}
test_data.domain_scoped_access_info = access.AccessInfo.factory(
resp=auth_response,
body=domain_token_dict
)
unscoped_token_dict = {
'token': {
'methods': ['password'],

View File

@@ -485,6 +485,32 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
client.projects.list(user=user.id).AndRaise(
keystone_exceptions.AuthorizationFailure)
def _mock_unscoped_and_domain_list_projects(self, user, projects):
client = self._mock_unscoped_client(user)
self._mock_scoped_for_domain(projects)
self._mock_unscoped_list_projects(client, user, projects)
def _mock_scoped_for_domain(self, projects):
url = settings.OPENSTACK_KEYSTONE_URL
plugin = self._create_token_auth(
project_id=None,
domain_name=DEFAULT_DOMAIN,
token=self.data.unscoped_access_info.auth_token,
url=url)
plugin.get_access(mox.IsA(session.Session)).AndReturn(
self.data.domain_scoped_access_info)
# if no projects or no enabled projects for user, but domain scoped
# token client auth gets set to domain scoped auth otherwise it's set
# to the project scoped auth and that happens in a different mock
enabled_projects = [project for project in projects if project.enabled]
if not projects or not enabled_projects:
return self.ks_client_module.Client(
session=mox.IsA(session.Session),
auth=plugin)
def _create_password_auth(self, username=None, password=None, url=None):
if not username:
username = self.data.user.name
@@ -501,17 +527,24 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
user_domain_name=DEFAULT_DOMAIN,
unscoped=True)
def _create_token_auth(self, project_id, token=None, url=None):
def _create_token_auth(self, project_id, token=None, url=None,
domain_name=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)
if domain_name:
return auth_v3.Token(auth_url=url,
token=token,
domain_name=domain_name,
reauthenticate=False)
else:
return auth_v3.Token(auth_url=url,
token=token,
project_id=project_id,
reauthenticate=False)
def setUp(self):
super(OpenStackAuthTestsV3, self).setUp()
@@ -527,7 +560,6 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
self.data = data_v3.generate_test_data()
self.ks_client_module = client_v3
settings.OPENSTACK_API_VERSIONS['identity'] = 3
settings.OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v3"
@@ -542,7 +574,7 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
unscoped = self.data.unscoped_access_info
form_data = self.get_form_data(user)
self._mock_unscoped_client_list_projects(user, projects)
self._mock_unscoped_and_domain_list_projects(user, projects)
self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id)
self.mox.ReplayAll()
@@ -565,7 +597,7 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
unscoped = self.data.unscoped_access_info
form_data = self.get_form_data(user)
self._mock_unscoped_client_list_projects(user, projects)
self._mock_unscoped_and_domain_list_projects(user, projects)
self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id)
self.mox.ReplayAll()
@@ -585,7 +617,7 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
form_data = self.get_form_data(user)
self._mock_unscoped_client_list_projects(user, projects)
self._mock_unscoped_and_domain_list_projects(user, projects)
self.mox.ReplayAll()
url = reverse('login')
@@ -596,15 +628,13 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
# POST to the page to log in.
response = self.client.post(url, form_data)
self.assertTemplateUsed(response, 'auth/login.html')
self.assertContains(response,
'You are not authorized for any projects.')
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
def test_no_projects(self):
user = self.data.user
form_data = self.get_form_data(user)
self._mock_unscoped_client_list_projects(user, [])
self._mock_unscoped_and_domain_list_projects(user, [])
self.mox.ReplayAll()
url = reverse('login')
@@ -615,9 +645,7 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
# POST to the page to log in.
response = self.client.post(url, form_data)
self.assertTemplateUsed(response, 'auth/login.html')
self.assertContains(response,
'You are not authorized for any projects.')
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
def test_fail_projects(self):
user = self.data.user
@@ -692,7 +720,7 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
form_data = self.get_form_data(user)
self._mock_unscoped_client_list_projects(user, projects)
self._mock_unscoped_and_domain_list_projects(user, projects)
self._mock_scoped_client_for_tenant(scoped, self.data.project_one.id)
self._mock_scoped_client_for_tenant(
scoped,
@@ -738,7 +766,7 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
sc = self.data.service_catalog
form_data = self.get_form_data(user)
self._mock_unscoped_client_list_projects(user, projects)
self._mock_unscoped_and_domain_list_projects(user, projects)
self._mock_scoped_client_for_tenant(scoped, self.data.project_one.id)
self.mox.ReplayAll()

View File

@@ -298,13 +298,18 @@ def fix_auth_url_version(auth_url):
return auth_url
def get_token_auth_plugin(auth_url, token, project_id=None):
def get_token_auth_plugin(auth_url, token, project_id=None, domain_name=None):
if get_keystone_version() >= 3:
return v3_auth.Token(auth_url=auth_url,
token=token,
project_id=project_id,
reauthenticate=False)
if domain_name:
return v3_auth.Token(auth_url=auth_url,
token=token,
domain_name=domain_name,
reauthenticate=False)
else:
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,
@@ -388,3 +393,8 @@ def get_endpoint_region(endpoint):
Keystone V2 and V3.
"""
return endpoint.get('region_id') or endpoint.get('region')
def using_cookie_backed_sessions():
engine = getattr(settings, 'SESSION_ENGINE', '')
return "signed_cookies" in engine

View File

@@ -165,9 +165,17 @@ def logout(request, login_url=None, **kwargs):
{'username': request.user.username}
LOG.info(msg)
endpoint = request.session.get('region_endpoint')
# delete the project scoped token
token = request.session.get('token')
if token and endpoint:
delete_token(endpoint=endpoint, token_id=token.id)
# delete the domain scoped token if set
domain_token = request.session.get('domain_token')
if domain_token and endpoint:
delete_token(endpoint=endpoint, token_id=domain_token.auth_token)
""" Securely logs a user out. """
return django_auth_views.logout_then_login(request, login_url=login_url,
**kwargs)