From ed1bb3362d1f83f0611730530330bf19bc117cb8 Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Sat, 27 Jun 2015 09:09:48 +0200 Subject: [PATCH] Improve support for SigAlg usage in HTTP redirect. --- example/sp-wsgi/sp.py | 8 ++++--- setup.py | 4 +++- src/saml2/client.py | 54 ++++++++++++++++++++++--------------------- src/saml2/httpbase.py | 4 ++-- src/saml2/pack.py | 9 ++++---- 5 files changed, 43 insertions(+), 36 deletions(-) diff --git a/example/sp-wsgi/sp.py b/example/sp-wsgi/sp.py index 1f1de9a..a5c598e 100755 --- a/example/sp-wsgi/sp.py +++ b/example/sp-wsgi/sp.py @@ -544,7 +544,7 @@ class SSO(object): logger.info("Chosen IdP: '%s'" % idp_entity_id) return 0, idp_entity_id - def redirect_to_auth(self, _cli, entity_id, came_from): + def redirect_to_auth(self, _cli, entity_id, came_from, sigalg=""): try: # Picks a binding to use for sending the Request to the IDP _binding, destination = _cli.pick_binding( @@ -573,11 +573,13 @@ class SSO(object): element_to_extension_element(spcertenc)]) req_id, req = _cli.create_authn_request(destination, - binding=return_binding, extensions=extensions) + binding=return_binding, + extensions=extensions) _rstate = rndstr() self.cache.relay_state[_rstate] = came_from ht_args = _cli.apply_binding(_binding, "%s" % req, destination, - relay_state=_rstate) + relay_state=_rstate, + sigalg=sigalg) _sid = req_id if cert is not None: diff --git a/setup.py b/setup.py index 281e773..55f8331 100755 --- a/setup.py +++ b/setup.py @@ -66,7 +66,9 @@ setup( "Development Status :: 4 - Beta", "License :: OSI Approved :: Apache Software License", "Topic :: Software Development :: Libraries :: Python Modules", - "Programming Language :: Python :: 2.7"], + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.4" + ], scripts=["tools/parse_xsd2.py", "tools/make_metadata.py", "tools/mdexport.py", "tools/merge_metadata.py"], diff --git a/src/saml2/client.py b/src/saml2/client.py index cdb66e0..33d24fc 100644 --- a/src/saml2/client.py +++ b/src/saml2/client.py @@ -24,15 +24,15 @@ from saml2.samlp import STATUS_UNKNOWN_PRINCIPAL from saml2.time_util import not_on_or_after from saml2.saml import AssertionIDRef from saml2.client_base import Base +from saml2.client_base import SignOnError from saml2.client_base import LogoutError from saml2.client_base import NoServiceDefined from saml2.mdstore import destinations try: - from urlparse import parse_qs + from urllib.parse import parse_qs except ImportError: - # Compatibility with Python <= 2.5 - from cgi import parse_qs + from urlparse import parse_qs import logging @@ -42,13 +42,11 @@ logger = logging.getLogger(__name__) class Saml2Client(Base): """ The basic pySAML2 service provider class """ - def prepare_for_authenticate(self, entityid=None, relay_state="", - binding=saml2.BINDING_HTTP_REDIRECT, vorg="", - nameid_format=None, - scoping=None, consent=None, extensions=None, - sign=None, - response_binding=saml2.BINDING_HTTP_POST, - **kwargs): + def prepare_for_authenticate( + self, entityid=None, relay_state="", + binding=saml2.BINDING_HTTP_REDIRECT, vorg="", nameid_format=None, + scoping=None, consent=None, extensions=None, sign=None, + response_binding=saml2.BINDING_HTTP_POST, **kwargs): """ Makes all necessary preparations for an authentication request. :param entityid: The entity ID of the IdP to send the request to @@ -82,14 +80,12 @@ class Saml2Client(Base): return reqid, info - def prepare_for_negotiated_authenticate(self, entityid=None, relay_state="", - binding=None, vorg="", - nameid_format=None, - scoping=None, consent=None, extensions=None, - sign=None, - response_binding=saml2.BINDING_HTTP_POST, - **kwargs): - """ Makes all necessary preparations for an authentication request that negotiates + def prepare_for_negotiated_authenticate( + self, entityid=None, relay_state="", binding=None, vorg="", + nameid_format=None, scoping=None, consent=None, extensions=None, + sign=None, response_binding=saml2.BINDING_HTTP_POST, **kwargs): + """ Makes all necessary preparations for an authentication request + that negotiates which binding to use for authentication. :param entityid: The entity ID of the IdP to send the request to @@ -117,20 +113,25 @@ class Saml2Client(Base): reqid, request = self.create_authn_request( destination, vorg, scoping, response_binding, nameid_format, - consent=consent, - extensions=extensions, sign=sign, + consent=consent, extensions=extensions, sign=sign, **kwargs) _req_str = str(request) logger.info("AuthNReq: %s" % _req_str) + try: + sigalg = kwargs["sigalg"] + except KeyError: + sigalg = "" + http_info = self.apply_binding(binding, _req_str, destination, - relay_state) + relay_state, sigalg=sigalg) return reqid, binding, http_info else: - raise SignOnError("No supported bindings available for authentication") + raise SignOnError( + "No supported bindings available for authentication") def global_logout(self, name_id, reason="", expire=None, sign=None): """ More or less a layer of indirection :-/ @@ -206,7 +207,7 @@ class Saml2Client(Base): destination, entity_id, name_id=name_id, reason=reason, expire=expire) - #to_sign = [] + # to_sign = [] if binding.startswith("http://"): sign = True @@ -230,7 +231,8 @@ class Saml2Client(Base): not_done.remove(entity_id) response = response.text logger.info("Response: %s" % response) - res = self.parse_logout_request_response(response, binding) + res = self.parse_logout_request_response(response, + binding) responses[entity_id] = res else: logger.info("NOT OK response from %s" % destination) @@ -324,7 +326,7 @@ class Saml2Client(Base): raise HTTPError("%d:%s" % (response.status_code, response.error)) if response: - #not_done.remove(entity_id) + # not_done.remove(entity_id) logger.info("OK response from %s" % destination) return response else: @@ -332,7 +334,7 @@ class Saml2Client(Base): return None - #noinspection PyUnusedLocal + # noinspection PyUnusedLocal def do_authz_decision_query(self, entity_id, action, subject_id, nameid_format, evidence=None, resource=None, diff --git a/src/saml2/httpbase.py b/src/saml2/httpbase.py index 91c227d..2fd0015 100644 --- a/src/saml2/httpbase.py +++ b/src/saml2/httpbase.py @@ -3,7 +3,6 @@ import six from six.moves import http_cookiejar import copy import re -import urllib from six.moves.urllib.parse import urlparse from six.moves.urllib.parse import urlencode import requests @@ -311,7 +310,8 @@ class HTTPBase(object): return info - def use_soap(self, request, destination="", soap_headers=None, sign=False): + def use_soap(self, request, destination="", soap_headers=None, sign=False, + **kwargs): """ Construct the necessary information for using SOAP+POST diff --git a/src/saml2/pack.py b/src/saml2/pack.py index 9a98704..91b6f97 100644 --- a/src/saml2/pack.py +++ b/src/saml2/pack.py @@ -45,7 +45,7 @@ FORM_SPEC = """
def http_form_post_message(message, location, relay_state="", - typ="SAMLRequest"): + typ="SAMLRequest", **kwargs): """The HTTP POST binding defines a mechanism by which SAML protocol messages may be transmitted within the base64-encoded content of a HTML form control. @@ -80,7 +80,7 @@ def http_form_post_message(message, location, relay_state="", def http_redirect_message(message, location, relay_state="", typ="SAMLRequest", - sigalg=None, key=None): + sigalg=None, key=None, **kwargs): """The HTTP Redirect binding defines a mechanism by which SAML protocol messages can be transmitted within URL parameters. Messages are encoded for use with this binding using a URL encoding @@ -256,5 +256,6 @@ def packager(identifier): raise Exception("Unknown binding type: %s" % identifier) -def factory(binding, message, location, relay_state="", typ="SAMLRequest"): - return PACKING[binding](message, location, relay_state, typ) +def factory(binding, message, location, relay_state="", typ="SAMLRequest", + **kwargs): + return PACKING[binding](message, location, relay_state, typ, **kwargs)