Skip to content

Commit 87d4c18

Browse files
author
Jesse Shapiro
committed
Adding hooks to retrieve most recent decrypted and sent documents; updating readme and adding tests
1 parent 5674f90 commit 87d4c18

9 files changed

Lines changed: 130 additions & 3 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,8 @@ Main class of OneLogin Python Toolkit
823823
* ***build_response_signature*** Builds the Signature of the SAML Response.
824824
* ***get_settings*** Returns the settings info.
825825
* ***set_strict*** Set the strict mode active/disable.
826+
* ***get_last_request_xml*** Returns the most recently-constructed XML request
827+
* ***get_last_response_xml*** Returns the most recently-decrypted XML response
826828

827829
####OneLogin_Saml2_Auth - authn_request.py####
828830

src/onelogin/saml2/auth.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from base64 import b64encode
1515
from urllib import quote_plus
16+
from lxml import etree
1617

1718
from onelogin.saml2.settings import OneLogin_Saml2_Settings
1819
from onelogin.saml2.response import OneLogin_Saml2_Response
@@ -57,6 +58,8 @@ def __init__(self, request_data, old_settings=None, custom_base_path=None):
5758
self.__errors = []
5859
self.__error_reason = None
5960
self.__last_request_id = None
61+
self.__last_request_xml = None
62+
self.__last_response_xml = None
6063

6164
def get_settings(self):
6265
"""
@@ -90,7 +93,7 @@ def process_response(self, request_id=None):
9093
if 'post_data' in self.__request_data and 'SAMLResponse' in self.__request_data['post_data']:
9194
# AuthnResponse -- HTTP_POST Binding
9295
response = OneLogin_Saml2_Response(self.__settings, self.__request_data['post_data']['SAMLResponse'])
93-
96+
self.__last_response_xml = response.get_xml_document()
9497
if response.is_valid(self.__request_data, request_id):
9598
self.__attributes = response.get_attributes()
9699
self.__nameid = response.get_nameid()
@@ -290,7 +293,7 @@ def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_
290293

291294
saml_request = authn_request.get_request()
292295
parameters = {'SAMLRequest': saml_request}
293-
296+
self.__last_request_xml = authn_request.get_request_as_xml()
294297
if return_to is not None:
295298
parameters['RelayState'] = return_to
296299
else:
@@ -451,3 +454,23 @@ def __build_signature(self, saml_data, relay_state, saml_type, sign_algorithm=On
451454

452455
signature = dsig_ctx.signBinary(str(msg), sign_algorithm_transform)
453456
return b64encode(signature)
457+
458+
def get_last_response_xml(self):
459+
"""
460+
Retrieves the decrypted XML of the last SAML response
461+
462+
:returns: SAML response XML
463+
:rtype: string|None
464+
"""
465+
if self.__last_response_xml:
466+
return etree.tostring(self.__last_response_xml, pretty_print=True)
467+
468+
def get_last_request_xml(self):
469+
"""
470+
Retrieves the raw XML sent in the last SAML request
471+
472+
:returns: SAML request XML
473+
:rtype: string|None
474+
"""
475+
if self.__last_request_xml:
476+
return self.__last_request_xml

src/onelogin/saml2/authn_request.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,11 @@ def get_id(self):
149149
:rtype: string
150150
"""
151151
return self.__id
152+
153+
def get_request_as_xml(self):
154+
"""
155+
Return the XML document that will be sent as part of the request
156+
:return: XML request body
157+
:rtype: string
158+
"""
159+
return self.__authn_request

