Skip to content

Commit a510f16

Browse files
committed
Add hooks to retrieve last-sent and last-received requests and responses
1 parent 73ee977 commit a510f16

15 files changed

+348
-41
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,8 @@ Main class of OneLogin Python Toolkit
794794
* ***build_response_signature*** Builds the Signature of the SAML Response.
795795
* ***get_settings*** Returns the settings info.
796796
* ***set_strict*** Set the strict mode active/disable.
797+
* ***get_last_request_xml*** Returns the most recently-constructed/processed XML SAML request (AuthNRequest, LogoutRequest)
798+
* ***get_last_response_xml*** Returns the most recently-constructed/processed XML SAML response (SAMLResponse, LogoutResponse). If the SAMLResponse was encrypted, by default tries to return the decrypted XML.
797799

798800
####OneLogin_Saml2_Auth - authn_request.py####
799801

@@ -802,7 +804,7 @@ SAML 2 Authentication Request class
802804
* `__init__` This class handles an AuthNRequest. It builds an AuthNRequest object.
803805
* ***get_request*** Returns unsigned AuthnRequest.
804806
* ***get_id*** Returns the AuthNRequest ID.
805-
807+
* ***get_xml*** Returns the XML that will be sent as part of the request.
806808

807809
####OneLogin_Saml2_Response - response.py####
808810

@@ -821,6 +823,7 @@ SAML 2 Authentication Response class
821823
* ***validate_num_assertions*** Verifies that the document only contains a single Assertion (encrypted or not)
822824
* ***validate_timestamps*** Verifies that the document is valid according to Conditions Element
823825
* ***get_error*** After execute a validation process, if fails this method returns the cause
826+
* ***get_xml_document*** If necessary, decrypt the XML response document, and return it.
824827

825828
####OneLogin_Saml2_LogoutRequest - logout_request.py####
826829

@@ -835,6 +838,7 @@ SAML 2 Logout Request class
835838
* ***get_session_indexes*** Gets the SessionIndexes from the Logout Request.
836839
* ***is_valid*** Checks if the Logout Request recieved is valid.
837840
* ***get_error*** After execute a validation process, if fails this method returns the cause.
841+
* ***get_xml*** Returns the XML that will be sent as part of the request or that was received at the SP
838842

839843
####OneLogin_Saml2_LogoutResponse - logout_response.py####
840844

@@ -847,7 +851,7 @@ SAML 2 Logout Response class
847851
* ***build*** Creates a Logout Response object.
848852
* ***get_response*** Returns a Logout Response object.
849853
* ***get_error*** After execute a validation process, if fails this method returns the cause.
850-
854+
* ***get_xml*** Returns the XML that will be sent as part of the response or that was received at the SP
851855

852856
####OneLogin_Saml2_Settings - settings.py####
853857

src/onelogin/saml2/auth.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"""
1313

1414
import xmlsec
15+
from lxml import etree
1516

1617
from onelogin.saml2 import compat
1718
from onelogin.saml2.settings import OneLogin_Saml2_Settings
@@ -59,6 +60,8 @@ def __init__(self, request_data, old_settings=None, custom_base_path=None):
5960
self.__errors = []
6061
self.__error_reason = None
6162
self.__last_request_id = None
63+
self.__last_request = None
64+
self.__last_response = None
6265

6366
def get_settings(self):
6467
"""
@@ -92,6 +95,7 @@ def process_response(self, request_id=None):
9295
if 'post_data' in self.__request_data and 'SAMLResponse' in self.__request_data['post_data']:
9396
# AuthnResponse -- HTTP_POST Binding
9497
response = OneLogin_Saml2_Response(self.__settings, self.__request_data['post_data']['SAMLResponse'])
98+
self.__last_response = response.get_xml_document()
9599

96100
if response.is_valid(self.__request_data, request_id):
97101
self.__attributes = response.get_attributes()
@@ -128,6 +132,7 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
128132
get_data = 'get_data' in self.__request_data and self.__request_data['get_data']
129133
if get_data and 'SAMLResponse' in get_data:
130134
logout_response = OneLogin_Saml2_Logout_Response(self.__settings, get_data['SAMLResponse'])
135+
self.__last_response = logout_response.get_xml()
131136
if not self.validate_response_signature(get_data):
132137
self.__errors.append('invalid_logout_response_signature')
133138
self.__errors.append('Signature validation failed. Logout Response rejected')
@@ -141,6 +146,7 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
141146

