@@ -110,31 +110,41 @@ def is_valid(self, request_data, request_id=None):
110110
111111 if security ['wantNameIdEncrypted' ]:
112112 encrypted_nameid_nodes = self .__query_assertion ('/saml:Subject/saml:EncryptedID/xenc:EncryptedData' )
113- if len (encrypted_nameid_nodes ) == 0 :
113+ if len (encrypted_nameid_nodes ) != 1 :
114114 raise Exception ('The NameID of the Response is not encrypted and the SP require it' )
115115
116- # Checks that there is at least one AttributeStatement if required
117- attribute_statement_nodes = self .__query_assertion ('/saml:AttributeStatement' )
118- if security ['wantAttributeStatement' ] and not attribute_statement_nodes :
119- raise Exception ('There is no AttributeStatement on the Response' )
116+ # Checks that a Conditions element exists
117+ if not self .check_one_condition ():
118+ raise Exception ('The Assertion must include a Conditions element' )
120119
121120 # Validates Assertion timestamps
122121 if not self .validate_timestamps ():
123122 raise Exception ('Timing issues (please check your clock settings)' )
124123
124+ # Checks that an AuthnStatement element exists and is unique
125+ if not self .check_one_authnstatement ():
126+ raise Exception ('The Assertion must include an AuthnStatement element' )
127+
128+ # Checks that there is at least one AttributeStatement if required
129+ attribute_statement_nodes = self .__query_assertion ('/saml:AttributeStatement' )
130+ if security .get ('wantAttributeStatement' , True ) and not attribute_statement_nodes :
131+ raise Exception ('There is no AttributeStatement on the Response' )
132+
125133 encrypted_attributes_nodes = self .__query_assertion ('/saml:AttributeStatement/saml:EncryptedAttribute' )
126134 if encrypted_attributes_nodes :
127135 raise Exception ('There is an EncryptedAttribute in the Response and this SP not support them' )
128136
129137 # Checks destination
130- destination = self .document .get ('Destination' , '' )
138+ destination = self .document .get ('Destination' , None )
131139 if destination :
132140 if not destination .startswith (current_url ):
133141 # TODO: Review if following lines are required, since we can control the
134142 # request_data
135143 # current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
136144 # if not destination.startswith(current_url_routed):
137145 raise Exception ('The response was received at %s instead of %s' % (current_url , destination ))
146+ elif destination == '' :
147+ raise Exception ('The response has an empty Destination value' )
138148
139149 # Checks audience
140150 valid_audiences = self .get_audiences ()
@@ -239,6 +249,26 @@ def check_status(self):
239249 status_exception_msg += ' -> ' + status_msg
240250 raise Exception (status_exception_msg )
241251
252+ def check_one_condition (self ):
253+ """
254+ Checks that the samlp:Response/saml:Assertion/saml:Conditions element exists and is unique.
255+ """
256+ condition_nodes = self .__query_assertion ('/saml:Conditions' )
257+ if len (condition_nodes ) == 1 :
258+ return True
259+ else :
260+ return False
261+
262+ def check_one_authnstatement (self ):
263+ """
264+ Checks that the samlp:Response/saml:Assertion/saml:AuthnStatement element exists and is unique.
265+ """
266+ authnstatement_nodes = self .__query_assertion ('/saml:AuthnStatement' )
267+ if len (authnstatement_nodes ) == 1 :
268+ return True
269+ else :
270+ return False
271+
242272 def get_audiences (self ):
243273 """
244274 Gets the audiences
@@ -259,14 +289,18 @@ def get_issuers(self):
259289 issuers = set ()
260290
261291 message_issuer_nodes = self .__query ('/samlp:Response/saml:Issuer' )
262- if message_issuer_nodes :
292+ if len ( message_issuer_nodes ) == 1 :
263293 issuers .add (message_issuer_nodes [0 ].text )
294+ else :
295+ raise Exception ('Issuer of the Response not found or multiple.' )
264296
265297 assertion_issuer_nodes = self .__query_assertion ('/saml:Issuer' )
266- if assertion_issuer_nodes :
298+ if len ( assertion_issuer_nodes ) == 1 :
267299 issuers .add (assertion_issuer_nodes [0 ].text )
300+ else :
301+ raise Exception ('Issuer of the Assertion not found or multiple.' )
268302
269- return list (issuers )
303+ return list (set ( issuers ) )
270304
271305 def get_nameid_data (self ):
272306 """
@@ -292,10 +326,19 @@ def get_nameid_data(self):
292326 if security .get ('wantNameId' , True ):
293327 raise Exception ('Not NameID found in the assertion of the Response' )
294328 else :
329+ if self .__settings .is_strict () and not nameid .text :
330+ raise Exception ('An empty NameID value found' )
331+
295332 nameid_data = {'Value' : nameid .text }
296333 for attr in ['Format' , 'SPNameQualifier' , 'NameQualifier' ]:
297334 value = nameid .get (attr , None )
298335 if value :
336+ if self .__settings .is_strict () and attr == 'SPNameQualifier' :
337+ sp_data = self .__settings .get_sp_data ()
338+ sp_entity_id = sp_data .get ('entityId' , '' )
339+ if sp_entity_id != value :
340+ raise Exception ('The SPNameQualifier value mistmatch the SP entityID value.' )
341+
299342 nameid_data [attr ] = value
300343 return nameid_data
301344
@@ -351,6 +394,9 @@ def get_attributes(self):
351394 attribute_nodes = self .__query_assertion ('/saml:AttributeStatement/saml:Attribute' )
352395 for attribute_node in attribute_nodes :
353396 attr_name = attribute_node .get ('Name' )
397+ if attr_name in attributes .keys ():
398+ raise Exception ('Found an Attribute element with duplicated Name' )
399+
354400 values = []
355401 for attr in attribute_node .iterchildren ('{%s}AttributeValue' % OneLogin_Saml2_Constants .NSMAP ['saml' ]):
356402 values .append (attr .text )
@@ -366,7 +412,14 @@ def validate_num_assertions(self):
366412 """
367413 encrypted_assertion_nodes = OneLogin_Saml2_XML .query (self .document , '//saml:EncryptedAssertion' )
368414 assertion_nodes = OneLogin_Saml2_XML .query (self .document , '//saml:Assertion' )
369- return (len (encrypted_assertion_nodes ) + len (assertion_nodes )) == 1
415+
416+ valid = len (encrypted_assertion_nodes ) + len (assertion_nodes ) == 1
417+
418+ if (self .encrypted ):
419+ assertion_nodes = OneLogin_Saml2_XML .query (self .decrypted_document , '//saml:Assertion' )
420+ valid = valid and len (assertion_nodes ) == 1
421+
422+ return valid
370423
371424 def process_signed_elements (self ):
372425 """
0 commit comments