Add support for SingleSignOnService negotiating which binding to use

This commit is contained in:
Erick Tryzelaar
2015-02-05 10:52:37 -08:00
parent f4169837c1
commit fdbd305fb7
3 changed files with 99 additions and 6 deletions

View File

@@ -42,7 +42,7 @@ class Saml2Client(Base):
""" The basic pySAML2 service provider class """
def prepare_for_authenticate(self, entityid=None, relay_state="",
binding=None, vorg="",
binding=saml2.BINDING_HTTP_REDIRECT, vorg="",
nameid_format=None,
scoping=None, consent=None, extensions=None,
sign=None,
@@ -64,6 +64,47 @@ class Saml2Client(Base):
:return: session id and AuthnRequest info
"""
reqid, negotiated_binding, info = self.prepare_for_negotiated_authenticate(
entityid=entityid,
relay_state=relay_state,
binding=binding,
vorg=vorg,
nameid_format=nameid_format,
scoping=scoping,
consent=consent,
extensions=extensions,
sign=sign,
response_binding=response_binding,
**kwargs)
assert negotiated_binding == binding
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
which binding to use for authentication.
:param entityid: The entity ID of the IdP to send the request to
:param relay_state: To where the user should be returned after
successfull log in.
:param binding: Which binding to use for sending the request
:param vorg: The entity_id of the virtual organization I'm a member of
:param scoping: For which IdPs this query are aimed.
:param consent: Whether the principal have given her consent
:param extensions: Possible extensions
:param sign: Whether the request should be signed or not.
:param response_binding: Which binding to use for receiving the response
:param kwargs: Extra key word arguments
:return: session id and AuthnRequest info
"""
expected_binding = binding
for binding in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]:
@@ -88,7 +129,7 @@ class Saml2Client(Base):
return reqid, binding, http_info
else:
raise SignonError("No binding available for singon")
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 :-/

View File

@@ -71,7 +71,7 @@ class VerifyError(SAMLError):
pass
class SignonError(SAMLError):
class SignOnError(SAMLError):
pass

View File

@@ -619,7 +619,26 @@ class TestClientWithDummy():
def test_do_authn(self):
binding = BINDING_HTTP_REDIRECT
response_binding = BINDING_HTTP_POST
sid, auth_binding, http_args = self.client.prepare_for_authenticate(
sid, http_args = self.client.prepare_for_authenticate(
IDP, "http://www.example.com/relay_state",
binding=binding, response_binding=response_binding)
assert isinstance(sid, basestring)
assert len(http_args) == 4
assert http_args["headers"][0][0] == "Location"
assert http_args["data"] == []
redirect_url = http_args["headers"][0][1]
_, _, _, _, qs, _ = urlparse.urlparse(redirect_url)
qs_dict = urlparse.parse_qs(qs)
req = self.server.parse_authn_request(qs_dict["SAMLRequest"][0],
binding)
resp_args = self.server.response_args(req.message, [response_binding])
assert resp_args["binding"] == response_binding
def test_do_negotiated_authn(self):
binding = BINDING_HTTP_REDIRECT
response_binding = BINDING_HTTP_POST
sid, auth_binding, http_args = self.client.prepare_for_negotiated_authenticate(
IDP, "http://www.example.com/relay_state",
binding=binding, response_binding=response_binding)
@@ -670,7 +689,40 @@ class TestClientWithDummy():
def test_post_sso(self):
binding = BINDING_HTTP_POST
response_binding = BINDING_HTTP_POST
sid, auth_binding, http_args = self.client.prepare_for_authenticate(
sid, http_args = self.client.prepare_for_authenticate(
"urn:mace:example.com:saml:roland:idp", relay_state="really",
binding=binding, response_binding=response_binding)
_dic = unpack_form(http_args["data"][3])
req = self.server.parse_authn_request(_dic["SAMLRequest"], binding)
resp_args = self.server.response_args(req.message, [response_binding])
assert resp_args["binding"] == response_binding
# Normally a response would now be sent back to the users web client
# Here I fake what the client will do
# create the form post
http_args["data"] = urllib.urlencode(_dic)
http_args["method"] = "POST"
http_args["dummy"] = _dic["SAMLRequest"]
http_args["headers"] = [('Content-type',
'application/x-www-form-urlencoded')]
response = self.client.send(**http_args)
print response.text
_dic = unpack_form(response.text[3], "SAMLResponse")
resp = self.client.parse_authn_request_response(_dic["SAMLResponse"],
BINDING_HTTP_POST,
{sid: "/"})
ac = resp.assertion.authn_statement[0].authn_context
assert ac.authenticating_authority[0].text == \
'http://www.example.com/login'
assert ac.authn_context_class_ref.text == INTERNETPROTOCOLPASSWORD
def test_negotiated_post_sso(self):
binding = BINDING_HTTP_POST
response_binding = BINDING_HTTP_POST
sid, auth_binding, http_args = self.client.prepare_for_negotiated_authenticate(
"urn:mace:example.com:saml:roland:idp", relay_state="really",
binding=binding, response_binding=response_binding)
_dic = unpack_form(http_args["data"][3])
@@ -711,4 +763,4 @@ class TestClientWithDummy():
if __name__ == "__main__":
tc = TestClient()
tc.setup_class()
tc.test_sign_then_encrypt_assertion_advice()
tc.test_sign_then_encrypt_assertion_advice()