142147
elif get_data and 'SAMLRequest' in get_data:
143148
logout_request = OneLogin_Saml2_Logout_Request(self.__settings, get_data['SAMLRequest'])
149+
self.__last_request = logout_request.get_xml()
144150
if not self.validate_request_signature(get_data):
145151
self.__errors.append("invalid_logout_request_signature")
146152
self.__errors.append('Signature validation failed. Logout Request rejected')
@@ -154,6 +160,7 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
154160
in_response_to = logout_request.id
155161
response_builder = OneLogin_Saml2_Logout_Response(self.__settings)
156162
response_builder.build(in_response_to)
163+
self.__last_response = response_builder.get_xml()
157164
logout_response = response_builder.get_response()
158165

159166
parameters = {'SAMLResponse': logout_response}
@@ -288,6 +295,7 @@ def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_
288295
:rtype: string
289296
"""
290297
authn_request = OneLogin_Saml2_Authn_Request(self.__settings, force_authn, is_passive, set_nameid_policy)
298+
self.__last_request = authn_request.get_xml()
291299
self.__last_request_id = authn_request.get_id()
292300

293301
saml_request = authn_request.get_request()
@@ -337,6 +345,7 @@ def logout(self, return_to=None, name_id=None, session_index=None, nq=None):
337345
session_index=session_index,
338346
nq=nq
339347
)
348+
self.__last_request = logout_request.get_xml()
340349
self.__last_request_id = logout_request.id
341350

342351
parameters = {'SAMLRequest': logout_request.get_request()}
@@ -547,3 +556,26 @@ def __validate_signature(self, data, saml_type, raise_exceptions=False):
547556
if raise_exceptions:
548557
raise e
549558
return False
559+
560+
def get_last_response_xml(self, pretty_print_if_possible=False):
561+
"""
562+
Retrieves the raw XML (decrypted) of the last SAML response,
563+
or the last Logout Response generated or processed
564+
:returns: SAML response XML
565+
:rtype: string|None
566+
"""
567+
response = None
568+
if self.__last_response is not None:
569+
if isinstance(self.__last_response, basestring):
570+
response = self.__last_response
571+
else:
572+
response = etree.tostring(self.__last_response, pretty_print=pretty_print_if_possible)
573+
return response
574+
575+
def get_last_request_xml(self):
576+
"""
577+
Retrieves the raw XML sent in the last SAML request
578+
:returns: SAML request XML
579+
:rtype: string|None
580+
"""
581+
return self.__last_request or None

src/onelogin/saml2/authn_request.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,11 @@ def get_id(self):
140140
:rtype: string
141141
"""
142142
return self.__id
143+
144+
def get_xml(self):
145+
"""
146+
Returns the XML that will be sent as part of the request
147+
:return: XML request body
148+
:rtype: string
149+
"""
150+
return self.__authn_request

src/onelogin/saml2/logout_request.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,15 @@ def get_request(self, deflate=True):
111111
request = OneLogin_Saml2_Utils.b64encode(self.__logout_request)
112112
return request
113113

114+
def get_xml(self):
115+
"""
116+
Returns the XML that will be sent as part of the request
117+
or that was received at the SP
118+
:return: XML request body
119+
:rtype: string
120+
"""
121+
return self.__logout_request
122+
114123
@staticmethod
115124
def get_id(request):
116125
"""

src/onelogin/saml2/logout_response.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,12 @@ def get_error(self):
187187
After executing a validation process, if it fails this method returns the cause
188188
"""
189189
return self.__error
190+
191+
def get_xml(self):
192+
"""
193+
Returns the XML that will be sent as part of the response
194+
or that was received at the SP
195+
:return: XML response body
196+
:rtype: string
197+
"""
198+
return self.__logout_response

