Skip to content

Commit a088a3e

Browse files
committed
Extend get_last_request_xml and get_last_response_xml to retrieve also Logout messages. Refactoring
1 parent 8bbddc5 commit a088a3e

11 files changed

Lines changed: 100 additions & 24 deletions

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -827,13 +827,13 @@ Main class of OneLogin Python Toolkit
827827
* ***get_last_error_reason*** Returns the reason of the last error
828828
* ***get_sso_url*** Gets the SSO url.
829829
* ***get_slo_url*** Gets the SLO url.
830-
* ***get_last_request_id*** The ID of the last Request SAML message generated.
830+
* ***get_last_request_id*** The ID of the last Request SAML message generated (AuthNRequest, LogoutRequest).
831831
* ***build_request_signature*** Builds the Signature of the SAML Request.
832832
* ***build_response_signature*** Builds the Signature of the SAML Response.
833833
* ***get_settings*** Returns the settings info.
834834
* ***set_strict*** Set the strict mode active/disable.
835-
* ***get_last_request_xml*** Returns the most recently-constructed XML request
836-
* ***get_last_response_xml*** Returns the most recently-decrypted XML response
835+
* ***get_last_request_xml*** Returns the most recently-constructed/processed XML SAML request (AuthNRequest, LogoutRequest)
836+
* ***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.
837837

838838
####OneLogin_Saml2_Auth - authn_request.py####
839839

src/onelogin/saml2/auth.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ def __init__(self, request_data, old_settings=None, custom_base_path=None):
5858
self.__errors = []
5959
self.__error_reason = None
6060
self.__last_request_id = None
61-
self.__last_request_xml = None
62-
self.__last_response_xml = None
61+
self.__last_request = None
62+
self.__last_response = None
6363

6464
def get_settings(self):
6565
"""
@@ -93,7 +93,7 @@ def process_response(self, request_id=None):
9393
if 'post_data' in self.__request_data and 'SAMLResponse' in self.__request_data['post_data']:
9494
# AuthnResponse -- HTTP_POST Binding
9595
response = OneLogin_Saml2_Response(self.__settings, self.__request_data['post_data']['SAMLResponse'])
96-
self.__last_response_xml = response.get_xml_document()
96+
self.__last_response = response.get_xml_document()
9797
if response.is_valid(self.__request_data, request_id):
9898
self.__attributes = response.get_attributes()
9999
self.__nameid = response.get_nameid()
@@ -128,6 +128,7 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
128128

129129
if 'get_data' in self.__request_data and 'SAMLResponse' in self.__request_data['get_data']:
130130
logout_response = OneLogin_Saml2_Logout_Response(self.__settings, self.__request_data['get_data']['SAMLResponse'])
131+
self.__logout_response = logout_response.get_xml()
131132
if not logout_response.is_valid(self.__request_data, request_id):
132133
self.__errors.append('invalid_logout_response')
133134
self.__error_reason = logout_response.get_error()
@@ -138,6 +139,7 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
138139

139140
elif 'get_data' in self.__request_data and 'SAMLRequest' in self.__request_data['get_data']:
140141
logout_request = OneLogin_Saml2_Logout_Request(self.__settings, self.__request_data['get_data']['SAMLRequest'])
142+
self.__last_request = logout_request.get_xml()
141143
if not logout_request.is_valid(self.__request_data):
142144
self.__errors.append('invalid_logout_request')
143145
self.__error_reason = logout_request.get_error()
@@ -148,6 +150,7 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
148150
in_response_to = logout_request.id
149151
response_builder = OneLogin_Saml2_Logout_Response(self.__settings)
150152
response_builder.build(in_response_to)
153+
self.__logout_response = response_builder.get_xml()
151154
logout_response = response_builder.get_response()
152155

153156
parameters = {'SAMLResponse': logout_response}
@@ -288,12 +291,11 @@ def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_
288291
:rtype: string
289292
"""
290293
authn_request = OneLogin_Saml2_Authn_Request(self.__settings, force_authn, is_passive, set_nameid_policy)
291-
294+
self.__last_request = authn_request.get_xml()
292295
self.__last_request_id = authn_request.get_id()
293-
294296
saml_request = authn_request.get_request()
297+
295298
parameters = {'SAMLRequest': saml_request}
296-
self.__last_request_xml = authn_request.get_request_as_xml()
297299
if return_to is not None:
298300
parameters['RelayState'] = return_to
299301
else:
@@ -339,9 +341,8 @@ def logout(self, return_to=None, name_id=None, session_index=None, nq=None):
339341
session_index=session_index,
340342
nq=nq
341343
)
342-
344+
self.__last_request = logout_request.get_xml()
343345
self.__last_request_id = logout_request.id
344-
345346
saml_request = logout_request.get_request()
346347

