Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/saml2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,31 @@ class members.
return None


def parse_xml_and_get_ns(file_like):
"""Behaves like ElementTree.fromstring, but also retrieves namespaces as
separate parameter.

:param file_like: File-like object, that implements read() method.
:return: ElementTree.Element and ns (as dict)
"""
events = "start", "start-ns"
root = None
ns = {}
for event, elem in defusedxml.ElementTree.iterparse(file_like, events):
if event == "start-ns":
# if elem[0] in ns and ns[elem[0]] != elem[1]:
# # NOTE: It is perfectly valid to have the same prefix refer
# # to different URI namespaces in different parts of the
# # document. This exception serves as a reminder that this
# # solution is not robust. Use at your own peril.
# raise KeyError("Duplicate prefix with different URI found.")
ns[elem[0]] = elem[1]
elif event == "start":
if root is None:
root = elem
return ElementTree.ElementTree(root).getroot(), ns


class Error(Exception):
"""Exception class thrown by this module."""
pass
Expand Down Expand Up @@ -640,6 +665,8 @@ def get_xml_string_with_self_contained_assertion_within_encrypted_assertion(

def set_prefixes(self, elem, prefix_map):

self.register_prefix(prefix_map)

# check if this is a tree wrapper
if not ElementTree.iselement(elem):
elem = elem.getroot()
Expand Down
16 changes: 8 additions & 8 deletions src/saml2/client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,19 +842,19 @@ def create_ecp_authn_request(self, entityid=None, relay_state="",
return req_id, "%s" % soap_envelope

def parse_ecp_authn_response(self, txt, outstanding=None):
rdict = soap.class_instances_from_soap_enveloped_saml_thingies(txt,
[paos,
ecp,
samlp])
rdict = soap.class_instances_from_soap_enveloped_saml_thingies(
txt, [paos, ecp, samlp])

_relay_state = None
for item in rdict["header"]:
if item.c_tag == "RelayState" and \
item.c_namespace == ecp.NAMESPACE:
if (item.c_tag == "RelayState" and
item.c_namespace == ecp.NAMESPACE):
_relay_state = item

response = self.parse_authn_request_response(rdict["body"],
BINDING_PAOS, outstanding)
xmlstr = rdict["body"].to_string(rdict['ns'])

response = self.parse_authn_request_response(xmlstr, BINDING_PAOS,
outstanding)

return response, _relay_state

Expand Down
4 changes: 3 additions & 1 deletion src/saml2/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ def unravel(txt, binding, msgtype="response"):
# logger.debug("unravel '%s'", txt)
if binding not in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST,
BINDING_SOAP, BINDING_URI, BINDING_HTTP_ARTIFACT,
None]:
BINDING_PAOS, None]:
raise UnknownBinding("Don't know how to handle '%s'" % binding)
else:
try:
Expand All @@ -389,6 +389,8 @@ def unravel(txt, binding, msgtype="response"):
func = getattr(soap,
"parse_soap_enveloped_saml_%s" % msgtype)
xmlstr = func(txt)
elif binding == BINDING_PAOS:
xmlstr = txt
elif binding == BINDING_HTTP_ARTIFACT:
xmlstr = base64.b64decode(txt)
else:
Expand Down
25 changes: 19 additions & 6 deletions src/saml2/soap.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@
Suppport for the client part of the SAML2.0 SOAP binding.
"""
import logging
from io import BytesIO
import re

from six import StringIO

from saml2 import create_class_from_element_tree
import six
from saml2 import create_class_from_element_tree, parse_xml_and_get_ns
from saml2.samlp import NAMESPACE as SAMLP_NAMESPACE
from saml2.schema import soapenv

Expand Down Expand Up @@ -126,6 +131,7 @@ def parse_soap_enveloped_saml_authn_response(text):
# expected_tag = '{%s}LogoutResponse' % SAMLP_NAMESPACE
# return parse_soap_enveloped_saml_thingy(text, [expected_tag])


def parse_soap_enveloped_saml_thingy(text, expected_tags):
"""Parses a SOAP enveloped SAML thing and returns the thing as
a string.
Expand Down Expand Up @@ -157,7 +163,6 @@ def parse_soap_enveloped_saml_thingy(text, expected_tags):
raise WrongMessageType("Was '%s' expected one of %s" % (saml_part.tag,
expected_tags))

import re

NS_AND_TAG = re.compile("\{([^}]+)\}(.*)")

Expand All @@ -184,13 +189,17 @@ def class_instances_from_soap_enveloped_saml_thingies(text, modules):
:return: The body and headers as class instances
"""
try:
envelope = defusedxml.ElementTree.fromstring(text)
if isinstance(text, six.string_types):
io_wrapper = StringIO
else:
io_wrapper = BytesIO
envelope, ns = parse_xml_and_get_ns(io_wrapper(text))
except Exception as exc:
raise XmlParseError("%s" % exc)

assert envelope.tag == '{%s}Envelope' % soapenv.NAMESPACE
assert len(envelope) >= 1
env = {"header": [], "body": None}
env = {"header": [], "body": None, "ns": ns}

for part in envelope:
if part.tag == '{%s}Body' % soapenv.NAMESPACE:
Expand All @@ -210,13 +219,17 @@ def open_soap_envelope(text):
:return: dictionary with two keys "body"/"header"
"""
try:
envelope = defusedxml.ElementTree.fromstring(text)
if isinstance(text, six.string_types):
io_wrapper = StringIO
else:
io_wrapper = BytesIO
envelope, ns = parse_xml_and_get_ns(io_wrapper(text))
except Exception as exc:
raise XmlParseError("%s" % exc)

assert envelope.tag == '{%s}Envelope' % soapenv.NAMESPACE
assert len(envelope) >= 1
content = {"header": [], "body": None}
content = {"header": [], "body": None, "ns": ns}

for part in envelope:
if part.tag == '{%s}Body' % soapenv.NAMESPACE:
Expand Down