Pass X_IS_ADMIN_PROJECT header from auth_token
To do policy enforcement around admin projects we need for auth_token middleware to pass this information down to context objects. Closes-Bug: #1577996 Change-Id: Ic680e6eaa683926914cf4b2152ec3bb67c6601ff
This commit is contained in:
parent
627ec923b9
commit
0562670d4e
|
@ -118,6 +118,12 @@ HTTP_X_USER_DOMAIN_NAME, HTTP_X_SERVICE_USER_DOMAIN_NAME
|
||||||
HTTP_X_ROLES, HTTP_X_SERVICE_ROLES
|
HTTP_X_ROLES, HTTP_X_SERVICE_ROLES
|
||||||
Comma delimited list of case-sensitive role names.
|
Comma delimited list of case-sensitive role names.
|
||||||
|
|
||||||
|
HTTP_X_IS_ADMIN_PROJECT
|
||||||
|
The string value 'True' or 'False' representing whether the user's token is
|
||||||
|
scoped to the admin project. As historically there was no admin project
|
||||||
|
this will default to True for tokens without this information to be
|
||||||
|
backwards with existing policy files.
|
||||||
|
|
||||||
HTTP_X_SERVICE_CATALOG
|
HTTP_X_SERVICE_CATALOG
|
||||||
service catalog (optional, JSON string).
|
service catalog (optional, JSON string).
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,15 @@ def _v3_to_v2_catalog(catalog):
|
||||||
return v2_services
|
return v2_services
|
||||||
|
|
||||||
|
|
||||||
|
def _is_admin_project(auth_ref):
|
||||||
|
"""Return an appropriate header value for X-Is-Admin-Project.
|
||||||
|
|
||||||
|
Headers must be strings so we can't simply pass a boolean value through so
|
||||||
|
return a True or False string to signal the admin project.
|
||||||
|
"""
|
||||||
|
return 'True' if auth_ref.is_admin_project else 'False'
|
||||||
|
|
||||||
|
|
||||||
# NOTE(jamielennox): this should probably be moved into its own file, but at
|
# NOTE(jamielennox): this should probably be moved into its own file, but at
|
||||||
# the moment there's no real logic here so just keep it locally.
|
# the moment there's no real logic here so just keep it locally.
|
||||||
class _AuthTokenResponse(webob.Response):
|
class _AuthTokenResponse(webob.Response):
|
||||||
|
@ -86,6 +95,8 @@ class _AuthTokenRequest(webob.Request):
|
||||||
_USER_STATUS_HEADER = 'X-Identity-Status'
|
_USER_STATUS_HEADER = 'X-Identity-Status'
|
||||||
_SERVICE_STATUS_HEADER = 'X-Service-Identity-Status'
|
_SERVICE_STATUS_HEADER = 'X-Service-Identity-Status'
|
||||||
|
|
||||||
|
_ADMIN_PROJECT_HEADER = 'X-Is-Admin-Project'
|
||||||
|
|
||||||
_SERVICE_CATALOG_HEADER = 'X-Service-Catalog'
|
_SERVICE_CATALOG_HEADER = 'X-Service-Catalog'
|
||||||
_TOKEN_AUTH = 'keystone.token_auth'
|
_TOKEN_AUTH = 'keystone.token_auth'
|
||||||
_TOKEN_INFO = 'keystone.token_info'
|
_TOKEN_INFO = 'keystone.token_info'
|
||||||
|
@ -155,6 +166,7 @@ class _AuthTokenRequest(webob.Request):
|
||||||
doc info at start of __init__ file for details of headers to be defined
|
doc info at start of __init__ file for details of headers to be defined
|
||||||
"""
|
"""
|
||||||
self._set_auth_headers(auth_ref, self._USER_HEADER_PREFIX)
|
self._set_auth_headers(auth_ref, self._USER_HEADER_PREFIX)
|
||||||
|
self.headers[self._ADMIN_PROJECT_HEADER] = _is_admin_project(auth_ref)
|
||||||
|
|
||||||
for k, v in six.iteritems(self._DEPRECATED_HEADER_MAP):
|
for k, v in six.iteritems(self._DEPRECATED_HEADER_MAP):
|
||||||
self.headers[k] = self.headers[v]
|
self.headers[k] = self.headers[v]
|
||||||
|
@ -192,6 +204,7 @@ class _AuthTokenRequest(webob.Request):
|
||||||
yield self._SERVICE_CATALOG_HEADER
|
yield self._SERVICE_CATALOG_HEADER
|
||||||
yield self._USER_STATUS_HEADER
|
yield self._USER_STATUS_HEADER
|
||||||
yield self._SERVICE_STATUS_HEADER
|
yield self._SERVICE_STATUS_HEADER
|
||||||
|
yield self._ADMIN_PROJECT_HEADER
|
||||||
|
|
||||||
for header in self._DEPRECATED_HEADER_MAP:
|
for header in self._DEPRECATED_HEADER_MAP:
|
||||||
yield header
|
yield header
|
||||||
|
|
|
@ -133,6 +133,14 @@ class _TokenData(object):
|
||||||
"""
|
"""
|
||||||
return frozenset(self._stored_auth_ref.role_names or [])
|
return frozenset(self._stored_auth_ref.role_names or [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_admin_project(self):
|
||||||
|
"""Return true if the current project scope is the admin project.
|
||||||
|
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
return self._stored_auth_ref.is_admin_project
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _log_format(self):
|
def _log_format(self):
|
||||||
roles = ','.join(self.role_names)
|
roles = ','.join(self.role_names)
|
||||||
|
|
|
@ -56,6 +56,7 @@ EXPECTED_V2_DEFAULT_ENV_RESPONSE = {
|
||||||
'HTTP_X_USER_ID': 'user_id1',
|
'HTTP_X_USER_ID': 'user_id1',
|
||||||
'HTTP_X_USER_NAME': 'user_name1',
|
'HTTP_X_USER_NAME': 'user_name1',
|
||||||
'HTTP_X_ROLES': 'role1,role2',
|
'HTTP_X_ROLES': 'role1,role2',
|
||||||
|
'HTTP_X_IS_ADMIN_PROJECT': 'True',
|
||||||
'HTTP_X_USER': 'user_name1', # deprecated (diablo-compat)
|
'HTTP_X_USER': 'user_name1', # deprecated (diablo-compat)
|
||||||
'HTTP_X_TENANT': 'tenant_name1', # deprecated (diablo-compat)
|
'HTTP_X_TENANT': 'tenant_name1', # deprecated (diablo-compat)
|
||||||
'HTTP_X_ROLE': 'role1,role2', # deprecated (diablo-compat)
|
'HTTP_X_ROLE': 'role1,role2', # deprecated (diablo-compat)
|
||||||
|
@ -75,6 +76,7 @@ EXPECTED_V3_DEFAULT_ENV_ADDITIONS = {
|
||||||
'HTTP_X_PROJECT_DOMAIN_NAME': 'domain_name1',
|
'HTTP_X_PROJECT_DOMAIN_NAME': 'domain_name1',
|
||||||
'HTTP_X_USER_DOMAIN_ID': 'domain_id1',
|
'HTTP_X_USER_DOMAIN_ID': 'domain_id1',
|
||||||
'HTTP_X_USER_DOMAIN_NAME': 'domain_name1',
|
'HTTP_X_USER_DOMAIN_NAME': 'domain_name1',
|
||||||
|
'HTTP_X_IS_ADMIN_PROJECT': 'True'
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECTED_V3_DEFAULT_SERVICE_ENV_ADDITIONS = {
|
EXPECTED_V3_DEFAULT_SERVICE_ENV_ADDITIONS = {
|
||||||
|
@ -1848,6 +1850,13 @@ class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
|
||||||
new_data = self.middleware.fetch_token(token)
|
new_data = self.middleware.fetch_token(token)
|
||||||
self.assertEqual(data, new_data)
|
self.assertEqual(data, new_data)
|
||||||
|
|
||||||
|
def test_not_is_admin_project(self):
|
||||||
|
token = self.examples.v3_NOT_IS_ADMIN_PROJECT
|
||||||
|
self.set_middleware(expected_env={'HTTP_X_IS_ADMIN_PROJECT': 'False'})
|
||||||
|
req = self.assert_valid_request_200(token)
|
||||||
|
self.assertIs(False,
|
||||||
|
req.environ['keystone.token_auth'].user.is_admin_project)
|
||||||
|
|
||||||
|
|
||||||
class DelayedAuthTests(BaseAuthTokenMiddlewareTest):
|
class DelayedAuthTests(BaseAuthTokenMiddlewareTest):
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import uuid
|
||||||
|
|
||||||
import fixtures
|
import fixtures
|
||||||
from keystoneauth1 import fixture
|
from keystoneauth1 import fixture
|
||||||
|
@ -131,6 +132,7 @@ class Examples(fixtures.Fixture):
|
||||||
self.UUID_SERVICE_TOKEN_BIND = '5e43439613d34a13a7e03b2762bd08ab'
|
self.UUID_SERVICE_TOKEN_BIND = '5e43439613d34a13a7e03b2762bd08ab'
|
||||||
self.v3_UUID_SERVICE_TOKEN_DEFAULT = 'g431071bbc2f492748596c1b53cb229'
|
self.v3_UUID_SERVICE_TOKEN_DEFAULT = 'g431071bbc2f492748596c1b53cb229'
|
||||||
self.v3_UUID_SERVICE_TOKEN_BIND = 'be705e4426d0449a89e35ae21c380a05'
|
self.v3_UUID_SERVICE_TOKEN_BIND = 'be705e4426d0449a89e35ae21c380a05'
|
||||||
|
self.v3_NOT_IS_ADMIN_PROJECT = uuid.uuid4().hex
|
||||||
|
|
||||||
revoked_token = self.REVOKED_TOKEN
|
revoked_token = self.REVOKED_TOKEN
|
||||||
if isinstance(revoked_token, six.text_type):
|
if isinstance(revoked_token, six.text_type):
|
||||||
|
@ -465,6 +467,21 @@ class Examples(fixtures.Fixture):
|
||||||
svc.add_endpoint('public', self.SERVICE_URL)
|
svc.add_endpoint('public', self.SERVICE_URL)
|
||||||
self.TOKEN_RESPONSES[self.v3_UUID_SERVICE_TOKEN_DEFAULT] = token
|
self.TOKEN_RESPONSES[self.v3_UUID_SERVICE_TOKEN_DEFAULT] = token
|
||||||
|
|
||||||
|
token = fixture.V3Token(user_id=USER_ID,
|
||||||
|
user_name=USER_NAME,
|
||||||
|
user_domain_id=DOMAIN_ID,
|
||||||
|
user_domain_name=DOMAIN_NAME,
|
||||||
|
project_id=PROJECT_ID,
|
||||||
|
project_name=PROJECT_NAME,
|
||||||
|
project_domain_id=DOMAIN_ID,
|
||||||
|
project_domain_name=DOMAIN_NAME,
|
||||||
|
is_admin_project=False)
|
||||||
|
token.add_role(name=ROLE_NAME1)
|
||||||
|
token.add_role(name=ROLE_NAME2)
|
||||||
|
svc = token.add_service(self.SERVICE_TYPE)
|
||||||
|
svc.add_endpoint('public', self.SERVICE_URL)
|
||||||
|
self.TOKEN_RESPONSES[self.v3_NOT_IS_ADMIN_PROJECT] = token
|
||||||
|
|
||||||
# PKIZ tokens generally link to above tokens
|
# PKIZ tokens generally link to above tokens
|
||||||
|
|
||||||
self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_PKIZ_KEY] = (
|
self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_PKIZ_KEY] = (
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
prelude: >
|
||||||
|
- Add the `X_IS_ADMIN_PROJECT` header.
|
||||||
|
features:
|
||||||
|
- Added the `X_IS_ADMIN_PROJECT` header to authenticated headers. This has
|
||||||
|
the string value of 'True' or 'False' and can be used to enforce admin
|
||||||
|
project policies.
|
Loading…
Reference in New Issue