From 665d5beda7e52916f4c97f26f63bce698dabdc28 Mon Sep 17 00:00:00 2001 From: Morgan Fainberg Date: Thu, 9 Aug 2018 14:24:00 -0700 Subject: [PATCH] Convert role_assignments API to flask native dispatching Convert the role_assignments API to flask native dispatching. Change-Id: I77f64f025d8dbc42e829bae626aace7deb2e7c6e Partial-Bug: #1776504 --- keystone/api/__init__.py | 3 + keystone/api/role_assignments.py | 355 +++++++++++++++++++++++++++ keystone/assignment/controllers.py | 334 ------------------------- keystone/assignment/routers.py | 9 +- keystone/server/flask/application.py | 1 + keystone/server/flask/common.py | 31 +++ 6 files changed, 391 insertions(+), 342 deletions(-) create mode 100644 keystone/api/role_assignments.py diff --git a/keystone/api/__init__.py b/keystone/api/__init__.py index 4bc5134d75..351d914dad 100644 --- a/keystone/api/__init__.py +++ b/keystone/api/__init__.py @@ -20,6 +20,7 @@ from keystone.api import os_revoke from keystone.api import os_simple_cert from keystone.api import regions from keystone.api import registered_limits +from keystone.api import role_assignments from keystone.api import role_inferences from keystone.api import roles from keystone.api import services @@ -36,6 +37,7 @@ __all__ = ( 'os_simple_cert', 'regions', 'registered_limits', + 'role_assignments', 'role_inferences', 'roles', 'services', @@ -53,6 +55,7 @@ __apis__ = ( os_simple_cert, regions, registered_limits, + role_assignments, role_inferences, roles, services, diff --git a/keystone/api/role_assignments.py b/keystone/api/role_assignments.py new file mode 100644 index 0000000000..10f71d3f9f --- /dev/null +++ b/keystone/api/role_assignments.py @@ -0,0 +1,355 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# This file handles all flask-restful resources for /v3/role_assignments + +import flask + +from keystone.common import provider_api +from keystone.common import rbac_enforcer +from keystone import exception +from keystone.i18n import _ +from keystone.server import flask as ks_flask + + +ENFORCER = rbac_enforcer.RBACEnforcer +PROVIDERS = provider_api.ProviderAPIs + + +class RoleAssignmentsResource(ks_flask.ResourceBase): + # TODO(henry-nash): The current implementation does not provide a full + # first class entity for role-assignment. There is no role_assignment_id + # and only the list_role_assignment call is supported. Further, since it + # is not a first class entity, the links for the individual entities + # reference the individual role grant APIs. + + collection_key = 'role_assignments' + member_key = 'role_assignment' + + def get(self): + """List all role assignments. + + GET/HEAD /v3/role_assignments + """ + if self.query_filter_is_true('include_subtree'): + return self._list_role_assignments_for_tree() + return self._list_role_assignments() + + def _list_role_assignments(self): + filters = [ + 'group.id', 'role.id', 'scope.domain.id', 'scope.project.id', + 'scope.OS-INHERIT:inherited_to', 'user.id' + ] + ENFORCER.enforce_call(action='identity:list_role_assignments', + filters=filters) + return self._build_role_assignments_list() + + def _list_role_assignments_for_tree(self): + filters = [ + 'group.id', 'role.id', 'scope.domain.id', 'scope.project.id', + 'scope.OS-INHERIT:inherited_to', 'user.id' + ] + target = {} + if 'scope.project.id' in flask.request.args: + project_id = flask.request.args['scope.project.id'] + if project_id: + target['project'] = PROVIDERS.resource_api.get_project( + project_id) + ENFORCER.enforce_call(action='identity:list_role_assignments_for_tree', + filters=filters, target_attr=target) + if not flask.request.args.get('scope.project.id'): + msg = _('scope.project.id must be specified if include_subtree ' + 'is also specified') + raise exception.ValidationError(message=msg) + return self._build_role_assignments_list(include_subtree=True) + + def _build_role_assignments_list(self, include_subtree=False): + """List role assignments to user and groups on domains and projects. + + Return a list of all existing role assignments in the system, filtered + by assignments attributes, if provided. + + If effective option is used and OS-INHERIT extension is enabled, the + following functions will be applied: + 1) For any group role assignment on a target, replace it by a set of + role assignments containing one for each user of that group on that + target; + 2) For any inherited role assignment for an actor on a target, replace + it by a set of role assignments for that actor on every project under + that target. + + It means that, if effective mode is used, no group or domain inherited + assignments will be present in the resultant list. Thus, combining + effective with them is invalid. + + As a role assignment contains only one actor and one target, providing + both user and group ids or domain and project ids is invalid as well. + """ + params = flask.request.args + include_names = self.query_filter_is_true('include_names') + + self._assert_domain_nand_project() + self._assert_system_nand_domain() + self._assert_system_nand_project() + self._assert_user_nand_group() + self._assert_effective_filters_if_needed() + + refs = PROVIDERS.assignment_api.list_role_assignments( + role_id=params.get('role.id'), + user_id=params.get('user.id'), + group_id=params.get('group.id'), + system=params.get('scope.system'), + domain_id=params.get('scope.domain.id'), + project_id=params.get('scope.project.id'), + include_subtree=include_subtree, + inherited=self._inherited, + effective=self._effective, + include_names=include_names) + formatted_refs = [self._format_entity(ref) for ref in refs] + return self.wrap_collection(formatted_refs) + + def _assert_domain_nand_project(self): + if (flask.request.args.get('scope.domain.id') and + flask.request.args.get('scope.project.id')): + msg = _('Specify a domain or project, not both') + raise exception.ValidationError(msg) + + def _assert_system_nand_domain(self): + if (flask.request.args.get('scope.domain.id') and + flask.request.args.get('scope.system')): + msg = _('Specify system or domain, not both') + raise exception.ValidationError(msg) + + def _assert_system_nand_project(self): + if (flask.request.args.get('scope.project.id') and + flask.request.args.get('scope.system')): + msg = _('Specify system or project, not both') + raise exception.ValidationError(msg) + + def _assert_user_nand_group(self): + if (flask.request.args.get('user.id') and + flask.request.args.get('group.id')): + msg = _('Specify a user or group, not both') + raise exception.ValidationError(msg) + + def _assert_effective_filters_if_needed(self): + """Assert that useless filter combinations are avoided. + + In effective mode, the following filter combinations are useless, since + they would always return an empty list of role assignments: + - group id, since no group assignment is returned in effective mode; + - domain id and inherited, since no domain inherited assignment is + returned in effective mode. + + """ + if self._effective: + if flask.request.args.get('group.id'): + msg = _('Combining effective and group filter will always ' + 'result in an empty list.') + raise exception.ValidationError(msg) + + if self._inherited and flask.request.args.get('scope.domain.id'): + msg = _( + 'Combining effective, domain and inherited filters will ' + 'always result in an empty list.') + raise exception.ValidationError(msg) + + @property + def _inherited(self): + inherited = None + req_args = flask.request.args + if 'scope.OS-INHERIT:inherited_to' in req_args: + inherited = req_args['scope.OS-INHERIT:inherited_to'] == 'projects' + return inherited + + @classmethod + def _add_self_referential_link(cls, ref, collection_name=None): + # NOTE(henry-nash): Since we are not yet a true collection, we override + # the wrapper as have already included the links in the entities + pass + + @property + def _effective(self): + return self.query_filter_is_true('effective') + + def _format_entity(self, entity): + """Format an assignment entity for API response. + + The driver layer returns entities as dicts containing the ids of the + actor (e.g. user or group), target (e.g. domain or project) and role. + If it is an inherited role, then this is also indicated. Examples: + + For a non-inherited expanded assignment from group membership: + {'user_id': user_id, + 'project_id': project_id, + 'role_id': role_id, + 'indirect': {'group_id': group_id}} + + or, for a project inherited role: + + {'user_id': user_id, + 'project_id': project_id, + 'role_id': role_id, + 'indirect': {'project_id': parent_id}} + + or, for a role that was implied by a prior role: + + {'user_id': user_id, + 'project_id': project_id, + 'role_id': role_id, + 'indirect': {'role_id': prior role_id}} + + It is possible to deduce if a role assignment came from group + membership if it has both 'user_id' in the main body of the dict and + 'group_id' in the 'indirect' subdict, as well as it is possible to + deduce if it has come from inheritance if it contains both a + 'project_id' in the main body of the dict and 'parent_id' in the + 'indirect' subdict. + + This function maps this into the format to be returned via the API, + e.g. for the second example above: + + { + 'user': { + {'id': user_id} + }, + 'scope': { + 'project': { + {'id': project_id} + }, + 'OS-INHERIT:inherited_to': 'projects' + }, + 'role': { + {'id': role_id} + }, + 'links': { + 'assignment': '/OS-INHERIT/projects/parent_id/users/user_id/' + 'roles/role_id/inherited_to_projects' + } + } + + """ + formatted_link = '' + formatted_entity = {'links': {}} + inherited_assignment = entity.get('inherited_to_projects') + + if 'project_id' in entity: + if 'project_name' in entity: + formatted_entity['scope'] = {'project': { + 'id': entity['project_id'], + 'name': entity['project_name'], + 'domain': {'id': entity['project_domain_id'], + 'name': entity['project_domain_name']}}} + else: + formatted_entity['scope'] = { + 'project': {'id': entity['project_id']}} + + if 'domain_id' in entity.get('indirect', {}): + inherited_assignment = True + formatted_link = ('/domains/%s' % + entity['indirect']['domain_id']) + elif 'project_id' in entity.get('indirect', {}): + inherited_assignment = True + formatted_link = ('/projects/%s' % + entity['indirect']['project_id']) + else: + formatted_link = '/projects/%s' % entity['project_id'] + elif 'domain_id' in entity: + if 'domain_name' in entity: + formatted_entity['scope'] = { + 'domain': {'id': entity['domain_id'], + 'name': entity['domain_name']}} + else: + formatted_entity['scope'] = { + 'domain': {'id': entity['domain_id']}} + formatted_link = '/domains/%s' % entity['domain_id'] + elif 'system' in entity: + formatted_link = '/system' + formatted_entity['scope'] = {'system': entity['system']} + + if 'user_id' in entity: + if 'user_name' in entity: + formatted_entity['user'] = { + 'id': entity['user_id'], + 'name': entity['user_name'], + 'domain': {'id': entity['user_domain_id'], + 'name': entity['user_domain_name']}} + else: + formatted_entity['user'] = {'id': entity['user_id']} + if 'group_id' in entity.get('indirect', {}): + membership_url = ( + ks_flask.base_url(path='/groups/%s/users/%s' % ( + entity['indirect']['group_id'], entity['user_id']))) + formatted_entity['links']['membership'] = membership_url + formatted_link += '/groups/%s' % entity['indirect']['group_id'] + else: + formatted_link += '/users/%s' % entity['user_id'] + elif 'group_id' in entity: + if 'group_name' in entity: + formatted_entity['group'] = { + 'id': entity['group_id'], + 'name': entity['group_name'], + 'domain': {'id': entity['group_domain_id'], + 'name': entity['group_domain_name']}} + else: + formatted_entity['group'] = {'id': entity['group_id']} + formatted_link += '/groups/%s' % entity['group_id'] + + if 'role_name' in entity: + formatted_entity['role'] = {'id': entity['role_id'], + 'name': entity['role_name']} + if 'role_domain_id' in entity and 'role_domain_name' in entity: + formatted_entity['role'].update( + {'domain': {'id': entity['role_domain_id'], + 'name': entity['role_domain_name']}}) + else: + formatted_entity['role'] = {'id': entity['role_id']} + prior_role_link = '' + if 'role_id' in entity.get('indirect', {}): + formatted_link += '/roles/%s' % entity['indirect']['role_id'] + prior_role_link = ( + '/prior_role/%(prior)s/implies/%(implied)s' % { + 'prior': entity['role_id'], + 'implied': entity['indirect']['role_id'] + }) + else: + formatted_link += '/roles/%s' % entity['role_id'] + + if inherited_assignment: + formatted_entity['scope']['OS-INHERIT:inherited_to'] = ( + 'projects') + formatted_link = ('/OS-INHERIT%s/inherited_to_projects' % + formatted_link) + + formatted_entity['links']['assignment'] = ks_flask.base_url( + path=formatted_link) + if prior_role_link: + formatted_entity['links']['prior_role'] = ( + ks_flask.base_url(path=prior_role_link)) + + return formatted_entity + + +class RoleAssignmentsAPI(ks_flask.APIBase): + _name = 'role_assignments' + _import_name = __name__ + resources = [] + resource_mapping = [ + ks_flask.construct_resource_map( + resource=RoleAssignmentsResource, + url='/role_assignments', + resource_kwargs={}, + rel='role_assignments') + ] + + +APIs = (RoleAssignmentsAPI,) diff --git a/keystone/assignment/controllers.py b/keystone/assignment/controllers.py index e791e9c7ee..d46fd8544a 100644 --- a/keystone/assignment/controllers.py +++ b/keystone/assignment/controllers.py @@ -269,337 +269,3 @@ class GrantAssignmentV3(controller.V3Controller): PROVIDERS.assignment_api.delete_system_grant_for_group( group_id, role_id ) - - -class RoleAssignmentV3(controller.V3Controller): - """The V3 Role Assignment APIs, really just list_role_assignment().""" - - # TODO(henry-nash): The current implementation does not provide a full - # first class entity for role-assignment. There is no role_assignment_id - # and only the list_role_assignment call is supported. Further, since it - # is not a first class entity, the links for the individual entities - # reference the individual role grant APIs. - - collection_name = 'role_assignments' - member_name = 'role_assignment' - - @classmethod - def wrap_member(cls, context, ref): - # NOTE(henry-nash): Since we are not yet a true collection, we override - # the wrapper as have already included the links in the entities - pass - - def _format_entity(self, context, entity): - """Format an assignment entity for API response. - - The driver layer returns entities as dicts containing the ids of the - actor (e.g. user or group), target (e.g. domain or project) and role. - If it is an inherited role, then this is also indicated. Examples: - - For a non-inherited expanded assignment from group membership: - {'user_id': user_id, - 'project_id': project_id, - 'role_id': role_id, - 'indirect': {'group_id': group_id}} - - or, for a project inherited role: - - {'user_id': user_id, - 'project_id': project_id, - 'role_id': role_id, - 'indirect': {'project_id': parent_id}} - - or, for a role that was implied by a prior role: - - {'user_id': user_id, - 'project_id': project_id, - 'role_id': role_id, - 'indirect': {'role_id': prior role_id}} - - It is possible to deduce if a role assignment came from group - membership if it has both 'user_id' in the main body of the dict and - 'group_id' in the 'indirect' subdict, as well as it is possible to - deduce if it has come from inheritance if it contains both a - 'project_id' in the main body of the dict and 'parent_id' in the - 'indirect' subdict. - - This function maps this into the format to be returned via the API, - e.g. for the second example above: - - { - 'user': { - {'id': user_id} - }, - 'scope': { - 'project': { - {'id': project_id} - }, - 'OS-INHERIT:inherited_to': 'projects' - }, - 'role': { - {'id': role_id} - }, - 'links': { - 'assignment': '/OS-INHERIT/projects/parent_id/users/user_id/' - 'roles/role_id/inherited_to_projects' - } - } - - """ - formatted_link = '' - formatted_entity = {'links': {}} - inherited_assignment = entity.get('inherited_to_projects') - - if 'project_id' in entity: - if 'project_name' in entity: - formatted_entity['scope'] = {'project': { - 'id': entity['project_id'], - 'name': entity['project_name'], - 'domain': {'id': entity['project_domain_id'], - 'name': entity['project_domain_name']}}} - else: - formatted_entity['scope'] = { - 'project': {'id': entity['project_id']}} - - if 'domain_id' in entity.get('indirect', {}): - inherited_assignment = True - formatted_link = ('/domains/%s' % - entity['indirect']['domain_id']) - elif 'project_id' in entity.get('indirect', {}): - inherited_assignment = True - formatted_link = ('/projects/%s' % - entity['indirect']['project_id']) - else: - formatted_link = '/projects/%s' % entity['project_id'] - elif 'domain_id' in entity: - if 'domain_name' in entity: - formatted_entity['scope'] = { - 'domain': {'id': entity['domain_id'], - 'name': entity['domain_name']}} - else: - formatted_entity['scope'] = { - 'domain': {'id': entity['domain_id']}} - formatted_link = '/domains/%s' % entity['domain_id'] - elif 'system' in entity: - formatted_link = '/system' - formatted_entity['scope'] = {'system': entity['system']} - - if 'user_id' in entity: - if 'user_name' in entity: - formatted_entity['user'] = { - 'id': entity['user_id'], - 'name': entity['user_name'], - 'domain': {'id': entity['user_domain_id'], - 'name': entity['user_domain_name']}} - else: - formatted_entity['user'] = {'id': entity['user_id']} - if 'group_id' in entity.get('indirect', {}): - membership_url = ( - self.base_url(context, '/groups/%s/users/%s' % ( - entity['indirect']['group_id'], entity['user_id']))) - formatted_entity['links']['membership'] = membership_url - formatted_link += '/groups/%s' % entity['indirect']['group_id'] - else: - formatted_link += '/users/%s' % entity['user_id'] - elif 'group_id' in entity: - if 'group_name' in entity: - formatted_entity['group'] = { - 'id': entity['group_id'], - 'name': entity['group_name'], - 'domain': {'id': entity['group_domain_id'], - 'name': entity['group_domain_name']}} - else: - formatted_entity['group'] = {'id': entity['group_id']} - formatted_link += '/groups/%s' % entity['group_id'] - - if 'role_name' in entity: - formatted_entity['role'] = {'id': entity['role_id'], - 'name': entity['role_name']} - if 'role_domain_id' in entity and 'role_domain_name' in entity: - formatted_entity['role'].update( - {'domain': {'id': entity['role_domain_id'], - 'name': entity['role_domain_name']}}) - else: - formatted_entity['role'] = {'id': entity['role_id']} - prior_role_link = '' - if 'role_id' in entity.get('indirect', {}): - formatted_link += '/roles/%s' % entity['indirect']['role_id'] - prior_role_link = ( - '/prior_role/%(prior)s/implies/%(implied)s' % { - 'prior': entity['role_id'], - 'implied': entity['indirect']['role_id'] - }) - else: - formatted_link += '/roles/%s' % entity['role_id'] - - if inherited_assignment: - formatted_entity['scope']['OS-INHERIT:inherited_to'] = ( - 'projects') - formatted_link = ('/OS-INHERIT%s/inherited_to_projects' % - formatted_link) - - formatted_entity['links']['assignment'] = self.base_url(context, - formatted_link) - if prior_role_link: - formatted_entity['links']['prior_role'] = ( - self.base_url(context, prior_role_link)) - - return formatted_entity - - def _assert_effective_filters(self, inherited, group, domain): - """Assert that useless filter combinations are avoided. - - In effective mode, the following filter combinations are useless, since - they would always return an empty list of role assignments: - - group id, since no group assignment is returned in effective mode; - - domain id and inherited, since no domain inherited assignment is - returned in effective mode. - - """ - if group: - msg = _('Combining effective and group filter will always ' - 'result in an empty list.') - raise exception.ValidationError(msg) - - if inherited and domain: - msg = _('Combining effective, domain and inherited filters will ' - 'always result in an empty list.') - raise exception.ValidationError(msg) - - def _assert_domain_nand_project(self, domain_id, project_id): - if domain_id and project_id: - msg = _('Specify a domain or project, not both') - raise exception.ValidationError(msg) - - def _assert_system_nand_domain(self, system, domain_id): - if system and domain_id: - msg = _('Specify system or domain, not both') - raise exception.ValidationError(msg) - - def _assert_system_nand_project(self, system, project_id): - if system and project_id: - msg = _('Specify system or project, not both') - raise exception.ValidationError(msg) - - def _assert_user_nand_group(self, user_id, group_id): - if user_id and group_id: - msg = _('Specify a user or group, not both') - raise exception.ValidationError(msg) - - def _list_role_assignments(self, request, filters, include_subtree=False): - """List role assignments to user and groups on domains and projects. - - Return a list of all existing role assignments in the system, filtered - by assignments attributes, if provided. - - If effective option is used and OS-INHERIT extension is enabled, the - following functions will be applied: - 1) For any group role assignment on a target, replace it by a set of - role assignments containing one for each user of that group on that - target; - 2) For any inherited role assignment for an actor on a target, replace - it by a set of role assignments for that actor on every project under - that target. - - It means that, if effective mode is used, no group or domain inherited - assignments will be present in the resultant list. Thus, combining - effective with them is invalid. - - As a role assignment contains only one actor and one target, providing - both user and group ids or domain and project ids is invalid as well. - - """ - params = request.params - effective = 'effective' in params and ( - self.query_filter_is_true(params['effective'])) - include_names = ('include_names' in params and - self.query_filter_is_true(params['include_names'])) - - if 'scope.OS-INHERIT:inherited_to' in params: - inherited = ( - params['scope.OS-INHERIT:inherited_to'] == 'projects') - else: - # None means querying both inherited and direct assignments - inherited = None - - self._assert_domain_nand_project(params.get('scope.domain.id'), - params.get('scope.project.id')) - self._assert_system_nand_domain( - params.get('scope.system'), params.get('scope.domain.id') - ) - self._assert_system_nand_project( - params.get('scope.system'), params.get('scope.project.id') - ) - self._assert_user_nand_group(params.get('user.id'), - params.get('group.id')) - - if effective: - self._assert_effective_filters(inherited=inherited, - group=params.get('group.id'), - domain=params.get( - 'scope.domain.id')) - - refs = PROVIDERS.assignment_api.list_role_assignments( - role_id=params.get('role.id'), - user_id=params.get('user.id'), - group_id=params.get('group.id'), - system=params.get('scope.system'), - domain_id=params.get('scope.domain.id'), - project_id=params.get('scope.project.id'), - include_subtree=include_subtree, - inherited=inherited, effective=effective, - include_names=include_names) - - formatted_refs = [self._format_entity(request.context_dict, ref) - for ref in refs] - - return self.wrap_collection(request.context_dict, formatted_refs) - - @controller.filterprotected('group.id', 'role.id', 'scope.system', - 'scope.domain.id', 'scope.project.id', - 'scope.OS-INHERIT:inherited_to', 'user.id') - def list_role_assignments(self, request, filters): - return self._list_role_assignments(request, filters) - - def _check_list_tree_protection(self, request, protection_info): - """Check protection for list assignment for tree API. - - The policy rule might want to inspect the domain of any project filter - so if one is defined, then load the project ref and pass it to the - check protection method. - - """ - ref = {} - for filter, value in protection_info.get('filter_attr', {}).items(): - if filter == 'scope.project.id' and value: - ref['project'] = PROVIDERS.resource_api.get_project(value) - - self.check_protection(request, protection_info, ref) - - @controller.filterprotected('group.id', 'role.id', - 'scope.domain.id', 'scope.project.id', - 'scope.OS-INHERIT:inherited_to', 'user.id', - callback=_check_list_tree_protection) - def list_role_assignments_for_tree(self, request, filters): - if not request.params.get('scope.project.id'): - msg = _('scope.project.id must be specified if include_subtree ' - 'is also specified') - raise exception.ValidationError(message=msg) - return self._list_role_assignments(request, filters, - include_subtree=True) - - def list_role_assignments_wrapper(self, request): - """Main entry point from router for list role assignments. - - Since we want different policy file rules to be applicable based on - whether there the include_subtree query parameter is part of the API - call, this method checks for this and then calls the appropriate - protected entry point. - - """ - params = request.params - if 'include_subtree' in params and ( - self.query_filter_is_true(params['include_subtree'])): - return self.list_role_assignments_for_tree(request) - else: - return self.list_role_assignments(request) diff --git a/keystone/assignment/routers.py b/keystone/assignment/routers.py index 3d12f06ce5..ee021ddfb6 100644 --- a/keystone/assignment/routers.py +++ b/keystone/assignment/routers.py @@ -38,8 +38,7 @@ class Public(wsgi.ComposableRouter): class Routers(wsgi.RoutersBase): - _path_prefixes = ('users', 'projects', - 'domains', 'system', 'role_assignments', 'OS-INHERIT') + _path_prefixes = ('users', 'projects', 'domains', 'system', 'OS-INHERIT') def append_v3_routers(self, mapper, routers): @@ -177,12 +176,6 @@ class Routers(wsgi.RoutersBase): 'group_id': json_home.Parameters.GROUP_ID }) - self._add_resource( - mapper, controllers.RoleAssignmentV3(), - path='/role_assignments', - get_head_action='list_role_assignments_wrapper', - rel=json_home.build_v3_resource_relation('role_assignments')) - self._add_resource( mapper, grant_controller, path='/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/' diff --git a/keystone/server/flask/application.py b/keystone/server/flask/application.py index 5a0dab7d37..e0b63a2233 100644 --- a/keystone/server/flask/application.py +++ b/keystone/server/flask/application.py @@ -50,6 +50,7 @@ _MOVED_API_PREFIXES = frozenset( 'limits', 'regions', 'registered_limits', + 'role_assignments', 'role_inferences', 'roles', 'services', diff --git a/keystone/server/flask/common.py b/keystone/server/flask/common.py index 7233d06da3..9836ca76bf 100644 --- a/keystone/server/flask/common.py +++ b/keystone/server/flask/common.py @@ -702,6 +702,37 @@ class ResourceBase(flask_restful.Resource): """ return build_audit_initiator() + @staticmethod + def query_filter_is_true(filter_name): + """Determine if bool query param is 'True'. + + We treat this the same way as we do for policy + enforcement: + + {bool_param}=0 is treated as False + + Any other value is considered to be equivalent to + True, including the absence of a value (but existence + as a parameter). + + False Examples for param named `p`: + + * http://host/url + * http://host/url?p=0 + + All other forms of the param 'p' would be result in a True value + including: `http://host/url?param`. + """ + val = False + if filter_name in flask.request.args: + filter_value = flask.request.args.get(filter_name) + if (isinstance(filter_value, six.string_types) and + filter_value == '0'): + val = False + else: + val = True + return val + @property def request_body_json(self): return flask.request.get_json(silent=True, force=True) or {}