diff options
Diffstat (limited to 'keystone-moon/keystone/contrib/federation')
6 files changed, 64 insertions, 39 deletions
diff --git a/keystone-moon/keystone/contrib/federation/backends/sql.py b/keystone-moon/keystone/contrib/federation/backends/sql.py index ed07c08f..dbd17025 100644 --- a/keystone-moon/keystone/contrib/federation/backends/sql.py +++ b/keystone-moon/keystone/contrib/federation/backends/sql.py @@ -155,7 +155,7 @@ class ServiceProviderModel(sql.ModelBase, sql.DictBase): return d -class Federation(core.Driver): +class Federation(core.FederationDriverV8): # Identity Provider CRUD @sql.handle_conflicts(conflict_type='identity_provider') diff --git a/keystone-moon/keystone/contrib/federation/controllers.py b/keystone-moon/keystone/contrib/federation/controllers.py index 912d45d5..d0bd2bce 100644 --- a/keystone-moon/keystone/contrib/federation/controllers.py +++ b/keystone-moon/keystone/contrib/federation/controllers.py @@ -316,6 +316,15 @@ class Auth(auth_controllers.Auth): 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.""" diff --git a/keystone-moon/keystone/contrib/federation/core.py b/keystone-moon/keystone/contrib/federation/core.py index 2ab75ecb..1595be1d 100644 --- a/keystone-moon/keystone/contrib/federation/core.py +++ b/keystone-moon/keystone/contrib/federation/core.py @@ -92,7 +92,7 @@ class Manager(manager.Manager): @six.add_metaclass(abc.ABCMeta) -class Driver(object): +class FederationDriverV8(object): @abc.abstractmethod def create_idp(self, idp_id, idp): @@ -350,3 +350,6 @@ class Driver(object): """ raise exception.NotImplemented() # pragma: no cover + + +Driver = manager.create_legacy_driver(FederationDriverV8) diff --git a/keystone-moon/keystone/contrib/federation/idp.py b/keystone-moon/keystone/contrib/federation/idp.py index 739fc01a..51689989 100644 --- a/keystone-moon/keystone/contrib/federation/idp.py +++ b/keystone-moon/keystone/contrib/federation/idp.py @@ -12,7 +12,6 @@ import datetime import os -import subprocess import uuid from oslo_config import cfg @@ -32,11 +31,14 @@ 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 +subprocess = environment.subprocess + LOG = log.getLogger(__name__) CONF = cfg.CONF @@ -426,11 +428,10 @@ def _sign_assertion(assertion): stdout = subprocess.check_output(command_list, stderr=subprocess.STDOUT) except Exception as e: - msg = _LE('Error when signing assertion, reason: %(reason)s') - msg = msg % {'reason': e} - if hasattr(e, 'output'): - msg += ' output: %(output)s' % {'output': e.output} - LOG.error(msg) + 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: diff --git a/keystone-moon/keystone/contrib/federation/routers.py b/keystone-moon/keystone/contrib/federation/routers.py index d8fa8175..ddf2f61f 100644 --- a/keystone-moon/keystone/contrib/federation/routers.py +++ b/keystone-moon/keystone/contrib/federation/routers.py @@ -72,6 +72,13 @@ class FederationExtension(wsgi.V3ExtensionRouter): protocols/{protocol}/auth POST /OS-FEDERATION/identity_providers/{identity_provider}/ protocols/{protocol}/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 @@ -185,11 +192,13 @@ class FederationExtension(wsgi.V3ExtensionRouter): 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')) @@ -223,6 +232,16 @@ class FederationExtension(wsgi.V3ExtensionRouter): 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( diff --git a/keystone-moon/keystone/contrib/federation/utils.py b/keystone-moon/keystone/contrib/federation/utils.py index b0db3cdd..bde19cfd 100644 --- a/keystone-moon/keystone/contrib/federation/utils.py +++ b/keystone-moon/keystone/contrib/federation/utils.py @@ -672,15 +672,18 @@ class RuleProcessor(object): 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, - requirement_type, + direct_map_values, self._EvalType.ANY_ONE_OF, - regex, - assertion): + regex): continue else: return None @@ -688,10 +691,9 @@ class RuleProcessor(object): not_any_values = requirement.get(self._EvalType.NOT_ANY_OF) if not_any_values is not None: if self._evaluate_requirement(not_any_values, - requirement_type, + direct_map_values, self._EvalType.NOT_ANY_OF, - regex, - assertion): + regex): continue else: return None @@ -699,23 +701,21 @@ class RuleProcessor(object): # 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. - direct_map_values = assertion.get(requirement_type) - if direct_map_values: - blacklisted_values = requirement.get(self._EvalType.BLACKLIST) - whitelisted_values = requirement.get(self._EvalType.WHITELIST) + 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] + # 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) + direct_maps.add(direct_map_values) - LOG.debug('updating a direct mapping: %s', direct_map_values) + LOG.debug('updating a direct mapping: %s', direct_map_values) return direct_maps @@ -726,8 +726,8 @@ class RuleProcessor(object): return True return False - def _evaluate_requirement(self, values, requirement_type, - eval_type, regex, assertion): + 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 @@ -737,23 +737,16 @@ class RuleProcessor(object): :param values: list of allowed values, defined in the requirement :type values: list - :param requirement_type: key to look for in the assertion - :type requirement_type: string + :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 - :param assertion: dict of attributes from the IdP - :type assertion: dict :returns: boolean, whether requirement is valid or not. """ - - assertion_values = assertion.get(requirement_type) - if not assertion_values: - return False - if regex: any_match = self._evaluate_values_by_regex(values, assertion_values) |