517 lines
16 KiB
Python
517 lines
16 KiB
Python
# 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 abc
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import versionutils
|
|
import six
|
|
|
|
from keystone.common import dependency
|
|
from keystone.common import extension
|
|
from keystone.common import manager
|
|
from keystone import exception
|
|
from keystone.federation import utils
|
|
|
|
|
|
CONF = cfg.CONF
|
|
EXTENSION_DATA = {
|
|
'name': 'OpenStack Federation APIs',
|
|
'namespace': 'http://docs.openstack.org/identity/api/ext/'
|
|
'OS-FEDERATION/v1.0',
|
|
'alias': 'OS-FEDERATION',
|
|
'updated': '2013-12-17T12:00:0-00:00',
|
|
'description': 'OpenStack Identity Providers Mechanism.',
|
|
'links': [{
|
|
'rel': 'describedby',
|
|
'type': 'text/html',
|
|
'href': 'http://specs.openstack.org/openstack/keystone-specs/api/v3/'
|
|
'identity-api-v3-os-federation-ext.html',
|
|
}]}
|
|
extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
|
|
extension.register_public_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
|
|
|
|
|
|
@dependency.provider('federation_api')
|
|
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'
|
|
|
|
def __init__(self):
|
|
super(Manager, self).__init__(CONF.federation.driver)
|
|
|
|
# Make sure it is a driver version we support, and if it is a legacy
|
|
# driver, then wrap it.
|
|
if isinstance(self.driver, FederationDriverV8):
|
|
self.driver = V9FederationWrapperForV8Driver(self.driver)
|
|
elif not isinstance(self.driver, FederationDriverV9):
|
|
raise exception.UnsupportedDriverVersion(
|
|
driver=CONF.federation.driver)
|
|
|
|
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 evaluate(self, idp_id, protocol_id, assertion_data):
|
|
mapping = self.get_mapping_from_idp_and_protocol(idp_id, protocol_id)
|
|
rules = mapping['rules']
|
|
rule_processor = utils.RuleProcessor(rules)
|
|
mapped_properties = rule_processor.process(assertion_data)
|
|
return mapped_properties, mapping['id']
|
|
|
|
|
|
# The FederationDriverBase class is the set of driver methods from earlier
|
|
# drivers that we still support, that have not been removed or modified. This
|
|
# class is then used to created the augmented V8 and V9 version abstract driver
|
|
# classes, without having to duplicate a lot of abstract method signatures.
|
|
# If you remove a method from V9, then move the abstact methods from this Base
|
|
# class to the V8 class. Do not modify any of the method signatures in the Base
|
|
# class - changes should only be made in the V8 and subsequent classes.
|
|
|
|
@six.add_metaclass(abc.ABCMeta)
|
|
class FederationDriverBase(object):
|
|
|
|
@abc.abstractmethod
|
|
def create_idp(self, idp_id, idp):
|
|
"""Create an identity provider.
|
|
|
|
:returns: idp_ref
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def delete_idp(self, idp_id):
|
|
"""Delete an identity provider.
|
|
|
|
:raises keystone.exception.IdentityProviderNotFound: If the IdP
|
|
doesn't exist.
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def list_idps(self):
|
|
"""List all identity providers.
|
|
|
|
:raises keystone.exception.IdentityProviderNotFound: If the IdP
|
|
doesn't exist.
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def get_idp(self, idp_id):
|
|
"""Get an identity provider by ID.
|
|
|
|
:raises keystone.exception.IdentityProviderNotFound: If the IdP
|
|
doesn't exist.
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def get_idp_from_remote_id(self, remote_id):
|
|
"""Get an identity provider by remote ID.
|
|
|
|
:raises keystone.exception.IdentityProviderNotFound: If the IdP
|
|
doesn't exist.
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def update_idp(self, idp_id, idp):
|
|
"""Update an identity provider by ID.
|
|
|
|
:raises keystone.exception.IdentityProviderNotFound: If the IdP
|
|
doesn't exist.
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def create_protocol(self, idp_id, protocol_id, protocol):
|
|
"""Add an IdP-Protocol configuration.
|
|
|
|
:raises keystone.exception.IdentityProviderNotFound: If the IdP
|
|
doesn't exist.
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def update_protocol(self, idp_id, protocol_id, protocol):
|
|
"""Change an IdP-Protocol configuration.
|
|
|
|
:raises keystone.exception.IdentityProviderNotFound: If the IdP
|
|
doesn't exist.
|
|
:raises keystone.exception.FederatedProtocolNotFound: If the federated
|
|
protocol cannot be found.
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def get_protocol(self, idp_id, protocol_id):
|
|
"""Get an IdP-Protocol configuration.
|
|
|
|
:raises keystone.exception.IdentityProviderNotFound: If the IdP
|
|
doesn't exist.
|
|
:raises keystone.exception.FederatedProtocolNotFound: If the federated
|
|
protocol cannot be found.
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def list_protocols(self, idp_id):
|
|
"""List an IdP's supported protocols.
|
|
|
|
:raises keystone.exception.IdentityProviderNotFound: If the IdP
|
|
doesn't exist.
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def delete_protocol(self, idp_id, protocol_id):
|
|
"""Delete an IdP-Protocol configuration.
|
|
|
|
:raises keystone.exception.IdentityProviderNotFound: If the IdP
|
|
doesn't exist.
|
|
:raises keystone.exception.FederatedProtocolNotFound: If the federated
|
|
protocol cannot be found.
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def create_mapping(self, mapping_id, mapping):
|
|
"""Create a mapping.
|
|
|
|
:param mapping_id: id of mapping ref
|
|
:type mapping_id: string
|
|
:param mapping: mapping ref with mapping name
|
|
:type mapping: dict
|
|
:returns: mapping ref
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def delete_mapping(self, mapping_id):
|
|
"""Delete a mapping.
|
|
|
|
:param mapping_id: id of mapping to delete
|
|
:type mapping_ref: string
|
|
:returns: None
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def update_mapping(self, mapping_id, mapping_ref):
|
|
"""Update a mapping.
|
|
|
|
:param mapping_id: id of mapping to update
|
|
:type mapping_id: string
|
|
:param mapping_ref: new mapping ref
|
|
:type mapping_ref: dict
|
|
:returns: mapping_ref
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def list_mappings(self):
|
|
"""List all mappings.
|
|
|
|
:returns: list of mappings
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def get_mapping(self, mapping_id):
|
|
"""Get a mapping, returns the mapping based
|
|
on mapping_id.
|
|
|
|
:param mapping_id: id of mapping to get
|
|
:type mapping_ref: string
|
|
:returns: mapping_ref
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def get_mapping_from_idp_and_protocol(self, idp_id, protocol_id):
|
|
"""Get mapping based on idp_id and protocol_id.
|
|
|
|
:param idp_id: id of the identity provider
|
|
:type idp_id: string
|
|
:param protocol_id: id of the protocol
|
|
:type protocol_id: string
|
|
:raises keystone.exception.IdentityProviderNotFound: If the IdP
|
|
doesn't exist.
|
|
:raises keystone.exception.FederatedProtocolNotFound: If the federated
|
|
protocol cannot be found.
|
|
:returns: mapping_ref
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def create_sp(self, sp_id, sp):
|
|
"""Create a service provider.
|
|
|
|
:param sp_id: id of the service provider
|
|
:type sp_id: string
|
|
:param sp: service prvider object
|
|
:type sp: dict
|
|
|
|
:returns: sp_ref
|
|
:rtype: dict
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def delete_sp(self, sp_id):
|
|
"""Delete a service provider.
|
|
|
|
:param sp_id: id of the service provider
|
|
:type sp_id: string
|
|
|
|
:raises keystone.exception.ServiceProviderNotFound: If the service
|
|
provider doesn't exist.
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def list_sps(self):
|
|
"""List all service providers.
|
|
|
|
:returns: List of sp_ref objects
|
|
:rtype: list of dicts
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def get_sp(self, sp_id):
|
|
"""Get a service provider.
|
|
|
|
:param sp_id: id of the service provider
|
|
:type sp_id: string
|
|
|
|
:returns: sp_ref
|
|
:raises keystone.exception.ServiceProviderNotFound: If the service
|
|
provider doesn't exist.
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
@abc.abstractmethod
|
|
def update_sp(self, sp_id, sp):
|
|
"""Update a service provider.
|
|
|
|
:param sp_id: id of the service provider
|
|
:type sp_id: string
|
|
:param sp: service prvider object
|
|
:type sp: dict
|
|
|
|
:returns: sp_ref
|
|
:rtype: dict
|
|
|
|
:raises keystone.exception.ServiceProviderNotFound: If the service
|
|
provider doesn't exist.
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
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
|
|
|
|
"""
|
|
raise exception.NotImplemented() # pragma: no cover
|
|
|
|
|
|
class FederationDriverV8(FederationDriverBase):
|
|
"""Removed or redefined methods from V8.
|
|
|
|
Move the abstract methods of any methods removed or modified in later
|
|
versions of the driver from FederationDriverBase to here. We maintain this
|
|
so that legacy drivers, which will be a subclass of FederationDriverV8, can
|
|
still reference them.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
class FederationDriverV9(FederationDriverBase):
|
|
"""New or redefined methods from V8.
|
|
|
|
Add any new V9 abstract methods (or those with modified signatures) to
|
|
this class.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
class V9FederationWrapperForV8Driver(FederationDriverV9):
|
|
"""Wrapper class to supported a V8 legacy driver.
|
|
|
|
In order to support legacy drivers without having to make the manager code
|
|
driver-version aware, we wrap legacy drivers so that they look like the
|
|
latest version. For the various changes made in a new driver, here are the
|
|
actions needed in this wrapper:
|
|
|
|
Method removed from new driver - remove the call-through method from this
|
|
class, since the manager will no longer be
|
|
calling it.
|
|
Method signature (or meaning) changed - wrap the old method in a new
|
|
signature here, and munge the input
|
|
and output parameters accordingly.
|
|
New method added to new driver - add a method to implement the new
|
|
functionality here if possible. If that is
|
|
not possible, then return NotImplemented,
|
|
since we do not guarantee to support new
|
|
functionality with legacy drivers.
|
|
|
|
"""
|
|
|
|
@versionutils.deprecated(
|
|
as_of=versionutils.deprecated.MITAKA,
|
|
what='keystone.federation.FederationDriverV8',
|
|
in_favor_of='keystone.federation.FederationDriverV9',
|
|
remove_in=+2)
|
|
def __init__(self, wrapped_driver):
|
|
self.driver = wrapped_driver
|
|
|
|
def create_idp(self, idp_id, idp):
|
|
return self.driver.create_idp(idp_id, idp)
|
|
|
|
def delete_idp(self, idp_id):
|
|
self.driver.delete_idp(idp_id)
|
|
|
|
def list_idps(self):
|
|
return self.driver.list_idps()
|
|
|
|
def get_idp(self, idp_id):
|
|
return self.driver.get_idp(idp_id)
|
|
|
|
def get_idp_from_remote_id(self, remote_id):
|
|
return self.driver.get_idp_from_remote_id(remote_id)
|
|
|
|
def update_idp(self, idp_id, idp):
|
|
return self.driver.update_idp(idp_id, idp)
|
|
|
|
def create_protocol(self, idp_id, protocol_id, protocol):
|
|
return self.driver.create_protocol(idp_id, protocol_id, protocol)
|
|
|
|
def update_protocol(self, idp_id, protocol_id, protocol):
|
|
return self.driver.update_protocol(idp_id, protocol_id, protocol)
|
|
|
|
def get_protocol(self, idp_id, protocol_id):
|
|
return self.driver.get_protocol(idp_id, protocol_id)
|
|
|
|
def list_protocols(self, idp_id):
|
|
return self.driver.list_protocols(idp_id)
|
|
|
|
def delete_protocol(self, idp_id, protocol_id):
|
|
self.driver.delete_protocol(idp_id, protocol_id)
|
|
|
|
def create_mapping(self, mapping_id, mapping):
|
|
return self.driver.create_mapping(mapping_id, mapping)
|
|
|
|
def delete_mapping(self, mapping_id):
|
|
self.driver.delete_mapping(mapping_id)
|
|
|
|
def update_mapping(self, mapping_id, mapping_ref):
|
|
return self.driver.update_mapping(mapping_id, mapping_ref)
|
|
|
|
def list_mappings(self):
|
|
return self.driver.list_mappings()
|
|
|
|
def get_mapping(self, mapping_id):
|
|
return self.driver.get_mapping(mapping_id)
|
|
|
|
def get_mapping_from_idp_and_protocol(self, idp_id, protocol_id):
|
|
return self.driver.get_mapping_from_idp_and_protocol(
|
|
idp_id, protocol_id)
|
|
|
|
def create_sp(self, sp_id, sp):
|
|
return self.driver.create_sp(sp_id, sp)
|
|
|
|
def delete_sp(self, sp_id):
|
|
self.driver.delete_sp(sp_id)
|
|
|
|
def list_sps(self):
|
|
return self.driver.list_sps()
|
|
|
|
def get_sp(self, sp_id):
|
|
return self.driver.get_sp(sp_id)
|
|
|
|
def update_sp(self, sp_id, sp):
|
|
return self.driver.update_sp(sp_id, sp)
|
|
|
|
def get_enabled_service_providers(self):
|
|
return self.driver.get_enabled_service_providers()
|
|
|
|
|
|
Driver = manager.create_legacy_driver(FederationDriverV8)
|