# keystone.endpoint_policy.core

# Source code for keystone.endpoint_policy.core

    # Copyright 2014 IBM Corp.
    #
    # 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.

    from oslo_log import log

    from keystone.common import manager
    from keystone.common import provider_api
    import keystone.conf
    from keystone import exception
    from keystone.i18n import _

    CONF = keystone.conf.CONF
    LOG = log.getLogger(__name__)
    PROVIDERS = provider_api.ProviderAPIs

    class Manager(manager.Manager):
        """Default pivot point for the Endpoint Policy backend.

        See :mod:`keystone.common.manager.Manager` for more details on how this
        dynamically calls the backend.

        """

        driver_namespace = 'keystone.endpoint_policy'
        _provides_api = 'endpoint_policy_api'

        def __init__(self):
            super().__init__(CONF.endpoint_policy.driver)

        def _assert_valid_association(self, endpoint_id, service_id, region_id):
            """Assert that the association is supported.

            There are three types of association supported:

            - Endpoint (in which case service and region must be None)
            - Service and region (in which endpoint must be None)
            - Service (in which case endpoint and region must be None)

            """
            if (
                endpoint_id is not None
                and service_id is None
                and region_id is None
            ):
                return
            if (
                service_id is not None
                and region_id is not None
                and endpoint_id is None
            ):
                return
            if (
                service_id is not None
                and endpoint_id is None
                and region_id is None
            ):
                return

            raise exception.InvalidPolicyAssociation(
                endpoint_id=endpoint_id, service_id=service_id, region_id=region_id
            )

        def create_policy_association(
            self, policy_id, endpoint_id=None, service_id=None, region_id=None
        ):
            self._assert_valid_association(endpoint_id, service_id, region_id)
            self.driver.create_policy_association(
                policy_id, endpoint_id, service_id, region_id
            )

        def check_policy_association(
            self, policy_id, endpoint_id=None, service_id=None, region_id=None
        ):
            self._assert_valid_association(endpoint_id, service_id, region_id)
            self.driver.check_policy_association(
                policy_id, endpoint_id, service_id, region_id
            )

        def delete_policy_association(
            self, policy_id, endpoint_id=None, service_id=None, region_id=None
        ):
            self._assert_valid_association(endpoint_id, service_id, region_id)
            self.driver.delete_policy_association(
                policy_id, endpoint_id, service_id, region_id
            )

        def list_endpoints_for_policy(self, policy_id):
            def _get_endpoint(endpoint_id, policy_id):
                try:
                    return PROVIDERS.catalog_api.get_endpoint(endpoint_id)
                except exception.EndpointNotFound:
                    msg = (
                        'Endpoint %(endpoint_id)s referenced in '
                        'association for policy %(policy_id)s not found.'
                    )
                    LOG.warning(
                        msg, {'policy_id': policy_id, 'endpoint_id': endpoint_id}
                    )
                    raise

            def _get_endpoints_for_service(service_id, endpoints):
                # TODO(henry-nash): Consider optimizing this in the future by
                # adding an explicit list_endpoints_for_service to the catalog API.
                return [ep for ep in endpoints if ep['service_id'] == service_id]

            def _get_endpoints_for_service_and_region(
                service_id, region_id, endpoints, regions
            ):
                # TODO(henry-nash): Consider optimizing this in the future.
                # The lack of a two-way pointer in the region tree structure
                # makes this somewhat inefficient.

                def _recursively_get_endpoints_for_region(
                    region_id,
                    service_id,
                    endpoint_list,
                    region_list,
                    endpoints_found,
                    regions_examined,
                ):
                    """Recursively search down a region tree for endpoints.

                    :param region_id: the point in the tree to examine
                    :param service_id: the service we are interested in
                    :param endpoint_list: list of all endpoints
                    :param region_list: list of all regions
                    :param endpoints_found: list of matching endpoints found so
                                            far - which will be updated if more are
                                            found in this iteration
                    :param regions_examined: list of regions we have already looked
                                             at - used to spot illegal circular
                                             references in the tree to avoid never
                                             completing search
                    :returns: list of endpoints that match

                    """
                    if region_id in regions_examined:
                        msg = (
                            'Circular reference or a repeated entry found '
                            'in region tree - %(region_id)s.'
                        )
                        LOG.error(msg, {'region_id': ref.region_id})
                        return

                    regions_examined.append(region_id)
                    endpoints_found += [
                        ep
                        for ep in endpoint_list
                        if ep['service_id'] == service_id
                        and ep['region_id'] == region_id
                    ]

                    for region in region_list:
                        if region['parent_region_id'] == region_id:
                            _recursively_get_endpoints_for_region(
                                region['id'],
                                service_id,
                                endpoints,
                                regions,
                                endpoints_found,
                                regions_examined,
                            )

                endpoints_found = []
                regions_examined = []

                # Now walk down the region tree
                _recursively_get_endpoints_for_region(
                    region_id,
                    service_id,
                    endpoints,
                    regions,
                    endpoints_found,
                    regions_examined,
                )

                return endpoints_found

            matching_endpoints = []
            endpoints = PROVIDERS.catalog_api.list_endpoints()
            regions = PROVIDERS.catalog_api.list_regions()
            for ref in self.list_associations_for_policy(policy_id):
                if ref.get('endpoint_id') is not None:
                    matching_endpoints.append(
                        _get_endpoint(ref['endpoint_id'], policy_id)
                    )
                    continue

                if (
                    ref.get('service_id') is not None
                    and ref.get('region_id') is None
                ):
                    matching_endpoints += _get_endpoints_for_service(
                        ref['service_id'], endpoints
                    )
                    continue

                if (
                    ref.get('service_id') is not None
                    and ref.get('region_id') is not None
                ):
                    matching_endpoints += _get_endpoints_for_service_and_region(
                        ref['service_id'], ref['region_id'], endpoints, regions
                    )
                    continue

                msg = (
                    'Unsupported policy association found - '
                    'Policy %(policy_id)s, Endpoint %(endpoint_id)s, '
                    'Service %(service_id)s, Region %(region_id)s, '
                )
                LOG.warning(
                    msg,
                    {
                        'policy_id': policy_id,
                        'endpoint_id': ref['endpoint_id'],
                        'service_id': ref['service_id'],
                        'region_id': ref['region_id'],
                    },
                )

            return matching_endpoints

        def get_policy_for_endpoint(self, endpoint_id):
            def _get_policy(policy_id, endpoint_id):
                try:
                    return PROVIDERS.policy_api.get_policy(policy_id)
                except exception.PolicyNotFound:
                    msg = (
                        'Policy %(policy_id)s referenced in association '
                        'for endpoint %(endpoint_id)s not found.'
                    )
                    LOG.warning(
                        msg, {'policy_id': policy_id, 'endpoint_id': endpoint_id}
                    )
                    raise

            def _look_for_policy_for_region_and_service(endpoint):
                """Look in the region and its parents for a policy.

                Examine the region of the endpoint for a policy appropriate for
                the service of the endpoint. If there isn't a match, then chase up
                the region tree to find one.

                """
                region_id = endpoint['region_id']
                regions_examined = []
                while region_id is not None:
                    try:
                        ref = self.get_policy_association(
                            service_id=endpoint['service_id'], region_id=region_id
                        )
                        return ref['policy_id']
                    except exception.PolicyAssociationNotFound:  # nosec
                        # There wasn't one for that region & service, handle below.
                        pass

                    # There wasn't one for that region & service, let's
                    # chase up the region tree
                    regions_examined.append(region_id)
                    region = PROVIDERS.catalog_api.get_region(region_id)
                    region_id = None
                    if region.get('parent_region_id') is not None:
                        region_id = region['parent_region_id']
                        if region_id in regions_examined:
                            msg = (
                                'Circular reference or a repeated entry '
                                'found in region tree - %(region_id)s.'
                            )
                            LOG.error(msg, {'region_id': region_id})
                            break

            # First let's see if there is a policy explicitly defined for
            # this endpoint.

            try:
                ref = self.get_policy_association(endpoint_id=endpoint_id)
                return _get_policy(ref['policy_id'], endpoint_id)
            except exception.PolicyAssociationNotFound:  # nosec
                # There wasn't a policy explicitly defined for this endpoint,
                # handled below.
                pass

            # There wasn't a policy explicitly defined for this endpoint, so
            # now let's see if there is one for the Region & Service.

            endpoint = PROVIDERS.catalog_api.get_endpoint(endpoint_id)
            policy_id = _look_for_policy_for_region_and_service(endpoint)
            if policy_id is not None:
                return _get_policy(policy_id, endpoint_id)

            # Finally, just check if there is one for the service.
            try:
                ref = self.get_policy_association(
                    service_id=endpoint['service_id']
                )
                return _get_policy(ref['policy_id'], endpoint_id)
            except exception.PolicyAssociationNotFound:  # nosec
                # No policy is associated with endpoint, handled below.
                pass

            msg = _('No policy is associated with endpoint %(endpoint_id)s.') % {
                'endpoint_id': endpoint_id
            }
            raise exception.NotFound(msg)

this page last updated: 2026-06-11 21:52:50

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