Final touch to ECP
This commit is contained in:
parent
b295a359b9
commit
db681c0841
@ -4,6 +4,8 @@ from hashlib import sha1
|
|||||||
from saml2.metadata import ENDPOINTS
|
from saml2.metadata import ENDPOINTS
|
||||||
from saml2.profile import paos, ecp
|
from saml2.profile import paos, ecp
|
||||||
from saml2.soap import parse_soap_enveloped_saml_artifact_resolve
|
from saml2.soap import parse_soap_enveloped_saml_artifact_resolve
|
||||||
|
from saml2.soap import class_instances_from_soap_enveloped_saml_thingies
|
||||||
|
from saml2.soap import open_soap_envelope
|
||||||
|
|
||||||
from saml2 import samlp
|
from saml2 import samlp
|
||||||
from saml2 import saml
|
from saml2 import saml
|
||||||
@ -259,7 +261,7 @@ class Entity(HTTPBase):
|
|||||||
elif binding == BINDING_SOAP:
|
elif binding == BINDING_SOAP:
|
||||||
func = getattr(soap, "parse_soap_enveloped_saml_%s" % msgtype)
|
func = getattr(soap, "parse_soap_enveloped_saml_%s" % msgtype)
|
||||||
xmlstr = func(txt)
|
xmlstr = func(txt)
|
||||||
elif binding == BINDING_URI:
|
elif binding == BINDING_URI or binding is None:
|
||||||
xmlstr = txt
|
xmlstr = txt
|
||||||
else:
|
else:
|
||||||
raise ValueError("Don't know how to handle '%s'" % binding)
|
raise ValueError("Don't know how to handle '%s'" % binding)
|
||||||
@ -272,11 +274,19 @@ class Entity(HTTPBase):
|
|||||||
:param text: The SOAP message
|
:param text: The SOAP message
|
||||||
:return: A dictionary with two keys "body" and "header"
|
:return: A dictionary with two keys "body" and "header"
|
||||||
"""
|
"""
|
||||||
return soap.class_instances_from_soap_enveloped_saml_thingies(text,
|
return class_instances_from_soap_enveloped_saml_thingies(text,
|
||||||
[paos,
|
[paos,
|
||||||
ecp,
|
ecp,
|
||||||
samlp])
|
samlp])
|
||||||
|
|
||||||
|
def unpack_soap_message(self, text):
|
||||||
|
"""
|
||||||
|
Picks out the parts of the SOAP message, body and headers apart
|
||||||
|
:param text: The SOAP message
|
||||||
|
:return: A dictionary with two keys "body"/"header"
|
||||||
|
"""
|
||||||
|
return open_soap_envelope(text)
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
def sign(self, msg, mid=None, to_sign=None):
|
def sign(self, msg, mid=None, to_sign=None):
|
||||||
|
@ -150,13 +150,25 @@ import re
|
|||||||
|
|
||||||
NS_AND_TAG = re.compile("\{([^}]+)\}(.*)")
|
NS_AND_TAG = re.compile("\{([^}]+)\}(.*)")
|
||||||
|
|
||||||
|
def instanciate_class(item, modules):
|
||||||
|
m = NS_AND_TAG.match(item.tag)
|
||||||
|
ns,tag = m.groups()
|
||||||
|
for module in modules:
|
||||||
|
if module.NAMESPACE == ns:
|
||||||
|
try:
|
||||||
|
target = module.ELEMENT_BY_TAG[tag]
|
||||||
|
return create_class_from_element_tree(target, item)
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
raise Exception("Unknown class: ns='%s', tag='%s'" % (ns, tag))
|
||||||
|
|
||||||
def class_instances_from_soap_enveloped_saml_thingies(text, modules):
|
def class_instances_from_soap_enveloped_saml_thingies(text, modules):
|
||||||
"""Parses a SOAP enveloped header and body SAML thing and returns the
|
"""Parses a SOAP enveloped header and body SAML thing and returns the
|
||||||
thing as a dictionary class instance.
|
thing as a dictionary class instance.
|
||||||
|
|
||||||
:param text: The SOAP object as XML
|
:param text: The SOAP object as XML
|
||||||
:param modules: modules representing xsd schemas
|
:param modules: modules representing xsd schemas
|
||||||
:return: SAML thingy as a class instance
|
:return: The body and headers as class instances
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
envelope = ElementTree.fromstring(text)
|
envelope = ElementTree.fromstring(text)
|
||||||
@ -170,32 +182,39 @@ def class_instances_from_soap_enveloped_saml_thingies(text, modules):
|
|||||||
for part in envelope:
|
for part in envelope:
|
||||||
if part.tag == '{%s}Body' % soapenv.NAMESPACE:
|
if part.tag == '{%s}Body' % soapenv.NAMESPACE:
|
||||||
assert len(part) == 1
|
assert len(part) == 1
|
||||||
m = NS_AND_TAG.match(part[0].tag)
|
env["body"] = instanciate_class(part[0], modules)
|
||||||
ns,tag = m.groups()
|
|
||||||
for module in modules:
|
|
||||||
if module.NAMESPACE == ns:
|
|
||||||
try:
|
|
||||||
target = module.ELEMENT_BY_TAG[tag]
|
|
||||||
env["body"] = create_class_from_element_tree(target,
|
|
||||||
part[0])
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
elif part.tag == "{%s}Header" % soapenv.NAMESPACE:
|
elif part.tag == "{%s}Header" % soapenv.NAMESPACE:
|
||||||
for item in part:
|
for item in part:
|
||||||
m = NS_AND_TAG.match(item.tag)
|
env["header"].append(instanciate_class(item, modules))
|
||||||
ns,tag = m.groups()
|
|
||||||
for module in modules:
|
|
||||||
if module.NAMESPACE == ns:
|
|
||||||
try:
|
|
||||||
target = module.ELEMENT_BY_TAG[tag]
|
|
||||||
env["header"].append(create_class_from_element_tree(
|
|
||||||
target,
|
|
||||||
item))
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
def open_soap_envelope(text):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param text: SOAP message
|
||||||
|
:return: dictionary with two keys "body"/"header"
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
envelope = ElementTree.fromstring(text)
|
||||||
|
except Exception, exc:
|
||||||
|
raise XmlParseError("%s" % exc)
|
||||||
|
|
||||||
|
assert envelope.tag == '{%s}Envelope' % soapenv.NAMESPACE
|
||||||
|
assert len(envelope) >= 1
|
||||||
|
content = {"header":[], "body":None}
|
||||||
|
|
||||||
|
for part in envelope:
|
||||||
|
if part.tag == '{%s}Body' % soapenv.NAMESPACE:
|
||||||
|
assert len(part) == 1
|
||||||
|
content["body"] = ElementTree.tostring(part[0], encoding="UTF-8")
|
||||||
|
elif part.tag == "{%s}Header" % soapenv.NAMESPACE:
|
||||||
|
for item in part:
|
||||||
|
_str = ElementTree.tostring(item, encoding="UTF-8")
|
||||||
|
content["header"].append(_str)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
def make_soap_enveloped_saml_thingy(thingy, headers=None):
|
def make_soap_enveloped_saml_thingy(thingy, headers=None):
|
||||||
""" Returns a soap envelope containing a SAML request
|
""" Returns a soap envelope containing a SAML request
|
||||||
as a text string.
|
as a text string.
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
|
from saml2.saml import AUTHN_PASSWORD
|
||||||
|
from saml2.httpbase import set_list2dict
|
||||||
|
from saml2.profile.ecp import RelayState
|
||||||
|
from saml2.profile.paos import Request
|
||||||
|
from saml2.request import AuthnRequest
|
||||||
|
from saml2.server import Server
|
||||||
|
from saml2.samlp import Response, STATUS_SUCCESS
|
||||||
|
|
||||||
__author__ = 'rolandh'
|
__author__ = 'rolandh'
|
||||||
|
|
||||||
from saml2 import soap
|
from saml2 import soap, ecp_client, BINDING_SOAP, BINDING_PAOS, \
|
||||||
|
create_class_from_xml_string
|
||||||
from saml2 import samlp
|
from saml2 import samlp
|
||||||
from saml2 import config
|
from saml2 import config
|
||||||
from saml2 import ecp
|
from saml2 import ecp
|
||||||
@ -66,3 +75,125 @@ class TestECPClient(object):
|
|||||||
assert _eq(headers,['{urn:liberty:paos:2003-08}Request',
|
assert _eq(headers,['{urn:liberty:paos:2003-08}Request',
|
||||||
#'{urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp}Request',
|
#'{urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp}Request',
|
||||||
'{urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp}RelayState'])
|
'{urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp}RelayState'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
from saml2.sigver import get_xmlsec_binary
|
||||||
|
except ImportError:
|
||||||
|
get_xmlsec_binary = None
|
||||||
|
|
||||||
|
if get_xmlsec_binary:
|
||||||
|
xmlsec_path = get_xmlsec_binary(["/opt/local/bin"])
|
||||||
|
else:
|
||||||
|
xmlsec_path = '/usr/bin/xmlsec1'
|
||||||
|
|
||||||
|
class DummyResponse(object):
|
||||||
|
def __init__(self, headers):
|
||||||
|
self.headers = headers
|
||||||
|
|
||||||
|
def test_complete_flow():
|
||||||
|
client = ecp_client.Client("user", "password", metadata_file="idp_all.xml",
|
||||||
|
xmlsec_binary=xmlsec_path)
|
||||||
|
|
||||||
|
sp = Saml2Client(config_file="servera_conf")
|
||||||
|
idp = Server(config_file="idp_all_conf")
|
||||||
|
|
||||||
|
IDP_ENTITY_ID = idp.config.entityid
|
||||||
|
SP_ENTITY_ID = sp.config.entityid
|
||||||
|
|
||||||
|
# ------------ @Client -----------------------------
|
||||||
|
|
||||||
|
headers = client.add_paos_headers([])
|
||||||
|
|
||||||
|
assert len(headers) == 2
|
||||||
|
|
||||||
|
# ------------ @SP -----------------------------
|
||||||
|
|
||||||
|
response = DummyResponse(set_list2dict(headers))
|
||||||
|
|
||||||
|
assert sp.can_handle_ecp_response(response)
|
||||||
|
|
||||||
|
id, message = sp.create_ecp_authn_request(IDP_ENTITY_ID, relay_state="XYZ")
|
||||||
|
|
||||||
|
# ------------ @Client -----------------------------
|
||||||
|
|
||||||
|
respdict = client.parse_soap_message(message)
|
||||||
|
|
||||||
|
cargs = client.parse_sp_ecp_response(respdict)
|
||||||
|
|
||||||
|
assert isinstance(respdict["body"], AuthnRequest)
|
||||||
|
assert len(respdict["header"]) == 2
|
||||||
|
item0 = respdict["header"][0]
|
||||||
|
assert isinstance(item0, Request) or isinstance(item0, RelayState)
|
||||||
|
|
||||||
|
destination = respdict["body"].destination
|
||||||
|
|
||||||
|
ht_args = client.apply_binding(BINDING_SOAP, respdict["body"], destination)
|
||||||
|
|
||||||
|
# Time to send to the IDP
|
||||||
|
# ----------- @IDP -------------------------------
|
||||||
|
|
||||||
|
req = idp.parse_authn_request(ht_args["data"], BINDING_SOAP)
|
||||||
|
|
||||||
|
assert isinstance(req.message, AuthnRequest)
|
||||||
|
|
||||||
|
# create Response and return in the SOAP response
|
||||||
|
sp_entity_id = req.sender()
|
||||||
|
|
||||||
|
name_id = idp.ident.transient_nameid( "id12", sp.config.entityid)
|
||||||
|
binding, destination = idp.pick_binding("assertion_consumer_service",
|
||||||
|
[BINDING_PAOS],
|
||||||
|
entity_id=sp_entity_id)
|
||||||
|
|
||||||
|
resp = idp.create_ecp_authn_request_response(destination,
|
||||||
|
{
|
||||||
|
"eduPersonEntitlement": "Short stop",
|
||||||
|
"surName": "Jeter",
|
||||||
|
"givenName": "Derek",
|
||||||
|
"mail": "derek.jeter@nyy.mlb.com",
|
||||||
|
"title": "The man"
|
||||||
|
},
|
||||||
|
req.message.id, destination, sp_entity_id,
|
||||||
|
name_id=name_id, authn=(AUTHN_PASSWORD,
|
||||||
|
"http://www.example.com/login"))
|
||||||
|
|
||||||
|
# ------------ @Client -----------------------------
|
||||||
|
# The client got the response from the IDP repackage and send it to the SP
|
||||||
|
|
||||||
|
respdict = client.parse_soap_message(resp)
|
||||||
|
idp_response = respdict["body"]
|
||||||
|
|
||||||
|
assert isinstance(idp_response, Response)
|
||||||
|
assert len(respdict["header"]) == 1
|
||||||
|
|
||||||
|
_ecp_response = None
|
||||||
|
for item in respdict["header"]:
|
||||||
|
if item.c_tag == "Response" and item.c_namespace == ecp_prof.NAMESPACE:
|
||||||
|
_ecp_response = item
|
||||||
|
|
||||||
|
_acs_url = _ecp_response.assertion_consumer_service_url
|
||||||
|
|
||||||
|
# done phase2 at the client
|
||||||
|
|
||||||
|
ht_args = client.use_soap(idp_response, cargs["rc_url"], [cargs["relay_state"]])
|
||||||
|
|
||||||
|
print ht_args
|
||||||
|
|
||||||
|
# ------------ @SP -----------------------------
|
||||||
|
|
||||||
|
respdict = sp.unpack_soap_message(ht_args["data"])
|
||||||
|
|
||||||
|
# verify the relay_state
|
||||||
|
|
||||||
|
for header in respdict["header"]:
|
||||||
|
inst = create_class_from_xml_string(RelayState, header)
|
||||||
|
if isinstance(inst, RelayState):
|
||||||
|
assert inst.text == "XYZ"
|
||||||
|
|
||||||
|
# parse the response
|
||||||
|
|
||||||
|
resp = sp.parse_authn_request_response(respdict["body"], None, {id: "/"})
|
||||||
|
|
||||||
|
print resp.response
|
||||||
|
|
||||||
|
assert resp.response.destination == "http://lingon.catalogix.se:8087/paos"
|
||||||
|
assert resp.response.status.status_code.value == STATUS_SUCCESS
|
||||||
|
Loading…
Reference in New Issue
Block a user