From 603f4fe1af884731a19e53e13696ef879bb69f88 Mon Sep 17 00:00:00 2001 From: kr043029 Date: Thu, 25 Feb 2021 09:43:15 -0600 Subject: [PATCH 1/2] malformed uri --- jaxrs/pom.xml | 4 ++ .../cerner/beadledom/jaxrs/JaxRsModule.java | 7 +++ .../FilteringJacksonJsonProvider.java | 10 ++++ .../provider/MalformedRequestFilter.java | 43 +++++++++++++++ .../FilteringJacksonJsonProviderSpec.scala | 28 ++++++++++ .../provider/MalformedRequestFilterSpec.scala | 54 +++++++++++++++++++ 6 files changed, 146 insertions(+) create mode 100644 jaxrs/src/main/java/com/cerner/beadledom/jaxrs/provider/MalformedRequestFilter.java create mode 100644 jaxrs/src/test/scala/com/cerner/beadledom/jaxrs/provider/MalformedRequestFilterSpec.scala diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml index a268264e..c2c0a1ef 100644 --- a/jaxrs/pom.xml +++ b/jaxrs/pom.xml @@ -65,6 +65,10 @@ javax.inject javax.inject + + javax.servlet + javax.servlet-api + javax.ws.rs javax.ws.rs-api diff --git a/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/JaxRsModule.java b/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/JaxRsModule.java index a5c19930..5dcb5b4d 100644 --- a/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/JaxRsModule.java +++ b/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/JaxRsModule.java @@ -1,7 +1,9 @@ package com.cerner.beadledom.jaxrs; import com.cerner.beadledom.jaxrs.provider.CorrelationIdFilter; +import com.cerner.beadledom.jaxrs.provider.MalformedRequestFilter; import com.google.inject.AbstractModule; +import com.google.inject.Provides; import javax.inject.Singleton; /** @@ -20,4 +22,9 @@ protected void configure() { bind(CorrelationIdFilter.class).toProvider(CorrelationIdFilterProvider.class) .in(Singleton.class); } + + @Provides + MalformedRequestFilter provideMalformedRequestFilter() { + return new MalformedRequestFilter(); + } } diff --git a/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/provider/FilteringJacksonJsonProvider.java b/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/provider/FilteringJacksonJsonProvider.java index 95279e77..f8274a2f 100644 --- a/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/provider/FilteringJacksonJsonProvider.java +++ b/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/provider/FilteringJacksonJsonProvider.java @@ -11,6 +11,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; import javax.inject.Inject; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @@ -32,6 +33,9 @@ public class FilteringJacksonJsonProvider extends JacksonJsonProvider { @Context UriInfo uriInfo; + @Context + HttpServletResponse httpServletResponse; + /** * Creates a new instance of {@link FilteringJacksonJsonProvider}. */ @@ -52,6 +56,12 @@ public long getSize( public void writeTo( Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream os) throws IOException { + + if (httpServletResponse.getStatus() >= 400 ) { + super.writeTo(o, type, genericType, annotations, mediaType, httpHeaders, os); + return; + } + String fields = uriInfo.getQueryParameters() == null ? null : uriInfo.getQueryParameters().getFirst("fields"); diff --git a/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/provider/MalformedRequestFilter.java b/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/provider/MalformedRequestFilter.java new file mode 100644 index 00000000..ced05ce9 --- /dev/null +++ b/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/provider/MalformedRequestFilter.java @@ -0,0 +1,43 @@ +package com.cerner.beadledom.jaxrs.provider; + +import com.cerner.beadledom.json.common.model.JsonError; +import java.net.URI; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.PreMatching; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; + +/** + * The ForwardedHeaderFilter reads the request context and determines if the client request was made + * from a secure protocol (HTTPS). If the request originated from secure context, it updates the + * request to reflect the original protocol. + * + *

To determine if the client request made in a secure context was forwarded by a load balancer + * or proxy, the "Forwarded" and "X-Forwarded-Proto" header values are extracted. If either of them + * show the request was made from a secure context, the internal request object is updated to + * reflect the original secure protocol. + * + * @author Nick Behrens + */ +@Provider +@Priority(Priorities.AUTHENTICATION) +@PreMatching +public class MalformedRequestFilter implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext requestContext) { + try { + URI.create(requestContext.getUriInfo().getAbsolutePath().toString()); + } catch (RuntimeException e) { + requestContext.abortWith( + Response.status(400) + .type(MediaType.APPLICATION_JSON) + .entity(JsonError.builder().code(400).message("Malformed URI").build()) + .build()); + } + } +} diff --git a/jaxrs/src/test/scala/com/cerner/beadledom/jaxrs/provider/FilteringJacksonJsonProviderSpec.scala b/jaxrs/src/test/scala/com/cerner/beadledom/jaxrs/provider/FilteringJacksonJsonProviderSpec.scala index 78738025..98665b62 100644 --- a/jaxrs/src/test/scala/com/cerner/beadledom/jaxrs/provider/FilteringJacksonJsonProviderSpec.scala +++ b/jaxrs/src/test/scala/com/cerner/beadledom/jaxrs/provider/FilteringJacksonJsonProviderSpec.scala @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.google.common.collect.Lists import java.io.{ByteArrayOutputStream, OutputStream} import java.nio.charset.Charset +import javax.servlet.http.HttpServletResponse import javax.ws.rs.core._ import org.jboss.resteasy.specimpl.MultivaluedMapImpl import org.mockito @@ -32,11 +33,14 @@ class FilteringJacksonJsonProviderSpec it("serializes a FakeModel") { val uriInfo = Mockito.mock(classOf[UriInfo]) + val httpServletResponse = Mockito.mock(classOf[HttpServletResponse]) Mockito.when(uriInfo.getQueryParameters).thenReturn(new MultivaluedMapImpl[String, String]) + Mockito.when(httpServletResponse.getStatus).thenReturn(200) val output = Mockito.mock(classOf[OutputStream]) val filter = new FilteringJacksonJsonProvider(objectMapper) filter.uriInfo = uriInfo + filter.httpServletResponse = httpServletResponse filter.writeTo(fakeModel, fakeModel.getClass, @@ -56,6 +60,9 @@ class FilteringJacksonJsonProviderSpec it("filters simple bean properties") { val uriInfo = Mockito.mock(classOf[UriInfo]) + val httpServletResponse = Mockito.mock(classOf[HttpServletResponse]) + Mockito.when(httpServletResponse.getStatus).thenReturn(200) + val queryParams = new MultivaluedMapImpl[String, String] queryParams.add("fields", "id,name,times") Mockito.when(uriInfo.getQueryParameters).thenReturn(queryParams) @@ -63,6 +70,7 @@ class FilteringJacksonJsonProviderSpec val filter = new FilteringJacksonJsonProvider(objectMapper) filter.uriInfo = uriInfo + filter.httpServletResponse = httpServletResponse filter.writeTo(fakeModel, fakeModel.getClass, @@ -82,6 +90,9 @@ class FilteringJacksonJsonProviderSpec it("filters nested bean properties") { val uriInfo = Mockito.mock(classOf[UriInfo]) + val httpServletResponse = Mockito.mock(classOf[HttpServletResponse]) + Mockito.when(httpServletResponse.getStatus).thenReturn(200) + val queryParams = new MultivaluedMapImpl[String, String] queryParams.add("fields", "id,name,inner_models/id") Mockito.when(uriInfo.getQueryParameters).thenReturn(queryParams) @@ -89,6 +100,7 @@ class FilteringJacksonJsonProviderSpec val filter = new FilteringJacksonJsonProvider(objectMapper) filter.uriInfo = uriInfo + filter.httpServletResponse = httpServletResponse filter.writeTo(fakeModel, fakeModel.getClass, @@ -123,6 +135,9 @@ class FilteringJacksonJsonProviderSpec Venue("venue_id1", "THE venue", "THE place", tenors, vocalists, guitarists, failures) val uriInfo = Mockito.mock(classOf[UriInfo]) + val httpServletResponse = Mockito.mock(classOf[HttpServletResponse]) + Mockito.when(httpServletResponse.getStatus).thenReturn(200) + val queryParams = new MultivaluedMapImpl[String, String] queryParams.add("fields", "id,name,tenors/pitch_pipe,tenors/name,tenors/albums/name,vocalists/albums/id,vocalists/albums/name," + @@ -132,6 +147,7 @@ class FilteringJacksonJsonProviderSpec val filter = new FilteringJacksonJsonProvider(objectMapper) filter.uriInfo = uriInfo + filter.httpServletResponse = httpServletResponse filter.writeTo(venue, venue.getClass, @@ -154,11 +170,15 @@ class FilteringJacksonJsonProviderSpec it("serializes a small FakeModel") { val uriInfo = Mockito.mock(classOf[UriInfo]) + val httpServletResponse = Mockito.mock(classOf[HttpServletResponse]) + Mockito.when(httpServletResponse.getStatus).thenReturn(200) + Mockito.when(uriInfo.getQueryParameters).thenReturn(new MultivaluedMapImpl[String, String]) val output = Mockito.mock(classOf[OutputStream]) val filter = new FilteringJacksonJsonProvider(objectMapper) filter.uriInfo = uriInfo + filter.httpServletResponse = httpServletResponse val startTime = System.currentTimeMillis() filter.writeTo(fakeModel, @@ -173,10 +193,14 @@ class FilteringJacksonJsonProviderSpec it("serializes a large FakeModel - about 6MB") { (0 until 100000).foreach({ value => fakeModel.innerModels.add(fakeInnerModel1) }) val uriInfo = Mockito.mock(classOf[UriInfo]) + val httpServletResponse = Mockito.mock(classOf[HttpServletResponse]) + Mockito.when(httpServletResponse.getStatus).thenReturn(200) + Mockito.when(uriInfo.getQueryParameters).thenReturn(new MultivaluedMapImpl[String, String]) val filter = new FilteringJacksonJsonProvider(objectMapper) filter.uriInfo = uriInfo + filter.httpServletResponse = httpServletResponse (0 until 10).foreach { value => System.gc() @@ -200,12 +224,16 @@ class FilteringJacksonJsonProviderSpec it("serializes a large FakeModel with filtering - about 6MB") { (0 until 100000).foreach({ value => fakeModel.innerModels.add(fakeInnerModel1) }) val uriInfo = Mockito.mock(classOf[UriInfo]) + val httpServletResponse = Mockito.mock(classOf[HttpServletResponse]) + Mockito.when(httpServletResponse.getStatus).thenReturn(200) + val queryParams = new MultivaluedMapImpl[String, String] queryParams.add("fields", "id,name,inner_models/id") Mockito.when(uriInfo.getQueryParameters).thenReturn(queryParams) val filter = new FilteringJacksonJsonProvider(objectMapper) filter.uriInfo = uriInfo + filter.httpServletResponse = httpServletResponse (0 until 10).foreach { value => System.gc() diff --git a/jaxrs/src/test/scala/com/cerner/beadledom/jaxrs/provider/MalformedRequestFilterSpec.scala b/jaxrs/src/test/scala/com/cerner/beadledom/jaxrs/provider/MalformedRequestFilterSpec.scala new file mode 100644 index 00000000..7e6a9c4c --- /dev/null +++ b/jaxrs/src/test/scala/com/cerner/beadledom/jaxrs/provider/MalformedRequestFilterSpec.scala @@ -0,0 +1,54 @@ +package com.cerner.beadledom.jaxrs.provider + +import java.net.URI + +import com.cerner.beadledom.json.common.model.JsonError +import javax.ws.rs.container.ContainerRequestContext +import javax.ws.rs.core.{MediaType, Response, UriInfo} +import org.jboss.resteasy.specimpl.ResteasyUriInfo +import org.mockito +import org.mockito.{ArgumentCaptor, Mockito} +import org.mockito.ArgumentMatchers.any +import org.scalatest._ + +import scala.collection.JavaConverters._ +import org.scalatestplus.mockito.MockitoSugar +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers + +class MalformedRequestFilterSpec + extends AnyFunSpec with BeforeAndAfterAll with Matchers with MockitoSugar { + + describe("MalformedRequestFilter with a valid URI") { + + val containerRequestContext = Mockito.mock(classOf[ContainerRequestContext]) + val uriInfo = new ResteasyUriInfo("http://localhost:8080", "") + Mockito.when(containerRequestContext.getUriInfo).thenReturn(uriInfo) + + val malformedRequestFilter = new MalformedRequestFilter() + + it("does not call abortWith") { + malformedRequestFilter.filter(containerRequestContext) + + Mockito.verify(containerRequestContext).getUriInfo(); + Mockito.verifyNoMoreInteractions(containerRequestContext); + } + + } + describe("MalformedRequestFilter with an invalid URI") { + + val containerRequestContext = Mockito.mock(classOf[ContainerRequestContext]) + val uriInfo = new ResteasyUriInfo("http://localhost:8080", "?test=%9") + Mockito.when(containerRequestContext.getUriInfo).thenReturn(uriInfo) + + val malformedRequestFilter = new MalformedRequestFilter() + it("calls abortWith") { + malformedRequestFilter.filter(containerRequestContext) + + Mockito.verify(containerRequestContext) + .abortWith(any(classOf[Response])) + + } + + } +} From 7ec6924bfdf219fa58de8dcd77da1d12cf520f72 Mon Sep 17 00:00:00 2001 From: kr043029 Date: Thu, 25 Feb 2021 10:29:46 -0600 Subject: [PATCH 2/2] documtation --- .../jaxrs/provider/MalformedRequestFilter.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/provider/MalformedRequestFilter.java b/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/provider/MalformedRequestFilter.java index ced05ce9..cc05625c 100644 --- a/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/provider/MalformedRequestFilter.java +++ b/jaxrs/src/main/java/com/cerner/beadledom/jaxrs/provider/MalformedRequestFilter.java @@ -12,16 +12,10 @@ import javax.ws.rs.ext.Provider; /** - * The ForwardedHeaderFilter reads the request context and determines if the client request was made - * from a secure protocol (HTTPS). If the request originated from secure context, it updates the - * request to reflect the original protocol. + * The MalformedRequestFilter reads the request context and determines if the request has a valid uri structure if it + * does not then it aborts the request and responds with a 400 * - *

To determine if the client request made in a secure context was forwarded by a load balancer - * or proxy, the "Forwarded" and "X-Forwarded-Proto" header values are extracted. If either of them - * show the request was made from a secure context, the internal request object is updated to - * reflect the original secure protocol. - * - * @author Nick Behrens + * @author Kyle Roush */ @Provider @Priority(Priorities.AUTHENTICATION) @@ -32,7 +26,7 @@ public class MalformedRequestFilter implements ContainerRequestFilter { public void filter(ContainerRequestContext requestContext) { try { URI.create(requestContext.getUriInfo().getAbsolutePath().toString()); - } catch (RuntimeException e) { + } catch (IllegalArgumentException e) { requestContext.abortWith( Response.status(400) .type(MediaType.APPLICATION_JSON)