[java-shib-shared] branch main updated: JSSH-58 - Add servlet request validation for method and content type

Scott Cantor cantor.2 at osu.edu
Fri Apr 4 19:57:51 UTC 2025


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

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

View the commit online:
http://git.shibboleth.net/view/?p=java-shib-shared.git;a=commit;h=9dcb9b5846b79023b9ab01574cdeb7d0bcb675e8

The following commit(s) were added to refs/heads/main by this push:
     new 9dcb9b58 JSSH-58 - Add servlet request validation for method and content type
9dcb9b58 is described below

commit 9dcb9b5846b79023b9ab01574cdeb7d0bcb675e8
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Fri Apr 4 15:57:48 2025 -0400

    JSSH-58 - Add servlet request validation for method and content type
    
    https://shibboleth.atlassian.net/browse/JSSH-58
---
 ...asicHttpServletRequestContentTypeValidator.java | 119 +++++++++++++++++++++
 .../BasicHttpServletRequestMethodValidator.java    |  79 ++++++++++++++
 ...BasicHttpServletRequestParametersValidator.java |   4 -
 ...HttpServletRequestContentTypeValidatorTest.java | 118 ++++++++++++++++++++
 ...BasicHttpServletRequestMethodValidatorTest.java |  96 +++++++++++++++++
 ...cHttpServletRequestParametersValidatorTest.java |   3 +-
 .../ChainingHttpServletRequestValidatorTest.java   |   1 +
 .../HttpServletRequestResponseContextTest.java     |   1 +
 8 files changed, 416 insertions(+), 5 deletions(-)

