[java-opensaml] branch main updated: OSJ-380 - Logging of assertion validation is overwriting error messages

Scott Cantor cantor.2 at osu.edu
Thu May 18 17:10:11 UTC 2023


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch main
in repository java-opensaml.

View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=bfb099a33ccb3a1096ff7a77742ddce894c0c16e

The following commit(s) were added to refs/heads/main by this push:
     new bfb099a33 OSJ-380 - Logging of assertion validation is overwriting  error messages
bfb099a33 is described below

commit bfb099a33ccb3a1096ff7a77742ddce894c0c16e
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu May 18 13:10:05 2023 -0400

    OSJ-380 - Logging of assertion validation is overwriting  error messages
    
    https://shibboleth.atlassian.net/browse/OSJ-380
---
 .../saml/common/assertion/ValidationContext.java   | 19 ++---
 .../saml/common/assertion/package-info.java        |  4 +-
 .../saml2/assertion/SAML20AssertionValidator.java  | 96 +++++++++-------------
 .../SAML2AssertionValidationParameters.java        | 36 +++++---
 .../saml/saml2/assertion/package-info.java         |  4 +-
 .../impl/AbstractSubjectConfirmationValidator.java | 59 +++++--------
 .../assertion/impl/AssertionValidationSupport.java | 36 ++++----
 .../AudienceRestrictionConditionValidator.java     | 23 +++---
 .../HolderOfKeySubjectConfirmationValidator.java   | 29 +++----
 .../impl/OneTimeUseConditionValidator.java         |  9 +-
 .../saml2/profile/impl/ValidateAssertions.java     |  4 +-
 ...ecuritySAML20AssertionTokenSecurityHandler.java | 13 +--
 .../assertion/tests/MockAssertionValidator.java    |  4 +-
 13 files changed, 146 insertions(+), 190 deletions(-)

diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/common/assertion/ValidationContext.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/common/assertion/ValidationContext.java
index 16c0c3b72..b779d31a6 100644
--- a/opensaml-saml-api/src/main/java/org/opensaml/saml/common/assertion/ValidationContext.java
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/common/assertion/ValidationContext.java
@@ -17,8 +17,10 @@
 
 package org.opensaml.saml.common.assertion;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.annotation.Nonnull;
@@ -30,7 +32,6 @@ import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.collection.LazyMap;
-import net.shibboleth.shared.primitive.StringSupport;
 
 /**
  * Context which holds state related to a single validation event.
@@ -45,7 +46,7 @@ public class ValidationContext {
     @Nonnull private Map<String, Object> dynamicParameters;
 
     /** Error messaging describing what validation check an assertion failed. */
-    @Nullable private String validationFailureMessage;
+    @Nonnull private final List<String> validationFailureMessages;
 
     /** Constructor. Creates a validation context with no global environment. */
     public ValidationContext() {
@@ -65,6 +66,7 @@ public class ValidationContext {
             staticParameters = Collections.unmodifiableMap(new HashMap<>(newStaticParameters));
         }
         dynamicParameters = new LazyMap<>();
+        validationFailureMessages = new ArrayList<>();
     }
 
     /**
@@ -90,17 +92,8 @@ public class ValidationContext {
      * 
      * @return message describing why the validation process failed
      */
-    @Nullable public String getValidationFailureMessage() {
-        return validationFailureMessage;
-    }
-
-    /**
-     * Sets the message describing why the validation process failed.
-     * 
-     * @param message message describing why the validation process failed
-     */
-    public void setValidationFailureMessage(@Nullable final String message) {
-        validationFailureMessage = StringSupport.trimOrNull(message);
+    @Nonnull @Live public List<String> getValidationFailureMessages() {
+        return validationFailureMessages;
     }
 
 }
\ No newline at end of file
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/common/assertion/package-info.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/common/assertion/package-info.java
index 3c19f53b2..0d860de35 100644
--- a/opensaml-saml-api/src/main/java/org/opensaml/saml/common/assertion/package-info.java
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/common/assertion/package-info.java
@@ -18,5 +18,7 @@
 /**
  * Common classes for validating SAML Assertions.
  */
+ at NonnullElements
+package org.opensaml.saml.common.assertion;
 