src/onelogin/saml2/response.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,3 +549,15 @@ def get_error(self):
549549
After executing a validation process, if it fails this method returns the cause
550550
"""
551551
return self.__error
552+
553+
def get_xml_document(self):
554+
"""
555+
If necessary, decrypt the XML response document, and return it.
556+
557+
:return: Decrypted XML response document
558+
:rtype: string
559+
"""
560+
if self.encrypted:
561+
return self.decrypted_document
562+
else:
563+
return self.document
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_5f468249609040c6a351ac1be0e9fc60533ff09d3d" Version="2.0" IssueInstant="2014-03-29T12:01:57Z" Destination="http://stuff.com/endpoints/endpoints/acs.php" InResponseTo="ONELOGIN_be60b8caf8e9d19b7a3551b244f116c947ff247d">
2+
<saml:Issuer>http://idp.example.com/</saml:Issuer>
3+
<samlp:Status>
4+
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
5+
</samlp:Status>
6+
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_519c2712648ee09a06d1f9a08e9e835715fea60267" Version="2.0" IssueInstant="2014-03-29T12:01:57Z"><saml:Issuer>http://idp.example.com/</saml:Issuer><saml:Subject><saml:NameID SPNameQualifier="http://stuff.com/endpoints/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_68392312d490db6d355555cfbbd8ec95d746516f60</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="2055-06-07T20:17:08Z" InResponseTo="ONELOGIN_be60b8caf8e9d19b7a3551b244f116c947ff247d"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2011-01-26T03:23:48Z" NotOnOrAfter="2055-06-07T20:17:08Z"><saml:AudienceRestriction><saml:Audience>http://stuff.com/endpoints/metadata.php</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2014-03-29T11:59:43Z" SessionNotOnOrAfter="2055-06-07T20:17:08Z" SessionIndex="_7164a9a9f97828bfdb8d0ebc004a05d2e7d873f70c"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue></saml:Attribute><saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue></saml:Attribute><saml:Attribute Name="cn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue></saml:Attribute><saml:Attribute Name="sn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">waa2</saml:AttributeValue></saml:Attribute><saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">user</saml:AttributeValue><saml:AttributeValue xsi:type="xs:string">admin</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion>
7+
</samlp:Response>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_5f468249609040c6a351ac1be0e9fc60533ff09d3d" Version="2.0" IssueInstant="2014-03-29T12:01:57Z" Destination="http://stuff.com/endpoints/endpoints/acs.php" InResponseTo="ONELOGIN_be60b8caf8e9d19b7a3551b244f116c947ff247d">
2+
<saml:Issuer>http://idp.example.com/</saml:Issuer>
3+
<samlp:Status>
4+
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
5+
</samlp:Status>
6+
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_519c2712648ee09a06d1f9a08e9e835715fea60267" Version="2.0" IssueInstant="2014-03-29T12:01:57Z"><saml:Issuer>http://idp.example.com/</saml:Issuer><saml:Subject><saml:NameID SPNameQualifier="http://stuff.com/endpoints/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_68392312d490db6d355555cfbbd8ec95d746516f60</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="2055-06-07T20:17:08Z" InResponseTo="ONELOGIN_be60b8caf8e9d19b7a3551b244f116c947ff247d"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2011-01-26T03:23:48Z" NotOnOrAfter="2055-06-07T20:17:08Z"><saml:AudienceRestriction><saml:Audience>http://stuff.com/endpoints/metadata.php</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2014-03-29T11:59:43Z" SessionNotOnOrAfter="2055-06-07T20:17:08Z" SessionIndex="_7164a9a9f97828bfdb8d0ebc004a05d2e7d873f70c"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue></saml:Attribute><saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue></saml:Attribute><saml:Attribute Name="cn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue></saml:Attribute><saml:Attribute Name="sn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">waa2</saml:AttributeValue></saml:Attribute><saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">user</saml:AttributeValue><saml:AttributeValue xsi:type="xs:string">admin</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion>
7+
</samlp:Response>

tests/src/OneLogin/saml2_tests/auth_test.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,33 @@ def testBuildResponseSignature(self):
896896
except Exception as e:
897897
self.assertIn("Trying to sign the SAMLResponse but can't load the SP private key", e.message)
898898

899+
def testGetLastDecryptedResponse(self):
900+
settings = self.loadSettingsJSON()
901+
message = self.file_contents(join(self.data_path, 'responses', 'valid_encrypted_assertion.xml.base64'))
902+
message_wrapper = {'post_data': {'SAMLResponse': message}}
903+
auth = OneLogin_Saml2_Auth(message_wrapper, old_settings=settings)
904+
auth.process_response()
905+
decrypted_response = self.file_contents(join(self.data_path, 'responses', 'pretty_decrypted_valid_encrypted_assertion.xml.base64.xml'))
906+
self.assertEqual(auth.get_last_response_xml(), decrypted_response)
907+
908+
def testGetLastSentRequest(self):
909+
settings = self.loadSettingsJSON()
910+
auth = OneLogin_Saml2_Auth({'http_host': 'localhost', 'script_name': 'thing'}, old_settings=settings)
911+
auth.login()
912+
expectedFragment = (
913+
'Destination="http://idp.example.com/SSOService.php"\n'
914+
' ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"\n'
915+
' AssertionConsumerServiceURL="http://stuff.com/endpoints/endpoints/acs.php"\n'
916+
' >\n'
917+
' <saml:Issuer>http://stuff.com/endpoints/metadata.php</saml:Issuer>\n'
918+
' <samlp:NameIDPolicy\n'
919+
' Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"\n'
920+
' AllowCreate="true" />\n'
921+
' <samlp:RequestedAuthnContext Comparison="exact">\n'
922+
' <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>\n'
923+
' </samlp:RequestedAuthnContext>\n</samlp:AuthnRequest>'
924+
)
925+
self.assertIn(expectedFragment, auth.get_last_request_xml())
899926

900927
if __name__ == '__main__':
901928
if is_running_under_teamcity():

tests/src/OneLogin/saml2_tests/authn_request_test.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,34 @@ def testCreateRequest(self):
6464
self.assertRegexpMatches(inflated, '^<samlp:AuthnRequest')
6565
self.assertNotIn('ProviderName="SP test"', inflated)
6666

67+
def testGetRequestXML(self):
68+
"""
69+
Tests that we can get the request XML directly without
70+
going through intermediate steps
71+
"""
72+
73+
saml_settings = self.loadSettingsJSON()
74+
settings = OneLogin_Saml2_Settings(saml_settings)
75+
settings._OneLogin_Saml2_Settings__organization = {
76+
u'en-US': {
77+
u'url': u'http://sp.example.com',
78+
u'name': u'sp_test'
79+
}
80+
}
81+
82+
authn_request = OneLogin_Saml2_Authn_Request(settings)
83+
inflated = authn_request.get_request_as_xml()
84+
self.assertRegexpMatches(inflated, '^<samlp:AuthnRequest')
85+
self.assertNotIn('ProviderName="SP test"', inflated)
86+
87+
saml_settings['organization'] = {}
88+
settings = OneLogin_Saml2_Settings(saml_settings)
89+
90+
authn_request = OneLogin_Saml2_Authn_Request(settings)
91+
inflated = authn_request.get_request_as_xml()
92+
self.assertRegexpMatches(inflated, '^<samlp:AuthnRequest')
93+
self.assertNotIn('ProviderName="SP test"', inflated)
94+
6795
def testCreateRequestAuthContext(self):
6896
"""
6997
Tests the OneLogin_Saml2_Authn_Request Constructor.

