diff --git a/keystone/api/domains.py b/keystone/api/domains.py index 6d7a1580fd..480bd62928 100644 --- a/keystone/api/domains.py +++ b/keystone/api/domains.py @@ -14,6 +14,7 @@ import flask import flask_restful +import functools from six.moves import http_client from keystone.common import json_home @@ -342,7 +343,7 @@ class DomainUserListResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:list_grants', - target_attr=_build_enforcement_target()) + build_target=_build_enforcement_target) refs = PROVIDERS.assignment_api.list_grants( domain_id=domain_id, user_id=user_id, inherited_to_projects=False) @@ -361,7 +362,7 @@ class DomainUserResource(ks_flask.ResourceBase): """ ENFORCER.enforce_call( action='identity:check_grant', - target_attr=_build_enforcement_target()) + build_target=_build_enforcement_target) PROVIDERS.assignment_api.get_grant( role_id, domain_id=domain_id, user_id=user_id, inherited_to_projects=False) @@ -374,7 +375,7 @@ class DomainUserResource(ks_flask.ResourceBase): """ ENFORCER.enforce_call( action='identity:create_grant', - target_attr=_build_enforcement_target()) + build_target=_build_enforcement_target) PROVIDERS.assignment_api.create_grant( role_id, domain_id=domain_id, user_id=user_id, inherited_to_projects=False, initiator=self.audit_initiator) @@ -387,7 +388,8 @@ class DomainUserResource(ks_flask.ResourceBase): """ ENFORCER.enforce_call( action='identity:revoke_grant', - target_attr=_build_enforcement_target(allow_non_existing=True)) + build_target=functools.partial(_build_enforcement_target, + allow_non_existing=True)) PROVIDERS.assignment_api.delete_grant( role_id, domain_id=domain_id, user_id=user_id, inherited_to_projects=False, initiator=self.audit_initiator) @@ -402,7 +404,7 @@ class DomainGroupListResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:list_grants', - target_attr=_build_enforcement_target()) + build_target=_build_enforcement_target) refs = PROVIDERS.assignment_api.list_grants( domain_id=domain_id, group_id=group_id, inherited_to_projects=False) @@ -421,7 +423,7 @@ class DomainGroupResource(ks_flask.ResourceBase): """ ENFORCER.enforce_call( action='identity:check_grant', - target_attr=_build_enforcement_target()) + build_target=_build_enforcement_target) PROVIDERS.assignment_api.get_grant( role_id, domain_id=domain_id, group_id=group_id, inherited_to_projects=False) @@ -434,7 +436,7 @@ class DomainGroupResource(ks_flask.ResourceBase): """ ENFORCER.enforce_call( action='identity:create_grant', - target_attr=_build_enforcement_target()) + build_target=_build_enforcement_target) PROVIDERS.assignment_api.create_grant( role_id, domain_id=domain_id, group_id=group_id, inherited_to_projects=False, initiator=self.audit_initiator) @@ -447,7 +449,8 @@ class DomainGroupResource(ks_flask.ResourceBase): """ ENFORCER.enforce_call( action='identity:revoke_grant', - target_attr=_build_enforcement_target(allow_non_existing=True)) + build_target=functools.partial(_build_enforcement_target, + allow_non_existing=True)) PROVIDERS.assignment_api.delete_grant( role_id, domain_id=domain_id, group_id=group_id, inherited_to_projects=False, initiator=self.audit_initiator) diff --git a/keystone/api/groups.py b/keystone/api/groups.py index 082a92d053..b683d0d5ea 100644 --- a/keystone/api/groups.py +++ b/keystone/api/groups.py @@ -13,6 +13,7 @@ # This file handles all flask-restful resources for /v3/groups import flask_restful +import functools from six.moves import http_client from keystone.common import json_home @@ -147,7 +148,8 @@ class UserGroupCRUDResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:check_user_in_group', - target_attr=self._build_enforcement_target_attr(user_id, group_id)) + build_target=functools.partial(self._build_enforcement_target_attr, + user_id, group_id)) PROVIDERS.identity_api.check_user_in_group(user_id, group_id) return None, http_client.NO_CONTENT @@ -158,7 +160,8 @@ class UserGroupCRUDResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:add_user_to_group', - target_attr=self._build_enforcement_target_attr(user_id, group_id)) + build_target=functools.partial(self._build_enforcement_target_attr, + user_id, group_id)) PROVIDERS.identity_api.add_user_to_group( user_id, group_id, initiator=ks_flask.build_audit_initiator()) return None, http_client.NO_CONTENT @@ -170,7 +173,8 @@ class UserGroupCRUDResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:remove_user_from_group', - target_attr=self._build_enforcement_target_attr(user_id, group_id)) + build_target=functools.partial(self._build_enforcement_target_attr, + user_id, group_id)) PROVIDERS.identity_api.remove_user_from_group( user_id, group_id, initiator=ks_flask.build_audit_initiator()) return None, http_client.NO_CONTENT diff --git a/keystone/api/os_inherit.py b/keystone/api/os_inherit.py index 8d1fab3020..63c32191e6 100644 --- a/keystone/api/os_inherit.py +++ b/keystone/api/os_inherit.py @@ -13,6 +13,7 @@ # This file handles all flask-restful resources for /v3/OS-INHERIT import flask_restful +import functools from oslo_log import log from six.moves import http_client @@ -110,8 +111,10 @@ class OSInheritDomainGroupRolesResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:check_grant', - target_attr=_build_enforcement_target_attr( - domain_id=domain_id, group_id=group_id, role_id=role_id)) + build_target=functools.partial(_build_enforcement_target_attr, + domain_id=domain_id, + group_id=group_id, + role_id=role_id)) PROVIDERS.assignment_api.get_grant( domain_id=domain_id, group_id=group_id, role_id=role_id, inherited_to_projects=True) @@ -125,8 +128,10 @@ class OSInheritDomainGroupRolesResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:create_grant', - target_attr=_build_enforcement_target_attr( - domain_id=domain_id, group_id=group_id, role_id=role_id)) + build_target=functools.partial(_build_enforcement_target_attr, + domain_id=domain_id, + group_id=group_id, + role_id=role_id)) PROVIDERS.assignment_api.create_grant( domain_id=domain_id, group_id=group_id, role_id=role_id, inherited_to_projects=True) @@ -140,8 +145,10 @@ class OSInheritDomainGroupRolesResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:revoke_grant', - target_attr=_build_enforcement_target_attr( - domain_id=domain_id, group_id=group_id, role_id=role_id)) + build_target=functools.partial(_build_enforcement_target_attr, + domain_id=domain_id, + group_id=group_id, + role_id=role_id)) PROVIDERS.assignment_api.delete_grant( domain_id=domain_id, group_id=group_id, role_id=role_id, inherited_to_projects=True) @@ -157,8 +164,9 @@ class OSInheritDomainGroupRolesListResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:list_grants', - target_attr=_build_enforcement_target_attr( - domain_id=domain_id, group_id=group_id)) + build_target=functools.partial(_build_enforcement_target_attr, + domain_id=domain_id, + group_id=group_id)) refs = PROVIDERS.assignment_api.list_grants( domain_id=domain_id, group_id=group_id, inherited_to_projects=True) return ks_flask.ResourceBase.wrap_collection( @@ -174,8 +182,10 @@ class OSInheritDomainUserRolesResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:check_grant', - target_attr=_build_enforcement_target_attr( - domain_id=domain_id, user_id=user_id, role_id=role_id)) + build_target=functools.partial(_build_enforcement_target_attr, + domain_id=domain_id, + user_id=user_id, + role_id=role_id)) PROVIDERS.assignment_api.get_grant( domain_id=domain_id, user_id=user_id, role_id=role_id, inherited_to_projects=True) @@ -189,8 +199,10 @@ class OSInheritDomainUserRolesResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:create_grant', - target_attr=_build_enforcement_target_attr( - domain_id=domain_id, user_id=user_id, role_id=role_id)) + build_target=functools.partial(_build_enforcement_target_attr, + domain_id=domain_id, + user_id=user_id, + role_id=role_id)) PROVIDERS.assignment_api.create_grant( domain_id=domain_id, user_id=user_id, role_id=role_id, inherited_to_projects=True) @@ -204,8 +216,10 @@ class OSInheritDomainUserRolesResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:revoke_grant', - target_attr=_build_enforcement_target_attr( - domain_id=domain_id, user_id=user_id, role_id=role_id)) + build_target=functools.partial(_build_enforcement_target_attr, + domain_id=domain_id, + user_id=user_id, + role_id=role_id)) PROVIDERS.assignment_api.delete_grant( domain_id=domain_id, user_id=user_id, role_id=role_id, inherited_to_projects=True) @@ -221,8 +235,9 @@ class OSInheritDomainUserRolesListResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:list_grants', - target_attr=_build_enforcement_target_attr( - domain_id=domain_id, user_id=user_id)) + build_target=functools.partial(_build_enforcement_target_attr, + domain_id=domain_id, + user_id=user_id)) refs = PROVIDERS.assignment_api.list_grants( domain_id=domain_id, user_id=user_id, inherited_to_projects=True) return ks_flask.ResourceBase.wrap_collection( @@ -238,8 +253,10 @@ class OSInheritProjectUserResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:check_grant', - target_attr=_build_enforcement_target_attr( - project_id=project_id, user_id=user_id, role_id=role_id)) + build_target=functools.partial(_build_enforcement_target_attr, + project_id=project_id, + user_id=user_id, + role_id=role_id)) PROVIDERS.assignment_api.get_grant( project_id=project_id, user_id=user_id, role_id=role_id, inherited_to_projects=True) @@ -253,8 +270,10 @@ class OSInheritProjectUserResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:create_grant', - target_attr=_build_enforcement_target_attr( - project_id=project_id, user_id=user_id, role_id=role_id)) + build_target=functools.partial(_build_enforcement_target_attr, + project_id=project_id, + user_id=user_id, + role_id=role_id)) PROVIDERS.assignment_api.create_grant( project_id=project_id, user_id=user_id, role_id=role_id, inherited_to_projects=True) @@ -268,8 +287,10 @@ class OSInheritProjectUserResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:revoke_grant', - target_attr=_build_enforcement_target_attr( - project_id=project_id, user_id=user_id, role_id=role_id)) + build_target=functools.partial(_build_enforcement_target_attr, + project_id=project_id, + user_id=user_id, + role_id=role_id)) PROVIDERS.assignment_api.delete_grant( project_id=project_id, user_id=user_id, role_id=role_id, inherited_to_projects=True) @@ -285,8 +306,10 @@ class OSInheritProjectGroupResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:check_grant', - target_attr=_build_enforcement_target_attr( - project_id=project_id, group_id=group_id, role_id=role_id)) + build_target=functools.partial(_build_enforcement_target_attr, + project_id=project_id, + group_id=group_id, + role_id=role_id)) PROVIDERS.assignment_api.get_grant( project_id=project_id, group_id=group_id, role_id=role_id, inherited_to_projects=True) @@ -300,8 +323,10 @@ class OSInheritProjectGroupResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:create_grant', - target_attr=_build_enforcement_target_attr( - project_id=project_id, group_id=group_id, role_id=role_id)) + build_target=functools.partial(_build_enforcement_target_attr, + project_id=project_id, + group_id=group_id, + role_id=role_id)) PROVIDERS.assignment_api.create_grant( project_id=project_id, group_id=group_id, role_id=role_id, inherited_to_projects=True) @@ -315,8 +340,10 @@ class OSInheritProjectGroupResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:revoke_grant', - target_attr=_build_enforcement_target_attr( - project_id=project_id, group_id=group_id, role_id=role_id)) + build_target=functools.partial(_build_enforcement_target_attr, + project_id=project_id, + group_id=group_id, + role_id=role_id)) PROVIDERS.assignment_api.delete_grant( project_id=project_id, group_id=group_id, role_id=role_id, inherited_to_projects=True) diff --git a/keystone/api/roles.py b/keystone/api/roles.py index 7d89bb5cbb..2dc38653b0 100644 --- a/keystone/api/roles.py +++ b/keystone/api/roles.py @@ -191,7 +191,7 @@ class RoleImplicationListResource(flask_restful.Resource): GET/HEAD /v3/roles/{prior_role_id}/implies """ ENFORCER.enforce_call(action='identity:list_implied_roles', - target_attr=_build_enforcement_target_ref()) + build_target=_build_enforcement_target_ref) ref = PROVIDERS.role_api.list_implied_roles(prior_role_id) implied_ids = [r['implied_role_id'] for r in ref] response_json = shared.role_inference_response(prior_role_id) @@ -216,7 +216,7 @@ class RoleImplicationResource(flask_restful.Resource): # Alternatively we can keep check_implied_role and reference # ._get_implied_role instead. ENFORCER.enforce_call(action='identity:check_implied_role', - target_attr=_build_enforcement_target_ref()) + build_target=_build_enforcement_target_ref) self.get(prior_role_id, implied_role_id) # NOTE(morgan): Our API here breaks HTTP Spec. This should be evaluated # for a future fix. This should just return the above "get" however, @@ -231,7 +231,7 @@ class RoleImplicationResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:get_implied_role', - target_attr=_build_enforcement_target_ref()) + build_target=_build_enforcement_target_ref) return self._get_implied_role(prior_role_id, implied_role_id) def _get_implied_role(self, prior_role_id, implied_role_id): @@ -255,7 +255,7 @@ class RoleImplicationResource(flask_restful.Resource): PUT /v3/roles/{prior_role_id}/implies/{implied_role_id} """ ENFORCER.enforce_call(action='identity:create_implied_role', - target_attr=_build_enforcement_target_ref()) + build_target=_build_enforcement_target_ref) PROVIDERS.role_api.create_implied_role(prior_role_id, implied_role_id) response_json = self._get_implied_role(prior_role_id, implied_role_id) return response_json, http_client.CREATED @@ -266,7 +266,7 @@ class RoleImplicationResource(flask_restful.Resource): DELETE /v3/roles/{prior_role_id}/implies/{implied_role_id} """ ENFORCER.enforce_call(action='identity:delete_implied_role', - target_attr=_build_enforcement_target_ref()) + build_target=_build_enforcement_target_ref) PROVIDERS.role_api.delete_implied_role(prior_role_id, implied_role_id) return None, http_client.NO_CONTENT diff --git a/keystone/api/system.py b/keystone/api/system.py index d04086991a..e9ee15d145 100644 --- a/keystone/api/system.py +++ b/keystone/api/system.py @@ -14,6 +14,7 @@ import flask import flask_restful +import functools from six.moves import http_client from keystone.common import json_home @@ -57,7 +58,7 @@ class SystemUsersListResource(flask_restful.Resource): GET/HEAD /system/users/{user_id}/roles """ ENFORCER.enforce_call(action='identity:list_system_grants_for_user', - target_attr=_build_enforcement_target()) + build_target=_build_enforcement_target) refs = PROVIDERS.assignment_api.list_system_grants_for_user(user_id) return ks_flask.ResourceBase.wrap_collection( refs, collection_name='roles') @@ -70,7 +71,7 @@ class SystemUsersResource(flask_restful.Resource): GET/HEAD /system/users/{user_id}/roles/{role_id} """ ENFORCER.enforce_call(action='identity:check_system_grant_for_user', - target_attr=_build_enforcement_target()) + build_target=_build_enforcement_target) PROVIDERS.assignment_api.check_system_grant_for_user(user_id, role_id) return None, http_client.NO_CONTENT @@ -80,7 +81,7 @@ class SystemUsersResource(flask_restful.Resource): PUT /system/users/{user_id}/roles/{role_id} """ ENFORCER.enforce_call(action='identity:create_system_grant_for_user', - target_attr=_build_enforcement_target()) + build_target=_build_enforcement_target) PROVIDERS.assignment_api.create_system_grant_for_user(user_id, role_id) return None, http_client.NO_CONTENT @@ -91,7 +92,9 @@ class SystemUsersResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:revoke_system_grant_for_user', - target_attr=_build_enforcement_target(allow_non_existing=True)) + build_target=functools.partial( + _build_enforcement_target, + allow_non_existing=True)) PROVIDERS.assignment_api.delete_system_grant_for_user(user_id, role_id) return None, http_client.NO_CONTENT @@ -103,7 +106,7 @@ class SystemGroupsRolesListResource(flask_restful.Resource): GET/HEAD /system/groups/{group_id}/roles """ ENFORCER.enforce_call(action='identity:list_system_grants_for_group', - target_attr=_build_enforcement_target()) + build_target=_build_enforcement_target) refs = PROVIDERS.assignment_api.list_system_grants_for_group(group_id) return ks_flask.ResourceBase.wrap_collection( refs, collection_name='roles') @@ -116,7 +119,7 @@ class SystemGroupsRolestResource(flask_restful.Resource): GET/HEAD /system/groups/{group_id}/roles/{role_id} """ ENFORCER.enforce_call(action='identity:check_system_grant_for_group', - target_attr=_build_enforcement_target()) + build_target=_build_enforcement_target) PROVIDERS.assignment_api.check_system_grant_for_group( group_id, role_id) return None, http_client.NO_CONTENT @@ -127,7 +130,7 @@ class SystemGroupsRolestResource(flask_restful.Resource): PUT /system/groups/{group_id}/roles/{role_id} """ ENFORCER.enforce_call(action='identity:create_system_grant_for_group', - target_attr=_build_enforcement_target()) + build_target=_build_enforcement_target) PROVIDERS.assignment_api.create_system_grant_for_group( group_id, role_id) return None, http_client.NO_CONTENT @@ -139,7 +142,9 @@ class SystemGroupsRolestResource(flask_restful.Resource): """ ENFORCER.enforce_call( action='identity:revoke_system_grant_for_group', - target_attr=_build_enforcement_target(allow_non_existing=True)) + build_target=functools.partial( + _build_enforcement_target, + allow_non_existing=True)) PROVIDERS.assignment_api.delete_system_grant_for_group( group_id, role_id) return None, http_client.NO_CONTENT diff --git a/keystone/common/rbac_enforcer/enforcer.py b/keystone/common/rbac_enforcer/enforcer.py index de7b694257..e408943240 100644 --- a/keystone/common/rbac_enforcer/enforcer.py +++ b/keystone/common/rbac_enforcer/enforcer.py @@ -245,7 +245,7 @@ class RBACEnforcer(object): @classmethod def enforce_call(cls, enforcer=None, action=None, target_attr=None, member_target_type=None, member_target=None, - filters=None): + filters=None, build_target=None): """Enforce RBAC on the current request. This will do some legwork and then instantiate the Enforcer if an @@ -282,6 +282,11 @@ class RBACEnforcer(object): various "list" APIs and are un-used in the default supplied policies. :type filters: iterable + :param build_target: A function to build the target for enforcement. + This is explicitly done after authentication + in order to not leak existance data before + auth. + :type build_target: function """ # NOTE(morgan) everything in the policy_dict may be used by the policy # DSL to action on RBAC and request information/response data. @@ -335,7 +340,7 @@ class RBACEnforcer(object): policy_dict.update(flask.request.view_args) # Get the Target Data Set. - if target_attr is None: + if target_attr is None and build_target is None: try: policy_dict.update(cls._extract_member_target_data( member_target_type, member_target)) @@ -362,7 +367,11 @@ class RBACEnforcer(object): policy_dict.setdefault('target', {}).update( subj_token_target_data) else: - policy_dict['target'] = target_attr + if target_attr and build_target: + raise ValueError('Programming Error: A target_attr or ' + 'build_target must be provided, but not both') + + policy_dict['target'] = target_attr or build_target() # Pull the data from the submitted json body to generate # appropriate input/target attributes, we take an explicit copy here diff --git a/keystone/tests/unit/common/test_rbac_enforcer.py b/keystone/tests/unit/common/test_rbac_enforcer.py index cb393f11c0..83ef883c3f 100644 --- a/keystone/tests/unit/common/test_rbac_enforcer.py +++ b/keystone/tests/unit/common/test_rbac_enforcer.py @@ -423,6 +423,43 @@ class TestRBACEnforcerRest(_TestRBACEnforcerBase): self.assertEqual({}, self.enforcer._extract_member_target_data( member_target={}, member_target_type=None)) + def test_call_build_enforcement_target(self): + assertIn = self.assertIn + assertEq = self.assertEqual + ref_uuid = uuid.uuid4().hex + + def _enforce_mock_func(credentials, action, target, + do_raise=True): + assertIn('target.domain.id', target) + assertEq(target['target.domain.id'], ref_uuid) + + def _build_enforcement_target(): + return {'domain': {'id': ref_uuid}} + + self.useFixture(fixtures.MockPatchObject( + self.enforcer, '_enforce', _enforce_mock_func)) + + argument_id = uuid.uuid4().hex + + with self.test_client() as c: + path = '/v3/auth/tokens' + body = self._auth_json() + + r = c.post( + path, + json=body, + follow_redirects=True, + expected_status_code=201) + + token_id = r.headers['X-Subject-Token'] + + c.get('%s/argument/%s' % (self.restful_api_url_prefix, + argument_id), + headers={'X-Auth-Token': token_id}) + self.enforcer.enforce_call( + action='example:allowed', + build_target=_build_enforcement_target) + def test_policy_enforcer_action_decorator(self): # Create a method that has an action pre-registered action = 'example:allowed'