src/onelogin/saml2/response.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,3 +754,14 @@ def get_error(self):
754754
After executing a validation process, if it fails this method returns the cause
755755
"""
756756
return self.__error
757+
758+
def get_xml_document(self):
759+
"""
760+
If necessary, decrypt the XML response document, and return it.
761+
:return: Decrypted XML response document
762+
:rtype: string
763+
"""
764+
if self.encrypted:
765+
return self.decrypted_document
766+
else:
767+
return self.document

src/onelogin/saml2/xml_templates.py

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -21,40 +21,40 @@ class OneLogin_Saml2_Templates(object):
2121

2222
AUTHN_REQUEST = """\
2323
<samlp:AuthnRequest
24-
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
25-
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
26-
ID="%(id)s"
27-
Version="2.0"%(provider_name)s%(force_authn_str)s%(is_passive_str)s
28-
IssueInstant="%(issue_instant)s"
29-
Destination="%(destination)s"
30-
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
31-
AssertionConsumerServiceURL="%(assertion_url)s"%(attr_consuming_service_str)s>
24+
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
25+
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
26+
ID="%(id)s"
27+
Version="2.0"%(provider_name)s%(force_authn_str)s%(is_passive_str)s
28+
IssueInstant="%(issue_instant)s"
29+
Destination="%(destination)s"
30+
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
31+
AssertionConsumerServiceURL="%(assertion_url)s"%(attr_consuming_service_str)s>
3232
<saml:Issuer>%(entity_id)s</saml:Issuer>%(nameid_policy_str)s
3333
%(requested_authn_context_str)s
3434
</samlp:AuthnRequest>"""
3535

3636
LOGOUT_REQUEST = """\
3737
<samlp:LogoutRequest
38-
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
39-
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
40-
ID="%(id)s"
41-
Version="2.0"
42-
IssueInstant="%(issue_instant)s"
43-
Destination="%(single_logout_url)s">
38+
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
39+
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
40+
ID="%(id)s"
41+
Version="2.0"
42+
IssueInstant="%(issue_instant)s"
43+
Destination="%(single_logout_url)s">
4444
<saml:Issuer>%(entity_id)s</saml:Issuer>
4545
%(name_id)s
4646
%(session_index)s
4747
</samlp:LogoutRequest>"""
4848

4949
LOGOUT_RESPONSE = """\
5050
<samlp:LogoutResponse
51-
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
52-
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
53-
ID="%(id)s"
54-
Version="2.0"
55-
IssueInstant="%(issue_instant)s"
56-
Destination="%(destination)s"
57-
InResponseTo="%(in_response_to)s">
51+
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
52+
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
53+
ID="%(id)s"
54+
Version="2.0"
55+
IssueInstant="%(issue_instant)s"
56+
Destination="%(destination)s"
57+
InResponseTo="%(in_response_to)s">
5858
<saml:Issuer>%(entity_id)s</saml:Issuer>
5959
<samlp:Status>
6060
<samlp:StatusCode Value="%(status)s" />
@@ -105,18 +105,18 @@ class OneLogin_Saml2_Templates(object):
105105

106106
RESPONSE = """\
107107
<samlp:Response
108-
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
109-
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
110-
ID="%(id)s"
111-
InResponseTo="%(in_response_to)s"
112-
Version="2.0"
113-
IssueInstant="%(issue_instant)s"
114-
Destination="%(destination)s">
108+
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
109+
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
110+
ID="%(id)s"
111+
InResponseTo="%(in_response_to)s"
112+
Version="2.0"
113+
IssueInstant="%(issue_instant)s"
114+
Destination="%(destination)s">
115115
<saml:Issuer>%(entity_id)s</saml:Issuer>
116116
<samlp:Status xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
117117
<samlp:StatusCode
118-
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
119-
Value="%(status)s">
118+
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
119+
Value="%(status)s">
120120
</samlp:StatusCode>
121121
</samlp:Status>
122122
<saml:Assertion
@@ -128,14 +128,14 @@ class OneLogin_Saml2_Templates(object):
128128
<saml:Issuer>%(entity_id)s</saml:Issuer>
129129
<saml:Subject>
130130
<saml:NameID
131-
NameQualifier="%(entity_id)s"
132-
SPNameQualifier="%(requester)s"
133-
Format="%(name_id_policy)s">%(name_id)s</saml:NameID>
131+
NameQualifier="%(entity_id)s"
132+
SPNameQualifier="%(requester)s"
133+
Format="%(name_id_policy)s">%(name_id)s</saml:NameID>
134134
<saml:SubjectConfirmation Method="%(cm)s">
135135
<saml:SubjectConfirmationData
136-
NotOnOrAfter="%(not_after)s"
137-
InResponseTo="%(in_response_to)s"
138-
Recipient="%(destination)s">
136+
NotOnOrAfter="%(not_after)s"
137+
InResponseTo="%(in_response_to)s"
138+
Recipient="%(destination)s">
139139
</saml:SubjectConfirmationData>
140140
</saml:SubjectConfirmation>
141141
</saml:Subject>
@@ -145,9 +145,9 @@ class OneLogin_Saml2_Templates(object):
145145
</saml:AudienceRestriction>
146146
</saml:Conditions>
147147
<saml:AuthnStatement
148-
AuthnInstant="%(issue_instant)s"
149-
SessionIndex="%(session_index)s"
150-
SessionNotOnOrAfter="%(not_after)s">
148+
AuthnInstant="%(issue_instant)s"
149+
SessionIndex="%(session_index)s"
150+
SessionNotOnOrAfter="%(not_after)s">
151151
%(authn_context)s
152152
</saml:AuthnStatement>
153153
<saml:AttributeStatement>
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>

0 commit comments

Comments
 (0)