@@ -199,22 +199,34 @@ public boolean isValid(String requestId) {
199199 }
200200 }
201201
202+ // Validate Conditions element exists
203+ if (!this .checkOneCondition ()) {
204+ throw new Exception ("The Assertion must include a Conditions element" );
205+ }
206+
202207 // Validate Assertion timestamps
203208 if (!this .validateTimestamps ()) {
204209 throw new Exception ("Timing issues (please check your clock settings)" );
205210 }
206211
212+ // Validate AuthnStatement element exists and is unique
213+ if (!this .checkOneAuthnStatement ()) {
214+ throw new Exception ("The Assertion must include an AuthnStatement element" );
215+ }
216+
207217 // EncryptedAttributes are not supported
208218 NodeList encryptedAttributeNodes = this .queryAssertion ("/saml:AttributeStatement/saml:EncryptedAttribute" );
209219 if (encryptedAttributeNodes .getLength () > 0 ) {
210220 throw new Exception ("There is an EncryptedAttribute in the Response and this SP not support them" );
211221 }
212-
222+
213223 // Check destination
214224 if (rootElement .hasAttribute ("Destination" )) {
215225 String destinationUrl = rootElement .getAttribute ("Destination" );
216226 if (destinationUrl != null ) {
217- if (!destinationUrl .isEmpty () && !destinationUrl .equals (currentUrl )) {
227+ if (destinationUrl .isEmpty ()) {
228+ throw new Exception ("The response has an empty Destination value" );
229+ } else if (!destinationUrl .equals (currentUrl )) {
218230 throw new Exception ("The response was received at " + currentUrl + " instead of "
219231 + destinationUrl );
220232 }
@@ -317,7 +329,6 @@ private void validateSubjectConfirmation(String responseInResponseTo) throws Exc
317329 continue ;
318330 }
319331
320-
321332 Node notOnOrAfter = subjectConfirmationDataNodes .item (c ).getAttributes ().getNamedItem ("NotOnOrAfter" );
322333 if (notOnOrAfter == null ) {
323334 validationIssues .add (new SubjectConfirmationIssue (i , "SubjectConfirmationData doesn't contain a NotOnOrAfter attribute" ));
@@ -370,7 +381,7 @@ public HashMap<String,String> getNameIdData() throws Exception {
370381 NodeList encryptedIDNodes = this .queryAssertion ("/saml:Subject/saml:EncryptedID/xenc:EncryptedData" );
371382 NodeList nameIdNodes ;
372383 Element nameIdElem ;
373- if (encryptedIDNodes .getLength () > 0 ) {
384+ if (encryptedIDNodes .getLength () == 1 ) {
374385 Element encryptedData = (Element ) encryptedIDNodes .item (0 );
375386 PrivateKey key = settings .getSPkey ();
376387 if (key == null ) {
@@ -387,17 +398,28 @@ public HashMap<String,String> getNameIdData() throws Exception {
387398 nameIdNodes = this .queryAssertion ("/saml:Subject/saml:NameID" );
388399 }
389400
390- if (nameIdNodes != null && nameIdNodes .getLength () > 0 ) {
401+ if (nameIdNodes != null && nameIdNodes .getLength () == 1 ) {
391402 nameIdElem = (Element ) nameIdNodes .item (0 );
392403
393404 if (nameIdElem != null ) {
394- nameIdData .put ("Value" , nameIdElem .getTextContent ());
405+ String value = nameIdElem .getTextContent ();
406+ if (value .isEmpty ()) {
407+ throw new Exception ("An empty NameID value found" );
408+ }
409+
410+ nameIdData .put ("Value" , value );
395411
396412 if (nameIdElem .hasAttribute ("Format" )) {
397413 nameIdData .put ("Format" , nameIdElem .getAttribute ("Format" ));
398414 }
399415 if (nameIdElem .hasAttribute ("SPNameQualifier" )) {
400- nameIdData .put ("SPNameQualifier" , nameIdElem .getAttribute ("SPNameQualifier" ));
416+ String spId = settings .getSpEntityId ();
417+ String spNameQualifier = nameIdElem .getAttribute ("SPNameQualifier" );
418+ if (!spNameQualifier .equals (settings .getSpEntityId ())) {
419+ throw new Exception ("The SPNameQualifier value mistmatch the SP entityID value." );
420+ } else {
421+ nameIdData .put ("SPNameQualifier" , spNameQualifier );
422+ }
401423 }
402424 if (nameIdElem .hasAttribute ("NameQualifier" )) {
403425 nameIdData .put ("NameQualifier" , nameIdElem .getAttribute ("NameQualifier" ));
@@ -433,17 +455,24 @@ public String getNameId() throws Exception {
433455 *
434456 * @return the attributes of the SAML Assertion
435457 *
436- * @throws XPathExpressionException
458+ * @throws Exception
437459 */
438- public HashMap <String , List <String >> getAttributes () throws XPathExpressionException {
460+ public HashMap <String , List <String >> getAttributes () throws Exception {
439461 HashMap <String , List <String >> attributes = new HashMap <String , List <String >>();
440462
441463 NodeList nodes = this .queryAssertion ("/saml:AttributeStatement/saml:Attribute" );
442-
464+ List <String > processedNames = new ArrayList <String >();
465+
443466 if (nodes .getLength () != 0 ) {
444467 for (int i = 0 ; i < nodes .getLength (); i ++) {
445468 NamedNodeMap attrName = nodes .item (i ).getAttributes ();
446469 String attName = attrName .getNamedItem ("Name" ).getNodeValue ();
470+ if (processedNames .contains (attName )) {
471+ throw new Exception ("Found an Attribute element with duplicated Name" );
472+ } else {
473+ processedNames .add (attName );
474+ }
475+
447476 NodeList childrens = nodes .item (i ).getChildNodes ();
448477
449478 List <String > attrValues = new ArrayList <String >();
@@ -495,14 +524,14 @@ public static SamlResponseStatus getStatus(Document dom) throws IllegalArgumentE
495524 String statusExpr = "/samlp:Response/samlp:Status" ;
496525
497526 NodeList statusEntry = Util .query (dom , statusExpr , null );
498- if (statusEntry .getLength () == 0 ) {
527+ if (statusEntry .getLength () != 1 ) {
499528 throw new IllegalArgumentException ("Missing Status on response" );
500529 }
501530 NodeList codeEntry ;
502531
503532 codeEntry = Util .query (dom , statusExpr + "/samlp:StatusCode" , (Element ) statusEntry .item (0 ));
504533
505- if (codeEntry .getLength () == 0 ) {
534+ if (codeEntry .getLength () != 1 ) {
506535 throw new IllegalArgumentException ("Missing Status Code on response" );
507536 }
508537
@@ -511,7 +540,7 @@ public static SamlResponseStatus getStatus(Document dom) throws IllegalArgumentE
511540
512541 NodeList messageEntry = Util .query (dom , statusExpr + "/samlp:StatusMessage" ,
513542 (Element ) statusEntry .item (0 ));
514- if (messageEntry .getLength () > 0 ) {
543+ if (messageEntry .getLength () == 1 ) {
515544 status .setStatusMessage (messageEntry .item (0 ).getTextContent ());
516545 }
517546 return status ;
@@ -521,6 +550,38 @@ public static SamlResponseStatus getStatus(Document dom) throws IllegalArgumentE
521550 }
522551 }
523552
553+ /**
554+ * Checks that the samlp:Response/saml:Assertion/saml:Conditions element exists and is unique.
555+ *
556+ * @return true if the Conditions element exists and is unique
557+ *
558+ * @throws XPathExpressionException
559+ */
560+ public Boolean checkOneCondition () throws XPathExpressionException {
561+ NodeList entries = this .queryAssertion ("/saml:Conditions" );
562+ if (entries .getLength () == 1 ) {
563+ return true ;
564+ } else {
565+ return false ;
566+ }
567+ }
568+
569+ /**
570+ * Checks that the samlp:Response/saml:Assertion/saml:AuthnStatement element exists and is unique.
571+ *
572+ * @return true if the AuthnStatement element exists and is unique
573+ *
574+ * @throws XPathExpressionException
575+ */
576+ public Boolean checkOneAuthnStatement () throws XPathExpressionException {
577+ NodeList entries = this .queryAssertion ("/saml:AuthnStatement" );
578+ if (entries .getLength () == 1 ) {
579+ return true ;
580+ } else {
581+ return false ;
582+ }
583+ }
584+
524585 /**
525586 * Gets the audiences.
526587 *
@@ -548,10 +609,9 @@ public List<String> getAudiences() throws XPathExpressionException {
548609 * Gets the Issuers (from Response and Assertion).
549610 *
550611 * @return the issuers of the assertion/response
551- *
552- * @throws XPathExpressionException
612+ * @throws Exception
553613 */
554- public List <String > getIssuers () throws XPathExpressionException {
614+ public List <String > getIssuers () throws Exception {
555615 List <String > issuers = new ArrayList <String >();
556616 String value ;
557617 NodeList responseIssuer = Util .query (samlResponseDocument , "/samlp:Response/saml:Issuer" );
@@ -560,6 +620,8 @@ public List<String> getIssuers() throws XPathExpressionException {
560620 if (!issuers .contains (value )) {
561621 issuers .add (value );
562622 }
623+ } else {
624+ throw new Exception ("Issuer of the Response not found." );
563625 }
564626
565627 NodeList assertionIssuer = this .queryAssertion ("/saml:Issuer" );
@@ -568,6 +630,8 @@ public List<String> getIssuers() throws XPathExpressionException {
568630 if (!issuers .contains (value )) {
569631 issuers .add (value );
570632 }
633+ } else {
634+ throw new Exception ("Issuer of the Assertion not found." );
571635 }
572636
573637 return issuers ;
@@ -616,7 +680,9 @@ public String getSessionIndex() throws XPathExpressionException {
616680 *
617681 */
618682 public String getAssertionId () throws XPathExpressionException {
619- validateNumAssertions ();
683+ if (!validateNumAssertions ()) {
684+ throw new IllegalArgumentException ("SAML Response must contain 1 Assertion." );
685+ }
620686 final NodeList assertionNode = queryAssertion ("" );
621687 return assertionNode .item (0 ).getAttributes ().getNamedItem ("ID" ).getNodeValue ();
622688 }
@@ -648,7 +714,13 @@ public Boolean validateNumAssertions() throws IllegalArgumentException {
648714 NodeList encryptedAssertionNodes = samlResponseDocument .getElementsByTagNameNS (Constants .NS_SAML , "EncryptedAssertion" );
649715 NodeList assertionNodes = samlResponseDocument .getElementsByTagNameNS (Constants .NS_SAML , "Assertion" );
650716
651- return (assertionNodes .getLength () + encryptedAssertionNodes .getLength () == 1 );
717+ Boolean valid = assertionNodes .getLength () + encryptedAssertionNodes .getLength () == 1 ;
718+
719+ if (encrypted ) {
720+ valid = valid && decryptedDocument .getElementsByTagNameNS (Constants .NS_SAML , "Assertion" ).getLength () == 1 ;
721+ }
722+
723+ return valid ;
652724 }
653725
654726 /**
@@ -835,7 +907,7 @@ private NodeList queryAssertion(String assertionXpath) throws XPathExpressionExc
835907 // let see if the whole response signed?
836908 String signedMessageQuery = "/samlp:Response/" + signatureExpr ;
837909 nodeList = query (signedMessageQuery , null );
838- if (nodeList .getLength () > 0 ) {
910+ if (nodeList .getLength () == 1 ) {
839911 Node responseReferenceNode = nodeList .item (0 );
840912 String responseId = responseReferenceNode .getAttributes ().getNamedItem ("URI" ).getNodeValue ();
841913 if (responseId != null && !responseId .isEmpty ()) {
0 commit comments