aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/federation/controllers.py
diff options
context:
space:
mode:
Diffstat (limited to 'keystone-moon/keystone/federation/controllers.py')
-rw-r--r--keystone-moon/keystone/federation/controllers.py519
1 files changed, 0 insertions, 519 deletions
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')])