Merge "Convert OS-INHERIT API to flask native dispatching"

This commit is contained in:
Zuul 2018-09-15 00:15:32 +00:00 committed by Gerrit Code Review
commit 95f11874dd
6 changed files with 412 additions and 88 deletions

View File

@ -17,6 +17,7 @@ from keystone.api import groups
from keystone.api import limits
from keystone.api import os_ep_filter
from keystone.api import os_federation
from keystone.api import os_inherit
from keystone.api import os_oauth1
from keystone.api import os_revoke
from keystone.api import os_simple_cert
@ -38,6 +39,7 @@ __all__ = (
'limits',
'os_ep_filter',
'os_federation',
'os_inherit',
'os_oauth1',
'os_revoke',
'os_simple_cert',
@ -60,6 +62,7 @@ __apis__ = (
limits,
os_ep_filter,
os_federation,
os_inherit,
os_oauth1,
os_revoke,
os_simple_cert,

View File

@ -65,3 +65,8 @@ os_federation_resource_rel_func = functools.partial(
os_federation_parameter_rel_func = functools.partial(
json_home.build_v3_extension_parameter_relation,
extension_name='OS-FEDERATION', extension_version='1.0')
# OS-INHERIT "extension"
os_inherit_resource_rel_func = functools.partial(
json_home.build_v3_extension_resource_relation,
extension_name='OS-INHERIT', extension_version='1.0')

399
keystone/api/os_inherit.py Normal file
View File

@ -0,0 +1,399 @@
# 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/OS-INHERIT
import flask_restful
from oslo_log import log
from six.moves import http_client
from keystone.api._shared import json_home_relations
from keystone.common import json_home
from keystone.common import provider_api
from keystone.common import rbac_enforcer
from keystone import exception
from keystone.server import flask as ks_flask
ENFORCER = rbac_enforcer.RBACEnforcer
PROVIDERS = provider_api.ProviderAPIs
LOG = log.getLogger(__name__)
_build_resource_relation = json_home_relations.os_inherit_resource_rel_func
def _build_enforcement_target_attr(role_id=None, user_id=None, group_id=None,
project_id=None, domain_id=None,
allow_non_existing=False):
"""Check protection for role grant APIs.
The policy rule might want to inspect attributes of any of the entities
involved in the grant. So we get these and pass them to the
check_protection() handler in the controller.
"""
# !!!!!!!!!! WARNING: Security Concern !!!!!!!!!!
#
# NOTE(morgan): This function must handle all expected exceptions,
# including NOT FOUNDs otherwise the exception will be raised up to the
# end user before enforcement, resulting in the exception being returned
# instead of an appropriate 403. In each case, it is logged that a value
# was not found and the target is explicitly set to empty. This allows for
# the enforcement rule to decide what to do (most of the time raise an
# appropriate 403).
#
# ###############################################
target = {}
if role_id:
try:
target['role'] = PROVIDERS.role_api.get_role(role_id)
except exception.RoleNotFound:
LOG.info('Role (%(role_id)s) not found, Enforcement target of '
'`role` remaind empty', {'role_id': role_id})
target['role'] = {}
if user_id:
try:
target['user'] = PROVIDERS.identity_api.get_user(user_id)
except exception.UserNotFound:
if not allow_non_existing:
LOG.info('User (%(user_id)s) was not found. Enforcement target'
' of `user` remains empty.', {'user_id': user_id})
target['user'] = {}
else:
try:
target['group'] = PROVIDERS.identity_api.get_group(group_id)
except exception.GroupNotFound:
if not allow_non_existing:
LOG.info('Group (%(group_id)s) was not found. Enforcement '
'target of `group` remains empty.',
{'group_id': group_id})
target['group'] = {}
# NOTE(lbragstad): This if/else check will need to be expanded in the
# future to handle system hierarchies if that is implemented.
if domain_id:
try:
target['domain'] = PROVIDERS.resource_api.get_domain(domain_id)
except exception.DomainNotFound:
LOG.info('Domain (%(domain_id)s) was not found. Enforcement '
'target of `domain` remains empty.',
{'domain_id': domain_id})
target['domain'] = {}
elif project_id:
try:
target['project'] = PROVIDERS.resource_api.get_project(project_id)
except exception.ProjectNotFound:
LOG.info('Project (%(project_id)s) was not found. Enforcement '
'target of `project` remains empty.',
{'project_id': project_id})
target['project'] = {}
return target
class OSInheritDomainGroupRolesResource(flask_restful.Resource):
def get(self, domain_id, group_id, role_id):
"""Check for an inherited grant for a group on a domain.
GET/HEAD /OS-INHERIT/domains/{domain_id}/groups/{group_id}
/roles/{role_id}/inherited_to_projects
"""
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))
PROVIDERS.assignment_api.get_grant(
domain_id=domain_id, group_id=group_id, role_id=role_id,
inherited_to_projects=True)
return None, http_client.NO_CONTENT
def put(self, domain_id, group_id, role_id):
"""Create an inherited grant for a group on a domain.
PUT /OS-INHERIT/domains/{domain_id}/groups/{group_id}
/roles/{role_id}/inherited_to_projects
"""
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))
PROVIDERS.assignment_api.create_grant(
domain_id=domain_id, group_id=group_id, role_id=role_id,
inherited_to_projects=True)
return None, http_client.NO_CONTENT
def delete(self, domain_id, group_id, role_id):
"""Revoke an inherited grant for a group on a domain.
DELETE /OS-INHERIT/domains/{domain_id}/groups/{group_id}
/roles/{role_id}/inherited_to_projects
"""
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))
PROVIDERS.assignment_api.delete_grant(
domain_id=domain_id, group_id=group_id, role_id=role_id,
inherited_to_projects=True)
return None, http_client.NO_CONTENT
class OSInheritDomainGroupRolesListResource(flask_restful.Resource):
def get(self, domain_id, group_id):
"""List roles (inherited) for a group on a domain.
GET/HEAD /OS-INHERIT/domains/{domain_id}/groups/{group_id}
/roles/inherited_to_projects
"""
ENFORCER.enforce_call(
action='identity:list_grants',
target_attr=_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(
refs, collection_name='roles')
class OSInheritDomainUserRolesResource(flask_restful.Resource):
def get(self, domain_id, user_id, role_id):
"""Check for an inherited grant for a user on a domain.
GET/HEAD /OS-INHERIT/domains/{domain_id}/users/{user_id}/roles
/{role_id}/inherited_to_projects
"""
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))
PROVIDERS.assignment_api.get_grant(
domain_id=domain_id, user_id=user_id, role_id=role_id,
inherited_to_projects=True)
return None, http_client.NO_CONTENT
def put(self, domain_id, user_id, role_id):
"""Create an inherited grant for a user on a domain.
PUT /OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}
/inherited_to_projects
"""
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))
PROVIDERS.assignment_api.create_grant(
domain_id=domain_id, user_id=user_id, role_id=role_id,
inherited_to_projects=True)
return None, http_client.NO_CONTENT
def delete(self, domain_id, user_id, role_id):
"""Revoke a grant from a user on a domain.
DELETE /OS-INHERIT/domains/{domain_id}/users/{user_id}/roles
/{role_id}/inherited_to_projects
"""
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))
PROVIDERS.assignment_api.delete_grant(
domain_id=domain_id, user_id=user_id, role_id=role_id,
inherited_to_projects=True)
return None, http_client.NO_CONTENT
class OSInheritDomainUserRolesListResource(flask_restful.Resource):
def get(self, domain_id, user_id):
"""List roles (inherited) for a user on a domain.
GET/HEAD /OS-INHERIT/domains/{domain_id}/users/{user_id}
/roles/inherited_to_projects
"""
ENFORCER.enforce_call(
action='identity:list_grants',
target_attr=_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(
refs, collection_name='roles')
class OSInheritProjectUserResource(flask_restful.Resource):
def get(self, project_id, user_id, role_id):
"""Check for an inherited grant for a user on a project.
GET/HEAD /OS-INHERIT/projects/{project_id}/users/{user_id}
/roles/{role_id}/inherited_to_projects
"""
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))
PROVIDERS.assignment_api.get_grant(
project_id=project_id, user_id=user_id, role_id=role_id,
inherited_to_projects=True)
return None, http_client.NO_CONTENT
def put(self, project_id, user_id, role_id):
"""Create an inherited grant for a user on a project.
PUT /OS-INHERIT/projects/{project_id}/users/{user_id}
/roles/{role_id}/inherited_to_projects
"""
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))
PROVIDERS.assignment_api.create_grant(
project_id=project_id, user_id=user_id, role_id=role_id,
inherited_to_projects=True)
return None, http_client.NO_CONTENT
def delete(self, project_id, user_id, role_id):
"""Revoke an inherited grant for a user on a project.
DELETE /OS-INHERIT/projects/{project_id}/users/{user_id}
/roles/{role_id}/inherited_to_projects
"""
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))
PROVIDERS.assignment_api.delete_grant(
project_id=project_id, user_id=user_id, role_id=role_id,
inherited_to_projects=True)
return None, http_client.NO_CONTENT
class OSInheritProjectGroupResource(flask_restful.Resource):
def get(self, project_id, group_id, role_id):
"""Check for an inherited grant for a group on a project.
GET/HEAD /OS-INHERIT/projects/{project_id}/groups/{group_id}
/roles/{role_id}/inherited_to_projects
"""
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))
PROVIDERS.assignment_api.get_grant(
project_id=project_id, group_id=group_id, role_id=role_id,
inherited_to_projects=True)
return None, http_client.NO_CONTENT
def put(self, project_id, group_id, role_id):
"""Create an inherited grant for a group on a project.
PUT /OS-INHERIT/projects/{project_id}/groups/{group_id}
/roles/{role_id}/inherited_to_projects
"""
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))
PROVIDERS.assignment_api.create_grant(
project_id=project_id, group_id=group_id, role_id=role_id,
inherited_to_projects=True)
return None, http_client.NO_CONTENT
def delete(self, project_id, group_id, role_id):
"""Revoke an inherited grant for a group on a project.
DELETE /OS-INHERIT/projects/{project_id}/groups/{group_id}
/roles/{role_id}/inherited_to_projects
"""
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))
PROVIDERS.assignment_api.delete_grant(
project_id=project_id, group_id=group_id, role_id=role_id,
inherited_to_projects=True)
return None, http_client.NO_CONTENT
class OSInheritAPI(ks_flask.APIBase):
_name = "OS-INHERIT"
_import_name = __name__
_api_url_prefix = '/OS-INHERIT'
resources = []
resource_mapping = [
ks_flask.construct_resource_map(
resource=OSInheritDomainGroupRolesResource,
url=('/domains/<string:domain_id>/groups/<string:group_id>/roles'
'/<string:role_id>/inherited_to_projects'),
resource_kwargs={},
rel='domain_group_role_inherited_to_projects',
resource_relation_func=_build_resource_relation,
path_vars={
'domain_id': json_home.Parameters.DOMAIN_ID,
'group_id': json_home.Parameters.GROUP_ID,
'role_id': json_home.Parameters.ROLE_ID}),
ks_flask.construct_resource_map(
resource=OSInheritDomainGroupRolesListResource,
url=('/domains/<string:domain_id>/groups/<string:group_id>/roles'
'/inherited_to_projects'),
resource_kwargs={},
rel='domain_group_roles_inherited_to_projects',
resource_relation_func=_build_resource_relation,
path_vars={
'domain_id': json_home.Parameters.DOMAIN_ID,
'group_id': json_home.Parameters.GROUP_ID}),
ks_flask.construct_resource_map(
resource=OSInheritDomainUserRolesResource,
url=('/domains/<string:domain_id>/users/<string:user_id>/roles'
'/<string:role_id>/inherited_to_projects'),
resource_kwargs={},
rel='domain_user_role_inherited_to_projects',
resource_relation_func=_build_resource_relation,
path_vars={
'domain_id': json_home.Parameters.DOMAIN_ID,
'user_id': json_home.Parameters.USER_ID,
'role_id': json_home.Parameters.ROLE_ID}),
ks_flask.construct_resource_map(
resource=OSInheritDomainUserRolesListResource,
url=('/domains/<string:domain_id>/users/<string:user_id>/roles'
'/inherited_to_projects'),
resource_kwargs={},
rel='domain_user_roles_inherited_to_projects',
resource_relation_func=_build_resource_relation,
path_vars={
'domain_id': json_home.Parameters.DOMAIN_ID,
'user_id': json_home.Parameters.USER_ID}),
ks_flask.construct_resource_map(
resource=OSInheritProjectUserResource,
url=('projects/<string:project_id>/users/<string:user_id>/roles'
'/<string:role_id>/inherited_to_projects'),
resource_kwargs={},
rel='project_user_role_inherited_to_projects',
resource_relation_func=_build_resource_relation,
path_vars={
'project_id': json_home.Parameters.PROJECT_ID,
'user_id': json_home.Parameters.USER_ID,
'role_id': json_home.Parameters.ROLE_ID}),
ks_flask.construct_resource_map(
resource=OSInheritProjectGroupResource,
url=('projects/<string:project_id>/groups/<string:group_id>/roles'
'/<string:role_id>/inherited_to_projects'),
resource_kwargs={},
rel='project_group_role_inherited_to_projects',
resource_relation_func=_build_resource_relation,
path_vars={
'project_id': json_home.Parameters.PROJECT_ID,
'group_id': json_home.Parameters.GROUP_ID,
'role_id': json_home.Parameters.ROLE_ID})
]
APIs = (OSInheritAPI,)

