[java-opensaml] 04/11: Support SubjectConfirmation InResponseTo in Assertion validation.

Brent Putman putmanb at georgetown.edu
Thu Jan 30 02:17:29 EST 2020


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

putmanb pushed a commit to branch master
in repository java-opensaml.

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

commit bfc5985cbcd8f74d9933ebb92224a407924b6714
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Wed Jan 29 19:54:53 2020 -0500

    Support SubjectConfirmation InResponseTo in Assertion validation.
---
 .../SAML2AssertionValidationParameters.java        | 12 +++
 .../impl/AbstractSubjectConfirmationValidator.java | 87 ++++++++++++++++++++++
 .../DefaultAssertionValidationContextBuilder.java  | 83 +++++++++++++++++++++
 .../assertion/BaseAssertionValidationTest.java     |  5 ++
 .../AbstractSubjectConfirmationValidatorTest.java  | 68 +++++++++++++++++
 5 files changed, 255 insertions(+)

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 32f83e6..6e0b0b4 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
@@ -93,6 +93,18 @@ public final class SAML2AssertionValidationParameters {
     public static final String SC_VALID_ADDRESSES = STD_PREFIX + SC_INFIX + ".ValidAddresses";
     
     /**
+     * Carries a {@link java.lang.Boolean} flag which indicates whether
+     * {@link org.opensaml.saml.saml2.core.SubjectConfirmationData} InResponseTo is required.
+     */
+    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.
+     */
+    public static final String SC_VALID_IN_RESPONSE_TO = STD_PREFIX + SC_INFIX + ".ValidInResponseTo";
+    
+    /**
      * Carries the {@link java.security.PublicKey} used by the presenter.
      */
     public static final String SC_HOK_PRESENTER_KEY = STD_PREFIX + SC_INFIX + ".HoK.PresenterKey";
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 3dc97ff..a4203b6 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
@@ -18,6 +18,7 @@
 package org.opensaml.saml.saml2.assertion.impl;
 
 import java.time.Instant;
+import java.util.Objects;
 import java.util.Set;
 
 import javax.annotation.Nonnull;
@@ -34,6 +35,7 @@ import org.opensaml.saml.saml2.core.SubjectConfirmation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import net.shibboleth.utilities.java.support.primitive.ObjectSupport;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
 /**
@@ -61,6 +63,10 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
  * {@link SAML2AssertionValidationParameters#SC_VALID_RECIPIENTS}:
  * Required.
  * </li>
+ * <li>
+ * {@link SAML2AssertionValidationParameters#SC_VALID_IN_RESPONSE_TO}:
+ * Required.
+ * </li>
  * </ul>
  * 
  * <p>
@@ -84,6 +90,8 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
     @Nonnull public ValidationResult validate(@Nonnull final SubjectConfirmation confirmation, 
             @Nonnull final Assertion assertion, @Nonnull final ValidationContext context)
             throws AssertionValidationException {
+        
+        final boolean inResponseToRequired = isInResponseToRequired(context);
 
         if (confirmation.getSubjectConfirmationData() != null) {
             ValidationResult result = validateNotBefore(confirmation, assertion, context);
@@ -105,11 +113,90 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
             if (result != ValidationResult.VALID) {
                 return result;
             }
+            
+            result = validateInResponseTo(confirmation, assertion, context, inResponseToRequired);
+            if (result != ValidationResult.VALID) {
+                return result;
+            }
+        } else {
+            if (inResponseToRequired) {
+                return ValidationResult.INVALID;
+            }
         }
 
         return doValidate(confirmation, assertion, context);
     }
 
+    protected boolean isInResponseToRequired(final ValidationContext context) {
+        return ObjectSupport.firstNonNull(
+                (Boolean) context.getStaticParameters().get(
+                        SAML2AssertionValidationParameters.SC_IN_RESPONSE_TO_REQUIRED),
+                Boolean.FALSE);
+    }
+
+    /**
+     * Validates the <code>InResponseTo</code> condition of the
+     * {@link org.opensaml.saml.saml2.core.SubjectConfirmationData}, if any is present.
+     * 
+     * @param confirmation confirmation method, with {@link org.opensaml.saml.saml2.core.SubjectConfirmationData},
+     *  being validated
+     * @param assertion assertion bearing the confirmation method
+     * @param context current validation context
+     * @param required whether the inResponseTo value is required
+     * 
+     * @return the result of the validation evaluation
+     * 
+     * @throws AssertionValidationException thrown if there is a problem determining the validity of the NotBefore
+     */
+    protected ValidationResult validateInResponseTo(@Nonnull final SubjectConfirmation confirmation,
+            @Nonnull final Assertion assertion, @Nonnull final ValidationContext context, final boolean required)
+                    throws AssertionValidationException {
+        
+        final String inResponseTo = 
+                StringSupport.trimOrNull(confirmation.getSubjectConfirmationData().getInResponseTo());
+        if (inResponseTo == null) {
+            if (required) {
+                log.warn("SubjectConfirmationData/@InResponseTo was missing and was required");
+                context.setValidationFailureMessage(
+                        "SubjectConfirmationData/@InResponseTo was missing and was required");
+                return ValidationResult.INVALID;
+            }
+            return ValidationResult.VALID;
+        }
+        
+        log.debug("Evaluating SubjectConfirmationData at InResponseTo of: {}", inResponseTo);
+
+        final String validInResponseTo;
+        try {
+            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(
+                    "Unable to determine valid subject confirmation InResponseTo");
+            return ValidationResult.INDETERMINATE;
+        }
+        if (validInResponseTo == null) {
+            log.warn("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;
+        }
+
+        if (Objects.equals(inResponseTo, validInResponseTo)) {
+            log.debug("Matched valid InResponseTo: {}", inResponseTo);
+            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()));
+        return ValidationResult.INVALID;
+    }
+
     /**
      * Validates the <code>NotBefore</code> condition of the
      * {@link org.opensaml.saml.saml2.core.SubjectConfirmationData}, if any is present.
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/profile/impl/DefaultAssertionValidationContextBuilder.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/profile/impl/DefaultAssertionValidationContextBuilder.java
index 0763973..7c8c5b6 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/profile/impl/DefaultAssertionValidationContextBuilder.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/profile/impl/DefaultAssertionValidationContextBuilder.java
@@ -57,6 +57,7 @@ import org.opensaml.saml.criterion.ProtocolCriterion;
 import org.opensaml.saml.criterion.RoleDescriptorCriterion;
 import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters;
 import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.RequestAbstractType;
 import org.opensaml.saml.saml2.profile.impl.ValidateAssertions.AssertionValidationInput;
 import org.opensaml.security.SecurityException;
 import org.opensaml.security.credential.UsageType;
@@ -96,6 +97,12 @@ public class DefaultAssertionValidationContextBuilder
     
     /** Function for determining additional valid audience values. */
     private Function<ProfileRequestContext, Set<String>> additionalAudiences;
+    
+    /** Predicate for determining whether an Assertion's InResponseTo is required. */
+    private Predicate<ProfileRequestContext> inResponseToRequired;
+    
+    /** Function for determining the valid InResponseTo value. */
+    private Function<ProfileRequestContext, String> inResponseTo;
 
     /** Resolver for security parameters context. */
     private Function<ProfileRequestContext, SecurityParametersContext> securityParametersLookupStrategy;
@@ -107,6 +114,8 @@ public class DefaultAssertionValidationContextBuilder
         signatureRequired = Predicates.alwaysTrue();
         includeSelfEntityIDAsRecipient = Predicates.alwaysFalse();
         checkAddress = Predicates.alwaysTrue();
+        inResponseToRequired = Predicates.alwaysFalse();
+        inResponseTo = new DefaultValidInResponseToLookupFunction();
 
         securityParametersLookupStrategy = new ChildContextLookup<>(SecurityParametersContext.class)
                 .compose(new InboundMessageContextLookup());
@@ -185,6 +194,58 @@ public class DefaultAssertionValidationContextBuilder
     }
 
     /**
+     * Get the predicate which determines whether an Assertion's InResponseTo is required.
+     * 
+     * <p>
+     * Defaults to an always false predicate;
+     * </p>
+     * 
+     * @return the predicate
+     */
+    public Predicate<ProfileRequestContext> getInResponseToRequired() {
+        return inResponseToRequired;
+    }
+
+    /**
+     * Set the predicate which determines whether an Assertion's InResponseTo is required.
+     * 
+     * <p>
+     * Defaults to an always false predicate.
+     * </p>
+     * 
+     * @param predicate the predicate, must be non-null
+     */
+    public void setInResponseToRequired(final @Nonnull Predicate<ProfileRequestContext> predicate) {
+        inResponseToRequired = Constraint.isNotNull(predicate, "InResponseTo required predicate was null");
+    }
+    
+    /**
+     * Set the function for determining the valid InResponseTo.
+     *
+     * <p>
+     * Defaults to null.
+     * </p>
+     *
+     * @param function the function, may be null
+     */
+    public void setInResponseTo(final @Nonnull Function<ProfileRequestContext,String> function) {
+        inResponseTo = function;
+    }
+    
+    /**
+     * Get the function for determining the valid InResponseTo.
+     *
+     * <p>
+     * Defaults to null.
+     * </p>
+     *
+     * @return the function
+     */
+    public Function<ProfileRequestContext,String> getInResponseTo() {
+        return inResponseTo;
+    }
+
+    /**
      * Get the predicate which determines whether an Assertion's network address(es) should be checked.
      *
      * <p>
@@ -339,6 +400,12 @@ public class DefaultAssertionValidationContextBuilder
         staticParams.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, getValidRecipients(input));
         staticParams.put(SAML2AssertionValidationParameters.SC_VALID_ADDRESSES, validAddresses);
         staticParams.put(SAML2AssertionValidationParameters.SC_CHECK_ADDRESS, checkAddressEnabled);
+        staticParams.put(SAML2AssertionValidationParameters.SC_IN_RESPONSE_TO_REQUIRED,
+                Boolean.valueOf(getInResponseToRequired().test(input.getProfileRequestContext())));
+        if (getInResponseTo() != null) {
+            staticParams.put(SAML2AssertionValidationParameters.SC_VALID_IN_RESPONSE_TO,
+                    getInResponseTo().apply(input.getProfileRequestContext()));
+        }
         
         // For Audience Condition
         staticParams.put(SAML2AssertionValidationParameters.COND_VALID_AUDIENCES, getValidAudiences(input));
@@ -642,5 +709,21 @@ public class DefaultAssertionValidationContextBuilder
         
         return null;
     }
+    
+    /** Default strategy for resolving the valid InResponseTo value. */
+    public static class DefaultValidInResponseToLookupFunction implements Function<ProfileRequestContext, String> {
+
+        /** {@inheritDoc} */
+        public String apply(@Nullable final ProfileRequestContext prc) {
+            if (prc == null 
+                    || prc.getOutboundMessageContext() == null 
+                    || prc.getOutboundMessageContext().getMessage() == null
+                    || ! RequestAbstractType.class.isInstance(prc.getOutboundMessageContext().getMessage())) {
+                return null;
+            }
+            return RequestAbstractType.class.cast(prc.getOutboundMessageContext().getMessage()).getID();
+        }
+        
+    }
 
 }
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/BaseAssertionValidationTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/BaseAssertionValidationTest.java
index bae3ed1..779f8f6 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/BaseAssertionValidationTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/BaseAssertionValidationTest.java
@@ -70,6 +70,8 @@ public class BaseAssertionValidationTest extends XMLObjectBaseTestCase {
     
     public static final String SUBJECT_CONFIRMATION_ADDRESS = "10.1.2.3";
     
+    public static final String SUBJECT_CONFIRMATION_IN_RESPONSE_TO = "id-123";
+    
     private Assertion assertion;
     
     protected Assertion getAssertion() {
@@ -112,6 +114,7 @@ public class BaseAssertionValidationTest extends XMLObjectBaseTestCase {
        else {
            scd = buildXMLObject(SubjectConfirmationData.DEFAULT_ELEMENT_NAME); 
        }
+       scd.setInResponseTo(SUBJECT_CONFIRMATION_IN_RESPONSE_TO);
        scd.setRecipient(SUBJECT_CONFIRMATION_RECIPIENT);
        scd.setAddress(SUBJECT_CONFIRMATION_ADDRESS);
        Instant now = Instant.now();
@@ -125,6 +128,8 @@ public class BaseAssertionValidationTest extends XMLObjectBaseTestCase {
         
         params.put(SAML2AssertionValidationParameters.CLOCK_SKEW, CLOCK_SKEW);
         
+        params.put(SAML2AssertionValidationParameters.SC_VALID_IN_RESPONSE_TO, SUBJECT_CONFIRMATION_IN_RESPONSE_TO);
+        
         params.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, 
                 Collections.singleton(SUBJECT_CONFIRMATION_RECIPIENT));
         try {
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/impl/AbstractSubjectConfirmationValidatorTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/impl/AbstractSubjectConfirmationValidatorTest.java
index 5fea727..3464b41 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/impl/AbstractSubjectConfirmationValidatorTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/impl/AbstractSubjectConfirmationValidatorTest.java
@@ -210,6 +210,74 @@ public class AbstractSubjectConfirmationValidatorTest extends BaseAssertionValid
                 ValidationResult.VALID);        
     }
     
+    @Test
+    public void testInvalidInResponseTo() throws AssertionValidationException {
+        subjectConfirmation.getSubjectConfirmationData().setInResponseTo("invalid");
+        
+        ValidationContext validationContext = new ValidationContext(buildBasicStaticParameters());
+        
+        Assert.assertEquals(validator.validate(subjectConfirmation, getAssertion(), validationContext), 
+                ValidationResult.INVALID);        
+    }
+    
+    @Test
+    public void testInvalidInResponseToParamType() throws AssertionValidationException {
+        Map<String,Object> staticParams = buildBasicStaticParameters();
+        // It should be a Set<String>, not a String
+        staticParams.put(SAML2AssertionValidationParameters.SC_VALID_IN_RESPONSE_TO, Integer.valueOf(42));
+        
+        ValidationContext validationContext = new ValidationContext(staticParams);
+        
+        Assert.assertEquals(validator.validate(subjectConfirmation, getAssertion(), validationContext), 
+                ValidationResult.INDETERMINATE);        
+    }
+    
+    @Test
+    public void testMissingInResponseToParam() throws AssertionValidationException {
+        Map<String,Object> staticParams = buildBasicStaticParameters();
+        staticParams.remove(SAML2AssertionValidationParameters.SC_VALID_IN_RESPONSE_TO);
+        
+        ValidationContext validationContext = new ValidationContext(staticParams);
+        
+        Assert.assertEquals(validator.validate(subjectConfirmation, getAssertion(), validationContext), 
+                ValidationResult.INDETERMINATE);        
+    }
+    
+    @Test
+    public void testNoInResponseTo() throws AssertionValidationException {
+        subjectConfirmation.getSubjectConfirmationData().setInResponseTo(null);
+        
+        ValidationContext validationContext = new ValidationContext(buildBasicStaticParameters());
+        
+        Assert.assertEquals(validator.validate(subjectConfirmation, getAssertion(), validationContext), 
+                ValidationResult.VALID);        
+    }
+    
+    @Test
+    public void testNoInResponseToAndRequired() throws AssertionValidationException {
+        subjectConfirmation.getSubjectConfirmationData().setInResponseTo(null);
+        
+        Map<String,Object> staticParams = buildBasicStaticParameters();
+        staticParams.put(SAML2AssertionValidationParameters.SC_IN_RESPONSE_TO_REQUIRED, Boolean.TRUE);
+        ValidationContext validationContext = new ValidationContext(staticParams);
+        
+        Assert.assertEquals(validator.validate(subjectConfirmation, getAssertion(), validationContext), 
+                ValidationResult.INVALID);        
+    }
+    
+    @Test
+    public void testNoConfirmationDataAndInResponseToRequired() throws AssertionValidationException {
+        subjectConfirmation.setSubjectConfirmationData(null);
+        
+        Map<String,Object> staticParams = buildBasicStaticParameters();
+        staticParams.put(SAML2AssertionValidationParameters.SC_IN_RESPONSE_TO_REQUIRED, Boolean.TRUE);
+        ValidationContext validationContext = new ValidationContext(staticParams);
+        
+        Assert.assertEquals(validator.validate(subjectConfirmation, getAssertion(), validationContext), 
+                ValidationResult.INVALID);        
+    }
+    
+    
     
     
     

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


More information about the commits mailing list