From 3dde92cd4fa36c9531fbeb5e9b6267c90f824e1d Mon Sep 17 00:00:00 2001 From: ajs6f Date: Wed, 3 May 2017 11:29:38 -0400 Subject: [PATCH 1/5] First draft of a simple auth-aware HttpClient service --- .gitignore | 2 + islandora-http-client/build.gradle | 29 +++++++++ .../cfg/ca.islandora.alpaca.http.client.cfg | 3 + .../client/StaticTokenRequestInterceptor.java | 61 +++++++++++++++++++ .../OSGI-INF/blueprint/blueprint.xml | 26 ++++++++ .../StaticTokenRequestInterceptorTest.java | 51 ++++++++++++++++ settings.gradle | 2 + 7 files changed, 174 insertions(+) create mode 100644 islandora-http-client/build.gradle create mode 100644 islandora-http-client/src/main/cfg/ca.islandora.alpaca.http.client.cfg create mode 100644 islandora-http-client/src/main/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptor.java create mode 100644 islandora-http-client/src/main/resources/OSGI-INF/blueprint/blueprint.xml create mode 100644 islandora-http-client/src/test/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptorTest.java diff --git a/.gitignore b/.gitignore index 7cc9f3f2..c4f45f5f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ workbench.xmi target build +.DS_Store +bin/ diff --git a/islandora-http-client/build.gradle b/islandora-http-client/build.gradle new file mode 100644 index 00000000..35ec2d5a --- /dev/null +++ b/islandora-http-client/build.gradle @@ -0,0 +1,29 @@ +apply plugin: 'osgi' + +description = 'Islandora CLAW HTTP Client' + +dependencies { + compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.3' + + testCompile group: 'junit', name: 'junit', version:'4.12' +} + +jar { + manifest { + description project.description + docURL project.docURL + vendor project.vendor + license project.license + + instruction 'Import-Package', 'org.apache.http.client,' + + defaultOsgiImports + instruction 'Export-Package', 'ca.islandora.indexing.http.client' + } +} + +artifacts { + archives (file('build/cfg/main/ca.islandora.alpaca.http.client.cfg')) { + classifier 'configuration' + type 'cfg' + } +} diff --git a/islandora-http-client/src/main/cfg/ca.islandora.alpaca.http.client.cfg b/islandora-http-client/src/main/cfg/ca.islandora.alpaca.http.client.cfg new file mode 100644 index 00000000..06cf1bb0 --- /dev/null +++ b/islandora-http-client/src/main/cfg/ca.islandora.alpaca.http.client.cfg @@ -0,0 +1,3 @@ +# The static token value to be used for authentication by the HttpClient available as an OSGi service for +# other services to use against the Fedora repository +token.value=islandora diff --git a/islandora-http-client/src/main/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptor.java b/islandora-http-client/src/main/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptor.java new file mode 100644 index 00000000..4cf3f6d8 --- /dev/null +++ b/islandora-http-client/src/main/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptor.java @@ -0,0 +1,61 @@ +/* + * Licensed to Islandora Foundation under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * The Islandora Foundation licenses this file to you under the MIT License. + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/MIT + * + * 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 ca.islandora.alpaca.http.client; + +import static java.util.Objects.requireNonNull; + +import org.apache.http.Header; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicHeader; +import org.apache.http.protocol.HttpContext; + +public class StaticTokenRequestInterceptor implements HttpRequestInterceptor { + + public static final String AUTH_HEADER = "Authorization"; + + private Header header; + + public StaticTokenRequestInterceptor() { + } + + public StaticTokenRequestInterceptor(String token) { + this.header = makeHeader(token); + } + + public void setToken(String token) { + this.header = makeHeader(token); + } + + private Header makeHeader(String token) { + return new BasicHeader(AUTH_HEADER, "Bearer " + requireNonNull(token, "Token must not be null!")); + } + + @Override + public void process(HttpRequest request, HttpContext context) { + // we do not inject if auth headers present + if (request.getFirstHeader(AUTH_HEADER)== null) request.addHeader(header); + } + + public static HttpClient defaultClient(StaticTokenRequestInterceptor interceptor) { + return HttpClientBuilder.create().addInterceptorFirst(interceptor).build(); + } +} diff --git a/islandora-http-client/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/islandora-http-client/src/main/resources/OSGI-INF/blueprint/blueprint.xml new file mode 100644 index 00000000..20946b78 --- /dev/null +++ b/islandora-http-client/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/islandora-http-client/src/test/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptorTest.java b/islandora-http-client/src/test/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptorTest.java new file mode 100644 index 00000000..c439b1ee --- /dev/null +++ b/islandora-http-client/src/test/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptorTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to Islandora Foundation under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * The Islandora Foundation licenses this file to you under the MIT License. + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/MIT + * + * 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 ca.islandora.alpaca.http.client; + +import static ca.islandora.alpaca.http.client.StaticTokenRequestInterceptor.AUTH_HEADER; + +import org.apache.http.Header; +import org.apache.http.HttpRequest; +import org.apache.http.client.methods.HttpGet; +import org.junit.Assert; +import org.junit.Test; + +public class StaticTokenRequestInterceptorTest extends Assert { + + @Test + public void shouldInjectHeaderWhenNoAuthHeadersPresent() { + StaticTokenRequestInterceptor testInterceptor = new StaticTokenRequestInterceptor("testToken"); + HttpRequest request = new HttpGet(); + testInterceptor.process(request, null); + Header[] authHeaders = request.getHeaders(AUTH_HEADER); + assertEquals("Should only be one auth header!", 1, authHeaders.length); + assertEquals("Wrong value for header!", "Bearer testToken", authHeaders[0].getValue()); + } + + @Test + public void shouldNotInjectHeaderWhenAuthHeadersPresent() { + StaticTokenRequestInterceptor testInterceptor = new StaticTokenRequestInterceptor("testToken"); + HttpRequest request = new HttpGet(); + request.addHeader(AUTH_HEADER, "fake header"); + testInterceptor.process(request, null); + Header[] authHeaders = request.getHeaders(AUTH_HEADER); + assertEquals("Should only be one auth header!", 1, authHeaders.length); + assertEquals("Wrong value for header!", "fake header", authHeaders[0].getValue()); + } +} diff --git a/settings.gradle b/settings.gradle index e05f19d0..abfbcf98 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,9 @@ include ':islandora-karaf' include ':islandora-indexing-triplestore' include ':islandora-connector-broadcast' +include ':islandora-http-client' project(':islandora-karaf').projectDir = "$rootDir/karaf" as File project(':islandora-indexing-triplestore').projectDir = "$rootDir/islandora-indexing-triplestore" as File project(':islandora-connector-broadcast').projectDir = "$rootDir/islandora-connector-broadcast" as File +project(':islandora-http-client').projectDir = "$rootDir/islandora-http-client" as File From bdb030fc6346ca02133a59dff27324acc0ff91cb Mon Sep 17 00:00:00 2001 From: ajs6f Date: Wed, 3 May 2017 15:22:26 -0400 Subject: [PATCH 2/5] Small improvements --- islandora-http-client/build.gradle | 2 +- .../alpaca/http/client/StaticTokenRequestInterceptorTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/islandora-http-client/build.gradle b/islandora-http-client/build.gradle index 35ec2d5a..1ecb7ea4 100644 --- a/islandora-http-client/build.gradle +++ b/islandora-http-client/build.gradle @@ -17,7 +17,7 @@ jar { instruction 'Import-Package', 'org.apache.http.client,' + defaultOsgiImports - instruction 'Export-Package', 'ca.islandora.indexing.http.client' + instruction 'Export-Package', 'ca.islandora.alpaca.http.client' } } diff --git a/islandora-http-client/src/test/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptorTest.java b/islandora-http-client/src/test/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptorTest.java index c439b1ee..7f2c88e9 100644 --- a/islandora-http-client/src/test/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptorTest.java +++ b/islandora-http-client/src/test/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptorTest.java @@ -40,7 +40,8 @@ public void shouldInjectHeaderWhenNoAuthHeadersPresent() { @Test public void shouldNotInjectHeaderWhenAuthHeadersPresent() { - StaticTokenRequestInterceptor testInterceptor = new StaticTokenRequestInterceptor("testToken"); + StaticTokenRequestInterceptor testInterceptor = new StaticTokenRequestInterceptor(); + testInterceptor.setToken("testToken"); HttpRequest request = new HttpGet(); request.addHeader(AUTH_HEADER, "fake header"); testInterceptor.process(request, null); From da190bf2cd283a46c1f4d759906d0439a5653975 Mon Sep 17 00:00:00 2001 From: ajs6f Date: Wed, 3 May 2017 15:31:05 -0400 Subject: [PATCH 3/5] Fix checkstyle problems --- .../client/StaticTokenRequestInterceptor.java | 68 +++++++++++++------ .../StaticTokenRequestInterceptorTest.java | 44 ++++++------ 2 files changed, 70 insertions(+), 42 deletions(-) diff --git a/islandora-http-client/src/main/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptor.java b/islandora-http-client/src/main/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptor.java index 4cf3f6d8..4a03f225 100644 --- a/islandora-http-client/src/main/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptor.java +++ b/islandora-http-client/src/main/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptor.java @@ -28,34 +28,58 @@ import org.apache.http.message.BasicHeader; import org.apache.http.protocol.HttpContext; +/** + * Adds a single authentication header to any request that does not + * already have at least one authentication header. + * + * @author ajs6f + * + */ public class StaticTokenRequestInterceptor implements HttpRequestInterceptor { - public static final String AUTH_HEADER = "Authorization"; + public static final String AUTH_HEADER = "Authorization"; + + private Header header; - private Header header; + /** + * Default constructor + */ + public StaticTokenRequestInterceptor() { + } - public StaticTokenRequestInterceptor() { - } + /** + * @param token the authentication token to use + */ + public StaticTokenRequestInterceptor(final String token) { + this.header = makeHeader(token); + } - public StaticTokenRequestInterceptor(String token) { - this.header = makeHeader(token); - } + /** + * @param token the authentication token to use + */ + public void setToken(final String token) { + this.header = makeHeader(token); + } - public void setToken(String token) { - this.header = makeHeader(token); - } + private static Header makeHeader(final String token) { + return new BasicHeader(AUTH_HEADER, "Bearer " + requireNonNull(token, "Token must not be null!")); + } - private Header makeHeader(String token) { - return new BasicHeader(AUTH_HEADER, "Bearer " + requireNonNull(token, "Token must not be null!")); - } + @Override + public void process(final HttpRequest request, final HttpContext context) { + // we do not inject if auth headers present + if (request.getFirstHeader(AUTH_HEADER) == null) { + request.addHeader(header); + } + } - @Override - public void process(HttpRequest request, HttpContext context) { - // we do not inject if auth headers present - if (request.getFirstHeader(AUTH_HEADER)== null) request.addHeader(header); - } - - public static HttpClient defaultClient(StaticTokenRequestInterceptor interceptor) { - return HttpClientBuilder.create().addInterceptorFirst(interceptor).build(); - } + /** + * Convenience factory method. + * + * @param interceptor + * @return a default-configuration {@link HttpClient} that is wrapped with this interceptor + */ + public static HttpClient defaultClient(final StaticTokenRequestInterceptor interceptor) { + return HttpClientBuilder.create().addInterceptorFirst(interceptor).build(); + } } diff --git a/islandora-http-client/src/test/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptorTest.java b/islandora-http-client/src/test/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptorTest.java index 7f2c88e9..d5556c12 100644 --- a/islandora-http-client/src/test/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptorTest.java +++ b/islandora-http-client/src/test/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptorTest.java @@ -26,27 +26,31 @@ import org.junit.Assert; import org.junit.Test; +/** + * @author ajs6f + * + */ public class StaticTokenRequestInterceptorTest extends Assert { - @Test - public void shouldInjectHeaderWhenNoAuthHeadersPresent() { - StaticTokenRequestInterceptor testInterceptor = new StaticTokenRequestInterceptor("testToken"); - HttpRequest request = new HttpGet(); - testInterceptor.process(request, null); - Header[] authHeaders = request.getHeaders(AUTH_HEADER); - assertEquals("Should only be one auth header!", 1, authHeaders.length); - assertEquals("Wrong value for header!", "Bearer testToken", authHeaders[0].getValue()); - } + @Test + public void shouldInjectHeaderWhenNoAuthHeadersPresent() { + final StaticTokenRequestInterceptor testInterceptor = new StaticTokenRequestInterceptor("testToken"); + final HttpRequest request = new HttpGet(); + testInterceptor.process(request, null); + final Header[] authHeaders = request.getHeaders(AUTH_HEADER); + assertEquals("Should only be one auth header!", 1, authHeaders.length); + assertEquals("Wrong value for header!", "Bearer testToken", authHeaders[0].getValue()); + } - @Test - public void shouldNotInjectHeaderWhenAuthHeadersPresent() { - StaticTokenRequestInterceptor testInterceptor = new StaticTokenRequestInterceptor(); - testInterceptor.setToken("testToken"); - HttpRequest request = new HttpGet(); - request.addHeader(AUTH_HEADER, "fake header"); - testInterceptor.process(request, null); - Header[] authHeaders = request.getHeaders(AUTH_HEADER); - assertEquals("Should only be one auth header!", 1, authHeaders.length); - assertEquals("Wrong value for header!", "fake header", authHeaders[0].getValue()); - } + @Test + public void shouldNotInjectHeaderWhenAuthHeadersPresent() { + final StaticTokenRequestInterceptor testInterceptor = new StaticTokenRequestInterceptor(); + testInterceptor.setToken("testToken"); + final HttpRequest request = new HttpGet(); + request.addHeader(AUTH_HEADER, "fake header"); + testInterceptor.process(request, null); + final Header[] authHeaders = request.getHeaders(AUTH_HEADER); + assertEquals("Should only be one auth header!", 1, authHeaders.length); + assertEquals("Wrong value for header!", "fake header", authHeaders[0].getValue()); + } } From dd75cca67da0bc6341fe31733accecb406d268a5 Mon Sep 17 00:00:00 2001 From: ajs6f Date: Wed, 3 May 2017 15:34:46 -0400 Subject: [PATCH 4/5] Making checkstyle problems fail the build --- build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 077f7ae3..94192029 100644 --- a/build.gradle +++ b/build.gradle @@ -87,8 +87,7 @@ subprojects { checkstyle { configFile = rootProject.file('gradle/checkstyle/checkstyle.xml') configProperties.checkstyleConfigDir = rootProject.file('gradle/checkstyle') - /* eventually, we should change this to fail builds on errors */ - ignoreFailures true + ignoreFailures false } license { From 4b5ce97f97be630d162e4404f9fe25cdc4bf440f Mon Sep 17 00:00:00 2001 From: ajs6f Date: Thu, 4 May 2017 19:09:30 -0400 Subject: [PATCH 5/5] Corrections to Karaf feature file and blueprint.xml --- islandora-http-client/build.gradle | 11 ++++++++--- .../http/client/StaticTokenRequestInterceptor.java | 2 +- .../main/resources/OSGI-INF/blueprint/blueprint.xml | 9 ++++++--- karaf/src/main/resources/features.xml | 10 ++++++++++ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/islandora-http-client/build.gradle b/islandora-http-client/build.gradle index 1ecb7ea4..20a4fbb6 100644 --- a/islandora-http-client/build.gradle +++ b/islandora-http-client/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'osgi' description = 'Islandora CLAW HTTP Client' dependencies { - compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.3' + compile group: 'org.apache.httpcomponents', name: 'httpclient-osgi', version: '4.5.3' testCompile group: 'junit', name: 'junit', version:'4.12' } @@ -15,8 +15,13 @@ jar { vendor project.vendor license project.license - instruction 'Import-Package', 'org.apache.http.client,' + - defaultOsgiImports + instruction 'Import-Package', + 'org.apache.http,' + + 'org.apache.http.protocol,' + + 'org.apache.http.message,' + + 'org.apache.http.impl.client,' + + 'org.apache.http.client,' + + defaultOsgiImports instruction 'Export-Package', 'ca.islandora.alpaca.http.client' } } diff --git a/islandora-http-client/src/main/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptor.java b/islandora-http-client/src/main/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptor.java index 4a03f225..84657836 100644 --- a/islandora-http-client/src/main/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptor.java +++ b/islandora-http-client/src/main/java/ca/islandora/alpaca/http/client/StaticTokenRequestInterceptor.java @@ -76,7 +76,7 @@ public void process(final HttpRequest request, final HttpContext context) { /** * Convenience factory method. * - * @param interceptor + * @param interceptor the interceptor to use, presumably an instance of {@link StaticTokenRequestInterceptor} * @return a default-configuration {@link HttpClient} that is wrapped with this interceptor */ public static HttpClient defaultClient(final StaticTokenRequestInterceptor interceptor) { diff --git a/islandora-http-client/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/islandora-http-client/src/main/resources/OSGI-INF/blueprint/blueprint.xml index 20946b78..ee156b74 100644 --- a/islandora-http-client/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/islandora-http-client/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -13,10 +13,13 @@ - - + + + + + - + diff --git a/karaf/src/main/resources/features.xml b/karaf/src/main/resources/features.xml index e4c572cf..58a275a1 100644 --- a/karaf/src/main/resources/features.xml +++ b/karaf/src/main/resources/features.xml @@ -30,5 +30,15 @@ mvn:ca.islandora.alpaca/islandora-connector-broadcast/${project.version}/cfg/configuration + + + + mvn:org.apache.httpcomponents/httpcore-osgi/4.4.6 + mvn:org.apache.httpcomponents/httpclient-osgi/4.5.3 + mvn:ca.islandora.alpaca/islandora-http-client/${project.version} + + mvn:ca.islandora.alpaca/islandora-http-client/${project.version}/cfg/configuration + +