@@ -114,31 +114,41 @@ def is_valid(self, request_data, request_id=None):
114114
115115 if security .get ('wantNameIdEncrypted' , False ):
116116 encrypted_nameid_nodes = self .__query_assertion ('/saml:Subject/saml:EncryptedID/xenc:EncryptedData' )
117- if len (encrypted_nameid_nodes ) == 0 :
117+ if len (encrypted_nameid_nodes ) != 1 :
118118 raise Exception ('The NameID of the Response is not encrypted and the SP require it' )
119119
120- # Checks that there is at least one AttributeStatement if required
121- attribute_statement_nodes = self .__query_assertion ('/saml:AttributeStatement' )
122- if security .get ('wantAttributeStatement' , True ) and not attribute_statement_nodes :
123- raise Exception ('There is no AttributeStatement on the Response' )
120+ # Checks that a Conditions element exists
121+ if not self .check_one_condition ():
122+ raise Exception ('The Assertion must include a Conditions element' )
124123
125124 # Validates Assertion timestamps
126125 if not self .validate_timestamps ():
127126 raise Exception ('Timing issues (please check your clock settings)' )
128127
128+ # Checks that an AuthnStatement element exists and is unique
129+ if not self .check_one_authnstatement ():
130+ raise Exception ('The Assertion must include an AuthnStatement element' )
131+
132+ # Checks that there is at least one AttributeStatement if required
133+ attribute_statement_nodes = self .__query_assertion ('/saml:AttributeStatement' )
134+ if security .get ('wantAttributeStatement' , True ) and not attribute_statement_nodes :
135+ raise Exception ('There is no AttributeStatement on the Response' )
136+
129137 encrypted_attributes_nodes = self .__query_assertion ('/saml:AttributeStatement/saml:EncryptedAttribute' )
130138 if encrypted_attributes_nodes :
131139 raise Exception ('There is an EncryptedAttribute in the Response and this SP not support them' )
132140
133141 # Checks destination
134- destination = self .document .get ('Destination' , '' )
142+ destination = self .document .get ('Destination' , None )
135143 if destination :
136144 if not destination .startswith (current_url ):
137145 # TODO: Review if following lines are required, since we can control the
138146 # request_data
139147 # current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
140148 # if not destination.startswith(current_url_routed):
141149 raise Exception ('The response was received at %s instead of %s' % (current_url , destination ))
150+ elif destination == '' :
151+ raise Exception ('The response has an empty Destination value' )
142152
143153 # Checks audience
144154 valid_audiences = self .get_audiences ()
@@ -242,6 +252,26 @@ def check_status(self):
242252 status_exception_msg += ' -> ' + status_msg
243253 raise Exception (status_exception_msg )
244254
255+ def check_one_condition (self ):
256+ """
257+ Checks that the samlp:Response/saml:Assertion/saml:Conditions element exists and is unique.
258+ """
259+ condition_nodes = self .__query_assertion ('/saml:Conditions' )
260+ if len (condition_nodes ) == 1 :
261+ return True
262+ else :
263+ return False
264+
265+ def check_one_authnstatement (self ):
266+ """
267+ Checks that the samlp:Response/saml:Assertion/saml:AuthnStatement element exists and is unique.
268+ """
269+ authnstatement_nodes = self .__query_assertion ('/saml:AuthnStatement' )
270+ if len (authnstatement_nodes ) == 1 :
271+ return True
272+ else :
273+ return False
274+
245275 def get_audiences (self ):
246276 """
247277 Gets the audiences
@@ -262,12 +292,16 @@ def get_issuers(self):
262292 issuers = []
263293
264294 message_issuer_nodes = self .__query ('/samlp:Response/saml:Issuer' )
265- if message_issuer_nodes :
295+ if len ( message_issuer_nodes ) == 1 :
266296 issuers .append (message_issuer_nodes [0 ].text )
297+ else :
298+ raise Exception ('Issuer of the Response not found or multiple.' )
267299
268300 assertion_issuer_nodes = self .__query_assertion ('/saml:Issuer' )
269- if assertion_issuer_nodes :
301+ if len ( assertion_issuer_nodes ) == 1 :
270302 issuers .append (assertion_issuer_nodes [0 ].text )
303+ else :
304+ raise Exception ('Issuer of the Assertion not found or multiple.' )
271305
272306 return list (set (issuers ))
273307
@@ -296,10 +330,19 @@ def get_nameid_data(self):
296330 if security .get ('wantNameId' , True ):
297331 raise Exception ('Not NameID found in the assertion of the Response' )
298332 else :
333+ if self .__settings .is_strict () and not nameid .text :
334+ raise Exception ('An empty NameID value found' )
335+
299336 nameid_data = {'Value' : nameid .text }
300337 for attr in ['Format' , 'SPNameQualifier' , 'NameQualifier' ]:
301338 value = nameid .get (attr , None )
302339 if value :
340+ if self .__settings .is_strict () and attr == 'SPNameQualifier' :
341+ sp_data = self .__settings .get_sp_data ()
342+ sp_entity_id = sp_data .get ('entityId' , '' )
343+ if sp_entity_id != value :
344+ raise Exception ('The SPNameQualifier value mistmatch the SP entityID value.' )
345+
303346 nameid_data [attr ] = value
304347 return nameid_data
305348
@@ -355,6 +398,9 @@ def get_attributes(self):
355398 attribute_nodes = self .__query_assertion ('/saml:AttributeStatement/saml:Attribute' )
356399 for attribute_node in attribute_nodes :
357400 attr_name = attribute_node .get ('Name' )
401+ if attr_name in attributes .keys ():
402+ raise Exception ('Found an Attribute element with duplicated Name' )
403+
358404 values = []
359405 for attr in attribute_node .iterchildren ('{%s}AttributeValue' % OneLogin_Saml2_Constants .NSMAP ['saml' ]):
360406 # Remove any whitespace (which may be present where attributes are
@@ -386,7 +432,14 @@ def validate_num_assertions(self):
386432 """
387433 encrypted_assertion_nodes = OneLogin_Saml2_Utils .query (self .document , '//saml:EncryptedAssertion' )
388434 assertion_nodes = OneLogin_Saml2_Utils .query (self .document , '//saml:Assertion' )
389- return (len (encrypted_assertion_nodes ) + len (assertion_nodes )) == 1
435+
436+ valid = len (encrypted_assertion_nodes ) + len (assertion_nodes ) == 1
437+
438+ if (self .encrypted ):
439+ assertion_nodes = OneLogin_Saml2_Utils .query (self .decrypted_document , '//saml:Assertion' )
440+ valid = valid and len (assertion_nodes ) == 1
441+
442+ return valid
390443
391444 def process_signed_elements (self ):
392445 """
0 commit comments