[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