View File

@ -15,18 +15,11 @@
"""WSGI Routers for the Assignment service."""
import functools
from keystone.assignment import controllers
from keystone.common import json_home
from keystone.common import wsgi
build_os_inherit_relation = functools.partial(
json_home.build_v3_extension_resource_relation,
extension_name='OS-INHERIT', extension_version='1.0')
class Public(wsgi.ComposableRouter):
def add_routes(self, mapper):
tenant_controller = controllers.TenantAssignment()
@ -38,7 +31,7 @@ class Public(wsgi.ComposableRouter):
class Routers(wsgi.RoutersBase):
_path_prefixes = ('users', 'projects', 'domains', 'OS-INHERIT')
_path_prefixes = ('users', 'projects', 'domains')
def append_v3_routers(self, mapper, routers):
@ -137,81 +130,3 @@ class Routers(wsgi.RoutersBase):
'domain_id': json_home.Parameters.DOMAIN_ID,
'group_id': json_home.Parameters.GROUP_ID,
})
self._add_resource(
mapper, grant_controller,
path='/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/'
'{role_id}/inherited_to_projects',
get_head_action='check_grant',
put_action='create_grant',
delete_action='revoke_grant',
rel=build_os_inherit_relation(
resource_name='domain_user_role_inherited_to_projects'),
path_vars={
'domain_id': json_home.Parameters.DOMAIN_ID,
'role_id': json_home.Parameters.ROLE_ID,
'user_id': json_home.Parameters.USER_ID,
})
self._add_resource(
mapper, grant_controller,
path='/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/'
'{role_id}/inherited_to_projects',
get_head_action='check_grant',
put_action='create_grant',
delete_action='revoke_grant',
rel=build_os_inherit_relation(
resource_name='domain_group_role_inherited_to_projects'),
path_vars={
'domain_id': json_home.Parameters.DOMAIN_ID,
'group_id': json_home.Parameters.GROUP_ID,
'role_id': json_home.Parameters.ROLE_ID,
})
self._add_resource(
mapper, grant_controller,
path='/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/'
'inherited_to_projects',
get_head_action='list_grants',
rel=build_os_inherit_relation(
resource_name='domain_group_roles_inherited_to_projects'),
path_vars={
'domain_id': json_home.Parameters.DOMAIN_ID,
'group_id': json_home.Parameters.GROUP_ID,
})
self._add_resource(
mapper, grant_controller,
path='/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/'
'inherited_to_projects',
get_head_action='list_grants',
rel=build_os_inherit_relation(
resource_name='domain_user_roles_inherited_to_projects'),
path_vars={
'domain_id': json_home.Parameters.DOMAIN_ID,
'user_id': json_home.Parameters.USER_ID,
})
self._add_resource(
mapper, grant_controller,
path='/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/'
'{role_id}/inherited_to_projects',
get_head_action='check_grant',
put_action='create_grant',
delete_action='revoke_grant',
rel=build_os_inherit_relation(
resource_name='project_user_role_inherited_to_projects'),
path_vars={
'project_id': json_home.Parameters.PROJECT_ID,
'user_id': json_home.Parameters.USER_ID,
'role_id': json_home.Parameters.ROLE_ID,
})
self._add_resource(
mapper, grant_controller,
path='/OS-INHERIT/projects/{project_id}/groups/{group_id}/'
'roles/{role_id}/inherited_to_projects',
get_head_action='check_grant',
put_action='create_grant',
delete_action='revoke_grant',
rel=build_os_inherit_relation(
resource_name='project_group_role_inherited_to_projects'),
path_vars={
'project_id': json_home.Parameters.PROJECT_ID,
'group_id': json_home.Parameters.GROUP_ID,
'role_id': json_home.Parameters.ROLE_ID,
})

View File

@ -41,9 +41,10 @@ _MOVED_API_PREFIXES = frozenset(
['credentials',
'endpoints',
'groups',
'OS-OAUTH1',
'OS-EP-FILTER',
'OS-FEDERATION',
'OS-INHERIT',
'OS-OAUTH1',
'OS-REVOKE',
'OS-SIMPLE-CERT',
'OS-TRUST',

View File

@ -1693,7 +1693,8 @@ class AssignmentInheritanceTestCase(test_v3.RestfulTestCase,
# Define URLs
direct_url = '%s/users/%s/roles/%s' % (
target_url, self.user_id, role['id'])
inherited_url = '/OS-INHERIT/%s/inherited_to_projects' % direct_url
inherited_url = ('/OS-INHERIT/%s/inherited_to_projects' %
direct_url.lstrip('/'))
# Create the direct assignment
self.put(direct_url)