Merge "Add domain scoped token to session in multidomain"
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user