Fixed so ECP now works both on SP, IdP and Client side. Minor tweaks left.
This commit is contained in:
@@ -18,13 +18,13 @@
|
|||||||
"""Contains classes and functions that a SAML2.0 Service Provider (SP) may use
|
"""Contains classes and functions that a SAML2.0 Service Provider (SP) may use
|
||||||
to conclude its tasks.
|
to conclude its tasks.
|
||||||
"""
|
"""
|
||||||
|
from saml2.schema import soapenv
|
||||||
from saml2.entity import Entity
|
from saml2.entity import Entity
|
||||||
|
|
||||||
from saml2.mdstore import destinations
|
from saml2.mdstore import destinations
|
||||||
from saml2.saml import AssertionIDRef
|
from saml2.profile import paos, ecp
|
||||||
from saml2.saml import NAMEID_FORMAT_TRANSIENT
|
from saml2.saml import NAMEID_FORMAT_TRANSIENT
|
||||||
from saml2.samlp import AuthnQuery
|
from saml2.samlp import AuthnQuery
|
||||||
from saml2.samlp import AssertionIDRequest
|
|
||||||
from saml2.samlp import NameIDMappingRequest
|
from saml2.samlp import NameIDMappingRequest
|
||||||
from saml2.samlp import AttributeQuery
|
from saml2.samlp import AttributeQuery
|
||||||
from saml2.samlp import AuthzDecisionQuery
|
from saml2.samlp import AuthzDecisionQuery
|
||||||
@@ -32,6 +32,7 @@ from saml2.samlp import AuthnRequest
|
|||||||
|
|
||||||
import saml2
|
import saml2
|
||||||
import time
|
import time
|
||||||
|
from saml2.soap import make_soap_enveloped_saml_thingy
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from urlparse import parse_qs
|
from urlparse import parse_qs
|
||||||
@@ -42,8 +43,9 @@ except ImportError:
|
|||||||
from saml2.s_utils import signature
|
from saml2.s_utils import signature
|
||||||
from saml2.s_utils import do_attributes
|
from saml2.s_utils import do_attributes
|
||||||
|
|
||||||
from saml2 import samlp, BINDING_SOAP
|
from saml2 import samlp, BINDING_SOAP, element_to_extension_element
|
||||||
from saml2 import saml
|
from saml2 import saml
|
||||||
|
from saml2 import soap
|
||||||
from saml2.population import Population
|
from saml2.population import Population
|
||||||
|
|
||||||
from saml2.response import AttributeResponse
|
from saml2.response import AttributeResponse
|
||||||
@@ -71,6 +73,10 @@ FORM_SPEC = """<form method="post" action="%s">
|
|||||||
LAX = False
|
LAX = False
|
||||||
IDPDISC_POLICY = "urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol:single"
|
IDPDISC_POLICY = "urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol:single"
|
||||||
|
|
||||||
|
ECP_SERVICE = "urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"
|
||||||
|
ACTOR = "http://schemas.xmlsoap.org/soap/actor/next"
|
||||||
|
MIME_PAOS = "application/vnd.paos+xml"
|
||||||
|
|
||||||
class IdpUnspecified(Exception):
|
class IdpUnspecified(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -219,24 +225,27 @@ class Base(Entity):
|
|||||||
:return: <samlp:AuthnRequest> instance
|
:return: <samlp:AuthnRequest> instance
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
args = {}
|
||||||
try:
|
try:
|
||||||
service_url = kwargs["assertion_consumer_service_url"]
|
args["assertion_consumer_service_url"] = kwargs["assertion_consumer_service_url"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if service_url_binding is None:
|
if service_url_binding is None:
|
||||||
service_url = self.service_url(binding)
|
service_url = self.service_url(binding)
|
||||||
else:
|
else:
|
||||||
service_url = self.service_url(service_url_binding)
|
service_url = self.service_url(service_url_binding)
|
||||||
|
args["assertion_consumer_service_url"] = service_url
|
||||||
|
|
||||||
try:
|
try:
|
||||||
my_name = kwargs["provider_name"]
|
args["provider_name"] = kwargs["provider_name"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if binding == BINDING_PAOS:
|
if binding == BINDING_PAOS:
|
||||||
my_name = None
|
pass
|
||||||
else:
|
else:
|
||||||
my_name = self._my_name()
|
args["provider_name"] = self._my_name()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
name_id_policy = kwargs["name_id_policy"]
|
args["name_id_policy"] = kwargs["name_id_policy"]
|
||||||
|
del kwargs["name_id_policy"]
|
||||||
except:
|
except:
|
||||||
if allow_create:
|
if allow_create:
|
||||||
allow_create="true"
|
allow_create="true"
|
||||||
@@ -257,22 +266,24 @@ class Base(Entity):
|
|||||||
name_id_policy.format = saml.NAMEID_FORMAT_PERSISTENT
|
name_id_policy.format = saml.NAMEID_FORMAT_PERSISTENT
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
args["name_id_policy"] = name_id_policy
|
||||||
|
|
||||||
if extensions is None:
|
if kwargs:
|
||||||
extensions = []
|
if extensions is None:
|
||||||
for key,val in kwargs.items():
|
extensions = []
|
||||||
if key not in AuthnRequest.c_attributes and \
|
fargs = [p for p,c,r in AuthnRequest.c_attributes.values()]
|
||||||
key not in AuthnRequest.c_children:
|
fargs.extend([p for p,c in AuthnRequest.c_children.values()])
|
||||||
# extension elements allowed
|
for key,val in kwargs.items():
|
||||||
extensions.append(saml2.element_to_extension_element(val))
|
if key not in fargs:
|
||||||
|
# extension elements allowed
|
||||||
|
extensions.append(saml2.element_to_extension_element(val))
|
||||||
|
else:
|
||||||
|
args[key] = val
|
||||||
|
|
||||||
return self._message(AuthnRequest, destination, id, consent,
|
return self._message(AuthnRequest, destination, id, consent,
|
||||||
extensions, sign,
|
extensions, sign,
|
||||||
assertion_consumer_service_url=service_url,
|
|
||||||
protocol_binding=binding,
|
protocol_binding=binding,
|
||||||
name_id_policy=name_id_policy,
|
scoping=scoping, **args)
|
||||||
provider_name=my_name,
|
|
||||||
scoping=scoping)
|
|
||||||
|
|
||||||
|
|
||||||
def create_attribute_query(self, destination, subject_id,
|
def create_attribute_query(self, destination, subject_id,
|
||||||
@@ -550,3 +561,87 @@ class Base(Entity):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
return self._parse_response(txt, NameIDMappingResponse, "", binding)
|
return self._parse_response(txt, NameIDMappingResponse, "", binding)
|
||||||
|
|
||||||
|
# ------------------- ECP ------------------------------------------------
|
||||||
|
|
||||||
|
def create_ecp_authn_request(self, entityid=None, relay_state="", sign=False):
|
||||||
|
""" Makes an authentication request.
|
||||||
|
|
||||||
|
:param entityid: The entity ID of the IdP to send the request to
|
||||||
|
:param relay_state: A token that can be used by the SP to know
|
||||||
|
where to continue the conversation with the client
|
||||||
|
:param sign: Whether the request should be signed or not.
|
||||||
|
:return: SOAP message with the AuthnRequest
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ----------------------------------------
|
||||||
|
# <paos:Request>
|
||||||
|
# ----------------------------------------
|
||||||
|
my_url = self.service_url(BINDING_PAOS)
|
||||||
|
|
||||||
|
# must_understand and act according to the standard
|
||||||
|
#
|
||||||
|
paos_request = paos.Request(must_understand="1", actor=ACTOR,
|
||||||
|
response_consumer_url=my_url,
|
||||||
|
service = ECP_SERVICE)
|
||||||
|
|
||||||
|
# ----------------------------------------
|
||||||
|
# <ecp:RelayState>
|
||||||
|
# ----------------------------------------
|
||||||
|
|
||||||
|
relay_state = ecp.RelayState(actor=ACTOR, must_understand="1",
|
||||||
|
text=relay_state)
|
||||||
|
|
||||||
|
# ----------------------------------------
|
||||||
|
# <samlp:AuthnRequest>
|
||||||
|
# ----------------------------------------
|
||||||
|
|
||||||
|
logger.info("entityid: %s, binding: %s" % (entityid, BINDING_SOAP))
|
||||||
|
|
||||||
|
# The IDP publishes support for ECP by using the SOAP binding on
|
||||||
|
# SingleSignOnService
|
||||||
|
_, location = self.pick_binding("single_sign_on_service",
|
||||||
|
[BINDING_SOAP], entity_id=entityid)
|
||||||
|
authn_req = self.create_authn_request(location, binding=BINDING_SOAP,
|
||||||
|
service_url_binding=BINDING_PAOS)
|
||||||
|
|
||||||
|
# ----------------------------------------
|
||||||
|
# The SOAP envelope
|
||||||
|
# ----------------------------------------
|
||||||
|
|
||||||
|
soap_envelope = make_soap_enveloped_saml_thingy(authn_req,[paos_request,
|
||||||
|
relay_state])
|
||||||
|
|
||||||
|
return authn_req.id, "%s" % soap_envelope
|
||||||
|
|
||||||
|
def parse_ecp_authn_response(self, str, outstanding=None):
|
||||||
|
rdict = soap.class_instances_from_soap_enveloped_saml_thingies(str,
|
||||||
|
[paos,
|
||||||
|
ecp,
|
||||||
|
samlp])
|
||||||
|
|
||||||
|
_relay_state = None
|
||||||
|
for item in rdict["header"]:
|
||||||
|
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)
|
||||||
|
|
||||||
|
return response, _relay_state
|
||||||
|
|
||||||
|
def can_handle_ecp_response(self, response):
|
||||||
|
try:
|
||||||
|
accept = response.headers["accept"]
|
||||||
|
except KeyError:
|
||||||
|
try:
|
||||||
|
accept = response.headers["Accept"]
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if MIME_PAOS in accept:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@@ -150,7 +150,8 @@ class ConfigurationError(Exception):
|
|||||||
class Config(object):
|
class Config(object):
|
||||||
def_context = ""
|
def_context = ""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, homedir="."):
|
||||||
|
self._homedir = homedir
|
||||||
self.entityid = None
|
self.entityid = None
|
||||||
self.xmlsec_binary= None
|
self.xmlsec_binary= None
|
||||||
self.debug=False
|
self.debug=False
|
||||||
|
@@ -37,7 +37,6 @@ from saml2.response import authn_response
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
SERVICE = "urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"
|
|
||||||
|
|
||||||
def ecp_capable(headers):
|
def ecp_capable(headers):
|
||||||
if "application/vnd.paos+xml" in headers["Accept"]:
|
if "application/vnd.paos+xml" in headers["Accept"]:
|
||||||
@@ -48,7 +47,6 @@ def ecp_capable(headers):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
ACTOR = "http://schemas.xmlsoap.org/soap/actor/next"
|
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
#noinspection PyUnusedLocal
|
||||||
def ecp_auth_request(cls, entityid=None, relay_state="", sign=False):
|
def ecp_auth_request(cls, entityid=None, relay_state="", sign=False):
|
||||||
@@ -68,7 +66,7 @@ def ecp_auth_request(cls, entityid=None, relay_state="", sign=False):
|
|||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
my_url = cls.service_url(BINDING_PAOS)
|
my_url = cls.service_url(BINDING_PAOS)
|
||||||
|
|
||||||
# must_understan and actor according to the standard
|
# must_understand and actor according to the standard
|
||||||
#
|
#
|
||||||
paos_request = paos.Request(must_understand="1", actor=ACTOR,
|
paos_request = paos.Request(must_understand="1", actor=ACTOR,
|
||||||
response_consumer_url=my_url,
|
response_consumer_url=my_url,
|
||||||
|
@@ -16,34 +16,38 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Contains a class that can be used handle all the ECP handling for other python
|
Contains a class that can do SAML ECP Authentication for other python
|
||||||
programs.
|
programs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import cookielib
|
import cookielib
|
||||||
import logging
|
import logging
|
||||||
import sys
|
|
||||||
|
|
||||||
from saml2 import soap
|
from saml2 import soap
|
||||||
|
from saml2 import saml
|
||||||
from saml2 import samlp
|
from saml2 import samlp
|
||||||
from saml2 import BINDING_PAOS
|
from saml2 import BINDING_PAOS
|
||||||
from saml2 import BINDING_SOAP
|
from saml2.client_base import MIME_PAOS
|
||||||
from saml2 import class_name
|
from saml2.config import Config
|
||||||
|
from saml2.entity import Entity
|
||||||
|
from saml2.httpbase import set_list2dict, dict2set_list
|
||||||
|
|
||||||
from saml2.profile import paos
|
from saml2.profile import paos
|
||||||
from saml2.profile import ecp
|
from saml2.profile import ecp
|
||||||
|
|
||||||
from saml2.metadata import MetaData
|
from saml2.mdstore import MetadataStore
|
||||||
|
from saml2.s_utils import BadRequest
|
||||||
|
|
||||||
SERVICE = "urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"
|
SERVICE = "urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"
|
||||||
PAOS_HEADER_INFO = 'ver="%s";"%s"' % (paos.NAMESPACE, SERVICE)
|
PAOS_HEADER_INFO = 'ver="%s";"%s"' % (paos.NAMESPACE, SERVICE)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Client(object):
|
class Client(Entity):
|
||||||
def __init__(self, user, passwd, sp="", idp=None, metadata_file=None,
|
def __init__(self, user, passwd, sp="", idp=None, metadata_file=None,
|
||||||
xmlsec_binary=None, verbose=0, ca_certs="",
|
xmlsec_binary=None, verbose=0, ca_certs="",
|
||||||
disable_ssl_certificate_validation=True):
|
disable_ssl_certificate_validation=True, key_file=None,
|
||||||
|
cert_file=None):
|
||||||
"""
|
"""
|
||||||
:param user: user name
|
:param user: user name
|
||||||
:param passwd: user password
|
:param passwd: user password
|
||||||
@@ -58,6 +62,13 @@ class Client(object):
|
|||||||
disable_ssl_certificate_validation is true, SSL cert validation
|
disable_ssl_certificate_validation is true, SSL cert validation
|
||||||
will not be performed.
|
will not be performed.
|
||||||
"""
|
"""
|
||||||
|
config = Config()
|
||||||
|
config.disable_ssl_certificate_validation = disable_ssl_certificate_validation
|
||||||
|
config.key_file = key_file
|
||||||
|
config.cert_file = cert_file
|
||||||
|
config.ca_certs = ca_certs
|
||||||
|
|
||||||
|
Entity.__init__(self, "sp", config)
|
||||||
self._idp = idp
|
self._idp = idp
|
||||||
self._sp = sp
|
self._sp = sp
|
||||||
self.user = user
|
self.user = user
|
||||||
@@ -65,10 +76,9 @@ class Client(object):
|
|||||||
self._verbose = verbose
|
self._verbose = verbose
|
||||||
|
|
||||||
if metadata_file:
|
if metadata_file:
|
||||||
self._metadata = MetaData()
|
self._metadata = MetadataStore([saml, samlp], None, xmlsec_binary)
|
||||||
self._metadata.import_metadata(open(metadata_file).read(),
|
self._metadata.load("local", metadata_file)
|
||||||
xmlsec_binary)
|
logger.debug("Loaded metadata from '%s'" % metadata_file)
|
||||||
self._debug_info("Loaded metadata from '%s'" % metadata_file)
|
|
||||||
else:
|
else:
|
||||||
self._metadata = None
|
self._metadata = None
|
||||||
|
|
||||||
@@ -76,91 +86,55 @@ class Client(object):
|
|||||||
|
|
||||||
self.done_ecp = False
|
self.done_ecp = False
|
||||||
self.cookie_jar = cookielib.LWPCookieJar()
|
self.cookie_jar = cookielib.LWPCookieJar()
|
||||||
self.http = soap.HTTPClient(self._sp, cookiejar=self.cookie_jar,
|
|
||||||
ca_certs=ca_certs,
|
|
||||||
disable_ssl_certificate_validation=disable_ssl_certificate_validation)
|
|
||||||
|
|
||||||
def _debug_info(self, text):
|
|
||||||
logger.debug(text)
|
|
||||||
|
|
||||||
if self._verbose:
|
|
||||||
print >> sys.stderr, text
|
|
||||||
|
|
||||||
def find_idp_endpoint(self, idp_entity_id):
|
|
||||||
if self._idp:
|
|
||||||
return self._idp
|
|
||||||
|
|
||||||
if idp_entity_id and not self._metadata:
|
|
||||||
raise Exception(
|
|
||||||
"Can't handle IdP entity ID if I don't have metadata")
|
|
||||||
|
|
||||||
if idp_entity_id:
|
|
||||||
for binding in [BINDING_PAOS, BINDING_SOAP]:
|
|
||||||
ssos = self._metadata.single_sign_on_services(idp_entity_id,
|
|
||||||
binding=binding)
|
|
||||||
if ssos:
|
|
||||||
self._idp = ssos[0]
|
|
||||||
logger.debug("IdP endpoint: '%s'" % self._idp)
|
|
||||||
return self._idp
|
|
||||||
|
|
||||||
raise Exception("No suitable endpoint found for entity id '%s'" % (
|
|
||||||
idp_entity_id,))
|
|
||||||
else:
|
|
||||||
raise Exception("No entity ID -> no endpoint")
|
|
||||||
|
|
||||||
def phase2(self, authn_request, rc_url, idp_entity_id, headers=None,
|
def phase2(self, authn_request, rc_url, idp_entity_id, headers=None,
|
||||||
idp_endpoint=None, sign=False, sec=""):
|
sign=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Doing the second phase of the ECP conversation
|
Doing the second phase of the ECP conversation, the conversation
|
||||||
|
with the IdP happens.
|
||||||
|
|
||||||
:param authn_request: The AuthenticationRequest
|
:param authn_request: The AuthenticationRequest
|
||||||
:param rc_url: The assertion consumer service url
|
:param rc_url: The assertion consumer service url of the SP
|
||||||
:param idp_entity_id: The EntityID of the IdP
|
:param idp_entity_id: The EntityID of the IdP
|
||||||
:param headers: Possible extra headers
|
:param headers: Possible extra headers
|
||||||
:param idp_endpoint: Where to send it all
|
|
||||||
:param sign: If the message should be signed
|
:param sign: If the message should be signed
|
||||||
:param sec: security context
|
|
||||||
:return: The response from the IdP
|
:return: The response from the IdP
|
||||||
"""
|
"""
|
||||||
idp_request = soap.make_soap_enveloped_saml_thingy(authn_request)
|
|
||||||
if sign:
|
|
||||||
_signed = sec.sign_statement_using_xmlsec(idp_request,
|
|
||||||
class_name(authn_request),
|
|
||||||
nodeid=authn_request.id)
|
|
||||||
idp_request = _signed
|
|
||||||
|
|
||||||
if not idp_endpoint:
|
_, destination = self.pick_binding("single_sign_on_service",
|
||||||
idp_endpoint = self.find_idp_endpoint(idp_entity_id)
|
[BINDING_PAOS], "idpsso",
|
||||||
|
entity_id=idp_entity_id)
|
||||||
|
|
||||||
if self.user and self.passwd:
|
ht_args = self.apply_binding(BINDING_PAOS, authn_request, destination,
|
||||||
self.http.add_credentials(self.user, self.passwd)
|
sign=sign)
|
||||||
|
|
||||||
self._debug_info("[P2] Sending request: %s" % idp_request)
|
if headers:
|
||||||
|
ht_args["headers"].extend(headers)
|
||||||
|
|
||||||
|
logger.debug("[P2] Sending request: %s" % ht_args["data"])
|
||||||
|
|
||||||
# POST the request to the IdP
|
# POST the request to the IdP
|
||||||
response = self.http.post(idp_request, headers=headers,
|
response = self.send(destination, **ht_args)
|
||||||
path=idp_endpoint)
|
|
||||||
|
|
||||||
self._debug_info("[P2] Got IdP response: %s" % response)
|
logger.debug("[P2] Got IdP response: %s" % response)
|
||||||
|
|
||||||
if response is None or response is False:
|
if response.status_code != 200:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Request to IdP failed (%s): %s" % (self.http.response.status,
|
"Request to IdP failed (%s): %s" % (response.status_code,
|
||||||
self.http.error_description))
|
response.error))
|
||||||
|
|
||||||
# SAMLP response in a SOAP envelope body, ecp response in headers
|
# SAMLP response in a SOAP envelope body, ecp response in headers
|
||||||
respdict = soap.class_instances_from_soap_enveloped_saml_thingies(
|
respdict = self.parse_soap_message(response.text)
|
||||||
response, [paos, ecp,samlp])
|
|
||||||
|
|
||||||
if respdict is None:
|
if respdict is None:
|
||||||
raise Exception("Unexpected reply from the IdP")
|
raise Exception("Unexpected reply from the IdP")
|
||||||
|
|
||||||
self._debug_info("[P2] IdP response dict: %s" % respdict)
|
logger.debug("[P2] IdP response dict: %s" % respdict)
|
||||||
|
|
||||||
idp_response = respdict["body"]
|
idp_response = respdict["body"]
|
||||||
assert idp_response.c_tag == "Response"
|
assert idp_response.c_tag == "Response"
|
||||||
|
|
||||||
self._debug_info("[P2] IdP AUTHN response: %s" % idp_response)
|
logger.debug("[P2] IdP AUTHN response: %s" % idp_response)
|
||||||
|
|
||||||
_ecp_response = None
|
_ecp_response = None
|
||||||
for item in respdict["header"]:
|
for item in respdict["header"]:
|
||||||
@@ -173,21 +147,17 @@ class Client(object):
|
|||||||
error = ("response_consumer_url '%s' does not match" % rc_url,
|
error = ("response_consumer_url '%s' does not match" % rc_url,
|
||||||
"assertion_consumer_service_url '%s" % _acs_url)
|
"assertion_consumer_service_url '%s" % _acs_url)
|
||||||
# Send an error message to the SP
|
# Send an error message to the SP
|
||||||
fault_text = soap.soap_fault(error)
|
_ = self.send(rc_url, "POST", data=soap.soap_fault(error))
|
||||||
_ = self.http.post(fault_text, path=rc_url)
|
|
||||||
# Raise an exception so the user knows something went wrong
|
# Raise an exception so the user knows something went wrong
|
||||||
raise Exception(error)
|
raise Exception(error)
|
||||||
|
|
||||||
return idp_response
|
return idp_response
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
def parse_sp_ecp_response(self, respdict):
|
||||||
def ecp_conversation(self, respdict, idp_entity_id=None):
|
|
||||||
""" """
|
|
||||||
|
|
||||||
if respdict is None:
|
if respdict is None:
|
||||||
raise Exception("Unexpected reply from the SP")
|
raise Exception("Unexpected reply from the SP")
|
||||||
|
|
||||||
self._debug_info("[P1] SP response dict: %s" % respdict)
|
logger.debug("[P1] SP response dict: %s" % respdict)
|
||||||
|
|
||||||
# AuthnRequest in the body or not
|
# AuthnRequest in the body or not
|
||||||
authn_request = respdict["body"]
|
authn_request = respdict["body"]
|
||||||
@@ -197,89 +167,112 @@ class Client(object):
|
|||||||
_relay_state = None
|
_relay_state = None
|
||||||
_paos_request = None
|
_paos_request = None
|
||||||
for item in respdict["header"]:
|
for item in respdict["header"]:
|
||||||
if item.c_tag == "RelayState" and\
|
if item.c_tag == "RelayState" and item.c_namespace == ecp.NAMESPACE:
|
||||||
item.c_namespace == ecp.NAMESPACE:
|
|
||||||
_relay_state = item
|
_relay_state = item
|
||||||
if item.c_tag == "Request" and\
|
if item.c_tag == "Request" and item.c_namespace == paos.NAMESPACE:
|
||||||
item.c_namespace == paos.NAMESPACE:
|
|
||||||
_paos_request = item
|
_paos_request = item
|
||||||
|
|
||||||
|
if _paos_request is None:
|
||||||
|
raise BadRequest("Missing request")
|
||||||
|
|
||||||
_rc_url = _paos_request.response_consumer_url
|
_rc_url = _paos_request.response_consumer_url
|
||||||
|
|
||||||
|
return {"authn_request": authn_request, "rc_url": _rc_url,
|
||||||
|
"relay_state": _relay_state}
|
||||||
|
|
||||||
|
def ecp_conversation(self, respdict, idp_entity_id=None):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param respdict:
|
||||||
|
:param idp_entity_id:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
args = self.parse_sp_ecp_response(respdict)
|
||||||
|
|
||||||
# **********************
|
# **********************
|
||||||
# Phase 2 - talk to the IdP
|
# Phase 2 - talk to the IdP
|
||||||
# **********************
|
# **********************
|
||||||
|
|
||||||
idp_response = self.phase2(authn_request, _rc_url, idp_entity_id)
|
idp_response = self.phase2(idp_entity_id=idp_entity_id, **args)
|
||||||
|
|
||||||
# **********************************
|
# **********************************
|
||||||
# Phase 3 - back to the SP
|
# Phase 3 - back to the SP
|
||||||
# **********************************
|
# **********************************
|
||||||
|
|
||||||
sp_response = soap.make_soap_enveloped_saml_thingy(idp_response,
|
ht_args = self.use_soap(idp_response, args["rc_url"],
|
||||||
[_relay_state])
|
[args["relay_state"]])
|
||||||
|
|
||||||
self._debug_info("[P3] Post to SP: %s" % sp_response)
|
logger.debug("[P3] Post to SP: %s" % ht_args["data"])
|
||||||
|
|
||||||
headers = {'Content-Type': 'application/vnd.paos+xml', }
|
ht_args["headers"].append(('Content-Type', 'application/vnd.paos+xml'))
|
||||||
|
|
||||||
# POST the package from the IdP to the SP
|
# POST the package from the IdP to the SP
|
||||||
response = self.http.post(sp_response, headers, _rc_url)
|
response = self.send(args["rc_url"], "POST", **ht_args)
|
||||||
|
|
||||||
if not response:
|
if response.status_code == 302:
|
||||||
if self.http.response.status == 302:
|
# ignore where the SP is redirecting us to and go for the
|
||||||
# ignore where the SP is redirecting us to and go for the
|
# url I started off with.
|
||||||
# url I started off with.
|
pass
|
||||||
pass
|
else:
|
||||||
else:
|
print response.error
|
||||||
print self.http.error_description
|
raise Exception(
|
||||||
raise Exception(
|
"Error POSTing package to SP: %s" % response.error)
|
||||||
"Error POSTing package to SP: %s" % self.http.response.reason)
|
|
||||||
|
|
||||||
self._debug_info("[P3] IdP response: %s" % response)
|
logger.debug("[P3] SP response: %s" % response.text)
|
||||||
|
|
||||||
self.done_ecp = True
|
self.done_ecp = True
|
||||||
logger.debug("Done ECP")
|
logger.debug("Done ECP")
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def add_paos_headers(self, headers=None):
|
||||||
|
if headers:
|
||||||
|
headers = set_list2dict(headers)
|
||||||
|
headers["PAOS"] = PAOS_HEADER_INFO
|
||||||
|
if "Accept" in headers:
|
||||||
|
headers["Accept"] += ";%s" % MIME_PAOS
|
||||||
|
elif "accept" in headers:
|
||||||
|
headers["Accept"] = headers["accept"]
|
||||||
|
headers["Accept"] += ";%s" % MIME_PAOS
|
||||||
|
del headers["accept"]
|
||||||
|
headers = dict2set_list(headers)
|
||||||
|
else:
|
||||||
|
headers = [
|
||||||
|
('Accept', 'text/html; %s' % MIME_PAOS),
|
||||||
|
('PAOS', PAOS_HEADER_INFO)
|
||||||
|
]
|
||||||
|
|
||||||
def operation(self, idp_entity_id, op, **opargs):
|
return headers
|
||||||
if "path" not in opargs:
|
|
||||||
opargs["path"] = self._sp
|
def operation(self, url, idp_entity_id, op, **opargs):
|
||||||
|
"""
|
||||||
|
This is the method that should be used by someone that wants
|
||||||
|
to authenticate using SAML ECP
|
||||||
|
|
||||||
|
:param url: The page that access is sought for
|
||||||
|
:param idp_entity_id: The entity ID of the IdP that should be
|
||||||
|
used for authentication
|
||||||
|
:param op: Which HTTP operation (GET/POST/PUT/DELETE)
|
||||||
|
:param opargs: Arguments to the HTTP call
|
||||||
|
:return: The page
|
||||||
|
"""
|
||||||
|
if url not in opargs:
|
||||||
|
url = self._sp
|
||||||
|
|
||||||
# ********************************************
|
# ********************************************
|
||||||
# Phase 1 - First conversation with the SP
|
# Phase 1 - First conversation with the SP
|
||||||
# ********************************************
|
# ********************************************
|
||||||
# headers needed to indicate to the SP that I'm ECP enabled
|
# headers needed to indicate to the SP that I'm ECP enabled
|
||||||
|
|
||||||
if "headers" in opargs and opargs["headers"]:
|
opargs["headers"] = self.add_paos_headers(opargs["headers"])
|
||||||
opargs["headers"]["PAOS"] = PAOS_HEADER_INFO
|
|
||||||
if "Accept" in opargs["headers"]:
|
|
||||||
opargs["headers"]["Accept"] += ";application/vnd.paos+xml"
|
|
||||||
elif "accept" in opargs["headers"]:
|
|
||||||
opargs["headers"]["Accept"] = opargs["headers"]["accept"]
|
|
||||||
opargs["headers"]["Accept"] += ";application/vnd.paos+xml"
|
|
||||||
del opargs["headers"]["accept"]
|
|
||||||
else:
|
|
||||||
opargs["headers"] = {
|
|
||||||
'Accept': 'text/html; application/vnd.paos+xml',
|
|
||||||
'PAOS': PAOS_HEADER_INFO
|
|
||||||
}
|
|
||||||
|
|
||||||
# request target from SP
|
response = self.send(url, op, **opargs)
|
||||||
# can remove the PAOS header now
|
logger.debug("[Op] SP response: %s" % response)
|
||||||
# try:
|
|
||||||
# del opargs["headers"]["PAOS"]
|
|
||||||
# except KeyError:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
response = op(**opargs)
|
|
||||||
self._debug_info("[Op] SP response: %s" % response)
|
|
||||||
|
|
||||||
if not response:
|
if response.status_code != 200:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Request to SP failed: %s" % self.http.error_description)
|
"Request to SP failed: %s" % response.error)
|
||||||
|
|
||||||
# The response might be a AuthnRequest instance in a SOAP envelope
|
# The response might be a AuthnRequest instance in a SOAP envelope
|
||||||
# body. If so it's the start of the ECP conversation
|
# body. If so it's the start of the ECP conversation
|
||||||
@@ -290,35 +283,34 @@ class Client(object):
|
|||||||
# if 'holder-of-key' option then one or more <ecp:SubjectConfirmation>
|
# if 'holder-of-key' option then one or more <ecp:SubjectConfirmation>
|
||||||
# header blocks may also be present
|
# header blocks may also be present
|
||||||
try:
|
try:
|
||||||
respdict = soap.class_instances_from_soap_enveloped_saml_thingies(
|
respdict = self.parse_soap_message(response.text)
|
||||||
response,[paos, ecp,samlp])
|
|
||||||
self.ecp_conversation(respdict, idp_entity_id)
|
self.ecp_conversation(respdict, idp_entity_id)
|
||||||
|
|
||||||
# should by now be authenticated so this should go smoothly
|
# should by now be authenticated so this should go smoothly
|
||||||
response = op(**opargs)
|
response = self.send(url, op, **opargs)
|
||||||
except (soap.XmlParseError, AssertionError, KeyError):
|
except (soap.XmlParseError, AssertionError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
#print "RESP",response, self.http.response
|
#print "RESP",response, self.http.response
|
||||||
|
|
||||||
if not response:
|
if response.status_code != 404:
|
||||||
if self.http.response.status != 404:
|
raise Exception("Error performing operation: %s" % (response.error,))
|
||||||
raise Exception("Error performing operation: %s" % (
|
|
||||||
self.http.error_description,))
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def delete(self, path=None, idp_entity_id=None):
|
# different HTTP operations
|
||||||
return self.operation(idp_entity_id, self.http.delete, path=path)
|
def delete(self, url=None, idp_entity_id=None):
|
||||||
|
return self.operation(url, idp_entity_id, "DELETE")
|
||||||
|
|
||||||
def get(self, path=None, idp_entity_id=None, headers=None):
|
def get(self, url=None, idp_entity_id=None, headers=None):
|
||||||
return self.operation(idp_entity_id, self.http.get, path=path,
|
return self.operation(url, idp_entity_id, "GET", headers=headers)
|
||||||
|
|
||||||
|
def post(self, url=None, data="", idp_entity_id=None, headers=None):
|
||||||
|
return self.operation(url, idp_entity_id, "POST", data=data,
|
||||||
headers=headers)
|
headers=headers)
|
||||||
|
|
||||||
def post(self, path=None, data="", idp_entity_id=None, headers=None):
|
def put(self, url=None, data="", idp_entity_id=None, headers=None):
|
||||||
return self.operation(idp_entity_id, self.http.post, data=data,
|
return self.operation(url, idp_entity_id, "PUT", data=data,
|
||||||
path=path, headers=headers)
|
headers=headers)
|
||||||
|
|
||||||
def put(self, path=None, data="", idp_entity_id=None, headers=None):
|
|
||||||
return self.operation(idp_entity_id, self.http.put, data=data,
|
|
||||||
path=path, headers=headers)
|
|
||||||
|
|
||||||
|
@@ -2,9 +2,15 @@ import base64
|
|||||||
import logging
|
import logging
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from saml2.metadata import ENDPOINTS
|
from saml2.metadata import ENDPOINTS
|
||||||
|
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 import samlp, saml, response, BINDING_URI, BINDING_HTTP_ARTIFACT
|
from saml2 import samlp
|
||||||
|
from saml2 import saml
|
||||||
|
from saml2 import response
|
||||||
|
from saml2 import BINDING_URI
|
||||||
|
from saml2 import BINDING_HTTP_ARTIFACT
|
||||||
|
from saml2 import BINDING_PAOS
|
||||||
from saml2 import request
|
from saml2 import request
|
||||||
from saml2 import soap
|
from saml2 import soap
|
||||||
from saml2 import element_to_extension_element
|
from saml2 import element_to_extension_element
|
||||||
@@ -106,7 +112,10 @@ class Entity(HTTPBase):
|
|||||||
self.vorg = None
|
self.vorg = None
|
||||||
|
|
||||||
self.artifact = {}
|
self.artifact = {}
|
||||||
self.sourceid = self.metadata.construct_source_id()
|
if self.metadata:
|
||||||
|
self.sourceid = self.metadata.construct_source_id()
|
||||||
|
else:
|
||||||
|
self.sourceid = {}
|
||||||
|
|
||||||
def _issuer(self, entityid=None):
|
def _issuer(self, entityid=None):
|
||||||
""" Return an Issuer instance """
|
""" Return an Issuer instance """
|
||||||
@@ -120,7 +129,7 @@ class Entity(HTTPBase):
|
|||||||
format=NAMEID_FORMAT_ENTITY)
|
format=NAMEID_FORMAT_ENTITY)
|
||||||
|
|
||||||
def apply_binding(self, binding, msg_str, destination="", relay_state="",
|
def apply_binding(self, binding, msg_str, destination="", relay_state="",
|
||||||
response=False):
|
response=False, sign=False):
|
||||||
"""
|
"""
|
||||||
Construct the necessary HTTP arguments dependent on Binding
|
Construct the necessary HTTP arguments dependent on Binding
|
||||||
|
|
||||||
@@ -148,8 +157,8 @@ class Entity(HTTPBase):
|
|||||||
info = self.use_http_get(msg_str, destination, relay_state, typ)
|
info = self.use_http_get(msg_str, destination, relay_state, typ)
|
||||||
info["url"] = destination
|
info["url"] = destination
|
||||||
info["method"] = "GET"
|
info["method"] = "GET"
|
||||||
elif binding == BINDING_SOAP:
|
elif binding == BINDING_SOAP or binding == BINDING_PAOS:
|
||||||
info = self.use_soap(msg_str, destination)
|
info = self.use_soap(msg_str, destination, sign=sign)
|
||||||
elif binding == BINDING_URI:
|
elif binding == BINDING_URI:
|
||||||
info = self.use_http_uri(msg_str, typ, destination)
|
info = self.use_http_uri(msg_str, typ, destination)
|
||||||
elif binding == BINDING_HTTP_ARTIFACT:
|
elif binding == BINDING_HTTP_ARTIFACT:
|
||||||
@@ -257,6 +266,17 @@ class Entity(HTTPBase):
|
|||||||
|
|
||||||
return xmlstr
|
return xmlstr
|
||||||
|
|
||||||
|
def parse_soap_message(self, text):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param text: The SOAP message
|
||||||
|
:return: A dictionary with two keys "body" and "header"
|
||||||
|
"""
|
||||||
|
return soap.class_instances_from_soap_enveloped_saml_thingies(text,
|
||||||
|
[paos,
|
||||||
|
ecp,
|
||||||
|
samlp])
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
def sign(self, msg, mid=None, to_sign=None):
|
def sign(self, msg, mid=None, to_sign=None):
|
||||||
@@ -292,7 +312,9 @@ class Entity(HTTPBase):
|
|||||||
if not id:
|
if not id:
|
||||||
id = sid(self.seed)
|
id = sid(self.seed)
|
||||||
|
|
||||||
kwargs.update(self.message_args(id))
|
for key, val in self.message_args(id).items():
|
||||||
|
if key not in kwargs:
|
||||||
|
kwargs[key] = val
|
||||||
|
|
||||||
req = request_cls(**kwargs)
|
req = request_cls(**kwargs)
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@ import urlparse
|
|||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
from Cookie import SimpleCookie
|
from Cookie import SimpleCookie
|
||||||
|
from saml2.profile import paos
|
||||||
from saml2.time_util import utc_now
|
from saml2.time_util import utc_now
|
||||||
from saml2 import class_name
|
from saml2 import class_name
|
||||||
from saml2.pack import http_form_post_message
|
from saml2.pack import http_form_post_message
|
||||||
@@ -67,6 +68,11 @@ def _since_epoch(cdate):
|
|||||||
#return int(time.mktime(t))
|
#return int(time.mktime(t))
|
||||||
return calendar.timegm(t)
|
return calendar.timegm(t)
|
||||||
|
|
||||||
|
def set_list2dict(sl):
|
||||||
|
return dict(sl)
|
||||||
|
|
||||||
|
def dict2set_list(dic):
|
||||||
|
return [(k,v) for k,v in dic.items()]
|
||||||
|
|
||||||
class HTTPBase(object):
|
class HTTPBase(object):
|
||||||
def __init__(self, verify=True, ca_bundle=None, key_file=None,
|
def __init__(self, verify=True, ca_bundle=None, key_file=None,
|
||||||
@@ -82,6 +88,8 @@ class HTTPBase(object):
|
|||||||
self.request_args["cert"] = (cert_file, key_file)
|
self.request_args["cert"] = (cert_file, key_file)
|
||||||
|
|
||||||
self.sec = None
|
self.sec = None
|
||||||
|
self.user = None
|
||||||
|
self.passwd = None
|
||||||
|
|
||||||
def cookies(self, url):
|
def cookies(self, url):
|
||||||
"""
|
"""
|
||||||
@@ -159,6 +167,9 @@ class HTTPBase(object):
|
|||||||
if self.cookiejar:
|
if self.cookiejar:
|
||||||
_kwargs["cookies"] = self.cookies(url)
|
_kwargs["cookies"] = self.cookies(url)
|
||||||
|
|
||||||
|
if self.user and self.passwd:
|
||||||
|
_kwargs["auth"]= (self.user, self.passwd)
|
||||||
|
|
||||||
#logger.info("SENT COOKIEs: %s" % (_kwargs["cookies"],))
|
#logger.info("SENT COOKIEs: %s" % (_kwargs["cookies"],))
|
||||||
try:
|
try:
|
||||||
r = requests.request(method, url, **_kwargs)
|
r = requests.request(method, url, **_kwargs)
|
||||||
@@ -245,7 +256,7 @@ class HTTPBase(object):
|
|||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def use_soap(self, request, destination="", headers=None, sign=False):
|
def use_soap(self, request, destination="", soap_headers=None, sign=False):
|
||||||
"""
|
"""
|
||||||
Construct the necessary information for using SOAP+POST
|
Construct the necessary information for using SOAP+POST
|
||||||
|
|
||||||
@@ -255,15 +266,12 @@ class HTTPBase(object):
|
|||||||
:param sign:
|
:param sign:
|
||||||
:return: dictionary
|
:return: dictionary
|
||||||
"""
|
"""
|
||||||
if headers is None:
|
headers = [("content-type", "application/soap+xml")]
|
||||||
headers = [("content-type", "application/soap+xml")]
|
|
||||||
else:
|
|
||||||
headers.append(("content-type", "application/soap+xml"))
|
|
||||||
|
|
||||||
soap_message = make_soap_enveloped_saml_thingy(request)
|
soap_message = make_soap_enveloped_saml_thingy(request, soap_headers)
|
||||||
|
|
||||||
logger.error("SOAP message: %s" % soap_message)
|
logger.error("SOAP message: %s" % soap_message)
|
||||||
|
|
||||||
if sign and self.sec:
|
if sign and self.sec:
|
||||||
_signed = self.sec.sign_statement_using_xmlsec(soap_message,
|
_signed = self.sec.sign_statement_using_xmlsec(soap_message,
|
||||||
class_name(request),
|
class_name(request),
|
||||||
@@ -301,3 +309,7 @@ class HTTPBase(object):
|
|||||||
raise HTTPError("%d:%s" % (response.status_code, response.error))
|
raise HTTPError("%d:%s" % (response.status_code, response.error))
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def add_credentials(self, user, passwd):
|
||||||
|
self.user = user
|
||||||
|
self.passwd = passwd
|
||||||
|
@@ -139,7 +139,7 @@ class AttributeQuery(Request):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
class AuthnRequest(Request):
|
class AuthnRequest(Request):
|
||||||
msgtype = "auth_request"
|
msgtype = "authn_request"
|
||||||
def __init__(self, sec_context, receiver_addrs, attribute_converters,
|
def __init__(self, sec_context, receiver_addrs, attribute_converters,
|
||||||
timeslack=0):
|
timeslack=0):
|
||||||
Request.__init__(self, sec_context, receiver_addrs,
|
Request.__init__(self, sec_context, receiver_addrs,
|
||||||
|
@@ -24,11 +24,12 @@ import shelve
|
|||||||
import sys
|
import sys
|
||||||
import memcache
|
import memcache
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
|
from saml2.schema import soapenv
|
||||||
|
|
||||||
from saml2.samlp import NameIDMappingResponse
|
from saml2.samlp import NameIDMappingResponse, Response
|
||||||
from saml2.entity import Entity
|
from saml2.entity import Entity
|
||||||
|
|
||||||
from saml2 import saml
|
from saml2 import saml, element_to_extension_element
|
||||||
from saml2 import class_name
|
from saml2 import class_name
|
||||||
from saml2 import BINDING_HTTP_REDIRECT
|
from saml2 import BINDING_HTTP_REDIRECT
|
||||||
|
|
||||||
@@ -40,8 +41,6 @@ from saml2.request import AuthzDecisionQuery
|
|||||||
from saml2.request import AuthnQuery
|
from saml2.request import AuthnQuery
|
||||||
|
|
||||||
from saml2.s_utils import MissingValue, Unknown
|
from saml2.s_utils import MissingValue, Unknown
|
||||||
from saml2.s_utils import BadRequest
|
|
||||||
from saml2.s_utils import error_status_factory
|
|
||||||
|
|
||||||
from saml2.sigver import pre_signature_part, signed_instance_factory
|
from saml2.sigver import pre_signature_part, signed_instance_factory
|
||||||
|
|
||||||
@@ -51,6 +50,8 @@ from saml2.assertion import restriction_from_attribute_spec
|
|||||||
from saml2.assertion import filter_attribute_value_assertions
|
from saml2.assertion import filter_attribute_value_assertions
|
||||||
|
|
||||||
from saml2.ident import IdentDB
|
from saml2.ident import IdentDB
|
||||||
|
#from saml2.profile import paos
|
||||||
|
from saml2.profile import ecp
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -448,7 +449,7 @@ class Server(Entity):
|
|||||||
:param assertion_id:
|
:param assertion_id:
|
||||||
:param in_response_to:
|
:param in_response_to:
|
||||||
:param issuer:
|
:param issuer:
|
||||||
:param sign_response:
|
:param sign:
|
||||||
:param status:
|
:param status:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
@@ -524,3 +525,40 @@ class Server(Entity):
|
|||||||
|
|
||||||
return self._response(in_response_to, "", status, issuer,
|
return self._response(in_response_to, "", status, issuer,
|
||||||
sign_response, to_sign=[], **args)
|
sign_response, to_sign=[], **args)
|
||||||
|
|
||||||
|
# ---------
|
||||||
|
|
||||||
|
def parse_ecp_authn_request(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_ecp_authn_request_response(self, acs_url, identity,
|
||||||
|
in_response_to, destination,
|
||||||
|
sp_entity_id, name_id_policy=None,
|
||||||
|
userid=None, name_id=None, authn=None,
|
||||||
|
authn_decl=None, issuer=None,
|
||||||
|
sign_response=False,
|
||||||
|
sign_assertion=False):
|
||||||
|
|
||||||
|
# ----------------------------------------
|
||||||
|
# <ecp:Response
|
||||||
|
# ----------------------------------------
|
||||||
|
|
||||||
|
ecp_response = ecp.Response(assertion_consumer_service_url=acs_url)
|
||||||
|
header = soapenv.Header()
|
||||||
|
header.extension_elements = [element_to_extension_element(ecp_response)]
|
||||||
|
|
||||||
|
# ----------------------------------------
|
||||||
|
# <samlp:Response
|
||||||
|
# ----------------------------------------
|
||||||
|
|
||||||
|
response = self.create_authn_response(identity, in_response_to,
|
||||||
|
destination, sp_entity_id,
|
||||||
|
name_id_policy, userid, name_id,
|
||||||
|
authn, authn_decl, issuer,
|
||||||
|
sign_response, sign_assertion)
|
||||||
|
body = soapenv.Body()
|
||||||
|
body.extension_elements = [element_to_extension_element(response)]
|
||||||
|
|
||||||
|
soap_envelope = soapenv.Envelope(header=header, body=body)
|
||||||
|
|
||||||
|
return "%s" % soap_envelope
|
||||||
|
@@ -65,8 +65,8 @@ def parse_soap_enveloped_saml_logout_request(text):
|
|||||||
expected_tag = '{%s}LogoutRequest' % SAMLP_NAMESPACE
|
expected_tag = '{%s}LogoutRequest' % SAMLP_NAMESPACE
|
||||||
return parse_soap_enveloped_saml_thingy(text, [expected_tag])
|
return parse_soap_enveloped_saml_thingy(text, [expected_tag])
|
||||||
|
|
||||||
def parse_soap_enveloped_saml_authentication_request(text):
|
def parse_soap_enveloped_saml_authn_request(text):
|
||||||
expected_tag = '{%s}AuthenticationRequest' % SAMLP_NAMESPACE
|
expected_tag = '{%s}AuthnRequest' % SAMLP_NAMESPACE
|
||||||
return parse_soap_enveloped_saml_thingy(text, [expected_tag])
|
return parse_soap_enveloped_saml_thingy(text, [expected_tag])
|
||||||
|
|
||||||
def parse_soap_enveloped_saml_artifact_resolve(text):
|
def parse_soap_enveloped_saml_artifact_resolve(text):
|
||||||
|
@@ -41,7 +41,8 @@ CONFIG = {
|
|||||||
"single_sign_on_service" : [
|
"single_sign_on_service" : [
|
||||||
("%s/sso/redirect" % BASE, BINDING_HTTP_REDIRECT),
|
("%s/sso/redirect" % BASE, BINDING_HTTP_REDIRECT),
|
||||||
("%s/sso/post" % BASE, BINDING_HTTP_POST),
|
("%s/sso/post" % BASE, BINDING_HTTP_POST),
|
||||||
("%s/sso/art" % BASE, BINDING_HTTP_ARTIFACT)
|
("%s/sso/art" % BASE, BINDING_HTTP_ARTIFACT),
|
||||||
|
("%s/sso/paos" % BASE, BINDING_SOAP)
|
||||||
],
|
],
|
||||||
"single_logout_service": [
|
"single_logout_service": [
|
||||||
("%s/slo/soap" % BASE, BINDING_SOAP),
|
("%s/slo/soap" % BASE, BINDING_SOAP),
|
||||||
|
Reference in New Issue
Block a user