From 399f2bd09012a4c2e738d2637b7ea0d5a070bfea Mon Sep 17 00:00:00 2001 From: Dolf Andringa Date: Fri, 23 Mar 2012 14:11:31 +0100 Subject: [PATCH 1/5] get_assertion_attribute_value added to Response object. This method can retrieve AssertionAttributes from the assertion tag --- onelogin/saml/Response.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/onelogin/saml/Response.py b/onelogin/saml/Response.py index cc75bbf5..2bffc4e4 100644 --- a/onelogin/saml/Response.py +++ b/onelogin/saml/Response.py @@ -84,6 +84,13 @@ def _get_name_id(self): doc="The value requested in the name_identifier_format, e.g., the user's email address", ) + def get_assertion_attribute_value(self,attribute_name): + """ + Get the value of an AssertionAttribute, located in an Assertion/AttributeStatement/Attribute[@Name=attribute_name/AttributeValue tag + """ + result = self._document.xpath('/samlp:Response/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name="%s"]/saml:AttributeValue'%attribute_name,namespaces=namespaces) + return [n.text.strip() for n in result] + def is_valid( self, _clock=None, From bcfa333f1bd4015a8c0dfb3a239c02e662a42d1b Mon Sep 17 00:00:00 2001 From: Dolf Andringa Date: Wed, 3 Apr 2013 17:31:40 +0200 Subject: [PATCH 2/5] Use dateutil to handle timezones properly. Now the SAML messages can also specify timezones --- README.rst | 1 + onelogin/saml/Response.py | 6 ++- onelogin/saml/test/TestAuthRequest.py | 6 +-- onelogin/saml/test/TestResponse.py | 71 ++++++++++++++++++++++++--- setup.py | 3 +- 5 files changed, 75 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index a7ccdadb..c5276ab4 100644 --- a/README.rst +++ b/README.rst @@ -30,6 +30,7 @@ Python Requirements - python2.6 - python2.6-dev - lxml 2.3 or greater +- python-dateutil<2.0 (>2.0 for python 3.x) Test Requirements ================= diff --git a/onelogin/saml/Response.py b/onelogin/saml/Response.py index 2bffc4e4..71c4eab5 100644 --- a/onelogin/saml/Response.py +++ b/onelogin/saml/Response.py @@ -2,6 +2,8 @@ from lxml import etree from datetime import datetime, timedelta +import dateutil.tz +import dateutil.parser from onelogin.saml import SignatureVerifier @@ -58,7 +60,7 @@ def __init__( self._signature = signature def _parse_datetime(self, dt): - return datetime.strptime(dt, '%Y-%m-%dT%H:%M:%SZ') + return dateutil.parser.parse(dt) def _get_name_id(self): result = self._document.xpath( @@ -101,7 +103,7 @@ def is_valid( Return True if valid, otherwise False. """ if _clock is None: - _clock = datetime.utcnow + _clock = lambda :datetime.now(dateutil.tz.tzutc()) if _verifier is None: _verifier = SignatureVerifier.verify diff --git a/onelogin/saml/test/TestAuthRequest.py b/onelogin/saml/test/TestAuthRequest.py index a3ede75d..1a63e411 100644 --- a/onelogin/saml/test/TestAuthRequest.py +++ b/onelogin/saml/test/TestAuthRequest.py @@ -3,9 +3,9 @@ from datetime import datetime from nose.tools import eq_ as eq -from onelogin.saml import AuthnRequest +from onelogin.saml import AuthRequest -class TestAuthnRequest(object): +class TestAuthRequest(object): def setUp(self): fudge.clear_expectations() @@ -42,7 +42,7 @@ def fake_clock(): ) fake_urlencode.returns('foo_urlencoded') - req = AuthnRequest.create( + req = AuthRequest.create( _clock=fake_clock, _uuid=fake_uuid_func, _zlib=fake_zlib, diff --git a/onelogin/saml/test/TestResponse.py b/onelogin/saml/test/TestResponse.py index 52a3bbf4..13d2653f 100644 --- a/onelogin/saml/test/TestResponse.py +++ b/onelogin/saml/test/TestResponse.py @@ -3,6 +3,8 @@ import base64 import fudge +import dateutil.tz + from nose.tools import eq_ as eq from onelogin.saml.test.util import assert_raises @@ -310,12 +312,11 @@ def test_is_valid_not_before_missing(self): signature=None, ) msg = assert_raises( - ResponseConditionError, + ResponseValidationError, res.is_valid, ) - eq(str(msg), - ('There was a problem validating a condition: Did not find NotBefore ' + ('There was a problem validating the response: Current time is on or after NotOnOrAfter ' + 'condition' ), ) @@ -399,7 +400,7 @@ def test_is_valid_current_time_earlier(self): ) def fake_clock(): - return datetime(2004, 12, 05, 9, 16, 45, 462796) + return datetime(2004, 12, 05, 9, 16, 45, 462796,tzinfo=dateutil.tz.tzutc()) msg = assert_raises( ResponseValidationError, res.is_valid, @@ -421,7 +422,7 @@ def test_is_valid_current_time_on_or_after(self): ) def fake_clock(): - return datetime(2004, 12, 05, 9, 30, 45, 462796) + return datetime(2004, 12, 05, 9, 30, 45, 462796,tzinfo=dateutil.tz.tzutc()) msg = assert_raises( ResponseValidationError, res.is_valid, @@ -443,7 +444,34 @@ def test_is_valid_simple(self): ) def fake_clock(): - return datetime(2004, 12, 05, 9, 18, 45, 462796) + return datetime(2004, 12, 05, 9, 18, 45, 462796,tzinfo=dateutil.tz.tzutc()) + + fake_verifier = fudge.Fake( + 'verifier', + callable=True, + ) + fake_verifier.times_called(1) + fake_verifier.with_args(res._document, 'foo signature') + + fake_verifier.returns(True) + + msg = res.is_valid( + _clock=fake_clock, + _verifier=fake_verifier, + ) + + eq(msg, True) + + @fudge.with_fakes + def test_is_valid_timezonedifferent(self): + encoded_response = base64.b64encode(test_response) + res = Response( + response=encoded_response, + signature='foo signature', + ) + + def fake_clock(): + return datetime(2004, 12, 05, 4, 18, 45, 462796,tzinfo=dateutil.tz.tzstr('EST5EDT')) fake_verifier = fudge.Fake( 'verifier', @@ -460,3 +488,34 @@ def fake_clock(): ) eq(msg, True) + @fudge.with_fakes + def test_is_invalid_timezonedifferent(self): + encoded_response = base64.b64encode(test_response) + res = Response( + response=encoded_response, + signature='foo signature', + ) + + def fake_clock(): + return datetime(2004, 12, 05, 9, 18, 45, 462796,tzinfo=dateutil.tz.tzstr('EST5EDT')) + + fake_verifier = fudge.Fake( + 'verifier', + callable=True, + ) + fake_verifier.times_called(1) + fake_verifier.with_args(res._document, 'foo signature') + + fake_verifier.returns(True) + + msg = assert_raises( + 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' + ), + ) diff --git a/setup.py b/setup.py index bc02394d..21053478 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ def run(self): install_requires = [ 'lxml>=2.3', + 'python-dateutil<2.0', ] tests_require = [ 'fudge >=0.9.5', @@ -42,7 +43,7 @@ def run(self): setup( name='onelogin.saml', - version='0.0.1', + version='0.0.2', description="Python client library for SAML Version 2.0", packages = find_packages(), namespace_packages = ['onelogin'], From 0eb453f0b14227f0ff43d2b3f8a055a6de8a17f6 Mon Sep 17 00:00:00 2001 From: Rob Martin Date: Thu, 4 Apr 2013 15:55:52 -0500 Subject: [PATCH 3/5] Move example.cfg to a dist file and protect example.cfg --- .gitignore | 4 ++++ README.rst | 4 ++-- example.cfg => example.cfg.dist | 0 3 files changed, 6 insertions(+), 2 deletions(-) rename example.cfg => example.cfg.dist (100%) diff --git a/.gitignore b/.gitignore index 31a3dfc8..60ea7449 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,7 @@ /eggs /build /dist + +example.cfg +*.crt +*.pem diff --git a/README.rst b/README.rst index a7ccdadb..28daa63c 100644 --- a/README.rst +++ b/README.rst @@ -57,8 +57,8 @@ To run the unit-tests:: Running the example app ======================= -To run with the default configuration file, example.cfg, which needs to be -filled out completely:: +To run with the default configuration, copy example.cfg.dist to example.cfg +and fill it out completely. Then run the example with:: python setup.py example diff --git a/example.cfg b/example.cfg.dist similarity index 100% rename from example.cfg rename to example.cfg.dist From 1c89198ce0c6f38e28ba5fd099a903d6e786302f Mon Sep 17 00:00:00 2001 From: Rob Martin Date: Thu, 4 Apr 2013 16:00:53 -0500 Subject: [PATCH 4/5] Changed AuthnRequest.create class method call to AuthRequest.create --- example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.py b/example.py index f244bef1..09a886c6 100644 --- a/example.py +++ b/example.py @@ -57,7 +57,7 @@ def do_GET(self): self._bad_request() return - url = AuthnRequest.create(**self.settings) + url = AuthRequest.create(**self.settings) self.send_response(301) self.send_header("Location", url) self.end_headers() From a154a2739986289c04e1918d0e9b8a4b4f08f866 Mon Sep 17 00:00:00 2001 From: John Weaver Date: Mon, 24 Mar 2014 11:18:43 -0700 Subject: [PATCH 5/5] Removes constraint on python-dateutil version. --- onelogin/saml/Response.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/onelogin/saml/Response.py b/onelogin/saml/Response.py index 71c4eab5..d7e531de 100644 --- a/onelogin/saml/Response.py +++ b/onelogin/saml/Response.py @@ -103,7 +103,7 @@ def is_valid( Return True if valid, otherwise False. """ if _clock is None: - _clock = lambda :datetime.now(dateutil.tz.tzutc()) + _clock = lambda: datetime.now(dateutil.tz.tzutc()) if _verifier is None: _verifier = SignatureVerifier.verify diff --git a/setup.py b/setup.py index 21053478..6441d299 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ def run(self): install_requires = [ 'lxml>=2.3', - 'python-dateutil<2.0', + 'python-dateutil', ] tests_require = [ 'fudge >=0.9.5',