diff --git a/shib-networking/src/main/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestContentTypeValidator.java b/shib-networking/src/main/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestContentTypeValidator.java
new file mode 100644
index 00000000..8246c13c
--- /dev/null
+++ b/shib-networking/src/main/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestContentTypeValidator.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.shared.servlet.impl;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.google.common.base.Predicates;
+import com.google.common.net.MediaType;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.servlet.HttpServletRequestValidator;
+import net.shibboleth.shared.servlet.HttpServletSupport;
+
+/**
+ * Component that validates the HTTP content type as one of an allowed set.
+ * 
+ * <p>Optionally allows for no type to be specified.</p>
+ * 
+ * @since 9.2.0
+ */
+public class BasicHttpServletRequestContentTypeValidator extends AbstractInitializableComponent
+        implements HttpServletRequestValidator {
+    
+    /** Allowed parameters. */
+    @Nonnull private Set<MediaType> allowedContentTypes;
+    
+    /** Whether an absent content type is allowed. */
+    private boolean allowNullContentType;
+    
+    /**
+     * Constructor.
+     */
+    public BasicHttpServletRequestContentTypeValidator() {
+        allowedContentTypes = CollectionSupport.emptySet();
+    }
+    
+    /**
+     * Get the allowed types. 
+     * 
+     * @return the allowed types
+     */
+    @Nonnull @Unmodifiable @NotLive public Set<String> getAllowedContentTypes() {
+        return allowedContentTypes.stream()
+                .map(MediaType::toString)
+                .collect(CollectionSupport.nonnullCollector(Collectors.toUnmodifiableSet())).get();
+    }
+
+    /**
+     * Set the allowed types.
+     * 
+     * @param types the allowed types
+     */
+    public void setAllowedContentTypes(@Nullable final Collection<String> types) {
+        checkSetterPreconditions();
+        if (types != null) {
+            allowedContentTypes = types.stream()
+                    .map(StringSupport::trimOrNull)
+                    .filter(Predicates.notNull())
+                    .map(MediaType::parse)
+                    .collect(CollectionSupport.nonnullCollector(Collectors.toUnmodifiableSet())).get();
+        } else {
+            allowedContentTypes = CollectionSupport.emptySet();
+        }
+    }
+
+    /**
+     * Gets whether to allow an absent content type.
+     * 
+     * @return whether to allow an absent content type
+     */
+    public boolean getAllowNullContentType() {
+        return allowNullContentType;
+    }
+    
+    /**
+     * Sets whether to allow an absent content type.
+     * 
+     * <p>Defaults to false.</p>
+     * 
+     * @param flag flag to set
+     */
+    public void setAllowNullContentType(final boolean flag) {
+        allowNullContentType = flag;
+    }
+    
+    /** {@inheritDoc} */
+    public void validate(@Nonnull final HttpServletRequest request) throws ServletException {
+        Constraint.isNotNull(request, "HttpServletRequest was null");
+
+        if (!HttpServletSupport.validateContentType(request, allowedContentTypes, allowNullContentType, false)) {
+            throw new ServletException("Content type not in allowed set.");
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestMethodValidator.java b/shib-networking/src/main/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestMethodValidator.java
new file mode 100644
index 00000000..59d8acf5
--- /dev/null
+++ b/shib-networking/src/main/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestMethodValidator.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.shared.servlet.impl;
+
+import java.util.Collection;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.servlet.HttpServletRequestValidator;
+
+/**
+ * Component that validates the HTTP request method as one of an allowed set.
+ * 
+ * @since 9.2.0
+ */
+public class BasicHttpServletRequestMethodValidator extends AbstractInitializableComponent
+        implements HttpServletRequestValidator {
+    
+    /** Allowed parameters. */
+    @Nonnull private Set<String> allowedMethods;
+    
+    /**
+     * Constructor.
+     */
+    public BasicHttpServletRequestMethodValidator() {
+        allowedMethods = CollectionSupport.emptySet();
+    }
+    
+    /**
+     * Get the allowed methods. 
+     * 
+     * @return the allowed methods
+     */
+    @Nonnull @Unmodifiable @NotLive public Set<String> getAllowedMethods() {
+        return allowedMethods;
+    }
+
+    /**
+     * Set the allowed methods.
+     * 
+     * @param methods the allowed methods
+     */
+    public void setAllowedMethods(@Nullable final Collection<String> methods) {
+        checkSetterPreconditions();
+        allowedMethods = CollectionSupport.copyToSet(StringSupport.normalizeStringCollection(methods));
+    }
+
+    /** {@inheritDoc} */
+    public void validate(@Nonnull final HttpServletRequest request) throws ServletException {
+        Constraint.isNotNull(request, "HttpServletRequest was null");
+        
+        if (!allowedMethods.contains(request.getMethod())) {
+            throw new ServletException("Request method '" + request.getMethod() + "' not in allowed set.");
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestParametersValidator.java b/shib-networking/src/main/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestParametersValidator.java
index 6704380a..5b175754 100644
--- a/shib-networking/src/main/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestParametersValidator.java
+++ b/shib-networking/src/main/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestParametersValidator.java
@@ -194,7 +194,6 @@ public class BasicHttpServletRequestParametersValidator extends AbstractInitiali
             log.debug("Evaluating request for allowed parameters: {}", getAllowedParameters());
             for (final String requestParam : requestParams) {
                 if (!getAllowedParameters().contains(requestParam)) {
-                    log.warn("HTTP request contained a disallowed parameter: {}", requestParam);
                     throw new ServletException("HTTP request contained a disallowed parameter: " + requestParam);
                 }
             }
@@ -205,7 +204,6 @@ public class BasicHttpServletRequestParametersValidator extends AbstractInitiali
         log.debug("Evaluating request for required parameters: {}", getRequiredParameters());
         for (final String param : getRequiredParameters()) {
             if (!requestParams.contains(param)) {
-                log.warn("HTTP request did not contain required parameter: {}", param);
                 throw new ServletException("HTTP request did not contain required parameter: " + param);
             }
         }
@@ -214,7 +212,6 @@ public class BasicHttpServletRequestParametersValidator extends AbstractInitiali
         for (final String param : getUniqueParameters()) {
             final String[] values = request.getParameterValues(param);
             if (values != null && values.length > 1) {
-                log.warn("HTTP request contained {} values for parameter: {}", values.length, param);
                 throw new ServletException("HTTP request contained multiple values for parameter: " + param);
             }
         }
@@ -231,7 +228,6 @@ public class BasicHttpServletRequestParametersValidator extends AbstractInitiali
                     .collect(Collectors.toSet());
 
            if (groupIntersection.size() > 1) {
-               log.warn("HTTP request contained mutuallly exclusive parameters: {}", groupIntersection);
                throw new ServletException("HTTP request contained mutually exclusive parameters: "
                        + groupIntersection);
            }
diff --git a/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestContentTypeValidatorTest.java b/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestContentTypeValidatorTest.java
new file mode 100644
index 00000000..bd07c754
--- /dev/null
+++ b/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestContentTypeValidatorTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.shared.servlet.impl;
+
+import java.util.Set;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import jakarta.servlet.ServletException;
+
+/**
+ * Unit test for {@link BasicHttpServletRequestContentTypeValidator}.
+ */
+ at SuppressWarnings("javadoc")
+public class BasicHttpServletRequestContentTypeValidatorTest {
+    
+    @DataProvider
+    Object[][] allowedMethodsSetterData() {
+        return new Object[][] {
+            new Object[] { Set.of(),
+                    Set.of(),
+                    true},
+            new Object[] { Set.of("  application/foo  ", "  text/bar  ", "   "),
+                    Set.of("application/foo", "text/bar"),
+                    false},
+            new Object[] { Set.of("  application/foo; charset=utf-8  ", "  text/bar  ", "   "),
+                    Set.of("application/foo; charset=utf-8", "text/bar"),
+                    false},
+        };
+    }
+    
+    @Test(dataProvider="allowedMethodsSetterData")
+    public void allowedSetter(final Set<String> methods, final Set<String> expected, final boolean allowNull)
+            throws Exception {
+        final BasicHttpServletRequestContentTypeValidator validator = new BasicHttpServletRequestContentTypeValidator();
+        validator.setAllowedContentTypes(methods);
+        validator.setAllowNullContentType(allowNull);
+        validator.initialize();
+        
+        Assert.assertEquals(validator.getAllowedContentTypes(), expected);
+        Assert.assertEquals(validator.getAllowNullContentType(), allowNull);
+    }
+        
+    @DataProvider
+    Object[][] allowedContentTypesEvalData() {
+        return new Object[][] {
+            new Object[] { "text/xml",
+                    Set.of(),
+                    false,
+                    false},
+            new Object[] { "text/xml",
+                    Set.of("text/xml"),
+                    false,
+                    true},
+            new Object[] { "text/xml; charset=utf-8",
+                    Set.of("text/xml"),
+                    false,
+                    true},
+            new Object[] { "text/xml; charset=utf-8",
+                    Set.of("text/xml; charset=iso8859-1"),
+                    false,
+                    true},
+            new Object[] { null,
+                    Set.of("text/xml"),
+                    false,
+                    false},
+            new Object[] { null,
+                    Set.of("text/xml"),
+                    true,
+                    true},
+        };
+    }
+    
+    @Test(dataProvider="allowedContentTypesEvalData")
+    public void requiredEval(final String requestedType, final Set<String> allowedTypes, final boolean allowNull,
+            final boolean valid) throws Exception {
+        BasicHttpServletRequestContentTypeValidator validator = new BasicHttpServletRequestContentTypeValidator();
+        validator.setAllowedContentTypes(allowedTypes);
+        validator.setAllowNullContentType(allowNull);
+        evaluateRequest(validator, requestedType, valid);
+    }
+    
+    private void evaluateRequest(final BasicHttpServletRequestContentTypeValidator validator, final String type,
+            final boolean valid) throws Exception{
+
+        final MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setContentType(type);
+
+        validator.initialize();
+
+        try {
+            validator.validate(request);
+            if (!valid) {
+                Assert.fail("Request evaled to valid on invalid type");
+            }
+        } catch (ServletException e) {
+            if (valid) {
+                Assert.fail("Request evaled to invalid on valid type");
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestMethodValidatorTest.java b/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestMethodValidatorTest.java
new file mode 100644
index 00000000..32a0fae2
--- /dev/null
+++ b/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestMethodValidatorTest.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.shared.servlet.impl;
+
+import java.util.Set;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import jakarta.servlet.ServletException;
+
+/**
+ * Unit test for {@link BasicHttpServletRequestMethodValidator}.
+ */
+ at SuppressWarnings("javadoc")
+public class BasicHttpServletRequestMethodValidatorTest {
+    
+    @DataProvider
+    Object[][] allowedMethodsSetterData() {
+        return new Object[][] {
+            new Object[] { Set.of(),
+                    Set.of()},
+            new Object[] { Set.of("  Foo  ", "  Bar  ", "   "),
+                    Set.of("Foo", "Bar")},
+        };
+    }
+    
+    @Test(dataProvider="allowedMethodsSetterData")
+    public void allowedSetter(final Set<String> methods, final Set<String> expected) throws Exception {
+        final BasicHttpServletRequestMethodValidator validator = new BasicHttpServletRequestMethodValidator();
+        validator.setAllowedMethods(methods);
+        validator.initialize();
+        
+        Assert.assertEquals(validator.getAllowedMethods(), expected);
+    }
+        
+    @DataProvider
+    Object[][] allowedMethodsEvalData() {
+        return new Object[][] {
+            new Object[] { "GET",
+                    Set.of(),
+                    false},
+            new Object[] { "POST",
+                    Set.of("POST"),
+                    true},
+            new Object[] { "POST",
+                    Set.of("POST", "GET"),
+                    true},
+            new Object[] { "HEAD",
+                    Set.of("GET", "POST"),
+                    false},
+        };
+    }
+    
+    @Test(dataProvider="allowedMethodsEvalData")
+    public void requiredEval(final String requestedMethod, final Set<String> allowedMethods, final boolean valid) throws Exception {
+        BasicHttpServletRequestMethodValidator validator = new BasicHttpServletRequestMethodValidator();
+        validator.setAllowedMethods(allowedMethods);
+        evaluateRequest(validator, requestedMethod, valid);
+    }
+    
+    private void evaluateRequest(final BasicHttpServletRequestMethodValidator validator, final String method, final boolean valid)
+            throws Exception{
+
+        final MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setMethod(method);
+
+        validator.initialize();
+
+        try {
+            validator.validate(request);
+            if (!valid) {
+                Assert.fail("Request evaled to valid on invalid method");
+            }
+        } catch (ServletException e) {
+            if (valid) {
+                Assert.fail("Request evaled to invalid on valid method");
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestParametersValidatorTest.java b/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestParametersValidatorTest.java
index 9f1613ea..f321261b 100644
--- a/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestParametersValidatorTest.java
+++ b/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/BasicHttpServletRequestParametersValidatorTest.java
@@ -28,6 +28,7 @@ import net.shibboleth.shared.collection.Pair;
 /**
  * Unit test for {@link BasicHttpServletRequestParametersValidator}
  */
+ at SuppressWarnings("javadoc")
 public class BasicHttpServletRequestParametersValidatorTest {
     
     @DataProvider
@@ -264,7 +265,7 @@ public class BasicHttpServletRequestParametersValidatorTest {
             }
         } catch (ServletException e) {
             if (valid) {
-                Assert.fail(String.format("Request evaled to invaid on valid %s params", desc));
+                Assert.fail(String.format("Request evaled to invalid on valid %s params", desc));
             }
         }
     }
diff --git a/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/ChainingHttpServletRequestValidatorTest.java b/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/ChainingHttpServletRequestValidatorTest.java
index c4ea9f95..7efd7552 100644
--- a/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/ChainingHttpServletRequestValidatorTest.java
+++ b/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/ChainingHttpServletRequestValidatorTest.java
@@ -30,6 +30,7 @@ import net.shibboleth.shared.servlet.HttpServletRequestValidator;
 /**
  * Unit test for {@link ChainingHttpServletRequestValidator}
  */
+ at SuppressWarnings("javadoc")
 public class ChainingHttpServletRequestValidatorTest {
     
     @DataProvider
diff --git a/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/HttpServletRequestResponseContextTest.java b/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/HttpServletRequestResponseContextTest.java
index 0035ddcf..3a4aacb8 100644
--- a/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/HttpServletRequestResponseContextTest.java
+++ b/shib-networking/src/test/java/net/shibboleth/shared/servlet/impl/HttpServletRequestResponseContextTest.java
@@ -28,6 +28,7 @@ import net.shibboleth.shared.logic.ConstraintViolationException;
 /**
  * Tests for {@link HttpServletRequestResponseContext}.
  */
+ at SuppressWarnings("javadoc")
 public class HttpServletRequestResponseContextTest {
     private HttpServletRequest request;
     private HttpServletResponse response;

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


More information about the commits mailing list