From 5b90f3d5f63dd483d978f8ccd2c1887e120b7480 Mon Sep 17 00:00:00 2001 From: Aaron Birkland Date: Tue, 13 Mar 2018 20:48:05 -0400 Subject: [PATCH] Add URI protocol filter Reacts to the X-Forwarded-Host header to set the scheme of request URLs --- .../jsonld/request/UriProtocolFilter.java | 92 ++++++++++++++ .../jsonld/request/UriProtocolFilterTest.java | 89 ++++++++++++++ .../jsonld/integration/CompactionIT.java | 29 +---- .../fcrepo/jsonld/integration/FcrepoIT.java | 49 ++++++++ .../jsonld/integration/UriProtocolIT.java | 116 ++++++++++++++++++ .../src/test/resources/web.xml | 11 ++ 6 files changed, 358 insertions(+), 28 deletions(-) create mode 100644 jsonld-addon-filters/src/main/java/org/dataconservancy/fcrepo/jsonld/request/UriProtocolFilter.java create mode 100644 jsonld-addon-filters/src/test/java/org/dataconservancy/fcrepo/jsonld/request/UriProtocolFilterTest.java create mode 100644 jsonld-addon-integration/src/test/java/org/dataconservancy/fcrepo/jsonld/integration/FcrepoIT.java create mode 100644 jsonld-addon-integration/src/test/java/org/dataconservancy/fcrepo/jsonld/integration/UriProtocolIT.java diff --git a/jsonld-addon-filters/src/main/java/org/dataconservancy/fcrepo/jsonld/request/UriProtocolFilter.java b/jsonld-addon-filters/src/main/java/org/dataconservancy/fcrepo/jsonld/request/UriProtocolFilter.java new file mode 100644 index 0000000..e2e09e1 --- /dev/null +++ b/jsonld-addon-filters/src/main/java/org/dataconservancy/fcrepo/jsonld/request/UriProtocolFilter.java @@ -0,0 +1,92 @@ +/* + * Copyright 2017 Johns Hopkins University + * + * 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 org.dataconservancy.fcrepo.jsonld.request; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +/** + * @author apb@jhu.edu + */ +public class UriProtocolFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // Nothing to do + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, + ServletException { + + final HttpServletRequest req = ((HttpServletRequest) request); + + final Protocol proto = Protocol.of(req); + + if (proto.isDefined()) { + chain.doFilter(new HttpServletRequestWrapper((HttpServletRequest) request) { + + @Override + public StringBuffer getRequestURL() { + return new StringBuffer(super.getRequestURL().toString() + .replaceFirst("^https?:", proto.getProtocol() + ":")); + } + }, response); + } else { + chain.doFilter(request, response); + } + } + + @Override + public void destroy() { + // Nothing to do + } + + private static class Protocol { + + final String proto; + + static Protocol of(HttpServletRequest request) { + if (request.getHeader("X-Forwarded-Proto") != null) { + return new Protocol(request.getHeader("X-Forwarded-Proto")); + } + + return new Protocol(null); + } + + private Protocol(String proto) { + this.proto = proto; + } + + boolean isDefined() { + return proto != null; + } + + String getProtocol() { + return proto; + } + } + +} diff --git a/jsonld-addon-filters/src/test/java/org/dataconservancy/fcrepo/jsonld/request/UriProtocolFilterTest.java b/jsonld-addon-filters/src/test/java/org/dataconservancy/fcrepo/jsonld/request/UriProtocolFilterTest.java new file mode 100644 index 0000000..6757da4 --- /dev/null +++ b/jsonld-addon-filters/src/test/java/org/dataconservancy/fcrepo/jsonld/request/UriProtocolFilterTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2017 Johns Hopkins University + * + * 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 org.dataconservancy.fcrepo.jsonld.request; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * @author apb@jhu.edu + */ +@RunWith(MockitoJUnitRunner.class) +public class UriProtocolFilterTest { + + final String ORIGINAL_URI = "http://example.org/original/uri"; + + final String HTTPS_URI = "https://example.org/original/uri"; + + @Mock + FilterChain chain; + + @Captor + ArgumentCaptor requestCaptor; + + @Mock + HttpServletRequest request; + + @Mock + HttpServletResponse response; + + @Before + public void setUp() { + when(request.getRequestURL()).thenReturn(new StringBuffer(ORIGINAL_URI)); + } + + @Test + public void noProtoTest() throws Exception { + + final UriProtocolFilter toTest = new UriProtocolFilter(); + + toTest.doFilter(request, response, chain); + + verify(chain).doFilter(requestCaptor.capture(), eq(response)); + + assertEquals(ORIGINAL_URI, requestCaptor.getValue().getRequestURL().toString()); + } + + @Test + public void httpsProtoTest() throws Exception { + + when(request.getHeader("X-Forwarded-Proto")).thenReturn("https"); + + final UriProtocolFilter toTest = new UriProtocolFilter(); + + toTest.doFilter(request, response, chain); + + verify(chain).doFilter(requestCaptor.capture(), eq(response)); + + assertEquals(HTTPS_URI, requestCaptor.getValue().getRequestURL().toString()); + } + +} diff --git a/jsonld-addon-integration/src/test/java/org/dataconservancy/fcrepo/jsonld/integration/CompactionIT.java b/jsonld-addon-integration/src/test/java/org/dataconservancy/fcrepo/jsonld/integration/CompactionIT.java index 06b9f72..48a2879 100644 --- a/jsonld-addon-integration/src/test/java/org/dataconservancy/fcrepo/jsonld/integration/CompactionIT.java +++ b/jsonld-addon-integration/src/test/java/org/dataconservancy/fcrepo/jsonld/integration/CompactionIT.java @@ -25,7 +25,6 @@ import java.net.URI; import java.util.Arrays; -import java.util.concurrent.Callable; import org.fcrepo.client.FcrepoClient; import org.fcrepo.client.FcrepoClient.FcrepoClientBuilder; @@ -37,13 +36,10 @@ /** * @author apb@jhu.edu */ -public class CompactionIT { +public class CompactionIT implements FcrepoIT { static final URI SERVER_MANAGED = URI.create("http://fedora.info/definitions/v4/repository#ServerManaged"); - String fcrepoBaseURI = String.format("http://localhost:%s/%s/rest/", System.getProperty( - "fcrepo.dynamic.test.port", "8080"), System.getProperty("fcrepo.cxtPath", "fcrepo")); - @Test public void CompactionTest() throws Exception { final FcrepoClient client = new FcrepoClientBuilder().throwExceptionOnFailure().build(); @@ -67,28 +63,5 @@ public void CompactionTest() throws Exception { assertTrue(isCompact(body)); } - } - - static T attempt(final int times, final Callable it) { - - Throwable caught = null; - - for (int tries = 0; tries < times; tries++) { - try { - return it.call(); - } catch (final Throwable e) { - caught = e; - try { - Thread.sleep(1000); - System.out.println("."); - } catch (final InterruptedException i) { - Thread.currentThread().interrupt(); - return null; - } - } - } - throw new RuntimeException("Failed executing task", caught); - } - } diff --git a/jsonld-addon-integration/src/test/java/org/dataconservancy/fcrepo/jsonld/integration/FcrepoIT.java b/jsonld-addon-integration/src/test/java/org/dataconservancy/fcrepo/jsonld/integration/FcrepoIT.java new file mode 100644 index 0000000..c76acf2 --- /dev/null +++ b/jsonld-addon-integration/src/test/java/org/dataconservancy/fcrepo/jsonld/integration/FcrepoIT.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Johns Hopkins University + * + * 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 org.dataconservancy.fcrepo.jsonld.integration; + +import java.util.concurrent.Callable; + +/** + * @author apb@jhu.edu + */ +public interface FcrepoIT { + + static final String fcrepoBaseURI = String.format("http://localhost:%s/%s/rest/", System.getProperty( + "fcrepo.dynamic.test.port", "8080"), System.getProperty("fcrepo.cxtPath", "fcrepo")); + + default T attempt(final int times, final Callable it) { + + Throwable caught = null; + + for (int tries = 0; tries < times; tries++) { + try { + return it.call(); + } catch (final Throwable e) { + caught = e; + try { + Thread.sleep(1000); + System.out.println("."); + } catch (final InterruptedException i) { + Thread.currentThread().interrupt(); + return null; + } + } + } + throw new RuntimeException("Failed executing task", caught); + } +} diff --git a/jsonld-addon-integration/src/test/java/org/dataconservancy/fcrepo/jsonld/integration/UriProtocolIT.java b/jsonld-addon-integration/src/test/java/org/dataconservancy/fcrepo/jsonld/integration/UriProtocolIT.java new file mode 100644 index 0000000..ea910cc --- /dev/null +++ b/jsonld-addon-integration/src/test/java/org/dataconservancy/fcrepo/jsonld/integration/UriProtocolIT.java @@ -0,0 +1,116 @@ +/* + * Copyright 2017 Johns Hopkins University + * + * 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 org.dataconservancy.fcrepo.jsonld.integration; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.dataconservancy.fcrepo.jsonld.compact.JsonldTestUtil.getUncompactedJsonld; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.net.URI; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.entity.EntityBuilder; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.junit.Test; + +/** + * @author apb@jhu.edu + */ +public class UriProtocolIT implements FcrepoIT { + + static final CloseableHttpClient c = HttpClients.createDefault(); + + static final String sslBaseUri = fcrepoBaseURI.replace("http:", "https:"); + + @Test + public void sslPostIT() { + + final URI resourceUri = createResource(true); + + assertTrue("Location does not start with https", resourceUri.getScheme().equals("https")); + + } + + @Test + public void noSSLPostIT() { + + final URI resourceUri = createResource(false); + + assertTrue("Location should start with http", resourceUri.getScheme().equals("http")); + + } + + @Test + public void sslGetIT() { + final URI resourceUri = createResource(false); + + final String body = getBody(resourceUri, true); + + assertTrue("Body didn't contain https baseuri", body.contains(sslBaseUri)); + assertFalse("Body contains non-ssl URIs", body.contains(fcrepoBaseURI)); + } + + @Test + public void noSSLIT() { + final URI resourceUri = createResource(false); + + final String body = getBody(resourceUri, false); + + assertFalse("Body contained an unwanted https baseuri", body.contains(sslBaseUri)); + assertTrue("Body doesn't contain the baseuri", body.contains(fcrepoBaseURI)); + } + + private String getBody(URI resource, boolean ssl) { + return attempt(1, () -> { + final HttpGet get = new HttpGet(resource); + get.setHeader("Accept", "application/n-triples"); + + if (ssl) { + get.setHeader("X-Forwarded-Proto", "https"); + } + + try (CloseableHttpResponse r = c.execute(get)) { + return IOUtils.toString(r.getEntity().getContent(), UTF_8); + } + }); + } + + private URI createResource(boolean https) { + return attempt(1, () -> { + + final HttpPost post = new HttpPost(fcrepoBaseURI); + post.setEntity(EntityBuilder.create() + .setBinary(getUncompactedJsonld().getBytes(UTF_8)) + .setContentType(ContentType.create("application/ld+json")) + .build()); + + if (https) { + post.setHeader("X-Forwarded-Proto", "https"); + } + + try (CloseableHttpResponse response = c.execute(post)) { + return URI.create(response.getFirstHeader("Location").getValue()); + } + }); + } +} diff --git a/jsonld-addon-integration/src/test/resources/web.xml b/jsonld-addon-integration/src/test/resources/web.xml index 86304da..6c09a40 100644 --- a/jsonld-addon-integration/src/test/resources/web.xml +++ b/jsonld-addon-integration/src/test/resources/web.xml @@ -4708,6 +4708,12 @@ true + + uri-protocol-filter + org.dataconservancy.fcrepo.jsonld.request.UriProtocolFilter + true + + jsonld-compaction-filter @@ -4718,5 +4724,10 @@ jsonld-deserialization-filter /* + + + uri-protocol-filter + /* +