347348
parameters = {'SAMLRequest': logout_request.get_request()}
@@ -455,15 +456,21 @@ def __build_signature(self, saml_data, relay_state, saml_type, sign_algorithm=On
455456
signature = dsig_ctx.signBinary(str(msg), sign_algorithm_transform)
456457
return b64encode(signature)
457458

458-
def get_last_response_xml(self):
459+
def get_last_response_xml(self, pretty_print_if_possible=False):
459460
"""
460-
Retrieves the decrypted XML of the last SAML response
461+
Retrieves the raw XML (decrypted) of the last SAML response,
462+
or the last Logout Response generated or processed
461463
462464
:returns: SAML response XML
463465
:rtype: string|None
464466
"""
465-
if self.__last_response_xml:
466-
return etree.tostring(self.__last_response_xml, pretty_print=True)
467+
response = None
468+
if self.__last_response:
469+
if isinstance(self.__last_response, basestring):
470+
response = self.__last_response
471+
else:
472+
response = etree.tostring(self.__last_response, pretty_print=pretty_print_if_possible)
473+
return response
467474

468475
def get_last_request_xml(self):
469476
"""
@@ -472,5 +479,4 @@ def get_last_request_xml(self):
472479
:returns: SAML request XML
473480
:rtype: string|None
474481
"""
475-
if self.__last_request_xml:
476-
return self.__last_request_xml
482+
return self.__last_request or None

src/onelogin/saml2/authn_request.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,9 @@ def get_id(self):
150150
"""
151151
return self.__id
152152

153-
def get_request_as_xml(self):
153+
def get_xml(self):
154154
"""
155-
Return the XML document that will be sent as part of the request
155+
Returns the XML that will be sent as part of the request
156156
:return: XML request body
157157
:rtype: string
158158
"""

src/onelogin/saml2/logout_request.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ def get_request(self, deflate=True):
130130
request = b64encode(self.__logout_request)
131131
return request
132132

133+
def get_xml(self):
134+
"""
135+
Returns the XML that will be sent as part of the request
136+
or that was received at the SP
137+
:return: XML request body
138+
:rtype: string
139+
"""
140+
return self.__logout_request
141+
133142
@staticmethod
134143
def get_id(request):
135144
"""

src/onelogin/saml2/logout_response.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,15 @@ def get_response(self, deflate=True):
205205
response = b64encode(self.__logout_response)
206206
return response
207207

208+
def get_xml(self):
209+
"""
210+
Returns the XML that will be sent as part of the response
211+
or that was received at the SP
212+
:return: XML response body
213+
:rtype: string
214+
"""
215+
return self.__logout_response
216+
208217
def get_error(self):
209218
"""
210219
After executing a validation process, if it fails this method returns the cause

tests/data/responses/decrypted_valid_encrypted_assertion.xml.base64.xml renamed to tests/data/responses/decrypted_valid_encrypted_assertion.xml

File renamed without changes.

tests/data/responses/pretty_decrypted_valid_encrypted_assertion.xml.base64.xml renamed to tests/data/responses/pretty_decrypted_valid_encrypted_assertion.xml

File renamed without changes.

tests/src/OneLogin/saml2_tests/auth_test.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -916,8 +916,10 @@ def testGetLastDecryptedResponse(self):
916916
message_wrapper = {'post_data': {'SAMLResponse': message}}
917917
auth = OneLogin_Saml2_Auth(message_wrapper, old_settings=settings)
918918
auth.process_response()
919-
decrypted_response = self.file_contents(join(self.data_path, 'responses', 'pretty_decrypted_valid_encrypted_assertion.xml.base64.xml'))
920-
self.assertEqual(auth.get_last_response_xml(), decrypted_response)
919+
decrypted_response = self.file_contents(join(self.data_path, 'responses', 'decrypted_valid_encrypted_assertion.xml'))
920+
self.assertEqual(auth.get_last_response_xml(False), decrypted_response)
921+
decrypted_response = self.file_contents(join(self.data_path, 'responses', 'pretty_decrypted_valid_encrypted_assertion.xml'))
922+
self.assertEqual(auth.get_last_response_xml(True), decrypted_response)
921923

922924
def testGetLastSentRequest(self):
923925
settings = self.loadSettingsJSON()

tests/src/OneLogin/saml2_tests/authn_request_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,15 @@ def testGetRequestXML(self):
8080
}
8181

8282
authn_request = OneLogin_Saml2_Authn_Request(settings)
83-
inflated = authn_request.get_request_as_xml()
83+
inflated = authn_request.get_xml()
8484
self.assertRegexpMatches(inflated, '^<samlp:AuthnRequest')
8585
self.assertNotIn('ProviderName="SP test"', inflated)
8686

8787
saml_settings['organization'] = {}
8888
settings = OneLogin_Saml2_Settings(saml_settings)
8989

9090
authn_request = OneLogin_Saml2_Authn_Request(settings)
91-
inflated = authn_request.get_request_as_xml()
91+
inflated = authn_request.get_xml()
9292
self.assertRegexpMatches(inflated, '^<samlp:AuthnRequest')
9393
self.assertNotIn('ProviderName="SP test"', inflated)
9494

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright (c) 2014, OneLogin, Inc.
4+
# All rights reserved.
5+
6+
from base64 import b64decode
7+
import json
8+
from lxml import etree
9+
from os.path import dirname, join, exists
10+
import unittest
11+
from teamcity import is_running_under_teamcity
12+
from teamcity.unittestpy import TeamcityTestRunner
13+
from xml.dom.minidom import Document, parseString
14+
15+
from onelogin.saml2.constants import OneLogin_Saml2_Constants
16+
from onelogin.saml2.settings import OneLogin_Saml2_Settings
17+
from onelogin.saml2.utils import OneLogin_Saml2_Utils
18+
19+
20+
class OneLogin_Saml2_Utils_Test(unittest.TestCase):
21+
data_path = join(dirname(dirname(dirname(dirname(__file__)))), 'data')
22+
23+
def file_contents(self, filename):
24+
f = open(filename, 'r')
25+
content = f.read()
26+
f.close()
27+
return content
28+
29+
30+
def testExpesify(self):
31+
xml = self.file_contents(join(self.data_path, 'responses', 'expensify.xml'))
32+
cert = """-----BEGIN CERTIFICATE-----
33+
MIIC5jCCAc6gAwIBAgIQejzcQ7LOo7NDphYfVqKB8jANBgkqhkiG9w0BAQsFADAv
34+
MS0wKwYDVQQDEyRBREZTIFNpZ25pbmcgLSBhZGZzLmZpbmlzaG1hc3Rlci5jb20w
35+
HhcNMTYwMzMwMjA1NjM2WhcNMTcwMzMwMjA1NjM2WjAvMS0wKwYDVQQDEyRBREZT
36+
IFNpZ25pbmcgLSBhZGZzLmZpbmlzaG1hc3Rlci5jb20wggEiMA0GCSqGSIb3DQEB
37+
AQUAA4IBDwAwggEKAoIBAQDrnGb3jm22UR0CqYQK55vj5dVMQIitpl5jUHuFgou0
38+
SsFfI5VJY5KNXQXCJoduXlxl12dRUQrd0h22TCLBWoneuDcor0UV4bRl7QyQWjuX
39+
M6pEDG8JsNWJEmnoiKGVmnfsaVgKTFZxO+Kydk/GRJauOqAyU7igABDpazMMHjjL
40+
c9iZ6tHjFjtWrUay3Hu3aaZheQWgzjnENUprgnH3zm5NQT5Dbd72z5TKrb4bbv08
41+
oInf52dnBNiSz0wa8uZRJPJJH2rGJmWUIJqp2fnhCxysMgf+ny5IfOkpcG4Cb/fk
42+
d4hoSmY5Bb/dKuD9/pTpmoylcinBtb3bMGASkulyQmENAgMBAAEwDQYJKoZIhvcN
43+
AQELBQADggEBAEx5l+gRoxUSY1nu6O2Cmu8WpnrFlEoNsko+Z/T34NYIm/2uaX6y
44+
kHoDSptgplwjtjE6lE0Zygm5R59BAU3tqGG1uHu4G7w++rcE6nnYunC3tZ8iKuzb
45+
VmkyUFjuf0AaWo/j/pl7zDJV1NHg6zm/NrqKhx4+Sr/LpVdBMOQmU9leqKsfVtkJ
46+
ktTOx1ChiEqu7R0yfKuaZwSlUfgnVtVS7yvLvk0KIbxhZe9BGdtJ03+XHYE0TeVN
47+
SpHh6Q1mylYurMX3WLMW5cQ+AcOWiDob2FuMq6wNWoUwOCvUwTu9NFQvuyzFpUrn
48+
wFgrDzL8lRc9nNA/iZh+pBIu+BM3/wWZLp8=
49+
-----END CERTIFICATE-----"""
50+
self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml, cert, debug=True))

0 commit comments

Comments
 (0)