# keystone.federation.core

# Source code for keystone.federation.core

    # 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.

    """Main entry point into the Federation service."""

    import uuid

    from oslo_log import log

    from keystone.common import cache
    from keystone.common import driver_hints
    from keystone.common import manager
    from keystone.common import provider_api
    import keystone.conf
    from keystone import exception
    from keystone.federation import utils
    from keystone.i18n import _
    from keystone import notifications

    LOG = log.getLogger(__name__)

    # This is a general cache region for service providers.
    MEMOIZE = cache.get_memoization_decorator(group='federation')

    CONF = keystone.conf.CONF
    PROVIDERS = provider_api.ProviderAPIs

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

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

        """

        driver_namespace = 'keystone.federation'
        _provides_api = 'federation_api'

        def __init__(self):
            super().__init__(CONF.federation.driver)
            notifications.register_event_callback(
                notifications.ACTIONS.internal,
                notifications.DOMAIN_DELETED,
                self._cleanup_identity_provider,
            )

        def _cleanup_identity_provider(
            self, service, resource_type, operation, payload
        ):
            domain_id = payload['resource_info']
            hints = driver_hints.Hints()
            hints.add_filter('domain_id', domain_id)
            idps = self.driver.list_idps(hints=hints)
            for idp in idps:
                try:
                    self.delete_idp(idp['id'])
                except exception.IdentityProviderNotFound:
                    LOG.debug(
                        (
                            'Identity Provider %(idpid)s not found when '
                            'deleting domain contents for %(domainid)s, '
                            'continuing with cleanup.'
                        ),
                        {'idpid': idp['id'], 'domainid': domain_id},
                    )

        def create_idp(self, idp_id, idp):
            auto_created_domain = False
            if not idp.get('domain_id'):
                idp['domain_id'] = self._create_idp_domain(idp_id)
                auto_created_domain = True
            else:
                self._assert_valid_domain_id(idp['domain_id'])

            try:
                return self.driver.create_idp(idp_id, idp)
            except exception.Conflict:
                # If there is a conflict storing the Identity Provider in the
                # backend, then we need to make sure we clean up the domain we just
                # created for it and raise the Conflict exception afterwards.
                if auto_created_domain:
                    self._cleanup_idp_domain(idp['domain_id'])
                raise

        def delete_idp(self, idp_id):
            self.driver.delete_idp(idp_id)
            # NOTE(lbragstad): If an identity provider is removed from the system,
            # then we need to invalidate the token cache. Otherwise it will be
            # possible for federated tokens to be considered valid after a service
            # provider removes a federated identity provider resource.
            reason = (
                'The token cache is being invalidated because identity provider '
                f'{idp_id} has been deleted. Authorization for federated users '
                'will be recalculated and enforced accordingly the next time '
                'they authenticate or validate a token.'
            )
            notifications.invalidate_token_cache_notification(reason)

        def _cleanup_idp_domain(self, domain_id):
            domain = {'enabled': False}
            PROVIDERS.resource_api.update_domain(domain_id, domain)
            PROVIDERS.resource_api.delete_domain(domain_id)

        def _create_idp_domain(self, idp_id):
            domain_id = uuid.uuid4().hex
            desc = 'Auto generated federated domain for Identity Provider: '
            desc += idp_id
            domain = {
                'id': domain_id,
                'name': domain_id,
                'description': desc,
                'enabled': True,
            }
            PROVIDERS.resource_api.create_domain(domain['id'], domain)
            return domain_id

        def _assert_valid_domain_id(self, domain_id):
            PROVIDERS.resource_api.get_domain(domain_id)

        @MEMOIZE
        def get_enabled_service_providers(self):
            """List enabled service providers for Service Catalog.

            Service Provider in a catalog contains three attributes: ``id``,
            ``auth_url``, ``sp_url``, where:

            - id is a unique, user defined identifier for service provider object
            - auth_url is an authentication URL of remote Keystone
            - sp_url a URL accessible at the remote service provider where SAML
              assertion is transmitted.

            :returns: list of dictionaries with enabled service providers
            :rtype: list of dicts

            """

            def normalize(sp):
                ref = {'auth_url': sp.auth_url, 'id': sp.id, 'sp_url': sp.sp_url}
                return ref

            service_providers = self.driver.get_enabled_service_providers()
            return [normalize(sp) for sp in service_providers]

        def create_sp(self, sp_id, service_provider):
            sp_ref = self.driver.create_sp(sp_id, service_provider)
            self.get_enabled_service_providers.invalidate(self)
            return sp_ref

        def delete_sp(self, sp_id):
            self.driver.delete_sp(sp_id)
            self.get_enabled_service_providers.invalidate(self)

        def update_sp(self, sp_id, service_provider):
            sp_ref = self.driver.update_sp(sp_id, service_provider)
            self.get_enabled_service_providers.invalidate(self)
            return sp_ref

        def evaluate(self, idp_id, protocol_id, assertion_data):
            mapping = self.get_mapping_from_idp_and_protocol(idp_id, protocol_id)

            rule_processor = utils.create_attribute_mapping_rules_processor(
                mapping
            )

            mapped_properties = rule_processor.process(assertion_data)
            return mapped_properties, mapping['id']

        def create_protocol(self, idp_id, protocol_id, protocol):
            self._validate_mapping_exists(protocol['mapping_id'])
            return self.driver.create_protocol(idp_id, protocol_id, protocol)

        def delete_protocol(self, idp_id, protocol_id):
            hints = driver_hints.Hints()
            hints.add_filter('protocol_id', protocol_id)

            self.driver.delete_protocol(idp_id, protocol_id)

        def update_protocol(self, idp_id, protocol_id, protocol):
            self._validate_mapping_exists(protocol['mapping_id'])
            return self.driver.update_protocol(idp_id, protocol_id, protocol)

        def _validate_mapping_exists(self, mapping_id):
            try:
                self.driver.get_mapping(mapping_id)
            except exception.MappingNotFound:
                msg = _('Invalid mapping id: %s')
                raise exception.ValidationError(message=(msg % mapping_id))

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

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