diff --git a/provider/gradle/README.md b/provider/gradle/README.md index 7c9338b50e..7e19903a75 100644 --- a/provider/gradle/README.md +++ b/provider/gradle/README.md @@ -99,6 +99,7 @@ The following project properties can be specified with `-Pproperty=value` on the |`pact.pactbroker.httpclient.usePreemptiveAuthentication`|Enables preemptive authentication with the pact broker when set to `true`| |`pact.provider.tag`|Sets the provider tag to push before publishing verification results (can use a comma separated list)| |`pact.content_type.override..=` where `` may be `text`, `json` or `binary`|Overrides the handling of a particular content type [4.1.3+]| +|`pact.verifier.enableRedirectHandling`|Enables automatically handling redirects [4.1.8+]| ## Specifying the provider hostname at runtime diff --git a/provider/maven/README.md b/provider/maven/README.md index 2eb51e5340..14049ed97b 100644 --- a/provider/maven/README.md +++ b/provider/maven/README.md @@ -266,6 +266,7 @@ The following plugin properties can be specified with `-Dproperty=value` on the |`pact.pactbroker.httpclient.usePreemptiveAuthentication`|Enables preemptive authentication with the pact broker when set to `true`| |`pact.consumer.tags`|Overrides the tags used when publishing pacts [version 4.0.7+]| |`pact.content_type.override..=text\|json\|binary`|Overrides the handling of a particular content type [version 4.1.3+]| +|`pact.verifier.enableRedirectHandling`|Enables automatically handling redirects [4.1.8+]| Example in the configuration section: diff --git a/provider/src/main/kotlin/au/com/dius/pact/provider/HttpClientFactory.kt b/provider/src/main/kotlin/au/com/dius/pact/provider/HttpClientFactory.kt index 77e885ce20..bc608f73fc 100644 --- a/provider/src/main/kotlin/au/com/dius/pact/provider/HttpClientFactory.kt +++ b/provider/src/main/kotlin/au/com/dius/pact/provider/HttpClientFactory.kt @@ -11,6 +11,7 @@ import org.apache.http.conn.ssl.SSLConnectionSocketFactory import org.apache.http.impl.client.CloseableHttpClient import org.apache.http.impl.client.HttpClientBuilder import org.apache.http.impl.client.HttpClients +import org.apache.http.impl.client.LaxRedirectStrategy import org.apache.http.impl.conn.PoolingHttpClientConnectionManager import org.apache.http.ssl.SSLContextBuilder import org.apache.http.ssl.TrustStrategy @@ -21,66 +22,82 @@ import java.security.cert.X509Certificate */ class HttpClientFactory : IHttpClientFactory { - override fun newClient(provider: IProviderInfo): CloseableHttpClient { - return if (provider.createClient != null) { - if (provider.createClient is Closure<*>) { - (provider.createClient as Closure<*>).call(provider) as CloseableHttpClient - } else { - val binding = Binding() - binding.setVariable("provider", provider) - val shell = GroovyShell(binding) - shell.evaluate(provider.createClient.toString()) as CloseableHttpClient - } - } else if (provider.insecure) { - createInsecure() - } else if (provider.trustStore != null && provider.trustStorePassword != null) { - createWithTrustStore(provider) - } else { - HttpClients.custom().useSystemProperties().disableRedirectHandling().build() - } + override fun newClient(provider: IProviderInfo): CloseableHttpClient { + return if (provider.createClient != null) { + if (provider.createClient is Closure<*>) { + (provider.createClient as Closure<*>).call(provider) as CloseableHttpClient + } else { + val binding = Binding() + binding.setVariable("provider", provider) + val shell = GroovyShell(binding) + shell.evaluate(provider.createClient.toString()) as CloseableHttpClient + } + } else if (provider.insecure) { + createInsecure() + } else if (provider.trustStore != null && provider.trustStorePassword != null) { + createWithTrustStore(provider) + } else { + val builder = HttpClients.custom().useSystemProperties() + val enableRedirectHandling = System.getProperty("pact.verifier.enableRedirectHandling") + if (enableRedirectHandling.isNullOrEmpty() || enableRedirectHandling != "true") { + builder.disableRedirectHandling() + } else { + builder.setRedirectStrategy(LaxRedirectStrategy()) + } + builder.build() } + } - private fun createWithTrustStore(provider: IProviderInfo): CloseableHttpClient { - val password = provider.trustStorePassword.orEmpty().toCharArray() - return HttpClients - .custom() - .useSystemProperties() - .disableRedirectHandling() - .setSslcontext(SSLContextBuilder().loadTrustMaterial(provider.trustStore, password).build()) - .build() + private fun createWithTrustStore(provider: IProviderInfo): CloseableHttpClient { + val password = provider.trustStorePassword.orEmpty().toCharArray() + val builder = HttpClients + .custom() + .useSystemProperties() + .setSslcontext(SSLContextBuilder().loadTrustMaterial(provider.trustStore, password).build()) + val enableRedirectHandling = System.getProperty("pact.verifier.enableRedirectHandling") + if (enableRedirectHandling.isNullOrEmpty() || enableRedirectHandling != "true") { + builder.disableRedirectHandling() + } else { + builder.setRedirectStrategy(LaxRedirectStrategy()) } + return builder.build() + } - private fun createInsecure(): CloseableHttpClient { - val b = HttpClientBuilder.create() - .useSystemProperties() - .disableRedirectHandling() + private fun createInsecure(): CloseableHttpClient { + val b = HttpClientBuilder.create().useSystemProperties() + val enableRedirectHandling = System.getProperty("pact.verifier.enableRedirectHandling") + if (enableRedirectHandling.isNullOrEmpty() || enableRedirectHandling != "true") { + b.disableRedirectHandling() + } else { + b.setRedirectStrategy(LaxRedirectStrategy()) + } - // setup a Trust Strategy that allows all certificates. - // - val trustStratergy = TrustStrategy { _: Array, _: String -> true } - val sslContext = SSLContextBuilder().loadTrustMaterial(null, trustStratergy).build() - b.setSslcontext(sslContext) - // don't check Hostnames, either. - // -- use SSLConnectionSocketFactory.getDefaultHostnameVerifier(), if you don't want to weaken - val hostnameVerifier = AllowAllHostnameVerifier() + // setup a Trust Strategy that allows all certificates. + // + val trustStratergy = TrustStrategy { _: Array, _: String -> true } + val sslContext = SSLContextBuilder().loadTrustMaterial(null, trustStratergy).build() + b.setSslcontext(sslContext) + // don't check Hostnames, either. + // -- use SSLConnectionSocketFactory.getDefaultHostnameVerifier(), if you don't want to weaken + val hostnameVerifier = AllowAllHostnameVerifier() - // here's the special part: - // -- need to create an SSL Socket Factory, to use our weakened "trust strategy"; - // -- and create a Registry, to register it. - // - val sslSocketFactory = SSLConnectionSocketFactory(sslContext, hostnameVerifier) - val socketFactoryRegistry = RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.getSocketFactory()) - .register("https", sslSocketFactory) - .build() + // here's the special part: + // -- need to create an SSL Socket Factory, to use our weakened "trust strategy"; + // -- and create a Registry, to register it. + // + val sslSocketFactory = SSLConnectionSocketFactory(sslContext, hostnameVerifier) + val socketFactoryRegistry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslSocketFactory) + .build() - // now, we create connection-manager using our Registry. - // -- allows multi-threaded use - val connMgr = PoolingHttpClientConnectionManager(socketFactoryRegistry) - b.setConnectionManager(connMgr) + // now, we create connection-manager using our Registry. + // -- allows multi-threaded use + val connMgr = PoolingHttpClientConnectionManager(socketFactoryRegistry) + b.setConnectionManager(connMgr) - // finally, build the HttpClient; - // -- done! - return b.build() - } + // finally, build the HttpClient; + // -- done! + return b.build() + } } diff --git a/provider/src/test/groovy/au/com/dius/pact/provider/HttpClientFactorySpec.groovy b/provider/src/test/groovy/au/com/dius/pact/provider/HttpClientFactorySpec.groovy index 907ba253fa..d4fce8ab13 100644 --- a/provider/src/test/groovy/au/com/dius/pact/provider/HttpClientFactorySpec.groovy +++ b/provider/src/test/groovy/au/com/dius/pact/provider/HttpClientFactorySpec.groovy @@ -1,7 +1,11 @@ package au.com.dius.pact.provider import org.apache.http.impl.client.CloseableHttpClient +import org.apache.http.impl.client.LaxRedirectStrategy +import org.apache.http.impl.execchain.RedirectExec +import spock.lang.Issue import spock.lang.Specification +import spock.util.environment.RestoreSystemProperties class HttpClientFactorySpec extends Specification { @@ -29,4 +33,44 @@ class HttpClientFactorySpec extends Specification { new HttpClientFactory().newClient(provider) != null } + @Issue('#1323') + @RestoreSystemProperties + def 'if pact.verifier.enableRedirectHandling is set, add a redirect handler'() { + given: + def provider = new ProviderInfo() + System.setProperty('pact.verifier.enableRedirectHandling', 'true') + + when: + def client = new HttpClientFactory().newClient(provider) + + then: + client.execChain instanceof RedirectExec + client.execChain.redirectStrategy instanceof LaxRedirectStrategy + } + + @Issue('#1323') + @RestoreSystemProperties + def 'if pact.verifier.enableRedirectHandling is not set to true, do not add a redirect handler'() { + given: + def provider = new ProviderInfo() + System.setProperty('pact.verifier.enableRedirectHandling', 'false') + + when: + def client = new HttpClientFactory().newClient(provider) + + then: + !(client.execChain instanceof RedirectExec) + } + + @Issue('#1323') + def 'if pact.verifier.enableRedirectHandling is not set, do not add a redirect handler'() { + given: + def provider = new ProviderInfo() + + when: + def client = new HttpClientFactory().newClient(provider) + + then: + !(client.execChain instanceof RedirectExec) + } }