aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/federation
diff options
context:
space:
mode:
Diffstat (limited to 'keystone-moon/keystone/federation')
-rw-r--r--keystone-moon/keystone/federation/V8_backends/__init__.py0
-rw-r--r--keystone-moon/keystone/federation/V8_backends/sql.py389
-rw-r--r--keystone-moon/keystone/federation/__init__.py15
-rw-r--r--keystone-moon/keystone/federation/backends/__init__.py0
-rw-r--r--keystone-moon/keystone/federation/backends/sql.py393
-rw-r--r--keystone-moon/keystone/federation/constants.py15
-rw-r--r--keystone-moon/keystone/federation/controllers.py519
-rw-r--r--keystone-moon/keystone/federation/core.py611
-rw-r--r--keystone-moon/keystone/federation/idp.py615
-rw-r--r--keystone-moon/keystone/federation/routers.py252
-rw-r--r--keystone-moon/keystone/federation/schema.py115
-rw-r--r--keystone-moon/keystone/federation/utils.py872
12 files changed, 0 insertions, 3796 deletions
diff --git a/keystone-moon/keystone/federation/V8_backends/__init__.py b/keystone-moon/keystone/federation/V8_backends/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/keystone-moon/keystone/federation/V8_backends/__init__.py
+++ /dev/null
diff --git a/keystone-moon/keystone/federation/V8_backends/sql.py b/keystone-moon/keystone/federation/V8_backends/sql.py
deleted file mode 100644
index d6b42aa0..00000000
--- a/keystone-moon/keystone/federation/V8_backends/sql.py
+++ /dev/null
@@ -1,389 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-#
-# 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 oslo_serialization import jsonutils
-import six
-from sqlalchemy import orm
-
-from keystone.common import sql
-from keystone import exception
-from keystone.federation import core
-from keystone.i18n import _
-
-
-LOG = log.getLogger(__name__)
-
-
-class FederationProtocolModel(sql.ModelBase, sql.DictBase):
- __tablename__ = 'federation_protocol'
- attributes = ['id', 'idp_id', 'mapping_id']
- mutable_attributes = frozenset(['mapping_id'])
-
- id = sql.Column(sql.String(64), primary_key=True)
- idp_id = sql.Column(sql.String(64), sql.ForeignKey('identity_provider.id',
- ondelete='CASCADE'), primary_key=True)
- mapping_id = sql.Column(sql.String(64), nullable=False)
-
- @classmethod
- def from_dict(cls, dictionary):
- new_dictionary = dictionary.copy()
- return cls(**new_dictionary)
-
- def to_dict(self):
- """Return a dictionary with model's attributes."""
- d = dict()
- for attr in self.__class__.attributes:
- d[attr] = getattr(self, attr)
- return d
-
-
-class IdentityProviderModel(sql.ModelBase, sql.DictBase):
- __tablename__ = 'identity_provider'
- attributes = ['id', 'enabled', 'description', 'remote_ids']
- mutable_attributes = frozenset(['description', 'enabled', 'remote_ids'])
-
- id = sql.Column(sql.String(64), primary_key=True)
- enabled = sql.Column(sql.Boolean, nullable=False)
- description = sql.Column(sql.Text(), nullable=True)
- remote_ids = orm.relationship('IdPRemoteIdsModel',
- order_by='IdPRemoteIdsModel.remote_id',
- cascade='all, delete-orphan')
-
- @classmethod
- def from_dict(cls, dictionary):
- new_dictionary = dictionary.copy()
- remote_ids_list = new_dictionary.pop('remote_ids', None)
- if not remote_ids_list:
- remote_ids_list = []
- identity_provider = cls(**new_dictionary)
- remote_ids = []
- # NOTE(fmarco76): the remote_ids_list contains only remote ids
- # associated with the IdP because of the "relationship" established in
- # sqlalchemy and corresponding to the FK in the idp_remote_ids table
- for remote in remote_ids_list:
- remote_ids.append(IdPRemoteIdsModel(remote_id=remote))
- identity_provider.remote_ids = remote_ids
- return identity_provider
-
- def to_dict(self):
- """Return a dictionary with model's attributes."""
- d = dict()
- for attr in self.__class__.attributes:
- d[attr] = getattr(self, attr)
- d['remote_ids'] = []
- for remote in self.remote_ids:
- d['remote_ids'].append(remote.remote_id)
- return d
-
-
-class IdPRemoteIdsModel(sql.ModelBase, sql.DictBase):
- __tablename__ = 'idp_remote_ids'
- attributes = ['idp_id', 'remote_id']
- mutable_attributes = frozenset(['idp_id', 'remote_id'])
-
- idp_id = sql.Column(sql.String(64),
- sql.ForeignKey('identity_provider.id',
- ondelete='CASCADE'))
- remote_id = sql.Column(sql.String(255),
- primary_key=True)
-
- @classmethod
- def from_dict(cls, dictionary):
- new_dictionary = dictionary.copy()
- return cls(**new_dictionary)
-
- def to_dict(self):
- """Return a dictionary with model's attributes."""
- d = dict()
- for attr in self.__class__.attributes:
- d[attr] = getattr(self, attr)
- return d
-
-
-class MappingModel(sql.ModelBase, sql.DictBase):
- __tablename__ = 'mapping'
- attributes = ['id', 'rules']
-
- id = sql.Column(sql.String(64), primary_key=True)
- rules = sql.Column(sql.JsonBlob(), nullable=False)
-
- @classmethod
- def from_dict(cls, dictionary):
- new_dictionary = dictionary.copy()
- new_dictionary['rules'] = jsonutils.dumps(new_dictionary['rules'])
- return cls(**new_dictionary)
-
- def to_dict(self):
- """Return a dictionary with model's attributes."""
- d = dict()
- for attr in self.__class__.attributes:
- d[attr] = getattr(self, attr)
- d['rules'] = jsonutils.loads(d['rules'])
- return d
-
-
-class ServiceProviderModel(sql.ModelBase, sql.DictBase):
- __tablename__ = 'service_provider'
- attributes = ['auth_url', 'id', 'enabled', 'description',
- 'relay_state_prefix', 'sp_url']
- mutable_attributes = frozenset(['auth_url', 'description', 'enabled',
- 'relay_state_prefix', 'sp_url'])
-
- id = sql.Column(sql.String(64), primary_key=True)
- enabled = sql.Column(sql.Boolean, nullable=False)
- description = sql.Column(sql.Text(), nullable=True)
- auth_url = sql.Column(sql.String(256), nullable=False)
- sp_url = sql.Column(sql.String(256), nullable=False)
- relay_state_prefix = sql.Column(sql.String(256), nullable=False)
-
- @classmethod
- def from_dict(cls, dictionary):
- new_dictionary = dictionary.copy()
- return cls(**new_dictionary)
-
- def to_dict(self):
- """Return a dictionary with model's attributes."""
- d = dict()
- for attr in self.__class__.attributes:
- d[attr] = getattr(self, attr)
- return d
-
-
-class Federation(core.FederationDriverV8):
-
- _CONFLICT_LOG_MSG = 'Conflict %(conflict_type)s: %(details)s'
-
- def _handle_idp_conflict(self, e):
- conflict_type = 'identity_provider'
- details = six.text_type(e)
- LOG.debug(self._CONFLICT_LOG_MSG, {'conflict_type': conflict_type,
- 'details': details})
- if 'remote_id' in details:
- msg = _('Duplicate remote ID: %s')
- else:
- msg = _('Duplicate entry: %s')
- msg = msg % e.value
- raise exception.Conflict(type=conflict_type, details=msg)
-
- # Identity Provider CRUD
- @sql.handle_conflicts(conflict_type='identity_provider')
- def create_idp(self, idp_id, idp):
- idp['id'] = idp_id
- with sql.session_for_write() as session:
- idp_ref = IdentityProviderModel.from_dict(idp)
- session.add(idp_ref)
- return idp_ref.to_dict()
-
- def delete_idp(self, idp_id):
- with sql.session_for_write() as session:
- self._delete_assigned_protocols(session, idp_id)
- idp_ref = self._get_idp(session, idp_id)
- session.delete(idp_ref)
-
- def _get_idp(self, session, idp_id):
- idp_ref = session.query(IdentityProviderModel).get(idp_id)
- if not idp_ref:
- raise exception.IdentityProviderNotFound(idp_id=idp_id)
- return idp_ref
-
- def _get_idp_from_remote_id(self, session, remote_id):
- q = session.query(IdPRemoteIdsModel)
- q = q.filter_by(remote_id=remote_id)
- try:
- return q.one()
- except sql.NotFound:
- raise exception.IdentityProviderNotFound(idp_id=remote_id)
-
- def list_idps(self):
- with sql.session_for_read() as session:
- idps = session.query(IdentityProviderModel)
- idps_list = [idp.to_dict() for idp in idps]
- return idps_list
-
- def get_idp(self, idp_id):
- with sql.session_for_read() as session:
- idp_ref = self._get_idp(session, idp_id)
- return idp_ref.to_dict()
-
- def get_idp_from_remote_id(self, remote_id):
- with sql.session_for_read() as session:
- ref = self._get_idp_from_remote_id(session, remote_id)
- return ref.to_dict()
-
- def update_idp(self, idp_id, idp):
- try:
- with sql.session_for_write() as session:
- idp_ref = self._get_idp(session, idp_id)
- old_idp = idp_ref.to_dict()
- old_idp.update(idp)
- new_idp = IdentityProviderModel.from_dict(old_idp)
- for attr in IdentityProviderModel.mutable_attributes:
- setattr(idp_ref, attr, getattr(new_idp, attr))
- return idp_ref.to_dict()
- except sql.DBDuplicateEntry as e:
- self._handle_idp_conflict(e)
-
- # Protocol CRUD
- def _get_protocol(self, session, idp_id, protocol_id):
- q = session.query(FederationProtocolModel)
- q = q.filter_by(id=protocol_id, idp_id=idp_id)
- try:
- return q.one()
- except sql.NotFound:
- kwargs = {'protocol_id': protocol_id,
- 'idp_id': idp_id}
- raise exception.FederatedProtocolNotFound(**kwargs)
-
- @sql.handle_conflicts(conflict_type='federation_protocol')
- def create_protocol(self, idp_id, protocol_id, protocol):
- protocol['id'] = protocol_id
- protocol['idp_id'] = idp_id
- with sql.session_for_write() as session:
- self._get_idp(session, idp_id)
- protocol_ref = FederationProtocolModel.from_dict(protocol)
- session.add(protocol_ref)
- return protocol_ref.to_dict()
-
- def update_protocol(self, idp_id, protocol_id, protocol):
- with sql.session_for_write() as session:
- proto_ref = self._get_protocol(session, idp_id, protocol_id)
- old_proto = proto_ref.to_dict()
- old_proto.update(protocol)
- new_proto = FederationProtocolModel.from_dict(old_proto)
- for attr in FederationProtocolModel.mutable_attributes:
- setattr(proto_ref, attr, getattr(new_proto, attr))
- return proto_ref.to_dict()
-
- def get_protocol(self, idp_id, protocol_id):
- with sql.session_for_read() as session:
- protocol_ref = self._get_protocol(session, idp_id, protocol_id)
- return protocol_ref.to_dict()
-
- def list_protocols(self, idp_id):
- with sql.session_for_read() as session:
- q = session.query(FederationProtocolModel)
- q = q.filter_by(idp_id=idp_id)
- protocols = [protocol.to_dict() for protocol in q]
- return protocols
-
- def delete_protocol(self, idp_id, protocol_id):
- with sql.session_for_write() as session:
- key_ref = self._get_protocol(session, idp_id, protocol_id)
- session.delete(key_ref)
-
- def _delete_assigned_protocols(self, session, idp_id):
- query = session.query(FederationProtocolModel)
- query = query.filter_by(idp_id=idp_id)
- query.delete()
-
- # Mapping CRUD
- def _get_mapping(self, session, mapping_id):
- mapping_ref = session.query(MappingModel).get(mapping_id)
- if not mapping_ref:
- raise exception.MappingNotFound(mapping_id=mapping_id)
- return mapping_ref
-
- @sql.handle_conflicts(conflict_type='mapping')
- def create_mapping(self, mapping_id, mapping):
- ref = {}
- ref['id'] = mapping_id
- ref['rules'] = mapping.get('rules')
- with sql.session_for_write() as session:
- mapping_ref = MappingModel.from_dict(ref)
- session.add(mapping_ref)
- return mapping_ref.to_dict()
-
- def delete_mapping(self, mapping_id):
- with sql.session_for_write() as session:
- mapping_ref = self._get_mapping(session, mapping_id)
- session.delete(mapping_ref)
-
- def list_mappings(self):
- with sql.session_for_read() as session:
- mappings = session.query(MappingModel)
- return [x.to_dict() for x in mappings]
-
- def get_mapping(self, mapping_id):
- with sql.session_for_read() as session:
- mapping_ref = self._get_mapping(session, mapping_id)
- return mapping_ref.to_dict()
-
- @sql.handle_conflicts(conflict_type='mapping')
- def update_mapping(self, mapping_id, mapping):
- ref = {}
- ref['id'] = mapping_id
- ref['rules'] = mapping.get('rules')
- with sql.session_for_write() as session:
- mapping_ref = self._get_mapping(session, mapping_id)
- old_mapping = mapping_ref.to_dict()
- old_mapping.update(ref)
- new_mapping = MappingModel.from_dict(old_mapping)
- for attr in MappingModel.attributes:
- setattr(mapping_ref, attr, getattr(new_mapping, attr))
- return mapping_ref.to_dict()
-
- def get_mapping_from_idp_and_protocol(self, idp_id, protocol_id):
- with sql.session_for_read() as session:
- protocol_ref = self._get_protocol(session, idp_id, protocol_id)
- mapping_id = protocol_ref.mapping_id
- mapping_ref = self._get_mapping(session, mapping_id)
- return mapping_ref.to_dict()
-
- # Service Provider CRUD
- @sql.handle_conflicts(conflict_type='service_provider')
- def create_sp(self, sp_id, sp):
- sp['id'] = sp_id
- with sql.session_for_write() as session:
- sp_ref = ServiceProviderModel.from_dict(sp)
- session.add(sp_ref)
- return sp_ref.to_dict()
-
- def delete_sp(self, sp_id):
- with sql.session_for_write() as session:
- sp_ref = self._get_sp(session, sp_id)
- session.delete(sp_ref)
-
- def _get_sp(self, session, sp_id):
- sp_ref = session.query(ServiceProviderModel).get(sp_id)
- if not sp_ref:
- raise exception.ServiceProviderNotFound(sp_id=sp_id)
- return sp_ref
-
- def list_sps(self):
- with sql.session_for_read() as session:
- sps = session.query(ServiceProviderModel)
- sps_list = [sp.to_dict() for sp in sps]
- return sps_list
-
- def get_sp(self, sp_id):
- with sql.session_for_read() as session:
- sp_ref = self._get_sp(session, sp_id)
- return sp_ref.to_dict()
-
- def update_sp(self, sp_id, sp):
- with sql.session_for_write() as session:
- sp_ref = self._get_sp(session, sp_id)
- old_sp = sp_ref.to_dict()
- old_sp.update(sp)
- new_sp = ServiceProviderModel.from_dict(old_sp)
- for attr in ServiceProviderModel.mutable_attributes:
- setattr(sp_ref, attr, getattr(new_sp, attr))
- return sp_ref.to_dict()
-
- def get_enabled_service_providers(self):
- with sql.session_for_read() as session:
- service_providers = session.query(ServiceProviderModel)
- service_providers = service_providers.filter_by(enabled=True)
- return service_providers
diff --git a/keystone-moon/keystone/federation/__init__.py b/keystone-moon/keystone/federation/__init__.py
deleted file mode 100644
index b62cfb6f..00000000
--- a/keystone-moon/keystone/federation/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-#
-# 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 keystone.federation.core import * # noqa
diff --git a/keystone-moon/keystone/federation/backends/__init__.py b/keystone-moon/keystone/federation/backends/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/keystone-moon/keystone/federation/backends/__init__.py
+++ /dev/null
diff --git a/keystone-moon/keystone/federation/backends/sql.py b/keystone-moon/keystone/federation/backends/sql.py
deleted file mode 100644
index add409e6..00000000
--- a/keystone-moon/keystone/federation/backends/sql.py
+++ /dev/null
@@ -1,393 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-#
-# 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 oslo_serialization import jsonutils
-import six
-from sqlalchemy import orm
-
-from keystone.common import sql
-from keystone import exception
-from keystone.federation import core
-from keystone.i18n import _
-
-
-LOG = log.getLogger(__name__)
-
-
-class FederationProtocolModel(sql.ModelBase, sql.DictBase):
- __tablename__ = 'federation_protocol'
- attributes = ['id', 'idp_id', 'mapping_id']
- mutable_attributes = frozenset(['mapping_id'])
-
- id = sql.Column(sql.String(64), primary_key=True)
- idp_id = sql.Column(sql.String(64), sql.ForeignKey('identity_provider.id',
- ondelete='CASCADE'), primary_key=True)
- mapping_id = sql.Column(sql.String(64), nullable=False)
-
- @classmethod
- def from_dict(cls, dictionary):
- new_dictionary = dictionary.copy()
- return cls(**new_dictionary)
-
- def to_dict(self):
- """Return a dictionary with model's attributes."""
- d = dict()
- for attr in self.__class__.attributes:
- d[attr] = getattr(self, attr)
- return d
-
-
-class IdentityProviderModel(sql.ModelBase, sql.DictBase):
- __tablename__ = 'identity_provider'
- attributes = ['id', 'enabled', 'description', 'remote_ids']
- mutable_attributes = frozenset(['description', 'enabled', 'remote_ids'])
-
- id = sql.Column(sql.String(64), primary_key=True)
- enabled = sql.Column(sql.Boolean, nullable=False)
- description = sql.Column(sql.Text(), nullable=True)
- remote_ids = orm.relationship('IdPRemoteIdsModel',
- order_by='IdPRemoteIdsModel.remote_id',
- cascade='all, delete-orphan')
-
- @classmethod
- def from_dict(cls, dictionary):
- new_dictionary = dictionary.copy()
- remote_ids_list = new_dictionary.pop('remote_ids', None)
- if not remote_ids_list:
- remote_ids_list = []
- identity_provider = cls(**new_dictionary)
- remote_ids = []
- # NOTE(fmarco76): the remote_ids_list contains only remote ids
- # associated with the IdP because of the "relationship" established in
- # sqlalchemy and corresponding to the FK in the idp_remote_ids table
- for remote in remote_ids_list:
- remote_ids.append(IdPRemoteIdsModel(remote_id=remote))
- identity_provider.remote_ids = remote_ids
- return identity_provider
-
- def to_dict(self):
- """Return a dictionary with model's attributes."""
- d = dict()
- for attr in self.__class__.attributes:
- d[attr] = getattr(self, attr)
- d['remote_ids'] = []
- for remote in self.remote_ids:
- d['remote_ids'].append(remote.remote_id)
- return d
-
-
-class IdPRemoteIdsModel(sql.ModelBase, sql.DictBase):
- __tablename__ = 'idp_remote_ids'
- attributes = ['idp_id', 'remote_id']
- mutable_attributes = frozenset(['idp_id', 'remote_id'])
-
- idp_id = sql.Column(sql.String(64),
- sql.ForeignKey('identity_provider.id',
- ondelete='CASCADE'))
- remote_id = sql.Column(sql.String(255),
- primary_key=True)
-
- @classmethod
- def from_dict(cls, dictionary):
- new_dictionary = dictionary.copy()
- return cls(**new_dictionary)
-
- def to_dict(self):
- """Return a dictionary with model's attributes."""
- d = dict()
- for attr in self.__class__.attributes:
- d[attr] = getattr(self, attr)
- return d
-
-
-class MappingModel(sql.ModelBase, sql.DictBase):
- __tablename__ = 'mapping'
- attributes = ['id', 'rules']
-
- id = sql.Column(sql.String(64), primary_key=True)
- rules = sql.Column(sql.JsonBlob(), nullable=False)
-
- @classmethod
- def from_dict(cls, dictionary):
- new_dictionary = dictionary.copy()
- new_dictionary['rules'] = jsonutils.dumps(new_dictionary['rules'])
- return cls(**new_dictionary)
-
- def to_dict(self):
- """Return a dictionary with model's attributes."""
- d = dict()
- for attr in self.__class__.attributes:
- d[attr] = getattr(self, attr)
- d['rules'] = jsonutils.loads(d['rules'])
- return d
-
-
-class ServiceProviderModel(sql.ModelBase, sql.DictBase):
- __tablename__ = 'service_provider'
- attributes = ['auth_url', 'id', 'enabled', 'description',
- 'relay_state_prefix', 'sp_url']
- mutable_attributes = frozenset(['auth_url', 'description', 'enabled',
- 'relay_state_prefix', 'sp_url'])
-
- id = sql.Column(sql.String(64), primary_key=True)
- enabled = sql.Column(sql.Boolean, nullable=False)
- description = sql.Column(sql.Text(), nullable=True)
- auth_url = sql.Column(sql.String(256), nullable=False)
- sp_url = sql.Column(sql.String(256), nullable=False)
- relay_state_prefix = sql.Column(sql.String(256), nullable=False)
-
- @classmethod
- def from_dict(cls, dictionary):
- new_dictionary = dictionary.copy()
- return cls(**new_dictionary)
-
- def to_dict(self):
- """Return a dictionary with model's attributes."""
- d = dict()
- for attr in self.__class__.attributes:
- d[attr] = getattr(self, attr)
- return d
-
-
-class Federation(core.FederationDriverV9):
-
- _CONFLICT_LOG_MSG = 'Conflict %(conflict_type)s: %(details)s'
-
- def _handle_idp_conflict(self, e):
- conflict_type = 'identity_provider'
- details = six.text_type(e)
- LOG.debug(self._CONFLICT_LOG_MSG, {'conflict_type': conflict_type,
- 'details': details})
- if 'remote_id' in details:
- msg = _('Duplicate remote ID: %s')
- else:
- msg = _('Duplicate entry: %s')
- msg = msg % e.value
- raise exception.Conflict(type=conflict_type, details=msg)
-
- # Identity Provider CRUD
- def create_idp(self, idp_id, idp):
- idp['id'] = idp_id
- try:
- with sql.session_for_write() as session:
- idp_ref = IdentityProviderModel.from_dict(idp)
- session.add(idp_ref)
- return idp_ref.to_dict()
- except sql.DBDuplicateEntry as e:
- self._handle_idp_conflict(e)
-
- def delete_idp(self, idp_id):
- with sql.session_for_write() as session:
- self._delete_assigned_protocols(session, idp_id)
- idp_ref = self._get_idp(session, idp_id)
- session.delete(idp_ref)
-
- def _get_idp(self, session, idp_id):
- idp_ref = session.query(IdentityProviderModel).get(idp_id)
- if not idp_ref:
- raise exception.IdentityProviderNotFound(idp_id=idp_id)
- return idp_ref
-
- def _get_idp_from_remote_id(self, session, remote_id):
- q = session.query(IdPRemoteIdsModel)
- q = q.filter_by(remote_id=remote_id)
- try:
- return q.one()
- except sql.NotFound:
- raise exception.IdentityProviderNotFound(idp_id=remote_id)
-
- def list_idps(self, hints=None):
- with sql.session_for_read() as session:
- query = session.query(IdentityProviderModel)
- idps = sql.filter_limit_query(IdentityProviderModel, query, hints)
- idps_list = [idp.to_dict() for idp in idps]
- return idps_list
-
- def get_idp(self, idp_id):
- with sql.session_for_read() as session:
- idp_ref = self._get_idp(session, idp_id)
- return idp_ref.to_dict()
-
- def get_idp_from_remote_id(self, remote_id):
- with sql.session_for_read() as session:
- ref = self._get_idp_from_remote_id(session, remote_id)
- return ref.to_dict()
-
- def update_idp(self, idp_id, idp):
- try:
- with sql.session_for_write() as session:
- idp_ref = self._get_idp(session, idp_id)
- old_idp = idp_ref.to_dict()
- old_idp.update(idp)
- new_idp = IdentityProviderModel.from_dict(old_idp)
- for attr in IdentityProviderModel.mutable_attributes:
- setattr(idp_ref, attr, getattr(new_idp, attr))
- return idp_ref.to_dict()
- except sql.DBDuplicateEntry as e:
- self._handle_idp_conflict(e)
-
- # Protocol CRUD
- def _get_protocol(self, session, idp_id, protocol_id):
- q = session.query(FederationProtocolModel)
- q = q.filter_by(id=protocol_id, idp_id=idp_id)
- try:
- return q.one()
- except sql.NotFound:
- kwargs = {'protocol_id': protocol_id,
- 'idp_id': idp_id}
- raise exception.FederatedProtocolNotFound(**kwargs)
-
- @sql.handle_conflicts(conflict_type='federation_protocol')
- def create_protocol(self, idp_id, protocol_id, protocol):
- protocol['id'] = protocol_id
- protocol['idp_id'] = idp_id
- with sql.session_for_write() as session:
- self._get_idp(session, idp_id)
- protocol_ref = FederationProtocolModel.from_dict(protocol)
- session.add(protocol_ref)
- return protocol_ref.to_dict()
-
- def update_protocol(self, idp_id, protocol_id, protocol):
- with sql.session_for_write() as session:
- proto_ref = self._get_protocol(session, idp_id, protocol_id)
- old_proto = proto_ref.to_dict()
- old_proto.update(protocol)
- new_proto = FederationProtocolModel.from_dict(old_proto)
- for attr in FederationProtocolModel.mutable_attributes:
- setattr(proto_ref, attr, getattr(new_proto, attr))
- return proto_ref.to_dict()
-
- def get_protocol(self, idp_id, protocol_id):
- with sql.session_for_read() as session:
- protocol_ref = self._get_protocol(session, idp_id, protocol_id)
- return protocol_ref.to_dict()
-
- def list_protocols(self, idp_id):
- with sql.session_for_read() as session:
- q = session.query(FederationProtocolModel)
- q = q.filter_by(idp_id=idp_id)
- protocols = [protocol.to_dict() for protocol in q]
- return protocols
-
- def delete_protocol(self, idp_id, protocol_id):
- with sql.session_for_write() as session:
- key_ref = self._get_protocol(session, idp_id, protocol_id)
- session.delete(key_ref)
-
- def _delete_assigned_protocols(self, session, idp_id):
- query = session.query(FederationProtocolModel)
- query = query.filter_by(idp_id=idp_id)
- query.delete()
-
- # Mapping CRUD
- def _get_mapping(self, session, mapping_id):
- mapping_ref = session.query(MappingModel).get(mapping_id)
- if not mapping_ref:
- raise exception.MappingNotFound(mapping_id=mapping_id)
- return mapping_ref
-
- @sql.handle_conflicts(conflict_type='mapping')
- def create_mapping(self, mapping_id, mapping):
- ref = {}
- ref['id'] = mapping_id
- ref['rules'] = mapping.get('rules')
- with sql.session_for_write() as session:
- mapping_ref = MappingModel.from_dict(ref)
- session.add(mapping_ref)
- return mapping_ref.to_dict()
-
- def delete_mapping(self, mapping_id):
- with sql.session_for_write() as session:
- mapping_ref = self._get_mapping(session, mapping_id)
- session.delete(mapping_ref)
-
- def list_mappings(self):
- with sql.session_for_read() as session:
- mappings = session.query(MappingModel)
- return [x.to_dict() for x in mappings]
-
- def get_mapping(self, mapping_id):
- with sql.session_for_read() as session:
- mapping_ref = self._get_mapping(session, mapping_id)
- return mapping_ref.to_dict()
-
- @sql.handle_conflicts(conflict_type='mapping')
- def update_mapping(self, mapping_id, mapping):
- ref = {}
- ref['id'] = mapping_id
- ref['rules'] = mapping.get('rules')
- with sql.session_for_write() as session:
- mapping_ref = self._get_mapping(session, mapping_id)
- old_mapping = mapping_ref.to_dict()
- old_mapping.update(ref)
- new_mapping = MappingModel.from_dict(old_mapping)
- for attr in MappingModel.attributes:
- setattr(mapping_ref, attr, getattr(new_mapping, attr))
- return mapping_ref.to_dict()
-
- def get_mapping_from_idp_and_protocol(self, idp_id, protocol_id):
- with sql.session_for_read() as session:
- protocol_ref = self._get_protocol(session, idp_id, protocol_id)
- mapping_id = protocol_ref.mapping_id
- mapping_ref = self._get_mapping(session, mapping_id)
- return mapping_ref.to_dict()
-
- # Service Provider CRUD
- @sql.handle_conflicts(conflict_type='service_provider')
- def create_sp(self, sp_id, sp):
- sp['id'] = sp_id
- with sql.session_for_write() as session:
- sp_ref = ServiceProviderModel.from_dict(sp)
- session.add(sp_ref)
- return sp_ref.to_dict()
-
- def delete_sp(self, sp_id):
- with sql.session_for_write() as session:
- sp_ref = self._get_sp(session, sp_id)
- session.delete(sp_ref)
-
- def _get_sp(self, session, sp_id):
- sp_ref = session.query(ServiceProviderModel).get(sp_id)
- if not sp_ref:
- raise exception.ServiceProviderNotFound(sp_id=sp_id)
- return sp_ref
-
- def list_sps(self, hints=None):
- with sql.session_for_read() as session:
- query = session.query(ServiceProviderModel)
- sps = sql.filter_limit_query(ServiceProviderModel, query, hints)
- sps_list = [sp.to_dict() for sp in sps]
- return sps_list
-
- def get_sp(self, sp_id):
- with sql.session_for_read() as session:
- sp_ref = self._get_sp(session, sp_id)
- return sp_ref.to_dict()
-
- def update_sp(self, sp_id, sp):
- with sql.session_for_write() as session:
- sp_ref = self._get_sp(session, sp_id)
- old_sp = sp_ref.to_dict()
- old_sp.update(sp)
- new_sp = ServiceProviderModel.from_dict(old_sp)
- for attr in ServiceProviderModel.mutable_attributes:
- setattr(sp_ref, attr, getattr(new_sp, attr))
- return sp_ref.to_dict()
-
- def get_enabled_service_providers(self):
- with sql.session_for_read() as session:
- service_providers = session.query(ServiceProviderModel)
- service_providers = service_providers.filter_by(enabled=True)
- return service_providers
diff --git a/keystone-moon/keystone/federation/constants.py b/keystone-moon/keystone/federation/constants.py
deleted file mode 100644
index afb38494..00000000
--- a/keystone-moon/keystone/federation/constants.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# 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.
-
-FEDERATION = 'OS-FEDERATION'
-IDENTITY_PROVIDER = 'OS-FEDERATION:identity_provider'
-PROTOCOL = 'OS-FEDERATION:protocol'
diff --git a/keystone-moon/keystone/federation/controllers.py b/keystone-moon/keystone/federation/controllers.py
deleted file mode 100644
index b9e2d883..00000000
--- a/keystone-moon/keystone/federation/controllers.py
+++ /dev/null
@@ -1,519 +0,0 @@
-# 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.
-
-"""Workflow logic for the Federation service."""
-
-import string
-
-from oslo_config import cfg
-from oslo_log import log
-import six
-from six.moves import urllib
-import webob
-
-from keystone.auth import controllers as auth_controllers
-from keystone.common import authorization
-from keystone.common import controller
-from keystone.common import dependency
-from keystone.common import utils as k_utils
-from keystone.common import validation
-from keystone.common import wsgi
-from keystone import exception
-from keystone.federation import idp as keystone_idp
-from keystone.federation import schema
-from keystone.federation import utils
-from keystone.i18n import _
-from keystone.models import token_model
-
-
-CONF = cfg.CONF
-LOG = log.getLogger(__name__)
-
-
-class _ControllerBase(controller.V3Controller):
- """Base behaviors for federation controllers."""
-
- @classmethod
- def base_url(cls, context, path=None):
- """Construct a path and pass it to V3Controller.base_url method."""
- path = '/OS-FEDERATION/' + cls.collection_name
- return super(_ControllerBase, cls).base_url(context, path=path)
-
-
-@dependency.requires('federation_api')
-class IdentityProvider(_ControllerBase):
- """Identity Provider representation."""
-
- collection_name = 'identity_providers'
- member_name = 'identity_provider'
-
- _public_parameters = frozenset(['id', 'enabled', 'description',
- 'remote_ids', 'links'
- ])
-
- @classmethod
- def _add_related_links(cls, context, ref):
- """Add URLs for entities related with Identity Provider.
-
- Add URLs pointing to:
- - protocols tied to the Identity Provider
-
- """
- ref.setdefault('links', {})
- base_path = ref['links'].get('self')
- if base_path is None:
- base_path = '/'.join([IdentityProvider.base_url(context),
- ref['id']])
- for name in ['protocols']:
- ref['links'][name] = '/'.join([base_path, name])
-
- @classmethod
- def _add_self_referential_link(cls, context, ref):
- id = ref['id']
- self_path = '/'.join([cls.base_url(context), id])
- ref.setdefault('links', {})
- ref['links']['self'] = self_path
-
- @classmethod
- def wrap_member(cls, context, ref):
- cls._add_self_referential_link(context, ref)
- cls._add_related_links(context, ref)
- ref = cls.filter_params(ref)
- return {cls.member_name: ref}
-
- @controller.protected()
- @validation.validated(schema.identity_provider_create, 'identity_provider')
- def create_identity_provider(self, context, idp_id, identity_provider):
- identity_provider = self._normalize_dict(identity_provider)
- identity_provider.setdefault('enabled', False)
- idp_ref = self.federation_api.create_idp(idp_id, identity_provider)
- response = IdentityProvider.wrap_member(context, idp_ref)
- return wsgi.render_response(body=response, status=('201', 'Created'))
-
- @controller.filterprotected('id', 'enabled')
- def list_identity_providers(self, context, filters):
- hints = self.build_driver_hints(context, filters)
- ref = self.federation_api.list_idps(hints=hints)
- ref = [self.filter_params(x) for x in ref]
- return IdentityProvider.wrap_collection(context, ref, hints=hints)
-
- @controller.protected()
- def get_identity_provider(self, context, idp_id):
- ref = self.federation_api.get_idp(idp_id)
- return IdentityProvider.wrap_member(context, ref)
-
- @controller.protected()
- def delete_identity_provider(self, context, idp_id):
- self.federation_api.delete_idp(idp_id)
-
- @controller.protected()
- @validation.validated(schema.identity_provider_update, 'identity_provider')
- def update_identity_provider(self, context, idp_id, identity_provider):
- identity_provider = self._normalize_dict(identity_provider)
- idp_ref = self.federation_api.update_idp(idp_id, identity_provider)
- return IdentityProvider.wrap_member(context, idp_ref)
-
-
-@dependency.requires('federation_api')
-class FederationProtocol(_ControllerBase):
- """A federation protocol representation.
-
- See keystone.common.controller.V3Controller docstring for explanation
- on _public_parameters class attributes.
-
- """
-
- collection_name = 'protocols'
- member_name = 'protocol'
-
- _public_parameters = frozenset(['id', 'mapping_id', 'links'])
-
- @classmethod
- def _add_self_referential_link(cls, context, ref):
- """Add 'links' entry to the response dictionary.
-
- Calls IdentityProvider.base_url() class method, as it constructs
- proper URL along with the 'identity providers' part included.
-
- :param ref: response dictionary
-
- """
- ref.setdefault('links', {})
- base_path = ref['links'].get('identity_provider')
- if base_path is None:
- base_path = [IdentityProvider.base_url(context), ref['idp_id']]
- base_path = '/'.join(base_path)
- self_path = [base_path, 'protocols', ref['id']]
- self_path = '/'.join(self_path)
- ref['links']['self'] = self_path
-
- @classmethod
- def _add_related_links(cls, context, ref):
- """Add new entries to the 'links' subdictionary in the response.
-
- Adds 'identity_provider' key with URL pointing to related identity
- provider as a value.
-
- :param ref: response dictionary
-
- """
- ref.setdefault('links', {})
- base_path = '/'.join([IdentityProvider.base_url(context),
- ref['idp_id']])
- ref['links']['identity_provider'] = base_path
-
- @classmethod
- def wrap_member(cls, context, ref):
- cls._add_related_links(context, ref)
- cls._add_self_referential_link(context, ref)
- ref = cls.filter_params(ref)
- return {cls.member_name: ref}
-
- @controller.protected()
- @validation.validated(schema.federation_protocol_schema, 'protocol')
- def create_protocol(self, context, idp_id, protocol_id, protocol):
- ref = self._normalize_dict(protocol)
- ref = self.federation_api.create_protocol(idp_id, protocol_id, ref)
- response = FederationProtocol.wrap_member(context, ref)
- return wsgi.render_response(body=response, status=('201', 'Created'))
-
- @controller.protected()
- @validation.validated(schema.federation_protocol_schema, 'protocol')
- def update_protocol(self, context, idp_id, protocol_id, protocol):
- ref = self._normalize_dict(protocol)
- ref = self.federation_api.update_protocol(idp_id, protocol_id,
- protocol)
- return FederationProtocol.wrap_member(context, ref)
-
- @controller.protected()
- def get_protocol(self, context, idp_id, protocol_id):
- ref = self.federation_api.get_protocol(idp_id, protocol_id)
- return FederationProtocol.wrap_member(context, ref)
-
- @controller.protected()
- def list_protocols(self, context, idp_id):
- protocols_ref = self.federation_api.list_protocols(idp_id)
- protocols = list(protocols_ref)
- return FederationProtocol.wrap_collection(context, protocols)
-
- @controller.protected()
- def delete_protocol(self, context, idp_id, protocol_id):
- self.federation_api.delete_protocol(idp_id, protocol_id)
-
-
-@dependency.requires('federation_api')
-class MappingController(_ControllerBase):
- collection_name = 'mappings'
- member_name = 'mapping'
-
- @controller.protected()
- def create_mapping(self, context, mapping_id, mapping):
- ref = self._normalize_dict(mapping)
- utils.validate_mapping_structure(ref)
- mapping_ref = self.federation_api.create_mapping(mapping_id, ref)
- response = MappingController.wrap_member(context, mapping_ref)
- return wsgi.render_response(body=response, status=('201', 'Created'))
-
- @controller.protected()
- def list_mappings(self, context):
- ref = self.federation_api.list_mappings()
- return MappingController.wrap_collection(context, ref)
-
- @controller.protected()
- def get_mapping(self, context, mapping_id):
- ref = self.federation_api.get_mapping(mapping_id)
- return MappingController.wrap_member(context, ref)
-
- @controller.protected()
- def delete_mapping(self, context, mapping_id):
- self.federation_api.delete_mapping(mapping_id)
-
- @controller.protected()
- def update_mapping(self, context, mapping_id, mapping):
- mapping = self._normalize_dict(mapping)
- utils.validate_mapping_structure(mapping)
- mapping_ref = self.federation_api.update_mapping(mapping_id, mapping)
- return MappingController.wrap_member(context, mapping_ref)
-
-
-@dependency.requires('federation_api')
-class Auth(auth_controllers.Auth):
-
- def _get_sso_origin_host(self, context):
- """Validate and return originating dashboard URL.
-
- Make sure the parameter is specified in the request's URL as well its
- value belongs to a list of trusted dashboards.
-
- :param context: request's context
- :raises keystone.exception.ValidationError: ``origin`` query parameter
- was not specified. The URL is deemed invalid.
- :raises keystone.exception.Unauthorized: URL specified in origin query
- parameter does not exist in list of websso trusted dashboards.
- :returns: URL with the originating dashboard
-
- """
- if 'origin' in context['query_string']:
- origin = context['query_string']['origin']
- host = urllib.parse.unquote_plus(origin)
- else:
- msg = _('Request must have an origin query parameter')
- LOG.error(msg)
- raise exception.ValidationError(msg)
-
- # change trusted_dashboard hostnames to lowercase before comparison
- trusted_dashboards = [k_utils.lower_case_hostname(trusted)
- for trusted in CONF.federation.trusted_dashboard]
-
- if host not in trusted_dashboards:
- msg = _('%(host)s is not a trusted dashboard host')
- msg = msg % {'host': host}
- LOG.error(msg)
- raise exception.Unauthorized(msg)
-
- return host
-
- def federated_authentication(self, context, idp_id, protocol_id):
- """Authenticate from dedicated url endpoint.
-
- Build HTTP request body for federated authentication and inject
- it into the ``authenticate_for_token`` function.
-
- """
- auth = {
- 'identity': {
- 'methods': [protocol_id],
- protocol_id: {
- 'identity_provider': idp_id,
- 'protocol': protocol_id
- }
- }
- }
-
- return self.authenticate_for_token(context, auth=auth)
-
- def federated_sso_auth(self, context, protocol_id):
- try:
- remote_id_name = utils.get_remote_id_parameter(protocol_id)
- remote_id = context['environment'][remote_id_name]
- except KeyError:
- msg = _('Missing entity ID from environment')
- LOG.error(msg)
- raise exception.Unauthorized(msg)
-
- host = self._get_sso_origin_host(context)
-
- ref = self.federation_api.get_idp_from_remote_id(remote_id)
- # NOTE(stevemar): the returned object is a simple dict that
- # contains the idp_id and remote_id.
- identity_provider = ref['idp_id']
- res = self.federated_authentication(context, identity_provider,
- protocol_id)
- token_id = res.headers['X-Subject-Token']
- return self.render_html_response(host, token_id)
-
- def federated_idp_specific_sso_auth(self, context, idp_id, protocol_id):
- host = self._get_sso_origin_host(context)
-
- # NOTE(lbragstad): We validate that the Identity Provider actually
- # exists in the Mapped authentication plugin.
- res = self.federated_authentication(context, idp_id, protocol_id)
- token_id = res.headers['X-Subject-Token']
- return self.render_html_response(host, token_id)
-
- def render_html_response(self, host, token_id):
- """Forms an HTML Form from a template with autosubmit."""
- headers = [('Content-Type', 'text/html')]
-
- with open(CONF.federation.sso_callback_template) as template:
- src = string.Template(template.read())
-
- subs = {'host': host, 'token': token_id}
- body = src.substitute(subs)
- return webob.Response(body=body, status='200',
- headerlist=headers)
-
- def _create_base_saml_assertion(self, context, auth):
- issuer = CONF.saml.idp_entity_id
- sp_id = auth['scope']['service_provider']['id']
- service_provider = self.federation_api.get_sp(sp_id)
- utils.assert_enabled_service_provider_object(service_provider)
- sp_url = service_provider['sp_url']
-
- token_id = auth['identity']['token']['id']
- token_data = self.token_provider_api.validate_token(token_id)
- token_ref = token_model.KeystoneToken(token_id, token_data)
-
- if not token_ref.project_scoped:
- action = _('Use a project scoped token when attempting to create '
- 'a SAML assertion')
- raise exception.ForbiddenAction(action=action)
-
- subject = token_ref.user_name
- roles = token_ref.role_names
- project = token_ref.project_name
- # NOTE(rodrigods): the domain name is necessary in order to distinguish
- # between projects and users with the same name in different domains.
- project_domain_name = token_ref.project_domain_name
- subject_domain_name = token_ref.user_domain_name
-
- generator = keystone_idp.SAMLGenerator()
- response = generator.samlize_token(
- issuer, sp_url, subject, subject_domain_name,
- roles, project, project_domain_name)
- return (response, service_provider)
-
- def _build_response_headers(self, service_provider):
- return [('Content-Type', 'text/xml'),
- ('X-sp-url', six.binary_type(service_provider['sp_url'])),
- ('X-auth-url', six.binary_type(service_provider['auth_url']))]
-
- @validation.validated(schema.saml_create, 'auth')
- def create_saml_assertion(self, context, auth):
- """Exchange a scoped token for a SAML assertion.
-
- :param auth: Dictionary that contains a token and service provider ID
- :returns: SAML Assertion based on properties from the token
- """
- t = self._create_base_saml_assertion(context, auth)
- (response, service_provider) = t
-
- headers = self._build_response_headers(service_provider)
- return wsgi.render_response(body=response.to_string(),
- status=('200', 'OK'),
- headers=headers)
-
- @validation.validated(schema.saml_create, 'auth')
- def create_ecp_assertion(self, context, auth):
- """Exchange a scoped token for an ECP assertion.
-
- :param auth: Dictionary that contains a token and service provider ID
- :returns: ECP Assertion based on properties from the token
- """
- t = self._create_base_saml_assertion(context, auth)
- (saml_assertion, service_provider) = t
- relay_state_prefix = service_provider['relay_state_prefix']
-
- generator = keystone_idp.ECPGenerator()
- ecp_assertion = generator.generate_ecp(saml_assertion,
- relay_state_prefix)
-
- headers = self._build_response_headers(service_provider)
- return wsgi.render_response(body=ecp_assertion.to_string(),
- status=('200', 'OK'),
- headers=headers)
-
-
-@dependency.requires('assignment_api', 'resource_api')
-class DomainV3(controller.V3Controller):
- collection_name = 'domains'
- member_name = 'domain'
-
- def __init__(self):
- super(DomainV3, self).__init__()
- self.get_member_from_driver = self.resource_api.get_domain
-
- @controller.protected()
- def list_domains_for_groups(self, context):
- """List all domains available to an authenticated user's groups.
-
- :param context: request context
- :returns: list of accessible domains
-
- """
- auth_context = context['environment'][authorization.AUTH_CONTEXT_ENV]
- domains = self.assignment_api.list_domains_for_groups(
- auth_context['group_ids'])
- return DomainV3.wrap_collection(context, domains)
-
-
-@dependency.requires('assignment_api', 'resource_api')
-class ProjectAssignmentV3(controller.V3Controller):
- collection_name = 'projects'
- member_name = 'project'
-
- def __init__(self):
- super(ProjectAssignmentV3, self).__init__()
- self.get_member_from_driver = self.resource_api.get_project
-
- @controller.protected()
- def list_projects_for_groups(self, context):
- """List all projects available to an authenticated user's groups.
-
- :param context: request context
- :returns: list of accessible projects
-
- """
- auth_context = context['environment'][authorization.AUTH_CONTEXT_ENV]
- projects = self.assignment_api.list_projects_for_groups(
- auth_context['group_ids'])
- return ProjectAssignmentV3.wrap_collection(context, projects)
-
-
-@dependency.requires('federation_api')
-class ServiceProvider(_ControllerBase):
- """Service Provider representation."""
-
- collection_name = 'service_providers'
- member_name = 'service_provider'
-
- _public_parameters = frozenset(['auth_url', 'id', 'enabled', 'description',
- 'links', 'relay_state_prefix', 'sp_url'])
-
- @controller.protected()
- @validation.validated(schema.service_provider_create, 'service_provider')
- def create_service_provider(self, context, sp_id, service_provider):
- service_provider = self._normalize_dict(service_provider)
- service_provider.setdefault('enabled', False)
- service_provider.setdefault('relay_state_prefix',
- CONF.saml.relay_state_prefix)
- sp_ref = self.federation_api.create_sp(sp_id, service_provider)
- response = ServiceProvider.wrap_member(context, sp_ref)
- return wsgi.render_response(body=response, status=('201', 'Created'))
-
- @controller.filterprotected('id', 'enabled')
- def list_service_providers(self, context, filters):
- hints = self.build_driver_hints(context, filters)
- ref = self.federation_api.list_sps(hints=hints)
- ref = [self.filter_params(x) for x in ref]
- return ServiceProvider.wrap_collection(context, ref, hints=hints)
-
- @controller.protected()
- def get_service_provider(self, context, sp_id):
- ref = self.federation_api.get_sp(sp_id)
- return ServiceProvider.wrap_member(context, ref)
-
- @controller.protected()
- def delete_service_provider(self, context, sp_id):
- self.federation_api.delete_sp(sp_id)
-
- @controller.protected()
- @validation.validated(schema.service_provider_update, 'service_provider')
- def update_service_provider(self, context, sp_id, service_provider):
- service_provider = self._normalize_dict(service_provider)
- sp_ref = self.federation_api.update_sp(sp_id, service_provider)
- return ServiceProvider.wrap_member(context, sp_ref)
-
-
-class SAMLMetadataV3(_ControllerBase):
- member_name = 'metadata'
-
- def get_metadata(self, context):
- metadata_path = CONF.saml.idp_metadata_path
- try:
- with open(metadata_path, 'r') as metadata_handler:
- metadata = metadata_handler.read()
- except IOError as e:
- # Raise HTTP 500 in case Metadata file cannot be read.
- raise exception.MetadataFileError(reason=e)
- return wsgi.render_response(body=metadata, status=('200', 'OK'),
- headers=[('Content-Type', 'text/xml')])
diff --git a/keystone-moon/keystone/federation/core.py b/keystone-moon/keystone/federation/core.py
deleted file mode 100644
index 23028dfd..00000000
--- a/keystone-moon/keystone/federation/core.py
+++ /dev/null
@@ -1,611 +0,0 @@
-# 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(mapping['id'], 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 abstract 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.
-
- :param idp_id: ID of IdP object
- :type idp_id: string
- :param idp: idp object
- :type idp: dict
- :returns: idp ref
- :rtype: dict
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def delete_idp(self, idp_id):
- """Delete an identity provider.
-
- :param idp_id: ID of IdP object
- :type idp_id: string
- :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.
-
- :param idp_id: ID of IdP object
- :type idp_id: string
- :raises keystone.exception.IdentityProviderNotFound: If the IdP
- doesn't exist.
- :returns: idp ref
- :rtype: dict
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def get_idp_from_remote_id(self, remote_id):
- """Get an identity provider by remote ID.
-
- :param remote_id: ID of remote IdP
- :type idp_id: string
- :raises keystone.exception.IdentityProviderNotFound: If the IdP
- doesn't exist.
- :returns: idp ref
- :rtype: dict
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def update_idp(self, idp_id, idp):
- """Update an identity provider by ID.
-
- :param idp_id: ID of IdP object
- :type idp_id: string
- :param idp: idp object
- :type idp: dict
- :raises keystone.exception.IdentityProviderNotFound: If the IdP
- doesn't exist.
- :returns: idp ref
- :rtype: dict
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def create_protocol(self, idp_id, protocol_id, protocol):
- """Add an IdP-Protocol configuration.
-
- :param idp_id: ID of IdP object
- :type idp_id: string
- :param protocol_id: ID of protocol object
- :type protocol_id: string
- :param protocol: protocol object
- :type protocol: dict
- :raises keystone.exception.IdentityProviderNotFound: If the IdP
- doesn't exist.
- :returns: protocol ref
- :rtype: dict
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def update_protocol(self, idp_id, protocol_id, protocol):
- """Change an IdP-Protocol configuration.
-
- :param idp_id: ID of IdP object
- :type idp_id: string
- :param protocol_id: ID of protocol object
- :type protocol_id: string
- :param protocol: protocol object
- :type protocol: dict
- :raises keystone.exception.IdentityProviderNotFound: If the IdP
- doesn't exist.
- :raises keystone.exception.FederatedProtocolNotFound: If the federated
- protocol cannot be found.
- :returns: protocol ref
- :rtype: dict
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def get_protocol(self, idp_id, protocol_id):
- """Get an IdP-Protocol configuration.
-
- :param idp_id: ID of IdP object
- :type idp_id: string
- :param protocol_id: ID of protocol object
- :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: protocol ref
- :rtype: dict
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_protocols(self, idp_id):
- """List an IdP's supported protocols.
-
- :param idp_id: ID of IdP object
- :type idp_id: string
- :raises keystone.exception.IdentityProviderNotFound: If the IdP
- doesn't exist.
- :returns: list of protocol ref
- :rtype: list of dict
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def delete_protocol(self, idp_id, protocol_id):
- """Delete an IdP-Protocol configuration.
-
- :param idp_id: ID of IdP object
- :type idp_id: string
- :param protocol_id: ID of protocol object
- :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.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def create_mapping(self, mapping_id, mapping):
- """Create a mapping.
-
- :param mapping_id: ID of mapping object
- :type mapping_id: string
- :param mapping: mapping ref with mapping name
- :type mapping: dict
- :returns: mapping ref
- :rtype: dict
-
- """
- 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
- :rtype: dict
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_mappings(self):
- """List all mappings.
-
- :returns: list of mapping refs
- :rtype: list of dicts
-
- """
- 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
- :raises keystone.exception.MappingNotFound: If the mapping cannot
- be found.
- :returns: mapping ref
- :rtype: dict
-
- """
- 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
- :rtype: dict
-
- """
- 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: service provider 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 get_sp(self, sp_id):
- """Get a service provider.
-
- :param sp_id: id of the service provider
- :type sp_id: string
- :returns: service provider ref
- :rtype: dict
-
- :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: service provider 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.
-
- """
-
- @abc.abstractmethod
- def list_idps(self):
- """List all identity providers.
-
- :returns: list of idp refs
- :rtype: list of dicts
-
- :raises keystone.exception.IdentityProviderNotFound: If the IdP
- doesn't exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_sps(self):
- """List all service providers.
-
- :returns: List of service provider ref objects
- :rtype: list of dicts
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
-
-class FederationDriverV9(FederationDriverBase):
- """New or redefined methods from V8.
-
- Add any new V9 abstract methods (or those with modified signatures) to
- this class.
-
- """
-
- @abc.abstractmethod
- def list_idps(self, hints):
- """List all identity providers.
-
- :param hints: filter hints which the driver should
- implement if at all possible.
- :returns: list of idp refs
- :rtype: list of dicts
-
- :raises keystone.exception.IdentityProviderNotFound: If the IdP
- doesn't exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_sps(self, hints):
- """List all service providers.
-
- :param hints: filter hints which the driver should
- implement if at all possible.
- :returns: List of service provider ref objects
- :rtype: list of dicts
-
- :raises keystone.exception.ServiceProviderNotFound: If the SP
- doesn't exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
-
-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)
-
- # NOTE(davechen): The hints is ignored here to support legacy drivers,
- # but the filters in hints will be remain unsatisfied and V3Controller
- # wrapper will apply these filters at the end. So that the result get
- # returned for list IdP will still be filtered with the legacy drivers.
- def list_idps(self, hints):
- 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)
-
- # NOTE(davechen): The hints is ignored here to support legacy drivers,
- # but the filters in hints will be remain unsatisfied and V3Controller
- # wrapper will apply these filters at the end. So that the result get
- # returned for list SPs will still be filtered with the legacy drivers.
- def list_sps(self, hints):
- 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)
diff --git a/keystone-moon/keystone/federation/idp.py b/keystone-moon/keystone/federation/idp.py
deleted file mode 100644
index 494d58b9..00000000
--- a/keystone-moon/keystone/federation/idp.py
+++ /dev/null
@@ -1,615 +0,0 @@
-# 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.
-
-import datetime
-import os
-import uuid
-
-from oslo_config import cfg
-from oslo_log import log
-from oslo_utils import fileutils
-from oslo_utils import importutils
-from oslo_utils import timeutils
-import saml2
-from saml2 import client_base
-from saml2 import md
-from saml2.profile import ecp
-from saml2 import saml
-from saml2 import samlp
-from saml2.schema import soapenv
-from saml2 import sigver
-xmldsig = importutils.try_import("saml2.xmldsig")
-if not xmldsig:
- xmldsig = importutils.try_import("xmldsig")
-
-from keystone.common import environment
-from keystone.common import utils
-from keystone import exception
-from keystone.i18n import _, _LE
-
-
-LOG = log.getLogger(__name__)
-CONF = cfg.CONF
-
-
-class SAMLGenerator(object):
- """A class to generate SAML assertions."""
-
- def __init__(self):
- self.assertion_id = uuid.uuid4().hex
-
- def samlize_token(self, issuer, recipient, user, user_domain_name, roles,
- project, project_domain_name, expires_in=None):
- """Convert Keystone attributes to a SAML assertion.
-
- :param issuer: URL of the issuing party
- :type issuer: string
- :param recipient: URL of the recipient
- :type recipient: string
- :param user: User name
- :type user: string
- :param user_domain_name: User Domain name
- :type user_domain_name: string
- :param roles: List of role names
- :type roles: list
- :param project: Project name
- :type project: string
- :param project_domain_name: Project Domain name
- :type project_domain_name: string
- :param expires_in: Sets how long the assertion is valid for, in seconds
- :type expires_in: int
-
- :returns: XML <Response> object
-
- """
- expiration_time = self._determine_expiration_time(expires_in)
- status = self._create_status()
- saml_issuer = self._create_issuer(issuer)
- subject = self._create_subject(user, expiration_time, recipient)
- attribute_statement = self._create_attribute_statement(
- user, user_domain_name, roles, project, project_domain_name)
- authn_statement = self._create_authn_statement(issuer, expiration_time)
- signature = self._create_signature()
-
- assertion = self._create_assertion(saml_issuer, signature,
- subject, authn_statement,
- attribute_statement)
-
- assertion = _sign_assertion(assertion)
-
- response = self._create_response(saml_issuer, status, assertion,
- recipient)
- return response
-
- def _determine_expiration_time(self, expires_in):
- if expires_in is None:
- expires_in = CONF.saml.assertion_expiration_time
- now = timeutils.utcnow()
- future = now + datetime.timedelta(seconds=expires_in)
- return utils.isotime(future, subsecond=True)
-
- def _create_status(self):
- """Create an object that represents a SAML Status.
-
- <ns0:Status xmlns:ns0="urn:oasis:names:tc:SAML:2.0:protocol">
- <ns0:StatusCode
- Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
- </ns0:Status>
-
- :returns: XML <Status> object
-
- """
- status = samlp.Status()
- status_code = samlp.StatusCode()
- status_code.value = samlp.STATUS_SUCCESS
- status_code.set_text('')
- status.status_code = status_code
- return status
-
- def _create_issuer(self, issuer_url):
- """Create an object that represents a SAML Issuer.
-
- <ns0:Issuer
- xmlns:ns0="urn:oasis:names:tc:SAML:2.0:assertion"
- Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">
- https://acme.com/FIM/sps/openstack/saml20</ns0:Issuer>
-
- :returns: XML <Issuer> object
-
- """
- issuer = saml.Issuer()
- issuer.format = saml.NAMEID_FORMAT_ENTITY
- issuer.set_text(issuer_url)
- return issuer
-
- def _create_subject(self, user, expiration_time, recipient):
- """Create an object that represents a SAML Subject.
-
- <ns0:Subject>
- <ns0:NameID>
- john@smith.com</ns0:NameID>
- <ns0:SubjectConfirmation
- Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
- <ns0:SubjectConfirmationData
- NotOnOrAfter="2014-08-19T11:53:57.243106Z"
- Recipient="http://beta.com/Shibboleth.sso/SAML2/POST" />
- </ns0:SubjectConfirmation>
- </ns0:Subject>
-
- :returns: XML <Subject> object
-
- """
- name_id = saml.NameID()
- name_id.set_text(user)
- subject_conf_data = saml.SubjectConfirmationData()
- subject_conf_data.recipient = recipient
- subject_conf_data.not_on_or_after = expiration_time
- subject_conf = saml.SubjectConfirmation()
- subject_conf.method = saml.SCM_BEARER
- subject_conf.subject_confirmation_data = subject_conf_data
- subject = saml.Subject()
- subject.subject_confirmation = subject_conf
- subject.name_id = name_id
- return subject
-
- def _create_attribute_statement(self, user, user_domain_name, roles,
- project, project_domain_name):
- """Create an object that represents a SAML AttributeStatement.
-
- <ns0:AttributeStatement>
- <ns0:Attribute Name="openstack_user">
- <ns0:AttributeValue
- xsi:type="xs:string">test_user</ns0:AttributeValue>
- </ns0:Attribute>
- <ns0:Attribute Name="openstack_user_domain">
- <ns0:AttributeValue
- xsi:type="xs:string">Default</ns0:AttributeValue>
- </ns0:Attribute>
- <ns0:Attribute Name="openstack_roles">
- <ns0:AttributeValue
- xsi:type="xs:string">admin</ns0:AttributeValue>
- <ns0:AttributeValue
- xsi:type="xs:string">member</ns0:AttributeValue>
- </ns0:Attribute>
- <ns0:Attribute Name="openstack_project">
- <ns0:AttributeValue
- xsi:type="xs:string">development</ns0:AttributeValue>
- </ns0:Attribute>
- <ns0:Attribute Name="openstack_project_domain">
- <ns0:AttributeValue
- xsi:type="xs:string">Default</ns0:AttributeValue>
- </ns0:Attribute>
- </ns0:AttributeStatement>
-
- :returns: XML <AttributeStatement> object
-
- """
- def _build_attribute(attribute_name, attribute_values):
- attribute = saml.Attribute()
- attribute.name = attribute_name
-
- for value in attribute_values:
- attribute_value = saml.AttributeValue()
- attribute_value.set_text(value)
- attribute.attribute_value.append(attribute_value)
-
- return attribute
-
- user_attribute = _build_attribute('openstack_user', [user])
- roles_attribute = _build_attribute('openstack_roles', roles)
- project_attribute = _build_attribute('openstack_project', [project])
- project_domain_attribute = _build_attribute(
- 'openstack_project_domain', [project_domain_name])
- user_domain_attribute = _build_attribute(
- 'openstack_user_domain', [user_domain_name])
-
- attribute_statement = saml.AttributeStatement()
- attribute_statement.attribute.append(user_attribute)
- attribute_statement.attribute.append(roles_attribute)
- attribute_statement.attribute.append(project_attribute)
- attribute_statement.attribute.append(project_domain_attribute)
- attribute_statement.attribute.append(user_domain_attribute)
- return attribute_statement
-
- def _create_authn_statement(self, issuer, expiration_time):
- """Create an object that represents a SAML AuthnStatement.
-
- <ns0:AuthnStatement xmlns:ns0="urn:oasis:names:tc:SAML:2.0:assertion"
- AuthnInstant="2014-07-30T03:04:25Z" SessionIndex="47335964efb"
- SessionNotOnOrAfter="2014-07-30T03:04:26Z">
- <ns0:AuthnContext>
- <ns0:AuthnContextClassRef>
- urn:oasis:names:tc:SAML:2.0:ac:classes:Password
- </ns0:AuthnContextClassRef>
- <ns0:AuthenticatingAuthority>
- https://acme.com/FIM/sps/openstack/saml20
- </ns0:AuthenticatingAuthority>
- </ns0:AuthnContext>
- </ns0:AuthnStatement>
-
- :returns: XML <AuthnStatement> object
-
- """
- authn_statement = saml.AuthnStatement()
- authn_statement.authn_instant = utils.isotime()
- authn_statement.session_index = uuid.uuid4().hex
- authn_statement.session_not_on_or_after = expiration_time
-
- authn_context = saml.AuthnContext()
- authn_context_class = saml.AuthnContextClassRef()
- authn_context_class.set_text(saml.AUTHN_PASSWORD)
-
- authn_authority = saml.AuthenticatingAuthority()
- authn_authority.set_text(issuer)
- authn_context.authn_context_class_ref = authn_context_class
- authn_context.authenticating_authority = authn_authority
-
- authn_statement.authn_context = authn_context
-
- return authn_statement
-
- def _create_assertion(self, issuer, signature, subject, authn_statement,
- attribute_statement):
- """Create an object that represents a SAML Assertion.
-
- <ns0:Assertion
- ID="35daed258ba647ba8962e9baff4d6a46"
- IssueInstant="2014-06-11T15:45:58Z"
- Version="2.0">
- <ns0:Issuer> ... </ns0:Issuer>
- <ns1:Signature> ... </ns1:Signature>
- <ns0:Subject> ... </ns0:Subject>
- <ns0:AuthnStatement> ... </ns0:AuthnStatement>
- <ns0:AttributeStatement> ... </ns0:AttributeStatement>
- </ns0:Assertion>
-
- :returns: XML <Assertion> object
-
- """
- assertion = saml.Assertion()
- assertion.id = self.assertion_id
- assertion.issue_instant = utils.isotime()
- assertion.version = '2.0'
- assertion.issuer = issuer
- assertion.signature = signature
- assertion.subject = subject
- assertion.authn_statement = authn_statement
- assertion.attribute_statement = attribute_statement
- return assertion
-
- def _create_response(self, issuer, status, assertion, recipient):
- """Create an object that represents a SAML Response.
-
- <ns0:Response
- Destination="http://beta.com/Shibboleth.sso/SAML2/POST"
- ID="c5954543230e4e778bc5b92923a0512d"
- IssueInstant="2014-07-30T03:19:45Z"
- Version="2.0" />
- <ns0:Issuer> ... </ns0:Issuer>
- <ns0:Assertion> ... </ns0:Assertion>
- <ns0:Status> ... </ns0:Status>
- </ns0:Response>
-
- :returns: XML <Response> object
-
- """
- response = samlp.Response()
- response.id = uuid.uuid4().hex
- response.destination = recipient
- response.issue_instant = utils.isotime()
- response.version = '2.0'
- response.issuer = issuer
- response.status = status
- response.assertion = assertion
- return response
-
- def _create_signature(self):
- """Create an object that represents a SAML <Signature>.
-
- This must be filled with algorithms that the signing binary will apply
- in order to sign the whole message.
- Currently we enforce X509 signing.
- Example of the template::
-
- <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
- <SignedInfo>
- <CanonicalizationMethod
- Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
- <SignatureMethod
- Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
- <Reference URI="#<Assertion ID>">
- <Transforms>
- <Transform
- Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
- <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
- </Transforms>
- <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
- <DigestValue />
- </Reference>
- </SignedInfo>
- <SignatureValue />
- <KeyInfo>
- <X509Data />
- </KeyInfo>
- </Signature>
-
- :returns: XML <Signature> object
-
- """
- canonicalization_method = xmldsig.CanonicalizationMethod()
- canonicalization_method.algorithm = xmldsig.ALG_EXC_C14N
- signature_method = xmldsig.SignatureMethod(
- algorithm=xmldsig.SIG_RSA_SHA1)
-
- transforms = xmldsig.Transforms()
- envelope_transform = xmldsig.Transform(
- algorithm=xmldsig.TRANSFORM_ENVELOPED)
-
- c14_transform = xmldsig.Transform(algorithm=xmldsig.ALG_EXC_C14N)
- transforms.transform = [envelope_transform, c14_transform]
-
- digest_method = xmldsig.DigestMethod(algorithm=xmldsig.DIGEST_SHA1)
- digest_value = xmldsig.DigestValue()
-
- reference = xmldsig.Reference()
- reference.uri = '#' + self.assertion_id
- reference.digest_method = digest_method
- reference.digest_value = digest_value
- reference.transforms = transforms
-
- signed_info = xmldsig.SignedInfo()
- signed_info.canonicalization_method = canonicalization_method
- signed_info.signature_method = signature_method
- signed_info.reference = reference
-
- key_info = xmldsig.KeyInfo()
- key_info.x509_data = xmldsig.X509Data()
-
- signature = xmldsig.Signature()
- signature.signed_info = signed_info
- signature.signature_value = xmldsig.SignatureValue()
- signature.key_info = key_info
-
- return signature
-
-
-def _sign_assertion(assertion):
- """Sign a SAML assertion.
-
- This method utilizes ``xmlsec1`` binary and signs SAML assertions in a
- separate process. ``xmlsec1`` cannot read input data from stdin so the
- prepared assertion needs to be serialized and stored in a temporary
- file. This file will be deleted immediately after ``xmlsec1`` returns.
- The signed assertion is redirected to a standard output and read using
- subprocess.PIPE redirection. A ``saml.Assertion`` class is created
- from the signed string again and returned.
-
- Parameters that are required in the CONF::
- * xmlsec_binary
- * private key file path
- * public key file path
- :returns: XML <Assertion> object
-
- """
- xmlsec_binary = CONF.saml.xmlsec1_binary
- idp_private_key = CONF.saml.keyfile
- idp_public_key = CONF.saml.certfile
-
- # xmlsec1 --sign --privkey-pem privkey,cert --id-attr:ID <tag> <file>
- certificates = '%(idp_private_key)s,%(idp_public_key)s' % {
- 'idp_public_key': idp_public_key,
- 'idp_private_key': idp_private_key
- }
-
- command_list = [xmlsec_binary, '--sign', '--privkey-pem', certificates,
- '--id-attr:ID', 'Assertion']
-
- file_path = None
- try:
- # NOTE(gyee): need to make the namespace prefixes explicit so
- # they won't get reassigned when we wrap the assertion into
- # SAML2 response
- file_path = fileutils.write_to_tempfile(assertion.to_string(
- nspair={'saml': saml2.NAMESPACE,
- 'xmldsig': xmldsig.NAMESPACE}))
- command_list.append(file_path)
- subprocess = environment.subprocess
- stdout = subprocess.check_output(command_list, # nosec : The contents
- # of the command list are coming from
- # a trusted source because the
- # executable and arguments all either
- # come from the config file or are
- # hardcoded. The command list is
- # initialized earlier in this function
- # to a list and it's still a list at
- # this point in the function. There is
- # no opportunity for an attacker to
- # attempt command injection via string
- # parsing.
- stderr=subprocess.STDOUT)
- except Exception as e:
- msg = _LE('Error when signing assertion, reason: %(reason)s%(output)s')
- LOG.error(msg,
- {'reason': e,
- 'output': ' ' + e.output if hasattr(e, 'output') else ''})
- raise exception.SAMLSigningError(reason=e)
- finally:
- try:
- if file_path:
- os.remove(file_path)
- except OSError: # nosec
- # The file is already gone, good.
- pass
-
- return saml2.create_class_from_xml_string(saml.Assertion, stdout)
-
-
-class MetadataGenerator(object):
- """A class for generating SAML IdP Metadata."""
-
- def generate_metadata(self):
- """Generate Identity Provider Metadata.
-
- Generate and format metadata into XML that can be exposed and
- consumed by a federated Service Provider.
-
- :returns: XML <EntityDescriptor> object.
- :raises keystone.exception.ValidationError: If the required
- config options aren't set.
- """
- self._ensure_required_values_present()
- entity_descriptor = self._create_entity_descriptor()
- entity_descriptor.idpsso_descriptor = (
- self._create_idp_sso_descriptor())
- return entity_descriptor
-
- def _create_entity_descriptor(self):
- ed = md.EntityDescriptor()
- ed.entity_id = CONF.saml.idp_entity_id
- return ed
-
- def _create_idp_sso_descriptor(self):
-
- def get_cert():
- try:
- return sigver.read_cert_from_file(CONF.saml.certfile, 'pem')
- except (IOError, sigver.CertificateError) as e:
- msg = _('Cannot open certificate %(cert_file)s. '
- 'Reason: %(reason)s')
- msg = msg % {'cert_file': CONF.saml.certfile, 'reason': e}
- LOG.error(msg)
- raise IOError(msg)
-
- def key_descriptor():
- cert = get_cert()
- return md.KeyDescriptor(
- key_info=xmldsig.KeyInfo(
- x509_data=xmldsig.X509Data(
- x509_certificate=xmldsig.X509Certificate(text=cert)
- )
- ), use='signing'
- )
-
- def single_sign_on_service():
- idp_sso_endpoint = CONF.saml.idp_sso_endpoint
- return md.SingleSignOnService(
- binding=saml2.BINDING_URI,
- location=idp_sso_endpoint)
-
- def organization():
- name = md.OrganizationName(lang=CONF.saml.idp_lang,
- text=CONF.saml.idp_organization_name)
- display_name = md.OrganizationDisplayName(
- lang=CONF.saml.idp_lang,
- text=CONF.saml.idp_organization_display_name)
- url = md.OrganizationURL(lang=CONF.saml.idp_lang,
- text=CONF.saml.idp_organization_url)
-
- return md.Organization(
- organization_display_name=display_name,
- organization_url=url, organization_name=name)
-
- def contact_person():
- company = md.Company(text=CONF.saml.idp_contact_company)
- given_name = md.GivenName(text=CONF.saml.idp_contact_name)
- surname = md.SurName(text=CONF.saml.idp_contact_surname)
- email = md.EmailAddress(text=CONF.saml.idp_contact_email)
- telephone = md.TelephoneNumber(
- text=CONF.saml.idp_contact_telephone)
- contact_type = CONF.saml.idp_contact_type
-
- return md.ContactPerson(
- company=company, given_name=given_name, sur_name=surname,
- email_address=email, telephone_number=telephone,
- contact_type=contact_type)
-
- def name_id_format():
- return md.NameIDFormat(text=saml.NAMEID_FORMAT_TRANSIENT)
-
- idpsso = md.IDPSSODescriptor()
- idpsso.protocol_support_enumeration = samlp.NAMESPACE
- idpsso.key_descriptor = key_descriptor()
- idpsso.single_sign_on_service = single_sign_on_service()
- idpsso.name_id_format = name_id_format()
- if self._check_organization_values():
- idpsso.organization = organization()
- if self._check_contact_person_values():
- idpsso.contact_person = contact_person()
- return idpsso
-
- def _ensure_required_values_present(self):
- """Ensure idp_sso_endpoint and idp_entity_id have values."""
- if CONF.saml.idp_entity_id is None:
- msg = _('Ensure configuration option idp_entity_id is set.')
- raise exception.ValidationError(msg)
- if CONF.saml.idp_sso_endpoint is None:
- msg = _('Ensure configuration option idp_sso_endpoint is set.')
- raise exception.ValidationError(msg)
-
- def _check_contact_person_values(self):
- """Determine if contact information is included in metadata."""
- # Check if we should include contact information
- params = [CONF.saml.idp_contact_company,
- CONF.saml.idp_contact_name,
- CONF.saml.idp_contact_surname,
- CONF.saml.idp_contact_email,
- CONF.saml.idp_contact_telephone]
- for value in params:
- if value is None:
- return False
-
- # Check if contact type is an invalid value
- valid_type_values = ['technical', 'other', 'support', 'administrative',
- 'billing']
- if CONF.saml.idp_contact_type not in valid_type_values:
- msg = _('idp_contact_type must be one of: [technical, other, '
- 'support, administrative or billing.')
- raise exception.ValidationError(msg)
- return True
-
- def _check_organization_values(self):
- """Determine if organization information is included in metadata."""
- params = [CONF.saml.idp_organization_name,
- CONF.saml.idp_organization_display_name,
- CONF.saml.idp_organization_url]
- for value in params:
- if value is None:
- return False
- return True
-
-
-class ECPGenerator(object):
- """A class for generating an ECP assertion."""
-
- @staticmethod
- def generate_ecp(saml_assertion, relay_state_prefix):
- ecp_generator = ECPGenerator()
- header = ecp_generator._create_header(relay_state_prefix)
- body = ecp_generator._create_body(saml_assertion)
- envelope = soapenv.Envelope(header=header, body=body)
- return envelope
-
- def _create_header(self, relay_state_prefix):
- relay_state_text = relay_state_prefix + uuid.uuid4().hex
- relay_state = ecp.RelayState(actor=client_base.ACTOR,
- must_understand='1',
- text=relay_state_text)
- header = soapenv.Header()
- header.extension_elements = (
- [saml2.element_to_extension_element(relay_state)])
- return header
-
- def _create_body(self, saml_assertion):
- body = soapenv.Body()
- body.extension_elements = (
- [saml2.element_to_extension_element(saml_assertion)])
- return body
diff --git a/keystone-moon/keystone/federation/routers.py b/keystone-moon/keystone/federation/routers.py
deleted file mode 100644
index a463ca63..00000000
--- a/keystone-moon/keystone/federation/routers.py
+++ /dev/null
@@ -1,252 +0,0 @@
-# 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.
-
-import functools
-
-from keystone.common import json_home
-from keystone.common import wsgi
-from keystone.federation import controllers
-
-
-build_resource_relation = functools.partial(
- json_home.build_v3_extension_resource_relation,
- extension_name='OS-FEDERATION', extension_version='1.0')
-
-build_parameter_relation = functools.partial(
- json_home.build_v3_extension_parameter_relation,
- extension_name='OS-FEDERATION', extension_version='1.0')
-
-IDP_ID_PARAMETER_RELATION = build_parameter_relation(parameter_name='idp_id')
-PROTOCOL_ID_PARAMETER_RELATION = build_parameter_relation(
- parameter_name='protocol_id')
-SP_ID_PARAMETER_RELATION = build_parameter_relation(parameter_name='sp_id')
-
-
-class Routers(wsgi.RoutersBase):
- """API Endpoints for the Federation extension.
-
- The API looks like::
-
- PUT /OS-FEDERATION/identity_providers/{idp_id}
- GET /OS-FEDERATION/identity_providers
- GET /OS-FEDERATION/identity_providers/{idp_id}
- DELETE /OS-FEDERATION/identity_providers/{idp_id}
- PATCH /OS-FEDERATION/identity_providers/{idp_id}
-
- PUT /OS-FEDERATION/identity_providers/
- {idp_id}/protocols/{protocol_id}
- GET /OS-FEDERATION/identity_providers/
- {idp_id}/protocols
- GET /OS-FEDERATION/identity_providers/
- {idp_id}/protocols/{protocol_id}
- PATCH /OS-FEDERATION/identity_providers/
- {idp_id}/protocols/{protocol_id}
- DELETE /OS-FEDERATION/identity_providers/
- {idp_id}/protocols/{protocol_id}
-
- PUT /OS-FEDERATION/mappings
- GET /OS-FEDERATION/mappings
- PATCH /OS-FEDERATION/mappings/{mapping_id}
- GET /OS-FEDERATION/mappings/{mapping_id}
- DELETE /OS-FEDERATION/mappings/{mapping_id}
-
- GET /OS-FEDERATION/projects
- GET /OS-FEDERATION/domains
-
- PUT /OS-FEDERATION/service_providers/{sp_id}
- GET /OS-FEDERATION/service_providers
- GET /OS-FEDERATION/service_providers/{sp_id}
- DELETE /OS-FEDERATION/service_providers/{sp_id}
- PATCH /OS-FEDERATION/service_providers/{sp_id}
-
- GET /OS-FEDERATION/identity_providers/{idp_id}/
- protocols/{protocol_id}/auth
- POST /OS-FEDERATION/identity_providers/{idp_id}/
- protocols/{protocol_id}/auth
- GET /auth/OS-FEDERATION/identity_providers/
- {idp_id}/protocols/{protocol_id}/websso
- ?origin=https%3A//horizon.example.com
- POST /auth/OS-FEDERATION/identity_providers/
- {idp_id}/protocols/{protocol_id}/websso
- ?origin=https%3A//horizon.example.com
-
-
- POST /auth/OS-FEDERATION/saml2
- POST /auth/OS-FEDERATION/saml2/ecp
- GET /OS-FEDERATION/saml2/metadata
-
- GET /auth/OS-FEDERATION/websso/{protocol_id}
- ?origin=https%3A//horizon.example.com
-
- POST /auth/OS-FEDERATION/websso/{protocol_id}
- ?origin=https%3A//horizon.example.com
-
- """
-
- def _construct_url(self, suffix):
- return "/OS-FEDERATION/%s" % suffix
-
- def append_v3_routers(self, mapper, routers):
- auth_controller = controllers.Auth()
- idp_controller = controllers.IdentityProvider()
- protocol_controller = controllers.FederationProtocol()
- mapping_controller = controllers.MappingController()
- project_controller = controllers.ProjectAssignmentV3()
- domain_controller = controllers.DomainV3()
- saml_metadata_controller = controllers.SAMLMetadataV3()
- sp_controller = controllers.ServiceProvider()
-
- # Identity Provider CRUD operations
-
- self._add_resource(
- mapper, idp_controller,
- path=self._construct_url('identity_providers/{idp_id}'),
- get_action='get_identity_provider',
- put_action='create_identity_provider',
- patch_action='update_identity_provider',
- delete_action='delete_identity_provider',
- rel=build_resource_relation(resource_name='identity_provider'),
- path_vars={
- 'idp_id': IDP_ID_PARAMETER_RELATION,
- })
- self._add_resource(
- mapper, idp_controller,
- path=self._construct_url('identity_providers'),
- get_action='list_identity_providers',
- rel=build_resource_relation(resource_name='identity_providers'))
-
- # Protocol CRUD operations
-
- self._add_resource(
- mapper, protocol_controller,
- path=self._construct_url('identity_providers/{idp_id}/protocols/'
- '{protocol_id}'),
- get_action='get_protocol',
- put_action='create_protocol',
- patch_action='update_protocol',
- delete_action='delete_protocol',
- rel=build_resource_relation(
- resource_name='identity_provider_protocol'),
- path_vars={
- 'idp_id': IDP_ID_PARAMETER_RELATION,
- 'protocol_id': PROTOCOL_ID_PARAMETER_RELATION,
- })
- self._add_resource(
- mapper, protocol_controller,
- path=self._construct_url('identity_providers/{idp_id}/protocols'),
- get_action='list_protocols',
- rel=build_resource_relation(
- resource_name='identity_provider_protocols'),
- path_vars={
- 'idp_id': IDP_ID_PARAMETER_RELATION,
- })
-
- # Mapping CRUD operations
-
- self._add_resource(
- mapper, mapping_controller,
- path=self._construct_url('mappings/{mapping_id}'),
- get_action='get_mapping',
- put_action='create_mapping',
- patch_action='update_mapping',
- delete_action='delete_mapping',
- rel=build_resource_relation(resource_name='mapping'),
- path_vars={
- 'mapping_id': build_parameter_relation(
- parameter_name='mapping_id'),
- })
- self._add_resource(
- mapper, mapping_controller,
- path=self._construct_url('mappings'),
- get_action='list_mappings',
- rel=build_resource_relation(resource_name='mappings'))
-
- # Service Providers CRUD operations
-
- self._add_resource(
- mapper, sp_controller,
- path=self._construct_url('service_providers/{sp_id}'),
- get_action='get_service_provider',
- put_action='create_service_provider',
- patch_action='update_service_provider',
- delete_action='delete_service_provider',
- rel=build_resource_relation(resource_name='service_provider'),
- path_vars={
- 'sp_id': SP_ID_PARAMETER_RELATION,
- })
-
- self._add_resource(
- mapper, sp_controller,
- path=self._construct_url('service_providers'),
- get_action='list_service_providers',
- rel=build_resource_relation(resource_name='service_providers'))
-
- self._add_resource(
- mapper, domain_controller,
- path=self._construct_url('domains'),
- new_path='/auth/domains',
- get_action='list_domains_for_groups',
- rel=build_resource_relation(resource_name='domains'))
- self._add_resource(
- mapper, project_controller,
- path=self._construct_url('projects'),
- new_path='/auth/projects',
- get_action='list_projects_for_groups',
- rel=build_resource_relation(resource_name='projects'))
-
- # Auth operations
- self._add_resource(
- mapper, auth_controller,
- path=self._construct_url('identity_providers/{idp_id}/'
- 'protocols/{protocol_id}/auth'),
- get_post_action='federated_authentication',
- rel=build_resource_relation(
- resource_name='identity_provider_protocol_auth'),
- path_vars={
- 'idp_id': IDP_ID_PARAMETER_RELATION,
- 'protocol_id': PROTOCOL_ID_PARAMETER_RELATION,
- })
- self._add_resource(
- mapper, auth_controller,
- path='/auth' + self._construct_url('saml2'),
- post_action='create_saml_assertion',
- rel=build_resource_relation(resource_name='saml2'))
- self._add_resource(
- mapper, auth_controller,
- path='/auth' + self._construct_url('saml2/ecp'),
- post_action='create_ecp_assertion',
- rel=build_resource_relation(resource_name='ecp'))
- self._add_resource(
- mapper, auth_controller,
- path='/auth' + self._construct_url('websso/{protocol_id}'),
- get_post_action='federated_sso_auth',
- rel=build_resource_relation(resource_name='websso'),
- path_vars={
- 'protocol_id': PROTOCOL_ID_PARAMETER_RELATION,
- })
- self._add_resource(
- mapper, auth_controller,
- path='/auth' + self._construct_url(
- 'identity_providers/{idp_id}/protocols/{protocol_id}/websso'),
- get_post_action='federated_idp_specific_sso_auth',
- rel=build_resource_relation(resource_name='identity_providers'),
- path_vars={
- 'idp_id': IDP_ID_PARAMETER_RELATION,
- 'protocol_id': PROTOCOL_ID_PARAMETER_RELATION,
- })
-
- # Keystone-Identity-Provider metadata endpoint
- self._add_resource(
- mapper, saml_metadata_controller,
- path=self._construct_url('saml2/metadata'),
- get_action='get_metadata',
- rel=build_resource_relation(resource_name='metadata'))
diff --git a/keystone-moon/keystone/federation/schema.py b/keystone-moon/keystone/federation/schema.py
deleted file mode 100644
index 6cdfd1f5..00000000
--- a/keystone-moon/keystone/federation/schema.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# 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 keystone.common import validation
-from keystone.common.validation import parameter_types
-
-
-basic_property_id = {
- 'type': 'object',
- 'properties': {
- 'id': {
- 'type': 'string'
- }
- },
- 'required': ['id'],
- 'additionalProperties': False
-}
-
-saml_create = {
- 'type': 'object',
- 'properties': {
- 'identity': {
- 'type': 'object',
- 'properties': {
- 'token': basic_property_id,
- 'methods': {
- 'type': 'array'
- }
- },
- 'required': ['token'],
- 'additionalProperties': False
- },
- 'scope': {
- 'type': 'object',
- 'properties': {
- 'service_provider': basic_property_id
- },
- 'required': ['service_provider'],
- 'additionalProperties': False
- },
- },
- 'required': ['identity', 'scope'],
- 'additionalProperties': False
-}
-
-_service_provider_properties = {
- # NOTE(rodrigods): The database accepts URLs with 256 as max length,
- # but parameter_types.url uses 225 as max length.
- 'auth_url': parameter_types.url,
- 'sp_url': parameter_types.url,
- 'description': validation.nullable(parameter_types.description),
- 'enabled': parameter_types.boolean,
- 'relay_state_prefix': validation.nullable(parameter_types.description)
-}
-
-service_provider_create = {
- 'type': 'object',
- 'properties': _service_provider_properties,
- # NOTE(rodrigods): 'id' is not required since it is passed in the URL
- 'required': ['auth_url', 'sp_url'],
- 'additionalProperties': False
-}
-
-service_provider_update = {
- 'type': 'object',
- 'properties': _service_provider_properties,
- # Make sure at least one property is being updated
- 'minProperties': 1,
- 'additionalProperties': False
-}
-
-_identity_provider_properties = {
- 'enabled': parameter_types.boolean,
- 'description': validation.nullable(parameter_types.description),
- 'remote_ids': {
- 'type': ['array', 'null'],
- 'items': {
- 'type': 'string'
- },
- 'uniqueItems': True
- }
-}
-
-identity_provider_create = {
- 'type': 'object',
- 'properties': _identity_provider_properties,
- 'additionalProperties': False
-}
-
-identity_provider_update = {
- 'type': 'object',
- 'properties': _identity_provider_properties,
- # Make sure at least one property is being updated
- 'minProperties': 1,
- 'additionalProperties': False
-}
-
-federation_protocol_schema = {
- 'type': 'object',
- 'properties': {
- 'mapping_id': parameter_types.mapping_id_string
- },
- # `mapping_id` is the property that cannot be ignored
- 'minProperties': 1,
- 'additionalProperties': False
-}
diff --git a/keystone-moon/keystone/federation/utils.py b/keystone-moon/keystone/federation/utils.py
deleted file mode 100644
index 1d215a68..00000000
--- a/keystone-moon/keystone/federation/utils.py
+++ /dev/null
@@ -1,872 +0,0 @@
-# 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.
-
-"""Utilities for Federation Extension."""
-
-import ast
-import re
-
-import jsonschema
-from oslo_config import cfg
-from oslo_log import log
-from oslo_utils import timeutils
-import six
-
-from keystone import exception
-from keystone.i18n import _, _LW
-
-
-CONF = cfg.CONF
-LOG = log.getLogger(__name__)
-
-
-class UserType(object):
- """User mapping type."""
-
- EPHEMERAL = 'ephemeral'
- LOCAL = 'local'
-
-
-MAPPING_SCHEMA = {
- "type": "object",
- "required": ['rules'],
- "properties": {
- "rules": {
- "minItems": 1,
- "type": "array",
- "items": {
- "type": "object",
- "required": ['local', 'remote'],
- "additionalProperties": False,
- "properties": {
- "local": {
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": False,
- "properties": {
- "user": {
- "type": "object",
- "properties": {
- "id": {"type": "string"},
- "name": {"type": "string"},
- "email": {"type": "string"},
- "domain": {
- "type": "object",
- "properties": {
- "id": {"type": "string"},
- "name": {"type": "string"}
- },
- "additionalProperties": False,
- },
- "type": {
- "type": "string",
- "enum": [UserType.EPHEMERAL,
- UserType.LOCAL]
- }
- },
- "additionalProperties": False
- },
- "group": {
- "type": "object",
- "properties": {
- "id": {"type": "string"},
- "name": {"type": "string"},
- "domain": {
- "type": "object",
- "properties": {
- "id": {"type": "string"},
- "name": {"type": "string"}
- },
- "additionalProperties": False,
- },
- },
- "additionalProperties": False,
- },
- "groups": {
- "type": "string"
- },
- "group_ids": {
- "type": "string"
- },
- "domain": {
- "type": "object",
- "properties": {
- "id": {"type": "string"},
- "name": {"type": "string"}
- },
- "additionalProperties": False
- }
- }
- }
- },
- "remote": {
- "minItems": 1,
- "type": "array",
- "items": {
- "type": "object",
- "oneOf": [
- {"$ref": "#/definitions/empty"},
- {"$ref": "#/definitions/any_one_of"},
- {"$ref": "#/definitions/not_any_of"},
- {"$ref": "#/definitions/blacklist"},
- {"$ref": "#/definitions/whitelist"}
- ],
- }
- }
- }
- }
- }
- },
- "definitions": {
- "empty": {
- "type": "object",
- "required": ['type'],
- "properties": {
- "type": {
- "type": "string"
- },
- },
- "additionalProperties": False,
- },
- "any_one_of": {
- "type": "object",
- "additionalProperties": False,
- "required": ['type', 'any_one_of'],
- "properties": {
- "type": {
- "type": "string"
- },
- "any_one_of": {
- "type": "array"
- },
- "regex": {
- "type": "boolean"
- }
- }
- },
- "not_any_of": {
- "type": "object",
- "additionalProperties": False,
- "required": ['type', 'not_any_of'],
- "properties": {
- "type": {
- "type": "string"
- },
- "not_any_of": {
- "type": "array"
- },
- "regex": {
- "type": "boolean"
- }
- }
- },
- "blacklist": {
- "type": "object",
- "additionalProperties": False,
- "required": ['type', 'blacklist'],
- "properties": {
- "type": {
- "type": "string"
- },
- "blacklist": {
- "type": "array"
- }
- }
- },
- "whitelist": {
- "type": "object",
- "additionalProperties": False,
- "required": ['type', 'whitelist'],
- "properties": {
- "type": {
- "type": "string"
- },
- "whitelist": {
- "type": "array"
- }
- }
- }
- }
-}
-
-
-class DirectMaps(object):
- """An abstraction around the remote matches.
-
- Each match is treated internally as a list.
- """
-
- def __init__(self):
- self._matches = []
-
- def add(self, values):
- """Adds a matched value to the list of matches.
-
- :param list value: the match to save
-
- """
- self._matches.append(values)
-
- def __getitem__(self, idx):
- """Used by Python when executing ``''.format(*DirectMaps())``."""
- value = self._matches[idx]
- if isinstance(value, list) and len(value) == 1:
- return value[0]
- else:
- return value
-
-
-def validate_mapping_structure(ref):
- v = jsonschema.Draft4Validator(MAPPING_SCHEMA)
-
- messages = ''
- for error in sorted(v.iter_errors(ref), key=str):
- messages = messages + error.message + "\n"
-
- if messages:
- raise exception.ValidationError(messages)
-
-
-def validate_expiration(token_ref):
- if timeutils.utcnow() > token_ref.expires:
- raise exception.Unauthorized(_('Federation token is expired'))
-
-
-def validate_groups_cardinality(group_ids, mapping_id):
- """Check if groups list is non-empty.
-
- :param group_ids: list of group ids
- :type group_ids: list of str
-
- :raises keystone.exception.MissingGroups: if ``group_ids`` cardinality is 0
-
- """
- if not group_ids:
- raise exception.MissingGroups(mapping_id=mapping_id)
-
-
-def get_remote_id_parameter(protocol):
- # NOTE(marco-fargetta): Since we support any protocol ID, we attempt to
- # retrieve the remote_id_attribute of the protocol ID. If it's not
- # registered in the config, then register the option and try again.
- # This allows the user to register protocols other than oidc and saml2.
- remote_id_parameter = None
- try:
- remote_id_parameter = CONF[protocol]['remote_id_attribute']
- except AttributeError:
- CONF.register_opt(cfg.StrOpt('remote_id_attribute'),
- group=protocol)
- try:
- remote_id_parameter = CONF[protocol]['remote_id_attribute']
- except AttributeError: # nosec
- # No remote ID attr, will be logged and use the default instead.
- pass
- if not remote_id_parameter:
- LOG.debug('Cannot find "remote_id_attribute" in configuration '
- 'group %s. Trying default location in '
- 'group federation.', protocol)
- remote_id_parameter = CONF.federation.remote_id_attribute
-
- return remote_id_parameter
-
-
-def validate_idp(idp, protocol, assertion):
- """The IdP providing the assertion should be registered for the mapping."""
- remote_id_parameter = get_remote_id_parameter(protocol)
- if not remote_id_parameter or not idp['remote_ids']:
- LOG.debug('Impossible to identify the IdP %s ', idp['id'])
- # If nothing is defined, the administrator may want to
- # allow the mapping of every IdP
- return
- try:
- idp_remote_identifier = assertion[remote_id_parameter]
- except KeyError:
- msg = _('Could not find Identity Provider identifier in '
- 'environment')
- raise exception.ValidationError(msg)
- if idp_remote_identifier not in idp['remote_ids']:
- msg = _('Incoming identity provider identifier not included '
- 'among the accepted identifiers.')
- raise exception.Forbidden(msg)
-
-
-def validate_groups_in_backend(group_ids, mapping_id, identity_api):
- """Iterate over group ids and make sure they are present in the backend.
-
- This call is not transactional.
- :param group_ids: IDs of the groups to be checked
- :type group_ids: list of str
-
- :param mapping_id: id of the mapping used for this operation
- :type mapping_id: str
-
- :param identity_api: Identity Manager object used for communication with
- backend
- :type identity_api: identity.Manager
-
- :raises keystone.exception.MappedGroupNotFound: If the group returned by
- mapping was not found in the backend.
-
- """
- for group_id in group_ids:
- try:
- identity_api.get_group(group_id)
- except exception.GroupNotFound:
- raise exception.MappedGroupNotFound(
- group_id=group_id, mapping_id=mapping_id)
-
-
-def validate_groups(group_ids, mapping_id, identity_api):
- """Check group ids cardinality and check their existence in the backend.
-
- This call is not transactional.
- :param group_ids: IDs of the groups to be checked
- :type group_ids: list of str
-
- :param mapping_id: id of the mapping used for this operation
- :type mapping_id: str
-
- :param identity_api: Identity Manager object used for communication with
- backend
- :type identity_api: identity.Manager
-
- :raises keystone.exception.MappedGroupNotFound: If the group returned by
- mapping was not found in the backend.
- :raises keystone.exception.MissingGroups: If ``group_ids`` cardinality
- is 0.
-
- """
- validate_groups_cardinality(group_ids, mapping_id)
- validate_groups_in_backend(group_ids, mapping_id, identity_api)
-
-
-# TODO(marek-denis): Optimize this function, so the number of calls to the
-# backend are minimized.
-def transform_to_group_ids(group_names, mapping_id,
- identity_api, resource_api):
- """Transform groups identified by name/domain to their ids
-
- Function accepts list of groups identified by a name and domain giving
- a list of group ids in return.
-
- Example of group_names parameter::
-
- [
- {
- "name": "group_name",
- "domain": {
- "id": "domain_id"
- },
- },
- {
- "name": "group_name_2",
- "domain": {
- "name": "domain_name"
- }
- }
- ]
-
- :param group_names: list of group identified by name and its domain.
- :type group_names: list
-
- :param mapping_id: id of the mapping used for mapping assertion into
- local credentials
- :type mapping_id: str
-
- :param identity_api: identity_api object
- :param resource_api: resource manager object
-
- :returns: generator object with group ids
-
- :raises keystone.exception.MappedGroupNotFound: in case asked group doesn't
- exist in the backend.
-
- """
- def resolve_domain(domain):
- """Return domain id.
-
- Input is a dictionary with a domain identified either by a ``id`` or a
- ``name``. In the latter case system will attempt to fetch domain object
- from the backend.
-
- :returns: domain's id
- :rtype: str
-
- """
- domain_id = (domain.get('id') or
- resource_api.get_domain_by_name(
- domain.get('name')).get('id'))
- return domain_id
-
- for group in group_names:
- try:
- group_dict = identity_api.get_group_by_name(
- group['name'], resolve_domain(group['domain']))
- yield group_dict['id']
- except exception.GroupNotFound:
- LOG.debug('Skip mapping group %s; has no entry in the backend',
- group['name'])
-
-
-def get_assertion_params_from_env(context):
- LOG.debug('Environment variables: %s', context['environment'])
- prefix = CONF.federation.assertion_prefix
- for k, v in list(context['environment'].items()):
- if not k.startswith(prefix):
- continue
- # These bytes may be decodable as ISO-8859-1 according to Section
- # 3.2.4 of RFC 7230. Let's assume that our web server plugins are
- # correctly encoding the data.
- if not isinstance(v, six.text_type) and getattr(v, 'decode', False):
- v = v.decode('ISO-8859-1')
- yield (k, v)
-
-
-class RuleProcessor(object):
- """A class to process assertions and mapping rules."""
-
- class _EvalType(object):
- """Mapping rule evaluation types."""
-
- ANY_ONE_OF = 'any_one_of'
- NOT_ANY_OF = 'not_any_of'
- BLACKLIST = 'blacklist'
- WHITELIST = 'whitelist'
-
- def __init__(self, mapping_id, rules):
- """Initialize RuleProcessor.
-
- Example rules can be found at:
- :class:`keystone.tests.mapping_fixtures`
-
- :param mapping_id: id for the mapping
- :type mapping_id: string
- :param rules: rules from a mapping
- :type rules: dict
-
- """
- self.mapping_id = mapping_id
- self.rules = rules
-
- def process(self, assertion_data):
- """Transform assertion to a dictionary.
-
- The dictionary contains mapping of user name and group ids
- based on mapping rules.
-
- This function will iterate through the mapping rules to find
- assertions that are valid.
-
- :param assertion_data: an assertion containing values from an IdP
- :type assertion_data: dict
-
- Example assertion_data::
-
- {
- 'Email': 'testacct@example.com',
- 'UserName': 'testacct',
- 'FirstName': 'Test',
- 'LastName': 'Account',
- 'orgPersonType': 'Tester'
- }
-
- :returns: dictionary with user and group_ids
-
- The expected return structure is::
-
- {
- 'name': 'foobar',
- 'group_ids': ['abc123', 'def456'],
- 'group_names': [
- {
- 'name': 'group_name_1',
- 'domain': {
- 'name': 'domain1'
- }
- },
- {
- 'name': 'group_name_1_1',
- 'domain': {
- 'name': 'domain1'
- }
- },
- {
- 'name': 'group_name_2',
- 'domain': {
- 'id': 'xyz132'
- }
- }
- ]
- }
-
- """
- # Assertions will come in as string key-value pairs, and will use a
- # semi-colon to indicate multiple values, i.e. groups.
- # This will create a new dictionary where the values are arrays, and
- # any multiple values are stored in the arrays.
- LOG.debug('assertion data: %s', assertion_data)
- assertion = {n: v.split(';') for n, v in assertion_data.items()
- if isinstance(v, six.string_types)}
- LOG.debug('assertion: %s', assertion)
- identity_values = []
-
- LOG.debug('rules: %s', self.rules)
- for rule in self.rules:
- direct_maps = self._verify_all_requirements(rule['remote'],
- assertion)
-
- # If the compare comes back as None, then the rule did not apply
- # to the assertion data, go on to the next rule
- if direct_maps is None:
- continue
-
- # If there are no direct mappings, then add the local mapping
- # directly to the array of saved values. However, if there is
- # a direct mapping, then perform variable replacement.
- if not direct_maps:
- identity_values += rule['local']
- else:
- for local in rule['local']:
- new_local = self._update_local_mapping(local, direct_maps)
- identity_values.append(new_local)
-
- LOG.debug('identity_values: %s', identity_values)
- mapped_properties = self._transform(identity_values)
- LOG.debug('mapped_properties: %s', mapped_properties)
- return mapped_properties
-
- def _transform(self, identity_values):
- """Transform local mappings, to an easier to understand format.
-
- Transform the incoming array to generate the return value for
- the process function. Generating content for Keystone tokens will
- be easier if some pre-processing is done at this level.
-
- :param identity_values: local mapping from valid evaluations
- :type identity_values: array of dict
-
- Example identity_values::
-
- [
- {
- 'group': {'id': '0cd5e9'},
- 'user': {
- 'email': 'bob@example.com'
- },
- },
- {
- 'groups': ['member', 'admin', tester'],
- 'domain': {
- 'name': 'default_domain'
- }
- },
- {
- 'group_ids': ['abc123', 'def456', '0cd5e9']
- }
- ]
-
- :returns: dictionary with user name, group_ids and group_names.
- :rtype: dict
-
- """
- def extract_groups(groups_by_domain):
- for groups in list(groups_by_domain.values()):
- for group in list({g['name']: g for g in groups}.values()):
- yield group
-
- def normalize_user(user):
- """Parse and validate user mapping."""
- user_type = user.get('type')
-
- if user_type and user_type not in (UserType.EPHEMERAL,
- UserType.LOCAL):
- msg = _("User type %s not supported") % user_type
- raise exception.ValidationError(msg)
-
- if user_type is None:
- user_type = user['type'] = UserType.EPHEMERAL
-
- if user_type == UserType.EPHEMERAL:
- user['domain'] = {
- 'id': CONF.federation.federated_domain_name
- }
-
- # initialize the group_ids as a set to eliminate duplicates
- user = {}
- group_ids = set()
- group_names = list()
- groups_by_domain = dict()
-
- # if mapping yield no valid identity values, we should bail right away
- # instead of continuing on with a normalized bogus user
- if not identity_values:
- msg = _("Could not map any federated user properties to identity "
- "values. Check debug logs or the mapping used for "
- "additional details.")
- LOG.warning(msg)
- raise exception.ValidationError(msg)
-
- for identity_value in identity_values:
- if 'user' in identity_value:
- # if a mapping outputs more than one user name, log it
- if user:
- LOG.warning(_LW('Ignoring user name'))
- else:
- user = identity_value.get('user')
- if 'group' in identity_value:
- group = identity_value['group']
- if 'id' in group:
- group_ids.add(group['id'])
- elif 'name' in group:
- domain = (group['domain'].get('name') or
- group['domain'].get('id'))
- groups_by_domain.setdefault(domain, list()).append(group)
- group_names.extend(extract_groups(groups_by_domain))
- if 'groups' in identity_value:
- if 'domain' not in identity_value:
- msg = _("Invalid rule: %(identity_value)s. Both 'groups' "
- "and 'domain' keywords must be specified.")
- msg = msg % {'identity_value': identity_value}
- raise exception.ValidationError(msg)
- # In this case, identity_value['groups'] is a string
- # representation of a list, and we want a real list. This is
- # due to the way we do direct mapping substitutions today (see
- # function _update_local_mapping() )
- try:
- group_names_list = ast.literal_eval(
- identity_value['groups'])
- except ValueError:
- group_names_list = [identity_value['groups']]
- domain = identity_value['domain']
- group_dicts = [{'name': name, 'domain': domain} for name in
- group_names_list]
-
- group_names.extend(group_dicts)
- if 'group_ids' in identity_value:
- # If identity_values['group_ids'] is a string representation
- # of a list, parse it to a real list. Also, if the provided
- # group_ids parameter contains only one element, it will be
- # parsed as a simple string, and not a list or the
- # representation of a list.
- try:
- group_ids.update(
- ast.literal_eval(identity_value['group_ids']))
- except (ValueError, SyntaxError):
- group_ids.update([identity_value['group_ids']])
-
- normalize_user(user)
-
- return {'user': user,
- 'group_ids': list(group_ids),
- 'group_names': group_names}
-
- def _update_local_mapping(self, local, direct_maps):
- """Replace any {0}, {1} ... values with data from the assertion.
-
- :param local: local mapping reference that needs to be updated
- :type local: dict
- :param direct_maps: identity values used to update local
- :type direct_maps: keystone.federation.utils.DirectMaps
-
- Example local::
-
- {'user': {'name': '{0} {1}', 'email': '{2}'}}
-
- Example direct_maps::
-
- ['Bob', 'Thompson', 'bob@example.com']
-
- :returns: new local mapping reference with replaced values.
-
- The expected return structure is::
-
- {'user': {'name': 'Bob Thompson', 'email': 'bob@example.org'}}
-
- :raises keystone.exception.DirectMappingError: when referring to a
- remote match from a local section of a rule
-
- """
- LOG.debug('direct_maps: %s', direct_maps)
- LOG.debug('local: %s', local)
- new = {}
- for k, v in local.items():
- if isinstance(v, dict):
- new_value = self._update_local_mapping(v, direct_maps)
- else:
- try:
- new_value = v.format(*direct_maps)
- except IndexError:
- raise exception.DirectMappingError(
- mapping_id=self.mapping_id)
-
- new[k] = new_value
- return new
-
- def _verify_all_requirements(self, requirements, assertion):
- """Compare remote requirements of a rule against the assertion.
-
- If a value of ``None`` is returned, the rule with this assertion
- doesn't apply.
- If an array of zero length is returned, then there are no direct
- mappings to be performed, but the rule is valid.
- Otherwise, then it will first attempt to filter the values according
- to blacklist or whitelist rules and finally return the values in
- order, to be directly mapped.
-
- :param requirements: list of remote requirements from rules
- :type requirements: list
-
- Example requirements::
-
- [
- {
- "type": "UserName"
- },
- {
- "type": "orgPersonType",
- "any_one_of": [
- "Customer"
- ]
- },
- {
- "type": "ADFS_GROUPS",
- "whitelist": [
- "g1", "g2", "g3", "g4"
- ]
- }
- ]
-
- :param assertion: dict of attributes from an IdP
- :type assertion: dict
-
- Example assertion::
-
- {
- 'UserName': ['testacct'],
- 'LastName': ['Account'],
- 'orgPersonType': ['Tester'],
- 'Email': ['testacct@example.com'],
- 'FirstName': ['Test'],
- 'ADFS_GROUPS': ['g1', 'g2']
- }
-
- :returns: identity values used to update local
- :rtype: keystone.federation.utils.DirectMaps or None
-
- """
- direct_maps = DirectMaps()
-
- for requirement in requirements:
- requirement_type = requirement['type']
- direct_map_values = assertion.get(requirement_type)
- regex = requirement.get('regex', False)
-
- if not direct_map_values:
- return None
-
- any_one_values = requirement.get(self._EvalType.ANY_ONE_OF)
- if any_one_values is not None:
- if self._evaluate_requirement(any_one_values,
- direct_map_values,
- self._EvalType.ANY_ONE_OF,
- regex):
- continue
- else:
- return None
-
- not_any_values = requirement.get(self._EvalType.NOT_ANY_OF)
- if not_any_values is not None:
- if self._evaluate_requirement(not_any_values,
- direct_map_values,
- self._EvalType.NOT_ANY_OF,
- regex):
- continue
- else:
- return None
-
- # If 'any_one_of' or 'not_any_of' are not found, then values are
- # within 'type'. Attempt to find that 'type' within the assertion,
- # and filter these values if 'whitelist' or 'blacklist' is set.
- blacklisted_values = requirement.get(self._EvalType.BLACKLIST)
- whitelisted_values = requirement.get(self._EvalType.WHITELIST)
-
- # If a blacklist or whitelist is used, we want to map to the
- # whole list instead of just its values separately.
- if blacklisted_values is not None:
- direct_map_values = [v for v in direct_map_values
- if v not in blacklisted_values]
- elif whitelisted_values is not None:
- direct_map_values = [v for v in direct_map_values
- if v in whitelisted_values]
-
- direct_maps.add(direct_map_values)
-
- LOG.debug('updating a direct mapping: %s', direct_map_values)
-
- return direct_maps
-
- def _evaluate_values_by_regex(self, values, assertion_values):
- for value in values:
- for assertion_value in assertion_values:
- if re.search(value, assertion_value):
- return True
- return False
-
- def _evaluate_requirement(self, values, assertion_values,
- eval_type, regex):
- """Evaluate the incoming requirement and assertion.
-
- If the requirement type does not exist in the assertion data, then
- return False. If regex is specified, then compare the values and
- assertion values. Otherwise, grab the intersection of the values
- and use that to compare against the evaluation type.
-
- :param values: list of allowed values, defined in the requirement
- :type values: list
- :param assertion_values: The values from the assertion to evaluate
- :type assertion_values: list/string
- :param eval_type: determine how to evaluate requirements
- :type eval_type: string
- :param regex: perform evaluation with regex
- :type regex: boolean
-
- :returns: boolean, whether requirement is valid or not.
-
- """
- if regex:
- any_match = self._evaluate_values_by_regex(values,
- assertion_values)
- else:
- any_match = bool(set(values).intersection(set(assertion_values)))
- if any_match and eval_type == self._EvalType.ANY_ONE_OF:
- return True
- if not any_match and eval_type == self._EvalType.NOT_ANY_OF:
- return True
-
- return False
-
-
-def assert_enabled_identity_provider(federation_api, idp_id):
- identity_provider = federation_api.get_idp(idp_id)
- if identity_provider.get('enabled') is not True:
- msg = _('Identity Provider %(idp)s is disabled') % {'idp': idp_id}
- LOG.debug(msg)
- raise exception.Forbidden(msg)
-
-
-def assert_enabled_service_provider_object(service_provider):
- if service_provider.get('enabled') is not True:
- sp_id = service_provider['id']
- msg = _('Service Provider %(sp)s is disabled') % {'sp': sp_id}
- LOG.debug(msg)
- raise exception.Forbidden(msg)