diff --git a/README.rst b/README.rst index 7315991a..af8ccc73 100644 --- a/README.rst +++ b/README.rst @@ -125,12 +125,15 @@ of the public certificate originally obtained from OneLogin:: def do_POST(self): ... + request_data = self.prepare_request() length = int(self.headers['Content-Length']) data = self.rfile.read(length) query = urlparse.parse_qs(data) res = Response( + request_data, query['SAMLResponse'].pop(), self.settings['idp_cert_fingerprint'], + issuer=self.settings['issuer'] ) valid = res.is_valid() name_id = res.name_id @@ -145,6 +148,10 @@ of the public certificate originally obtained from OneLogin:: ) self._serve_msg(401, msg) +The request_data must be used to build the Response due is_valid method checks Destination, Recipient, etc +and need to know info like SERVER_NAME, SERVER_PORT, PATH_INFO, SCRIPT_NAME, REQUEST_URI. If you using a +python framework be sure to build a dict with those indexs and provide it to the Response constructor + Once again, the self.settings variable is populated from an entry in the configuration file. You can find the public certificate under Security->SAML after you login to OneLogin. diff --git a/onelogin/saml/Response.py b/onelogin/saml/Response.py index 1cbba110..45759655 100644 --- a/onelogin/saml/Response.py +++ b/onelogin/saml/Response.py @@ -174,8 +174,6 @@ def is_valid(self, _clock=None, _verifier=None): if not self.validate_num_assertions(): raise ResponseFormatError('Only 1 Assertion in the SAMLResponse is supported') - - if _clock is None: _clock = datetime.utcnow if _verifier is None: @@ -189,10 +187,10 @@ def is_valid(self, _clock=None, _verifier=None): now = _clock() for condition in conditions: - + not_before = condition.attrib.get('NotBefore', None) not_on_or_after = condition.attrib.get('NotOnOrAfter', None) - + if not_before is None: not_before = (now - timedelta(0, 5, 0)).strftime('%Y-%m-%dT%H:%M:%SZ') if not_on_or_after is None: @@ -256,7 +254,6 @@ def is_valid(self, _clock=None, _verifier=None): if not any_subject_confirmation: raise ResponseSubjectConfirmationError('A valid SubjectConfirmation was not found on this Response') - return _verifier( self._document, self._signature, diff --git a/onelogin/saml/test/TestAuthRequest.py b/onelogin/saml/test/TestAuthRequest.py index e9837905..0da8a0cb 100644 --- a/onelogin/saml/test/TestAuthRequest.py +++ b/onelogin/saml/test/TestAuthRequest.py @@ -5,6 +5,7 @@ from onelogin.saml import AuthRequest + class TestAuthRequest(object): def setUp(self): fudge.clear_expectations() @@ -23,7 +24,6 @@ def fake_clock(): fake_zlib = fudge.Fake('zlib') fake_zlib.remember_order() fake_compress = fake_zlib.expects('compress') - uncompressed_req = """foo_issuerurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport""" fake_compress.returns('HDfoo_compressedCHCK') fake_base64 = fudge.Fake('base64') @@ -37,7 +37,7 @@ def fake_clock(): fake_urlencode = fake_urllib.expects('urlencode') fake_urlencode.with_args( [('SAMLRequest', 'foo_encoded')], - ) + ) fake_urlencode.returns('foo_urlencoded') req = AuthRequest.create( @@ -52,6 +52,6 @@ def fake_clock(): + 'emailAddress' ), idp_sso_target_url='http://foo.idp.bar', - ) + ) eq(req, 'http://foo.idp.bar?foo_urlencoded') diff --git a/onelogin/saml/test/TestResponse.py b/onelogin/saml/test/TestResponse.py index a1db2015..7a4db955 100644 --- a/onelogin/saml/test/TestResponse.py +++ b/onelogin/saml/test/TestResponse.py @@ -11,7 +11,7 @@ ResponseValidationError, ResponseNameIDError, ResponseConditionError, - ) +) test_response = """ """ + class TestResponse(object): + def setUp(self): fudge.clear_expectations() @@ -89,12 +91,18 @@ def test__init__(self): from_string.with_args('foo decoded response', parser=fake_xmlparser) from_string.returns('foo document') + request_data = { + 'http_host': 'example.com', + 'script_name': 'index.html' + } + res = Response( + request_data=request_data, response='foo response', signature='foo signature', _base64=fake_base64, _etree=fake_etree, - ) + ) eq(res._document, 'foo document') eq(res._signature, 'foo signature') @@ -105,7 +113,7 @@ def test_get_name_id_simple(self): res = Response( response=encoded_response, signature=None, - ) + ) name_id = res.name_id eq('3f7b3dcf-1674-4ecd-92c8-1544f346baf8', name_id) @@ -173,18 +181,16 @@ def test_get_name_id_multiple(self): res = Response( response=encoded_response, signature=None, - ) + ) msg = assert_raises( ResponseNameIDError, res._get_name_id, - ) + ) eq( - str(msg), - ('There was a problem getting the name ID: Found more than one ' - + 'name ID' - ), - ) + str(msg), ('There was a problem getting the name ID:' + + ' Found more than one name ID'), + ) @fudge.with_fakes def test_get_name_id_none(self): @@ -241,18 +247,18 @@ def test_get_name_id_none(self): res = Response( response=encoded_response, signature=None, - ) + ) msg = assert_raises( ResponseNameIDError, res._get_name_id, - ) + ) eq( str(msg), ('There was a problem getting the name ID: Did not find a name ' + 'ID' ), - ) + ) @fudge.with_fakes def test_is_valid_not_before_missing(self): @@ -312,12 +318,12 @@ def test_is_valid_not_before_missing(self): res = Response( response=encoded_response, signature='foo signature', - ) + ) fake_verifier = fudge.Fake( 'verifier', callable=True, - ) + ) fake_verifier.times_called(1) fake_verifier.with_args(res._document, 'foo signature') @@ -325,7 +331,7 @@ def test_is_valid_not_before_missing(self): msg = res.is_valid( _verifier=fake_verifier, - ) + ) eq(msg, True) @@ -387,17 +393,17 @@ def test_is_valid_not_on_or_after_missing(self): res = Response( response=encoded_response, signature=None, - ) + ) msg = assert_raises( ResponseConditionError, res.is_valid, - ) + ) - eq(str(msg), - ('There was a problem validating a condition: Did not find ' - + 'NotOnOrAfter condition' - ), - ) + eq( + str(msg), + ('There was a problem validating a condition:' + + ' Did not find NotOnOrAfter condition'), + ) @fudge.with_fakes def test_is_valid_current_time_earlier(self): @@ -405,7 +411,7 @@ def test_is_valid_current_time_earlier(self): res = Response( response=encoded_response, signature=None, - ) + ) def fake_clock(): return datetime(2004, 12, 05, 9, 16, 45, 462796) @@ -413,13 +419,13 @@ def fake_clock(): ResponseValidationError, res.is_valid, _clock=fake_clock, - ) + ) - eq(str(msg), - ('There was a problem validating the response: Current time is ' - + 'earlier than NotBefore condition' - ), - ) + eq( + str(msg), + ('There was a problem validating the response: Current time is ' + + 'earlier than NotBefore condition'), + ) @fudge.with_fakes def test_is_valid_current_time_on_or_after(self): @@ -427,7 +433,7 @@ def test_is_valid_current_time_on_or_after(self): res = Response( response=encoded_response, signature=None, - ) + ) def fake_clock(): return datetime(2004, 12, 05, 9, 30, 45, 462796) @@ -435,13 +441,13 @@ def fake_clock(): ResponseValidationError, res.is_valid, _clock=fake_clock, - ) + ) - eq(str(msg), - ('There was a problem validating the response: Current time is ' - + 'on or after NotOnOrAfter condition' - ), - ) + eq( + str(msg), + ('There was a problem validating the response: Current time is ' + + 'on or after NotOnOrAfter condition'), + ) @fudge.with_fakes def test_is_valid_simple(self): @@ -449,7 +455,7 @@ def test_is_valid_simple(self): res = Response( response=encoded_response, signature='foo signature', - ) + ) def fake_clock(): return datetime(2004, 12, 05, 9, 18, 45, 462796) @@ -457,7 +463,7 @@ def fake_clock(): fake_verifier = fudge.Fake( 'verifier', callable=True, - ) + ) fake_verifier.times_called(1) fake_verifier.with_args(res._document, 'foo signature') @@ -466,6 +472,6 @@ def fake_clock(): msg = res.is_valid( _clock=fake_clock, _verifier=fake_verifier, - ) + ) eq(msg, True) diff --git a/onelogin/saml/test/TestSignatureVerifier.py b/onelogin/saml/test/TestSignatureVerifier.py index 6c6cd46a..95780b8c 100644 --- a/onelogin/saml/test/TestSignatureVerifier.py +++ b/onelogin/saml/test/TestSignatureVerifier.py @@ -8,6 +8,7 @@ from onelogin.saml.test.util import assert_raises + class TestSignatureVerifier(object): def setUp(self): fudge.clear_expectations() @@ -26,7 +27,7 @@ def test_verify_simple(self): fake_tempfile.remember_order() named_xmlfile = fake_tempfile.expects( 'NamedTemporaryFile' - ) + ) named_xmlfile.with_args(delete=False) xmlfile = named_xmlfile.returns_fake() xmlfile.remember_order() @@ -41,13 +42,13 @@ def test_verify_simple(self): seek.with_args(0) exit = xmlfile.expects('__exit__') - exit.with_args(None, None,None) + exit.with_args(None, None, None) xmlfile.has_attr(name='xmlfile') named_certfile = fake_tempfile.next_call( 'NamedTemporaryFile' - ) + ) named_certfile.with_args(delete=False) certfile = named_certfile.returns_fake() certfile.remember_order() @@ -61,16 +62,15 @@ def test_verify_simple(self): ('-----BEGIN CERTIFICATE-----\nfoo signature\n' + '-----END CERTIFICATE-----' ) - ) + ) seek = certfile.expects('seek') seek.with_args(0) exit = certfile.expects('__exit__') - exit.with_args(None, None,None) + exit.with_args(None, None, None) certfile.has_attr(name='certfile') - fake_subprocess = fudge.Fake('subprocess') fake_subprocess.remember_order() popen = fake_subprocess.expects('Popen') @@ -84,10 +84,10 @@ def test_verify_simple(self): '--id-attr:ID', 'urn:oasis:names:tc:SAML:2.0:assertion:Assertion', 'xmlfile', - ], + ], stderr=1, stdout=1, - ) + ) proc = popen.returns_fake() proc.remember_order() wait = proc.expects('wait') @@ -109,7 +109,7 @@ def test_verify_simple(self): _tempfile=fake_tempfile, _subprocess=fake_subprocess, _os=fake_os, - ) + ) @fudge.with_fakes def test_get_xmlsec_bin_default(self): @@ -183,13 +183,13 @@ def test_get_parse_stderr_error(self): SignatureVerifier.SignatureVerifierError, SignatureVerifier._parse_stderr, fake_proc - ) + ) - eq(str(msg), - ('There was a problem validating the response: XMLSec returned error ' - + 'code 1. Please check your certficate.' - ), - ) + eq( + str(msg), + ('There was a problem validating the response: XMLSec returned error ' + + 'code 1. Please check your certficate.'), + ) @fudge.with_fakes def test_get_parse_stderr_error_should_not_happen(self): @@ -203,13 +203,13 @@ def test_get_parse_stderr_error_should_not_happen(self): SignatureVerifier.SignatureVerifierError, SignatureVerifier._parse_stderr, fake_proc - ) + ) - eq(str(msg), - ('There was a problem validating the response: XMLSec exited with ' - + 'code 0 but did not return OK when verifying the SAML response.' - ) - ) + eq( + str(msg), + ('There was a problem validating the response: XMLSec exited with ' + + 'code 0 but did not return OK when verifying the SAML response.') + ) @fudge.with_fakes def test_get_parse_stderr_ok_windows(self): diff --git a/onelogin/saml/test/util.py b/onelogin/saml/test/util.py index f693d408..b02ea084 100644 --- a/onelogin/saml/test/util.py +++ b/onelogin/saml/test/util.py @@ -7,6 +7,8 @@ def assert_raises(excClass, callableObj, *args, **kwargs): except excClass, e: return e else: - if hasattr(excClass,'__name__'): excName = excClass.__name__ - else: excName = str(excClass) + if hasattr(excClass, '__name__'): + excName = excClass.__name__ + else: + excName = str(excClass) raise AssertionError("%s not raised" % excName)