-package org.opensaml.saml.common.assertion;
\ No newline at end of file
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML20AssertionValidator.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML20AssertionValidator.java
index ccc5e2135..a1a67f140 100644
--- a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML20AssertionValidator.java
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML20AssertionValidator.java
@@ -379,7 +379,7 @@ public class SAML20AssertionValidator {
             @Nonnull final ValidationContext context) throws AssertionValidationException {
         
         if (assertion.getVersion() != SAMLVersion.VERSION_20) {
-            context.setValidationFailureMessage(String.format(
+            context.getValidationFailureMessages().add(String.format(
                     "Assertion '%s' is not a SAML 2.0 version Assertion", assertion.getID()));
             return ValidationResult.INVALID;
         }
@@ -402,7 +402,7 @@ public class SAML20AssertionValidator {
         final Instant issueInstant = assertion.getIssueInstant();
 
         if (issueInstant == null) {
-            context.setValidationFailureMessage(String.format(
+            context.getValidationFailureMessages().add(String.format(
                     "Assertion '%s' did not contain the required IssueInstant", assertion.getID()));
             return ValidationResult.INVALID; 
         }
@@ -416,17 +416,18 @@ public class SAML20AssertionValidator {
 
         // Check assertion wasn't issued in the future
         if (issueInstant.isAfter(latestValid)) {
-            log.warn("Assertion was not yet valid: IssueInstant: '{}', latest valid: '{}'", issueInstant, latestValid);
-            context.setValidationFailureMessage("Assertion IssueInstant was invalid, issued in future");
+            context.getValidationFailureMessages().add(
+                    String.format("Assertion was not yet valid: IssueInstant: '%s', latest valid: '%s'",
+                            issueInstant, latestValid));
             return ValidationResult.INVALID;
             
         }
 
         // Check assertion has not expired
         if (expiration.isBefore(now)) {
-            log.warn("Assertion IssueInstant was expired: IssueInstant: '{}', expiration: '{}', now: '{}'",
-                    issueInstant, expiration, now);
-            context.setValidationFailureMessage("Assertion IssueInstant was invalid, expired");
+            context.getValidationFailureMessages().add(
+                    String.format("Assertion IssueInstant was expired: IssueInstant: '{}', expiration: '{}', now: '{}'",
+                            issueInstant, expiration, now));
             return ValidationResult.INVALID;
         }
         
@@ -453,8 +454,7 @@ public class SAML20AssertionValidator {
             issuer = StringSupport.trimOrNull(issuerElement.getValue());
         }
         if (issuer == null) {
-            log.warn("Assertion Issuer was missing and was required");
-            context.setValidationFailureMessage("Assertion Issuer was missing and was required");
+            context.getValidationFailureMessages().add("Assertion Issuer was missing and was required");
             return ValidationResult.INVALID;
         }
         
@@ -465,9 +465,7 @@ public class SAML20AssertionValidator {
             validIssuers = (Set<String>) context.getStaticParameters().get(
                     SAML2AssertionValidationParameters.VALID_ISSUERS);
         } catch (final ClassCastException e) {
-            log.warn("The value of the static validation parameter '{}' was not java.util.Set<String>",
-                    SAML2AssertionValidationParameters.VALID_ISSUERS);
-            context.setValidationFailureMessage("Unable to determine list of valid issuers");
+            context.getValidationFailureMessages().add("Unable to determine list of valid issuers");
             return ValidationResult.INDETERMINATE;
         }
         if (validIssuers == null || validIssuers.isEmpty()) {
@@ -486,9 +484,7 @@ public class SAML20AssertionValidator {
             return ValidationResult.VALID;
         }
         
-        log.debug("Failed to match Issuer to any supplied valid issuers: {}", validIssuers);
-
-        context.setValidationFailureMessage(String.format(
+        context.getValidationFailureMessages().add(String.format(
                 "Issuer of Assertion '%s' did not match any valid issuers", assertion.getID()));
         return ValidationResult.INVALID;
     }
@@ -515,7 +511,7 @@ public class SAML20AssertionValidator {
         // Validate params and requirements
         if (!token.isSigned()) {
             if (signatureRequired) {
-                context.setValidationFailureMessage("Assertion was required to be signed, but was not");
+                context.getValidationFailureMessages().add("Assertion was required to be signed, but was not");
                 return ValidationResult.INVALID;
             }
             log.debug("Assertion was not required to be signed, and was not signed.  " 
@@ -541,8 +537,8 @@ public class SAML20AssertionValidator {
 
         final SignatureTrustEngine signatureTrustEngine = getSignatureValidationTrustEngine(token, context);
         if (signatureTrustEngine == null) {
-            log.warn("Signature validation was necessary, but no signature trust engine was available");
-            context.setValidationFailureMessage("Assertion signature could not be evaluated due to internal error");
+            context.getValidationFailureMessages().add(
+                    "Signature validation was necessary, but no signature trust engine was available");
             return ValidationResult.INDETERMINATE;
         }
 
@@ -566,9 +562,8 @@ public class SAML20AssertionValidator {
                 log.warn("No SignaturePrevalidator was available, skipping pre-validation");
             }
         } catch (final SignatureException e) {
-            final String msg = String.format("Assertion Signature failed pre-validation: %s", e.getMessage());
-            log.warn(msg);
-            context.setValidationFailureMessage(msg);
+            context.getValidationFailureMessages().add(
+                    String.format("Assertion Signature failed pre-validation: %s", e.getMessage()));
             return ValidationResult.INVALID;
         }
         
@@ -580,20 +575,16 @@ public class SAML20AssertionValidator {
                         token.getID(), tokenIssuer);
                 return ValidationResult.VALID;
             }
-            final String msg = String.format(
-                    "Signature of Assertion '%s' from Issuer '%s' was not valid", token.getID(), tokenIssuer);
-            log.warn(msg);
-            context.setValidationFailureMessage(msg);
+            context.getValidationFailureMessages().add(
+                    String.format("Signature of Assertion '%s' from Issuer '%s' was not valid",
+                            token.getID(), tokenIssuer));
             return ValidationResult.INVALID;
         } catch (final SecurityException e) {
-            final String msg = String.format(
-                    "A problem was encountered evaluating the signature over Assertion with ID '%s': %s",
-                    token.getID(), e.getMessage());
-            log.warn(msg);
-            context.setValidationFailureMessage(msg);
+            context.getValidationFailureMessages().add(
+                    String.format("A problem was encountered evaluating the signature over Assertion with ID '%s': %s",
+                            token.getID(), e.getMessage()));
             return ValidationResult.INDETERMINATE;
         }
-        
     }
 
     /**
@@ -709,22 +700,15 @@ public class SAML20AssertionValidator {
             }
 
             if (validator == null) {
-                final String msg = String.format(
-                        "Unknown Condition '%s' of type '%s' in assertion '%s'", 
-                                condition.getElementQName(), condition.getSchemaType(), assertion.getID());
-                log.debug(msg);
-                context.setValidationFailureMessage(msg);
+                context.getValidationFailureMessages().add(
+                        String.format("Unknown Condition '%s' of type '%s' in assertion '%s'", 
+                                condition.getElementQName(), condition.getSchemaType(), assertion.getID()));
                 return ValidationResult.INDETERMINATE;
             }
             if (validator.validate(condition, assertion, context) != ValidationResult.VALID) {
-                String msg = String.format(
-                        "Condition '%s' of type '%s' in assertion '%s' was not valid.",
-                                condition.getElementQName(), condition.getSchemaType(), assertion.getID());
-                if (context.getValidationFailureMessage() != null) {
-                    msg = msg + ": " + context.getValidationFailureMessage();
-                }
-                log.debug(msg);
-                context.setValidationFailureMessage(msg);
+                context.getValidationFailureMessages().add(
+                        String.format("Condition '%s' of type '%s' in assertion '%s' was not valid.",
+                                condition.getElementQName(), condition.getSchemaType(), assertion.getID()));
                 return ValidationResult.INVALID;
             }
         }
@@ -754,8 +738,7 @@ public class SAML20AssertionValidator {
         
         final Conditions conditions = assertion.getConditions();
         if (conditions == null || conditions.getConditions().isEmpty()) {
-            log.warn("At least 1 Condition was indicated as required, but Assertion contained no Conditions");
-            context.setValidationFailureMessage(
+            context.getValidationFailureMessages().add(
                     "At least 1 Condition was indicated as required, but Assertion contained no Conditions");
             return ValidationResult.INVALID;
         }
@@ -764,13 +747,9 @@ public class SAML20AssertionValidator {
             assert requiredCondition != null;
             final List<Condition> found = conditions.getConditions(requiredCondition);
             if (found == null || found.isEmpty()) {
-                String msg = String.format("Condition '%s' was required, but was not found in assertion '%s'",
-                        requiredCondition, assertion.getID());
-                if (context.getValidationFailureMessage() != null) {
-                    msg = msg + ": " + context.getValidationFailureMessage();
-                }
-                log.warn(msg);
-                context.setValidationFailureMessage(msg);
+                context.getValidationFailureMessages().add(
+                        String.format("Condition '%s' was required, but was not found in assertion '%s'",
+                                requiredCondition, assertion.getID()));
                 return ValidationResult.INVALID;
             }
         }
@@ -803,7 +782,7 @@ public class SAML20AssertionValidator {
         log.debug("Evaluating Conditions NotBefore '{}' against 'skewed now' time '{}'",
                 notBefore, now.plus(clockSkew));
         if (notBefore != null && notBefore.isAfter(now.plus(clockSkew))) {
-            context.setValidationFailureMessage(String.format(
+            context.getValidationFailureMessages().add(String.format(
                     "Assertion '%s' with NotBefore condition of '%s' is not yet valid", assertion.getID(), notBefore));
             return ValidationResult.INVALID;
         }
@@ -812,7 +791,7 @@ public class SAML20AssertionValidator {
         log.debug("Evaluating Conditions NotOnOrAfter '{}' against 'skewed now' time '{}'",
                 notOnOrAfter, now.minus(clockSkew));
         if (notOnOrAfter != null && notOnOrAfter.isBefore(now.minus(clockSkew))) {
-            context.setValidationFailureMessage(String.format(
+            context.getValidationFailureMessages().add(String.format(
                     "Assertion '%s' with NotOnOrAfter condition of '%s' is no longer valid", assertion.getID(),
                     notOnOrAfter));
             return ValidationResult.INVALID;
@@ -865,10 +844,9 @@ public class SAML20AssertionValidator {
             }
         }
 
-        final String msg = String.format(
-                "No subject confirmation methods were met for assertion with ID '%s'", assertion.getID());
-        log.debug(msg);
-        context.setValidationFailureMessage(msg);
+        context.getValidationFailureMessages().add(
+                String.format("No subject confirmation methods were met for assertion with ID '%s'",
+                        assertion.getID()));
         return ValidationResult.INVALID;
     }
 
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML2AssertionValidationParameters.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML2AssertionValidationParameters.java
index f6f8d9df2..90e60c51d 100644
--- a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML2AssertionValidationParameters.java
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML2AssertionValidationParameters.java
@@ -55,7 +55,8 @@ public final class SAML2AssertionValidationParameters {
     /**
      * Carries the {@link org.opensaml.saml.saml2.core.SubjectConfirmation} that confirmed the subject.
      */
-    @Nonnull @NotEmpty public static final String CONFIRMED_SUBJECT_CONFIRMATION = STD_PREFIX + ".ConfirmedSubjectConfirmation";
+    @Nonnull @NotEmpty public static final String CONFIRMED_SUBJECT_CONFIRMATION =
+            STD_PREFIX + ".ConfirmedSubjectConfirmation";
     
     /**
      * Carries a {@link java.util.Set}<code><</code>{@link java.lang.String}<code>></code>
@@ -73,20 +74,23 @@ public final class SAML2AssertionValidationParameters {
      * Carries a {@link net.shibboleth.shared.resolver.CriteriaSet} which will be used as the 
      * input to a {@link org.opensaml.xmlsec.signature.support.SignatureTrustEngine}.
      */
-    @Nonnull @NotEmpty public static final String SIGNATURE_VALIDATION_CRITERIA_SET = STD_PREFIX + ".SignatureValidationCriteriaSet";
+    @Nonnull @NotEmpty public static final String SIGNATURE_VALIDATION_CRITERIA_SET =
+            STD_PREFIX + ".SignatureValidationCriteriaSet";
 
     /**
      * Carries a {@link org.opensaml.xmlsec.signature.support.SignatureTrustEngine} which will be used
      * to validate the Assertion signature.
      */
-    @Nonnull @NotEmpty public static final String SIGNATURE_VALIDATION_TRUST_ENGINE = STD_PREFIX + ".SignatureValidationTrustEngine";
+    @Nonnull @NotEmpty public static final String SIGNATURE_VALIDATION_TRUST_ENGINE =
+            STD_PREFIX + ".SignatureValidationTrustEngine";
 
     /**
      * Carries a {@link org.opensaml.xmlsec.signature.support.SignaturePrevalidator} which will be used
      * to pre-validate the Assertion signature.
      * 
      */
-    @Nonnull @NotEmpty public static final String SIGNATURE_VALIDATION_PREVALIDATOR = STD_PREFIX + ".SignatureValidationPrevalidator";
+    @Nonnull @NotEmpty public static final String SIGNATURE_VALIDATION_PREVALIDATOR =
+            STD_PREFIX + ".SignatureValidationPrevalidator";
 
     /**
      * Carries a {@link java.lang.Boolean} flag which indicates whether
@@ -98,7 +102,8 @@ public final class SAML2AssertionValidationParameters {
      * Carries a {@link java.lang.Boolean} flag which indicates whether
      * {@link org.opensaml.saml.saml2.core.SubjectConfirmationData} NotOnOrAfter is required.
      */
-    @Nonnull @NotEmpty public static final String SC_NOT_ON_OR_AFTER_REQUIRED = STD_PREFIX + SC_INFIX + ".NoOnOrAfterRequired";
+    @Nonnull @NotEmpty public static final String SC_NOT_ON_OR_AFTER_REQUIRED =
+            STD_PREFIX + SC_INFIX + ".NoOnOrAfterRequired";
     
     /**
      * Carries a {@link java.lang.Boolean} flag which indicates whether
@@ -136,13 +141,15 @@ public final class SAML2AssertionValidationParameters {
      * Carries a {@link java.lang.Boolean} flag which indicates whether
      * {@link org.opensaml.saml.saml2.core.SubjectConfirmationData} InResponseTo is required.
      */
-    @Nonnull @NotEmpty public static final String SC_IN_RESPONSE_TO_REQUIRED = STD_PREFIX + SC_INFIX + ".InResponseToRequired";
+    @Nonnull @NotEmpty public static final String SC_IN_RESPONSE_TO_REQUIRED =
+            STD_PREFIX + SC_INFIX + ".InResponseToRequired";
     
     /**
      * Carries a {@link java.lang.String} whose value is the acceptable 
      * {@link org.opensaml.saml.saml2.core.SubjectConfirmationData} InResponseTo.
      */
-    @Nonnull @NotEmpty public static final String SC_VALID_IN_RESPONSE_TO = STD_PREFIX + SC_INFIX + ".ValidInResponseTo";
+    @Nonnull @NotEmpty public static final String SC_VALID_IN_RESPONSE_TO =
+            STD_PREFIX + SC_INFIX + ".ValidInResponseTo";
     
     /**
      * Carries the {@link java.security.PublicKey} used by the presenter.
@@ -152,19 +159,22 @@ public final class SAML2AssertionValidationParameters {
     /**
      * Carries the {@link java.security.cert.X509Certificate} used by the presenter.
      */
-    @Nonnull @NotEmpty public static final String SC_HOK_PRESENTER_CERT = STD_PREFIX + SC_INFIX + ".HoK.PresenterCertificate";
+    @Nonnull @NotEmpty public static final String SC_HOK_PRESENTER_CERT =
+            STD_PREFIX + SC_INFIX + ".HoK.PresenterCertificate";
 
     /**
      * Carries the {@link org.opensaml.xmlsec.signature.KeyInfo} that successfully confirmed the subject 
      * via holder-of-key subject confirmation.
      */
-    @Nonnull @NotEmpty public static final String SC_HOK_CONFIRMED_KEYINFO = STD_PREFIX + SC_INFIX + ".HoK.ConfirmedKeyInfo";
+    @Nonnull @NotEmpty public static final String SC_HOK_CONFIRMED_KEYINFO =
+            STD_PREFIX + SC_INFIX + ".HoK.ConfirmedKeyInfo";
 
     /**
      * Carries a {@link java.util.Set}<code><</code>{@link javax.xml.namespace.QName}<code>></code>
      * of required conditions which must be present in the assertion.
      */
-    @Nonnull @NotEmpty public static final String COND_REQUIRED_CONDITIONS = STD_PREFIX + COND_INFIX + ".RequiredConditions";
+    @Nonnull @NotEmpty public static final String COND_REQUIRED_CONDITIONS =
+            STD_PREFIX + COND_INFIX + ".RequiredConditions";
 
     /**
      * Carries a {@link java.util.Set}<code><</code>{@link java.lang.String}<code>></code>
@@ -178,7 +188,8 @@ public final class SAML2AssertionValidationParameters {
      * Carries a {@link java.time.Duration} representing the per-invocation value for the Assertion 
      * replay cache expiration.
      */
-    @Nonnull @NotEmpty public static final String COND_ONE_TIME_USE_EXPIRES = STD_PREFIX + COND_INFIX + ".OneTimeUseExpires";
+    @Nonnull @NotEmpty public static final String COND_ONE_TIME_USE_EXPIRES =
+            STD_PREFIX + COND_INFIX + ".OneTimeUseExpires";
 
     /**
      * Carries a {@link java.lang.Boolean} flag which indicates whether the
@@ -200,7 +211,8 @@ public final class SAML2AssertionValidationParameters {
      * Carries a {@link java.time.Duration} used to evaluate the 
      * allowed value of an AuthnInstant attribute of an {@link AuthnStatement}.
      */
-    @Nonnull @NotEmpty public static final String STMT_AUTHN_MAX_TIME = STD_PREFIX + STMT_INFIX + ".Authn.MaxTimeSinceAuthn";
+    @Nonnull @NotEmpty public static final String STMT_AUTHN_MAX_TIME =
+            STD_PREFIX + STMT_INFIX + ".Authn.MaxTimeSinceAuthn";
     
     
     /** Constructor. */
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/package-info.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/package-info.java
index 6e7a14dc0..3e8874493 100644
--- a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/package-info.java
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/package-info.java
@@ -18,5 +18,7 @@
 /**
  * Interfaces and API classes for validating SAML 2 Assertions.
  */
+ at NonnullElements
+package org.opensaml.saml.saml2.assertion;
 
-package org.opensaml.saml.saml2.assertion;
\ No newline at end of file
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AbstractSubjectConfirmationValidator.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AbstractSubjectConfirmationValidator.java
index 7037a2c46..f82c34144 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AbstractSubjectConfirmationValidator.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AbstractSubjectConfirmationValidator.java
@@ -144,8 +144,7 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
         } else {
             if (inResponseToRequired || recipientRequired || notOnOrAfterRequired || notBeforeRequired 
                     || addressRequired) {
-                log.warn("SubjectConfirmationData was null, and one of more data elements were required");
-                context.setValidationFailureMessage(
+                context.getValidationFailureMessages().add(
                         "SubjectConfirmationData was null and one or more data elements were required");
                 return ValidationResult.INVALID;
             }
@@ -250,8 +249,7 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
                 StringSupport.trimOrNull(confirmationData.getInResponseTo());
         if (inResponseTo == null) {
             if (required) {
-                log.warn("SubjectConfirmationData/@InResponseTo was missing and was required");
-                context.setValidationFailureMessage(
+                context.getValidationFailureMessages().add(
                         "SubjectConfirmationData/@InResponseTo was missing and was required");
                 return ValidationResult.INVALID;
             }
@@ -265,16 +263,13 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
             validInResponseTo = (String) context.getStaticParameters().get(
                     SAML2AssertionValidationParameters.SC_VALID_IN_RESPONSE_TO);
         } catch (final ClassCastException e) {
-            log.warn("The value of the static validation parameter '{}' was not java.lang.String",
-                    SAML2AssertionValidationParameters.SC_VALID_IN_RESPONSE_TO);
-            context.setValidationFailureMessage(
+            context.getValidationFailureMessages().add(
                     "Unable to determine valid subject confirmation InResponseTo");
             return ValidationResult.INDETERMINATE;
         }
         if (validInResponseTo == null) {
-            log.warn("Valid InResponseTo was not available from the validation context, " 
+            context.getValidationFailureMessages().add("Valid InResponseTo was not available from the validation context, " 
                     + "unable to evaluate SubjectConfirmationData at InResponseTo");
-            context.setValidationFailureMessage("Unable to determine valid subject confirmation InResponseTo");
             return ValidationResult.INDETERMINATE;
         }
 
@@ -283,11 +278,9 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
             return ValidationResult.VALID;
         }
         
-        log.debug("Failed to match SubjectConfirmationData at InResponse to the valid value: {}", validInResponseTo);
-
-        context.setValidationFailureMessage(String.format(
-                "Subject confirmation InResponseTo for assertion '%s' did not match the valid value",
-                assertion.getID()));
+        context.getValidationFailureMessages().add(String.format(
+                "Subject confirmation InResponseTo for assertion '%s' did not match the valid value: %s",
+                assertion.getID(), validInResponseTo));
         return ValidationResult.INVALID;
     }
 
@@ -310,8 +303,7 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
         final Instant notBefore = confirmationData.getNotBefore();
         if (notBefore == null) {
             if (required) {
-                log.warn("SubjectConfirmationData/@NotBefore was missing and was required");
-                context.setValidationFailureMessage(
+                context.getValidationFailureMessages().add(
                         "SubjectConfirmationData/@NotBefore was missing and was required");
                 return ValidationResult.INVALID;
             }
@@ -323,7 +315,7 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
         log.debug("Evaluating SubjectConfirmationData NotBefore '{}' against 'skewed now' time '{}'",
                 notBefore, skewedNow);
         if (notBefore != null && notBefore.isAfter(skewedNow)) {
-            context.setValidationFailureMessage(String.format(
+            context.getValidationFailureMessages().add(String.format(
                     "Subject confirmation, in assertion '%s', with NotBefore condition of '%s' is not yet valid",
                     assertion.getID(), notBefore));
             return ValidationResult.INVALID;
@@ -351,8 +343,7 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
         final Instant notOnOrAfter = confirmationData.getNotOnOrAfter();
         if (notOnOrAfter == null) {
             if (required) {
-                log.warn("SubjectConfirmationData/@NotOnOrAfter was missing and was required");
-                context.setValidationFailureMessage(
+                context.getValidationFailureMessages().add(
                         "SubjectConfirmationData/@NotOnOrAfter was missing and was required");
                 return ValidationResult.INVALID;
             }
@@ -364,7 +355,7 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
         log.debug("Evaluating SubjectConfirmationData NotOnOrAfter '{}' against 'skewed now' time '{}'",
                 notOnOrAfter, skewedNow);
         if (notOnOrAfter != null && notOnOrAfter.isBefore(skewedNow)) {
-            context.setValidationFailureMessage(String.format(
+            context.getValidationFailureMessages().add(String.format(
                     "Subject confirmation, in assertion '%s', with NotOnOrAfter condition of '%s' is no longer valid",
                     assertion.getID(), notOnOrAfter));
             return ValidationResult.INVALID;
@@ -393,32 +384,28 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
                 StringSupport.trimOrNull(confirmationData.getRecipient());
         if (recipient == null) {
             if (required) {
-                log.warn("SubjectConfirmationData/@Recipient was missing and was required");
-                context.setValidationFailureMessage(
+                context.getValidationFailureMessages().add(
                         "SubjectConfirmationData/@Recipient was missing and was required");
                 return ValidationResult.INVALID;
             }
             return ValidationResult.VALID;
         }
         
-        log.debug("Evaluating SubjectConfirmationData at Recipient of : {}", recipient);
+        log.debug("Evaluating SubjectConfirmationData at Recipient of: {}", recipient);
 
         final Set<String> validRecipients;
         try {
             validRecipients = (Set<String>) context.getStaticParameters().get(
                     SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS);
         } catch (final ClassCastException e) {
-            log.warn("The value of the static validation parameter '{}' was not java.util.Set<String>",
-                    SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS);
-            context.setValidationFailureMessage(
+            context.getValidationFailureMessages().add(
                     "Unable to determine list of valid subject confirmation recipient endpoints");
             return ValidationResult.INDETERMINATE;
         }
         if (validRecipients == null || validRecipients.isEmpty()) {
-            log.warn("Set of valid recipient URI's was not available from the validation context, " 
-                    + "unable to evaluate SubjectConfirmationData at Recipient");
-            context.setValidationFailureMessage(
-                    "Unable to determine list of valid subject confirmation recipient endpoints");
+            context.getValidationFailureMessages().add(
+                    "Set of valid recipient URI's was not available from the validation context, " 
+                            + "unable to evaluate SubjectConfirmationData at Recipient");
             return ValidationResult.INDETERMINATE;
         }
 
@@ -426,13 +413,10 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
             log.debug("Matched valid recipient: {}", recipient);
             return ValidationResult.VALID;
         }
-        
-        log.debug("Failed to match SubjectConfirmationData at Recipient to any supplied valid recipients: {}",
-                validRecipients);
 
-        context.setValidationFailureMessage(String.format(
-                "Subject confirmation recipient for assertion '%s' did not match any valid recipients", assertion
-                        .getID()));
+        context.getValidationFailureMessages().add(String.format(
+                "Subject confirmation recipient for assertion '%s' did not match any valid recipients: %s",
+                assertion.getID(), validRecipients));
         return ValidationResult.INVALID;
     }
 
@@ -463,8 +447,7 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
         final String address = StringSupport.trimOrNull(confirmationData.getAddress());
         if (address == null) {
             if (required) {
-                log.warn("SubjectConfirmationData/@Address was missing and was required");
-                context.setValidationFailureMessage(
+                context.getValidationFailureMessages().add(
                         "SubjectConfirmationData/@Address was missing and was required");
                 return ValidationResult.INVALID;
             }
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AssertionValidationSupport.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AssertionValidationSupport.java
index 8098310ea..81dc80547 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AssertionValidationSupport.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AssertionValidationSupport.java
@@ -19,7 +19,6 @@ package org.opensaml.saml.saml2.assertion.impl;
 
 import java.net.InetAddress;
 import java.net.UnknownHostException;
-import java.util.Arrays;
 import java.util.Set;
 
 import javax.annotation.Nonnull;
@@ -29,7 +28,9 @@ import org.opensaml.saml.common.assertion.ValidationContext;
 import org.opensaml.saml.common.assertion.ValidationResult;
 import org.opensaml.saml.saml2.core.Assertion;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
  * Support methods for assertion validation.
@@ -37,7 +38,7 @@ import org.slf4j.LoggerFactory;
 public final class AssertionValidationSupport {
     
     /** Logger. */
-    private static final Logger LOG = LoggerFactory.getLogger(AssertionValidationSupport.class);
+    @Nonnull private static final Logger LOG = LoggerFactory.getLogger(AssertionValidationSupport.class);
     
     /** Constructor. */
     private AssertionValidationSupport() { }
@@ -69,33 +70,29 @@ public final class AssertionValidationSupport {
         try {
             confirmingAddresses = InetAddress.getAllByName(address);
         } catch (final UnknownHostException e) {
-            LOG.warn("The {} value '{}' in assertion '{}' can not be resolved to a valid set of IP address(s)",
-                    description, address, assertion.getID());
-            context.setValidationFailureMessage(String.format(
+            context.getValidationFailureMessages().add(String.format(
                     "%s '%s' is not resolvable to hostname or IP address", description, address));
             return ValidationResult.INDETERMINATE;
         }
         
         if (LOG.isDebugEnabled()) {
-            LOG.debug("{} was resolved to addresses: {}", description, Arrays.asList(confirmingAddresses));
+            assert confirmingAddresses != null;
+            LOG.debug("{} was resolved to addresses: {}", description, CollectionSupport.listOf(confirmingAddresses));
         }
 
         final Set<InetAddress> validAddresses;
         try {
             validAddresses = (Set<InetAddress>) context.getStaticParameters().get(validAddressesParam);
         } catch (final ClassCastException e) {
-            LOG.warn("The value of the static validation parameter '{}' was not a java.util.Set<InetAddress>",
-                    validAddressesParam);
-            context.setValidationFailureMessage(String.format("Unable to determine list of valid values for %s",
-                    description));
+            context.getValidationFailureMessages().add(
+                    String.format("Unable to determine list of valid values for %s", description));
             return ValidationResult.INDETERMINATE;
         }
         
         if (validAddresses == null || validAddresses.isEmpty()) {
-            LOG.warn("Set of valid addresses was not available from the validation context, unable to evaluate {}",
-                    description);
-            context.setValidationFailureMessage(String.format("Unable to determine list of valid values for %s",
-                    description));
+            context.getValidationFailureMessages().add(String.format(
+                    "Set of valid addresses was not available from the validation context, unable to evaluate %s",
+                            description));
             return ValidationResult.INDETERMINATE;
         }
 
@@ -106,11 +103,10 @@ public final class AssertionValidationSupport {
             }
         }
         
-        LOG.debug("Failed to match {} to any supplied valid addresses: {}", description, validAddresses);
-
-        context.setValidationFailureMessage(String.format(
-                "%s for assertion '%s' did not match any valid addresses", description, assertion.getID()));
+        context.getValidationFailureMessages().add(
+                String.format("%s for assertion '%s' did not match supplied valid addresses: %s",
+                        description, assertion.getID(), validAddresses));
         return ValidationResult.INVALID;
     }
  
-}
+}
\ No newline at end of file
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AudienceRestrictionConditionValidator.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AudienceRestrictionConditionValidator.java
index 5e6b97524..06609543d 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AudienceRestrictionConditionValidator.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AudienceRestrictionConditionValidator.java
@@ -86,15 +86,13 @@ public class AudienceRestrictionConditionValidator implements ConditionValidator
             validAudiences = (Set<String>) context.getStaticParameters().get(
                     SAML2AssertionValidationParameters.COND_VALID_AUDIENCES);
         } catch (final ClassCastException e) {
-            log.warn("The value of the static validation parameter '{}' was not java.util.Set<String>",
-                    SAML2AssertionValidationParameters.COND_VALID_AUDIENCES);
-            context.setValidationFailureMessage("Unable to determine list of valid audiences");
+            context.getValidationFailureMessages().add("Unable to determine list of valid audiences");
             return ValidationResult.INDETERMINATE;
         }
         if (validAudiences == null || validAudiences.isEmpty()) {
-            log.warn("Set of valid audiences was not available from the validation context, " 
-                    + "unable to evaluate AudienceRestriction Condition");
-            context.setValidationFailureMessage("Unable to determine list of valid audiences");
+            context.getValidationFailureMessages().add(
+                    "Set of valid audiences was not available from the validation context, " 
+                            + "unable to evaluate AudienceRestriction Condition");
             return ValidationResult.INDETERMINATE;
         }
         log.debug("Evaluating the Assertion's AudienceRestriction/Audience values " 
@@ -104,9 +102,9 @@ public class AudienceRestrictionConditionValidator implements ConditionValidator
         final AudienceRestriction audienceRestriction = (AudienceRestriction) condition;
         final List<Audience> audiences = audienceRestriction.getAudiences();
         if (audiences == null || audiences.isEmpty()) {
-            context.setValidationFailureMessage(String.format(
-                    "'%s' condition in assertion '%s' is malformed as it does not contain any audiences",
-                    getServicedCondition(), assertion.getID()));
+            context.getValidationFailureMessages().add(
+                    String.format("'%s' condition in assertion '%s' is malformed as it does not contain any audiences",
+                            getServicedCondition(), assertion.getID()));
             return ValidationResult.INVALID;
         }
 
@@ -118,10 +116,9 @@ public class AudienceRestrictionConditionValidator implements ConditionValidator
             }
         }
 
-        final String msg = String.format(
-                "None of the audiences within Assertion '%s' matched the list of valid audiances", assertion.getID());
-        log.debug(msg);
-        context.setValidationFailureMessage(msg);
+        context.getValidationFailureMessages().add(String.format(
+                "None of the audiences within Assertion '%s' matched the list of valid audiances", assertion.getID()));
         return ValidationResult.INVALID;
     }
+
 }
\ No newline at end of file
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/HolderOfKeySubjectConfirmationValidator.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/HolderOfKeySubjectConfirmationValidator.java
index 6b68a6259..e77c47de9 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/HolderOfKeySubjectConfirmationValidator.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/HolderOfKeySubjectConfirmationValidator.java
@@ -29,6 +29,7 @@ import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
 import javax.xml.namespace.QName;
 
+import net.shibboleth.shared.annotation.constraint.Live;
 import net.shibboleth.shared.collection.LazyList;
 import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.primitive.LoggerFactory;
@@ -132,19 +133,17 @@ public class HolderOfKeySubjectConfirmationValidator extends AbstractSubjectConf
         
         log.debug("Attempting holder-of-key subject confirmation");
         if (!isValidConfirmationDataType(confirmationData)) {
-            final String msg = String.format(
-                    "Subject confirmation data is not of type '%s'", KeyInfoConfirmationDataType.TYPE_NAME);
-            log.debug(msg);
-            context.setValidationFailureMessage(msg);
+            context.getValidationFailureMessages().add(
+                    String.format("Subject confirmation data is not of type '%s'",
+                            KeyInfoConfirmationDataType.TYPE_NAME));
             return ValidationResult.INVALID;
         }
 
         final List<KeyInfo> possibleKeys = getSubjectConfirmationKeyInformation(confirmationData, assertion, context);
         if (possibleKeys.isEmpty()) {
-            final String msg = String.format(
-                    "No key information for holder of key subject confirmation in assertion '%s'", assertion.getID());
-            log.debug(msg);
-            context.setValidationFailureMessage(msg);
+            context.getValidationFailureMessages().add(
+                    String.format("No key information for holder of key subject confirmation in assertion '%s'",
+                            assertion.getID()));
             return ValidationResult.INVALID;
         }
 
@@ -152,14 +151,14 @@ public class HolderOfKeySubjectConfirmationValidator extends AbstractSubjectConf
         try {
             keyCertPair = getKeyAndCertificate(context);
         } catch (final IllegalArgumentException e) {
-            log.warn("Problem with the validation context presenter key/cert params: {}", e.getMessage());
-            context.setValidationFailureMessage("Unable to obtain presenter key/cert params from validation context");
+            context.getValidationFailureMessages().add(String.format(
+                    "Unable to obtain presenter key/cert params from validation context: %s", e.getMessage()));
             return ValidationResult.INDETERMINATE;
         }
         
         if (keyCertPair.getFirst() == null && keyCertPair.getSecond() == null) {
-            log.debug("Neither the presenter's certificate nor its public key were provided");
-            context.setValidationFailureMessage("Neither the presenter's certificate nor its public key were provided");
+            context.getValidationFailureMessages().add(
+                    "Neither the presenter's certificate nor its public key were provided");
             return ValidationResult.INDETERMINATE;
         }
 
@@ -265,15 +264,13 @@ public class HolderOfKeySubjectConfirmationValidator extends AbstractSubjectConf
      * @throws AssertionValidationException if there is a problem processing the SubjectConfirmation
      *
      */
-    @Nonnull protected List<KeyInfo> getSubjectConfirmationKeyInformation(
+    @Nonnull @Live protected List<KeyInfo> getSubjectConfirmationKeyInformation(
             @Nonnull final SubjectConfirmationData confirmationData, @Nonnull final Assertion assertion, 
             @Nonnull final ValidationContext context) throws AssertionValidationException {
         
         final List<KeyInfo> keyInfos = new LazyList<>();
         for (final XMLObject object : confirmationData.getUnknownXMLObjects(KeyInfo.DEFAULT_ELEMENT_NAME)) {
-            if (object != null) {
-                keyInfos.add((KeyInfo) object);
-            }
+            keyInfos.add((KeyInfo) object);
         }
 
         log.debug("Found '{}' KeyInfo children of SubjectConfirmationData", keyInfos.size());
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/OneTimeUseConditionValidator.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/OneTimeUseConditionValidator.java
index a5c7d2cd9..227a5313c 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/OneTimeUseConditionValidator.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/OneTimeUseConditionValidator.java
@@ -111,14 +111,15 @@ public class OneTimeUseConditionValidator implements ConditionValidator {
         
         if (!(condition instanceof OneTimeUse) 
                 && !Objects.equals(condition.getElementQName(), getServicedCondition())) {
-            log.warn("Condition '{}' of type '{}' in assertion '{}' was not an '{}' condition.  Unable to process.",
-                    new Object[] { condition.getElementQName(), condition.getSchemaType(), assertion.getID(),
-                            getServicedCondition(), });
+            context.getValidationFailureMessages().add(
+                    String.format("Condition '%s' of type '%s' in assertion '%s' was not an '%s' condition",
+                            condition.getElementQName(), condition.getSchemaType(), assertion.getID(),
+                            getServicedCondition()));
             return ValidationResult.INDETERMINATE;
         }
         
         if (!replayCache.check(CACHE_CONTEXT, getCacheValue(assertion), getExpires(assertion, context))) {
-            context.setValidationFailureMessage(String.format(
+            context.getValidationFailureMessages().add(String.format(
                     "Assertion '%s' has a one time use condition and has been used before", assertion.getID()));
             return ValidationResult.INVALID;
         }
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/profile/impl/ValidateAssertions.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/profile/impl/ValidateAssertions.java
index 60522c33c..538246b2b 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/profile/impl/ValidateAssertions.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/profile/impl/ValidateAssertions.java
@@ -270,8 +270,8 @@ public class ValidateAssertions extends AbstractProfileAction {
 
         log.debug("{} Assertion validation result was: {}", getLogPrefix(), validationResult);
         if (validationResult != ValidationResult.VALID) {
-            log.info("{} Assertion validation failure msg was: {}",
-                    getLogPrefix(), validationContext.getValidationFailureMessage());
+            log.info("{} Assertion validation failure(s): {}", getLogPrefix(),
+                    validationContext.getValidationFailureMessages());
         }
 
         assertion.getObjectMetadata().put(new ValidationProcessingData(validationContext, validationResult));
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/wssecurity/messaging/impl/WSSecuritySAML20AssertionTokenSecurityHandler.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/wssecurity/messaging/impl/WSSecuritySAML20AssertionTokenSecurityHandler.java
index f2eabb597..d101c06ca 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/wssecurity/messaging/impl/WSSecuritySAML20AssertionTokenSecurityHandler.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/wssecurity/messaging/impl/WSSecuritySAML20AssertionTokenSecurityHandler.java
@@ -43,8 +43,6 @@ import org.opensaml.soap.wssecurity.messaging.Token.ValidationStatus;
 import org.opensaml.soap.wssecurity.messaging.WSSecurityContext;
 import org.slf4j.Logger;
 
-import com.google.common.base.Strings;
-
 import jakarta.servlet.http.HttpServletRequest;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.collection.CollectionSupport;
@@ -292,11 +290,6 @@ public class WSSecuritySAML20AssertionTokenSecurityHandler extends AbstractMessa
         
         log.debug("Assertion token validation result was: {}", validationResult);
         
-        String validationMsg = validationContext.getValidationFailureMessage();
-        if (Strings.isNullOrEmpty(validationMsg)) {
-            validationMsg  = "unspecified";
-        }
-                    
         switch (validationResult) {
             case VALID:
                 token.setValidationStatus(ValidationStatus.VALID);
@@ -304,7 +297,8 @@ public class WSSecuritySAML20AssertionTokenSecurityHandler extends AbstractMessa
                         .get(SAML2AssertionValidationParameters.CONFIRMED_SUBJECT_CONFIRMATION));
                 break;
             case INVALID:
-                log.warn("Assertion token validation was INVALID.  Reason: {}", validationMsg);
+                log.warn("Assertion token validation was INVALID. Reason(s): {}",
+                        validationContext.getValidationFailureMessages());
                 if (isInvalidFatal()) {
                     SOAPMessagingSupport.registerSOAP11Fault(messageContext,
                             WSSecurityConstants.SOAP_FAULT_INVALID_SECURITY_TOKEN, 
@@ -316,7 +310,8 @@ public class WSSecuritySAML20AssertionTokenSecurityHandler extends AbstractMessa
                         .get(SAML2AssertionValidationParameters.CONFIRMED_SUBJECT_CONFIRMATION));
                 break;
             case INDETERMINATE:
-                log.warn("Assertion token validation was INDETERMINATE. Reason: {}", validationMsg);
+                log.warn("Assertion token validation was INDETERMINATE. Reason(s): {}",
+                        validationContext.getValidationFailureMessages());
                 if (isInvalidFatal()) {
                     SOAPMessagingSupport.registerSOAP11Fault(messageContext,
                             WSSecurityConstants.SOAP_FAULT_INVALID_SECURITY_TOKEN, 
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/tests/MockAssertionValidator.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/tests/MockAssertionValidator.java
index 0c511481c..136c8dd20 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/tests/MockAssertionValidator.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/tests/MockAssertionValidator.java
@@ -67,7 +67,7 @@ public class MockAssertionValidator extends SAML20AssertionValidator {
         if (ValidationResult.class.isInstance(result)) {
             ValidationResult vr = ValidationResult.class.cast(result);
             if (!ValidationResult.VALID.equals(vr)) {
-                context.setValidationFailureMessage("Mock validation was not valid");
+                context.getValidationFailureMessages().add("Mock validation was not valid");
             }
             return vr;
         }
@@ -75,7 +75,7 @@ public class MockAssertionValidator extends SAML20AssertionValidator {
         if (Pair.class.isInstance(result)) {
             Pair<ValidationResult,String> pair = Pair.class.cast(result);
             if (!ValidationResult.VALID.equals(pair.getFirst())) {
-                context.setValidationFailureMessage(pair.getSecond());
+                context.getValidationFailureMessages().add(pair.getSecond());
             }
             return Constraint.isNotNull(pair.getFirst(), "ValidationResult was null");
         }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list