99
1010"""
1111
12+ from zlib import decompress
1213from base64 import b64decode
14+ from lxml import etree
1315from defusedxml .lxml import fromstring
1416from urllib import quote_plus
1517from xml .dom .minidom import Document
16- from defusedxml .minidom import parseString
1718
1819from onelogin .saml2 .constants import OneLogin_Saml2_Constants
1920from onelogin .saml2 .utils import OneLogin_Saml2_Utils
@@ -28,51 +29,61 @@ class OneLogin_Saml2_Logout_Request(object):
2829
2930 """
3031
31- def __init__ (self , settings ):
32+ def __init__ (self , settings , request = None ):
3233 """
3334 Constructs the Logout Request object.
3435
3536 Arguments are:
3637 * (OneLogin_Saml2_Settings) settings. Setting data
3738 """
3839 self .__settings = settings
40+ self .__error = None
3941
40- sp_data = self .__settings .get_sp_data ()
41- idp_data = self .__settings .get_idp_data ()
42- security = self .__settings .get_security_data ()
43-
44- uid = OneLogin_Saml2_Utils .generate_unique_id ()
45- name_id_value = OneLogin_Saml2_Utils .generate_unique_id ()
46- issue_instant = OneLogin_Saml2_Utils .parse_time_to_SAML (OneLogin_Saml2_Utils .now ())
47-
48- cert = None
49- if 'nameIdEncrypted' in security and security ['nameIdEncrypted' ]:
50- cert = idp_data ['x509cert' ]
51-
52- name_id = OneLogin_Saml2_Utils .generate_name_id (
53- name_id_value ,
54- sp_data ['entityId' ],
55- sp_data ['NameIDFormat' ],
56- cert
57- )
58-
59- logout_request = """<samlp:LogoutRequest
60- xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
61- xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
62- ID="%(id)s"
63- Version="2.0"
64- IssueInstant="%(issue_instant)s"
65- Destination="%(single_logout_url)s">
66- <saml:Issuer>%(entity_id)s</saml:Issuer>
67- %(name_id)s
68- </samlp:LogoutRequest>""" % \
69- {
70- 'id' : uid ,
71- 'issue_instant' : issue_instant ,
72- 'single_logout_url' : idp_data ['singleLogoutService' ]['url' ],
73- 'entity_id' : sp_data ['entityId' ],
74- 'name_id' : name_id ,
75- }
42+ if request is None :
43+ sp_data = self .__settings .get_sp_data ()
44+ idp_data = self .__settings .get_idp_data ()
45+ security = self .__settings .get_security_data ()
46+
47+ uid = OneLogin_Saml2_Utils .generate_unique_id ()
48+ name_id_value = OneLogin_Saml2_Utils .generate_unique_id ()
49+ issue_instant = OneLogin_Saml2_Utils .parse_time_to_SAML (OneLogin_Saml2_Utils .now ())
50+
51+ cert = None
52+ if 'nameIdEncrypted' in security and security ['nameIdEncrypted' ]:
53+ cert = idp_data ['x509cert' ]
54+
55+ name_id = OneLogin_Saml2_Utils .generate_name_id (
56+ name_id_value ,
57+ sp_data ['entityId' ],
58+ sp_data ['NameIDFormat' ],
59+ cert
60+ )
61+
62+ logout_request = """<samlp:LogoutRequest
63+ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
64+ xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
65+ ID="%(id)s"
66+ Version="2.0"
67+ IssueInstant="%(issue_instant)s"
68+ Destination="%(single_logout_url)s">
69+ <saml:Issuer>%(entity_id)s</saml:Issuer>
70+ %(name_id)s
71+ </samlp:LogoutRequest>""" % \
72+ {
73+ 'id' : uid ,
74+ 'issue_instant' : issue_instant ,
75+ 'single_logout_url' : idp_data ['singleLogoutService' ]['url' ],
76+ 'entity_id' : sp_data ['entityId' ],
77+ 'name_id' : name_id ,
78+ }
79+ else :
80+ decoded = b64decode (request )
81+ # We try to inflate
82+ try :
83+ inflated = decompress (decoded , - 15 )
84+ logout_request = inflated
85+ except Exception :
86+ logout_request = decoded
7687
7788 self .__logout_request = logout_request
7889
@@ -93,11 +104,13 @@ def get_id(request):
93104 :return: string ID
94105 :rtype: str object
95106 """
96- if isinstance (request , Document ):
97- dom = request
107+ if isinstance (request , etree . _Element ):
108+ elem = request
98109 else :
99- dom = parseString (request )
100- return dom .documentElement .getAttribute ('ID' )
110+ if isinstance (request , Document ):
111+ request = request .toxml ()
112+ elem = fromstring (request )
113+ return elem .get ('ID' , None )
101114
102115 @staticmethod
103116 def get_nameid_data (request , key = None ):
@@ -110,23 +123,26 @@ def get_nameid_data(request, key=None):
110123 :return: Name ID Data (Value, Format, NameQualifier, SPNameQualifier)
111124 :rtype: dict
112125 """
113- if isinstance (request , Document ):
114- request = request .toxml ()
115- doc = fromstring (request )
126+ if isinstance (request , etree ._Element ):
127+ elem = request
128+ else :
129+ if isinstance (request , Document ):
130+ request = request .toxml ()
131+ elem = fromstring (request )
116132
117133 name_id = None
118- encrypted_entries = OneLogin_Saml2_Utils .query (doc , '/samlp:LogoutRequest/saml:EncryptedID' )
134+ encrypted_entries = OneLogin_Saml2_Utils .query (elem , '/samlp:LogoutRequest/saml:EncryptedID' )
119135
120136 if len (encrypted_entries ) == 1 :
121137 if key is None :
122138 raise Exception ('Key is required in order to decrypt the NameID' )
123139
124- encrypted_data_nodes = OneLogin_Saml2_Utils .query (doc , '/samlp:LogoutRequest/saml:EncryptedID/xenc:EncryptedData' )
140+ encrypted_data_nodes = OneLogin_Saml2_Utils .query (elem , '/samlp:LogoutRequest/saml:EncryptedID/xenc:EncryptedData' )
125141 if len (encrypted_data_nodes ) == 1 :
126142 encrypted_data = encrypted_data_nodes [0 ]
127143 name_id = OneLogin_Saml2_Utils .decrypt_element (encrypted_data , key )
128144 else :
129- entries = OneLogin_Saml2_Utils .query (doc , '/samlp:LogoutRequest/saml:NameID' )
145+ entries = OneLogin_Saml2_Utils .query (elem , '/samlp:LogoutRequest/saml:NameID' )
130146 if len (entries ) == 1 :
131147 name_id = entries [0 ]
132148
@@ -165,12 +181,15 @@ def get_issuer(request):
165181 :return: The Issuer
166182 :rtype: string
167183 """
168- if isinstance (request , Document ):
169- request = request .toxml ()
170- dom = fromstring (request )
184+ if isinstance (request , etree ._Element ):
185+ elem = request
186+ else :
187+ if isinstance (request , Document ):
188+ request = request .toxml ()
189+ elem = fromstring (request )
171190
172191 issuer = None
173- issuer_nodes = OneLogin_Saml2_Utils .query (dom , '/samlp:LogoutRequest/saml:Issuer' )
192+ issuer_nodes = OneLogin_Saml2_Utils .query (elem , '/samlp:LogoutRequest/saml:Issuer' )
174193 if len (issuer_nodes ) == 1 :
175194 issuer = issuer_nodes [0 ].text
176195 return issuer
@@ -184,54 +203,58 @@ def get_session_indexes(request):
184203 :return: The SessionIndex value
185204 :rtype: list
186205 """
187- if isinstance (request , Document ):
188- request = request .toxml ()
189- dom = fromstring (request )
206+ if isinstance (request , etree ._Element ):
207+ elem = request
208+ else :
209+ if isinstance (request , Document ):
210+ request = request .toxml ()
211+ elem = fromstring (request )
190212
191213 session_indexes = []
192- session_index_nodes = OneLogin_Saml2_Utils .query (dom , '/samlp:LogoutRequest/samlp:SessionIndex' )
214+ session_index_nodes = OneLogin_Saml2_Utils .query (elem , '/samlp:LogoutRequest/samlp:SessionIndex' )
193215 for session_index_node in session_index_nodes :
194216 session_indexes .append (session_index_node .text )
195217 return session_indexes
196218
197- @staticmethod
198- def is_valid (settings , request , get_data , debug = False ):
219+ def is_valid (self , request_data ):
199220 """
200221 Checks if the Logout Request recieved is valid
201- :param settings: Settings
202- :type settings: OneLogin_Saml2_Settings
203- :param request: Logout Request Message
204- :type request: string|DOMDocument
222+ :param request_data: Request Data
223+ :type request_data: dict
224+
205225 :return: If the Logout Request is or not valid
206226 :rtype: boolean
207227 """
228+ self .__error = None
208229 try :
209- if isinstance (request , Document ):
210- dom = request
211- else :
212- dom = parseString (request )
230+ dom = fromstring (self .__logout_request )
213231
214- idp_data = settings .get_idp_data ()
232+ idp_data = self . __settings .get_idp_data ()
215233 idp_entity_id = idp_data ['entityId' ]
216234
217- if settings .is_strict ():
218- res = OneLogin_Saml2_Utils .validate_xml (dom , 'saml-schema-protocol-2.0.xsd' , debug )
235+ if 'get_data' in request_data .keys ():
236+ get_data = request_data ['get_data' ]
237+ else :
238+ get_data = {}
239+
240+ if self .__settings .is_strict ():
241+ res = OneLogin_Saml2_Utils .validate_xml (dom , 'saml-schema-protocol-2.0.xsd' , self .__settings .is_debug_active ())
219242 if not isinstance (res , Document ):
220243 raise Exception ('Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd' )
221244
222- security = settings .get_security_data ()
245+ security = self . __settings .get_security_data ()
223246
224- current_url = OneLogin_Saml2_Utils .get_self_url_no_query (get_data )
247+ current_url = OneLogin_Saml2_Utils .get_self_url_no_query (request_data )
225248
226249 # Check NotOnOrAfter
227- if dom .documentElement . hasAttribute ('NotOnOrAfter' ):
228- na = OneLogin_Saml2_Utils .parse_SAML_to_time (dom .documentElement . getAttribute ('NotOnOrAfter' ))
250+ if dom .get ('NotOnOrAfter' , None ):
251+ na = OneLogin_Saml2_Utils .parse_SAML_to_time (dom .get ('NotOnOrAfter' ))
229252 if na <= OneLogin_Saml2_Utils .now ():
230253 raise Exception ('Timing issues (please check your clock settings)' )
231254
232255 # Check destination
233- if dom .documentElement . hasAttribute ('Destination' ):
234- destination = dom .documentElement . getAttribute ('Destination' )
256+ if dom .get ('Destination' , None ):
257+ destination = dom .get ('Destination' )
235258 if destination != '' :
236259 if current_url not in destination :
237260 raise Exception ('The LogoutRequest was received at $currentURL instead of $destination' )
@@ -268,7 +291,14 @@ def is_valid(settings, request, get_data, debug=False):
268291
269292 return True
270293 except Exception as err :
271- debug = settings .is_debug_active ()
294+ self .__error = err .__str__ ()
295+ debug = self .__settings .is_debug_active ()
272296 if debug :
273- print err
297+ print err . __str__ ()
274298 return False
299+
300+ def get_error (self ):
301+ """
302+ After execute a validation process, if fails this method returns the cause
303+ """
304+ return self .__error
0 commit comments