From d3af1d06b4046c25c199bf1c389a9e440a634bc6 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Tue, 21 Jun 2016 12:26:25 +1000 Subject: [PATCH] Add is_admin_project to context is_admin_project is provided by keystonemiddleware and used by oslo.policy to enforce that a project scoped token exists in the admin project. To make this usable we add the ability to read the X-Is-Admin-Project header from the environment, and add it to the outputted policy values. Note the value is added to keystonemiddleware in the depend review however it must work even with older auth_token middlewares so is fine to merge prior to a middleware release. Closes-Bug: #1577996 Depends-On: Ic680e6eaa683926914cf4b2152ec3bb67c6601ff Change-Id: Ie48fedb8092e33e9645a37ea3fe44b88d34ad3b8 --- oslo_context/context.py | 21 ++++++++++++++--- oslo_context/tests/test_context.py | 37 +++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/oslo_context/context.py b/oslo_context/context.py index bbf26a4..09da29e 100644 --- a/oslo_context/context.py +++ b/oslo_context/context.py @@ -71,11 +71,16 @@ class RequestContext(object): read_only=False, show_deleted=False, request_id=None, resource_uuid=None, overwrite=True, roles=None, user_name=None, project_name=None, domain_name=None, - user_domain_name=None, project_domain_name=None): + user_domain_name=None, project_domain_name=None, + is_admin_project=True): """Initialize the RequestContext :param overwrite: Set to False to ensure that the greenthread local copy of the index is not overwritten. + :param is_admin_project: Whether the specified project is specified in + the token as the admin project. Defaults to + True for backwards compatibility. + :type is_admin_project: bool """ self.auth_token = auth_token self.user = user @@ -91,6 +96,7 @@ class RequestContext(object): self.project_domain = project_domain self.project_domain_name = project_domain_name self.is_admin = is_admin + self.is_admin_project = is_admin_project self.read_only = read_only self.show_deleted = show_deleted self.resource_uuid = resource_uuid @@ -121,7 +127,8 @@ class RequestContext(object): 'user_domain_id': self.user_domain, 'project_id': self.tenant, 'project_domain_id': self.project_domain, - 'roles': self.roles} + 'roles': self.roles, + 'is_admin_project': self.is_admin_project} def to_dict(self): """Return a dictionary of context attributes.""" @@ -144,7 +151,8 @@ class RequestContext(object): 'request_id': self.request_id, 'resource_uuid': self.resource_uuid, 'roles': self.roles, - 'user_identity': user_idt} + 'user_identity': user_idt, + 'is_admin_project': self.is_admin_project} def get_logging_values(self): """Return a dictionary of logging specific context attributes.""" @@ -194,6 +202,13 @@ class RequestContext(object): roles = [r.strip() for r in roles.split(',')] if roles else [] kwargs['roles'] = roles + if 'is_admin_project' not in kwargs: + # NOTE(jamielennox): we default is_admin_project to true because if + # nothing is provided we have to assume it is the admin project to + # make old policy continue to work. + is_admin_proj_str = environ.get('HTTP_X_IS_ADMIN_PROJECT', 'true') + kwargs['is_admin_project'] = is_admin_proj_str.lower() == 'true' + return cls(**kwargs) diff --git a/oslo_context/tests/test_context.py b/oslo_context/tests/test_context.py index 6829416..2141c8d 100644 --- a/oslo_context/tests/test_context.py +++ b/oslo_context/tests/test_context.py @@ -241,6 +241,22 @@ class ContextTest(test_base.BaseTestCase): ctx = context.RequestContext.from_environ(environ=environ) self.assertEqual(['abc', 'def', 'ghi'], ctx.roles) + def test_environ_admin_project(self): + environ = {} + ctx = context.RequestContext.from_environ(environ=environ) + self.assertIs(True, ctx.is_admin_project) + self.assertIs(True, ctx.to_policy_values()['is_admin_project']) + + environ = {'HTTP_X_IS_ADMIN_PROJECT': 'True'} + ctx = context.RequestContext.from_environ(environ=environ) + self.assertIs(True, ctx.is_admin_project) + self.assertIs(True, ctx.to_policy_values()['is_admin_project']) + + environ = {'HTTP_X_IS_ADMIN_PROJECT': 'False'} + ctx = context.RequestContext.from_environ(environ=environ) + self.assertIs(False, ctx.is_admin_project) + self.assertIs(False, ctx.to_policy_values()['is_admin_project']) + def test_from_function_and_args(self): ctx = context.RequestContext(user="user1") arg = [] @@ -390,6 +406,7 @@ class ContextTest(test_base.BaseTestCase): project_domain = uuid.uuid4().hex roles = [uuid.uuid4().hex, uuid.uuid4().hex, uuid.uuid4().hex] + # default is_admin_project is True ctx = context.RequestContext(user=user, user_domain=user_domain, tenant=tenant, @@ -400,4 +417,22 @@ class ContextTest(test_base.BaseTestCase): 'user_domain_id': user_domain, 'project_id': tenant, 'project_domain_id': project_domain, - 'roles': roles}, ctx.to_policy_values()) + 'roles': roles, + 'is_admin_project': True}, + ctx.to_policy_values()) + + # is_admin_project False gets passed through + ctx = context.RequestContext(user=user, + user_domain=user_domain, + tenant=tenant, + project_domain=project_domain, + roles=roles, + is_admin_project=False) + + self.assertEqual({'user_id': user, + 'user_domain_id': user_domain, + 'project_id': tenant, + 'project_domain_id': project_domain, + 'roles': roles, + 'is_admin_project': False}, + ctx.to_policy_values())