From 0562670d4e56c257aec8db5a2bb651b5e59fddb2 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Sat, 18 Jun 2016 09:14:26 +1000 Subject: [PATCH] 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 --- keystonemiddleware/auth_token/__init__.py | 6 ++++++ keystonemiddleware/auth_token/_request.py | 13 +++++++++++++ keystonemiddleware/auth_token/_user_plugin.py | 8 ++++++++ .../auth_token/test_auth_token_middleware.py | 9 +++++++++ .../tests/unit/client_fixtures.py | 17 +++++++++++++++++ ...s-admin-project-header-97f1882e209fe727.yaml | 7 +++++++ 6 files changed, 60 insertions(+) create mode 100644 releasenotes/notes/x-is-admin-project-header-97f1882e209fe727.yaml diff --git a/keystonemiddleware/auth_token/__init__.py b/keystonemiddleware/auth_token/__init__.py index 8603b1c5..2855bfa4 100644 --- a/keystonemiddleware/auth_token/__init__.py +++ b/keystonemiddleware/auth_token/__init__.py @@ -118,6 +118,12 @@ HTTP_X_USER_DOMAIN_NAME, HTTP_X_SERVICE_USER_DOMAIN_NAME HTTP_X_ROLES, HTTP_X_SERVICE_ROLES 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 service catalog (optional, JSON string). diff --git a/keystonemiddleware/auth_token/_request.py b/keystonemiddleware/auth_token/_request.py index f64ef28a..7fe940f6 100644 --- a/keystonemiddleware/auth_token/_request.py +++ b/keystonemiddleware/auth_token/_request.py @@ -54,6 +54,15 @@ def _v3_to_v2_catalog(catalog): 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 # the moment there's no real logic here so just keep it locally. class _AuthTokenResponse(webob.Response): @@ -86,6 +95,8 @@ class _AuthTokenRequest(webob.Request): _USER_STATUS_HEADER = 'X-Identity-Status' _SERVICE_STATUS_HEADER = 'X-Service-Identity-Status' + _ADMIN_PROJECT_HEADER = 'X-Is-Admin-Project' + _SERVICE_CATALOG_HEADER = 'X-Service-Catalog' _TOKEN_AUTH = 'keystone.token_auth' _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 """ 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): self.headers[k] = self.headers[v] @@ -192,6 +204,7 @@ class _AuthTokenRequest(webob.Request): yield self._SERVICE_CATALOG_HEADER yield self._USER_STATUS_HEADER yield self._SERVICE_STATUS_HEADER + yield self._ADMIN_PROJECT_HEADER for header in self._DEPRECATED_HEADER_MAP: yield header diff --git a/keystonemiddleware/auth_token/_user_plugin.py b/keystonemiddleware/auth_token/_user_plugin.py index 75d09e73..c513ab9a 100644 --- a/keystonemiddleware/auth_token/_user_plugin.py +++ b/keystonemiddleware/auth_token/_user_plugin.py @@ -133,6 +133,14 @@ class _TokenData(object): """ 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 def _log_format(self): roles = ','.join(self.role_names) diff --git a/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py b/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py index 7c63032b..30c2795c 100644 --- a/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py +++ b/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py @@ -56,6 +56,7 @@ EXPECTED_V2_DEFAULT_ENV_RESPONSE = { 'HTTP_X_USER_ID': 'user_id1', 'HTTP_X_USER_NAME': 'user_name1', 'HTTP_X_ROLES': 'role1,role2', + 'HTTP_X_IS_ADMIN_PROJECT': 'True', 'HTTP_X_USER': 'user_name1', # deprecated (diablo-compat) 'HTTP_X_TENANT': 'tenant_name1', # 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_USER_DOMAIN_ID': 'domain_id1', 'HTTP_X_USER_DOMAIN_NAME': 'domain_name1', + 'HTTP_X_IS_ADMIN_PROJECT': 'True' } EXPECTED_V3_DEFAULT_SERVICE_ENV_ADDITIONS = { @@ -1848,6 +1850,13 @@ class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest, new_data = self.middleware.fetch_token(token) 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): diff --git a/keystonemiddleware/tests/unit/client_fixtures.py b/keystonemiddleware/tests/unit/client_fixtures.py index f150b4e6..ca9e2c87 100644 --- a/keystonemiddleware/tests/unit/client_fixtures.py +++ b/keystonemiddleware/tests/unit/client_fixtures.py @@ -13,6 +13,7 @@ # under the License. import os +import uuid import fixtures from keystoneauth1 import fixture @@ -131,6 +132,7 @@ class Examples(fixtures.Fixture): self.UUID_SERVICE_TOKEN_BIND = '5e43439613d34a13a7e03b2762bd08ab' self.v3_UUID_SERVICE_TOKEN_DEFAULT = 'g431071bbc2f492748596c1b53cb229' self.v3_UUID_SERVICE_TOKEN_BIND = 'be705e4426d0449a89e35ae21c380a05' + self.v3_NOT_IS_ADMIN_PROJECT = uuid.uuid4().hex revoked_token = self.REVOKED_TOKEN if isinstance(revoked_token, six.text_type): @@ -465,6 +467,21 @@ class Examples(fixtures.Fixture): svc.add_endpoint('public', self.SERVICE_URL) 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 self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_PKIZ_KEY] = ( diff --git a/releasenotes/notes/x-is-admin-project-header-97f1882e209fe727.yaml b/releasenotes/notes/x-is-admin-project-header-97f1882e209fe727.yaml new file mode 100644 index 00000000..a433a73d --- /dev/null +++ b/releasenotes/notes/x-is-admin-project-header-97f1882e209fe727.yaml @@ -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.