Convert role_assignments API to flask native dispatching
Convert the role_assignments API to flask native dispatching. Change-Id: I77f64f025d8dbc42e829bae626aace7deb2e7c6e Partial-Bug: #1776504
This commit is contained in:
parent
e1381fe3f5
commit
665d5beda7
@ -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,
|
||||
|
355
keystone/api/role_assignments.py
Normal file
355
keystone/api/role_assignments.py
Normal file
@ -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,)
|
@ -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)
|
||||
|
@ -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/'
|
||||
|
@ -50,6 +50,7 @@ _MOVED_API_PREFIXES = frozenset(
|
||||
'limits',
|
||||
'regions',
|
||||
'registered_limits',
|
||||
'role_assignments',
|
||||
'role_inferences',
|
||||
'roles',
|
||||
'services',
|
||||
|
@ -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 {}
|
||||
|
Loading…
Reference in New Issue
Block a user