tests/src/OneLogin/saml2_tests/response_test.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from teamcity import is_running_under_teamcity
1414
from teamcity.unittestpy import TeamcityTestRunner
1515
from xml.dom.minidom import parseString
16-
16+
from lxml import etree
1717
from onelogin.saml2.response import OneLogin_Saml2_Response
1818
from onelogin.saml2.settings import OneLogin_Saml2_Settings
1919
from onelogin.saml2.utils import OneLogin_Saml2_Utils
@@ -60,6 +60,19 @@ def testConstruct(self):
6060

6161
self.assertIsInstance(response_enc, OneLogin_Saml2_Response)
6262

63+
def test_get_decrypted_xml(self):
64+
"""
65+
Tests that we can retrieve the raw text of an encrypted XML response
66+
without going through intermediate steps
67+
"""
68+
json_settings = self.loadSettingsJSON()
69+
70+
settings = OneLogin_Saml2_Settings(json_settings)
71+
xml = self.file_contents(join(self.data_path, 'responses', 'valid_encrypted_assertion.xml.base64'))
72+
response = OneLogin_Saml2_Response(settings, xml)
73+
decrypted = self.file_contents(join(self.data_path, 'responses', 'decrypted_valid_encrypted_assertion.xml.base64.xml'))
74+
self.assertEqual(etree.tostring(response.get_xml_document()), decrypted)
75+
6376
def testReturnNameId(self):
6477
"""
6578
Tests the get_nameid method of the OneLogin_Saml2_Response

0 commit comments

Comments
 (0)