# keystone.api.os_inherit

# Source code for keystone.api.os_inherit

    #    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 functools
    import http.client

    import flask_restful
    from oslo_log import log

    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',
                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,
            )
            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',
                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,
            )
            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',
                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,
            )
            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',
                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(
                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',
                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,
            )
            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',
                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,
            )
            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',
                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,
            )
            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',
                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(
                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',
                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,
            )
            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',
                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,
            )
            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',
                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,
            )
            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',
                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,
            )
            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',
                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,
            )
            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',
                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,
            )
            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,)

this page last updated: 2026-08-17 12:45:24

Except where otherwise noted, this document is licensed under Creative Commons Attribution 3.0 License. See all OpenStack Legal Documents.

found an error? report a bug