Proxy support for pull, push and mirror commands (#1773)

Apply proxy support for jGit by extracting the required functionality from the DefaultAdvancedHttpClient into its own class HttpURLConnectionFactory. This new class is now used by the DefaultAdvancedHttpClient and jGit.
The HttpURLConnection also fixes proxy server authentication, which was non functional in DefaultAdvancedHttpClient.
The proxy support for SVNKit is implemented by using the provided method of the BasicAuthenticationManager.
For mercurial the support is configured by writing the required settings to a temporary hgrc file.
This commit is contained in:
Sebastian Sdorra
2021-08-19 11:27:51 +02:00
committed by GitHub
parent a7bb67f36b
commit 7f9f4e566c
54 changed files with 2996 additions and 1098 deletions

View File

@@ -24,110 +24,135 @@
package sonia.scm.net.ahc;
//~--- non-JDK imports --------------------------------------------------------
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.google.inject.util.Providers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.net.SSLContextProvider;
import sonia.scm.net.TrustAllHostnameVerifier;
import sonia.scm.net.GlobalProxyConfiguration;
import sonia.scm.net.HttpURLConnectionFactory;
import sonia.scm.trace.Span;
import sonia.scm.trace.Tracer;
import sonia.scm.util.HttpUtil;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.SocketAddress;
import java.net.URL;
import java.net.Proxy;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
//~--- JDK imports ------------------------------------------------------------
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
*
* @author Sebastian Sdorra
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultAdvancedHttpClientTest
{
@ExtendWith(MockitoExtension.class)
class DefaultAdvancedHttpClientTest {
private static final int TIMEOUT_CONNECTION = 30000;
private static final int TIMEOUT_READ = 1200000;
@Mock
private HttpsURLConnection connection;
@Mock
private Tracer tracer;
@Mock
private Span span;
@Mock
private TrustManager trustManager;
private Set<ContentTransformer> transformers;
private ScmConfiguration configuration;
private DefaultAdvancedHttpClient client;
private Proxy proxy;
@BeforeEach
void setUp() {
configuration = new ScmConfiguration();
transformers = new HashSet<>();
HttpURLConnectionFactory connectionFactory = new HttpURLConnectionFactory(
new GlobalProxyConfiguration(configuration),
Providers.of(trustManager),
(url, proxy) -> {
this.proxy = proxy;
return connection;
},
() -> SSLContext.getInstance("TLS")
);
client = new DefaultAdvancedHttpClient(connectionFactory, tracer, transformers);
lenient().when(tracer.span(anyString())).thenReturn(span);
}
/**
* Method description
*
*
* @throws IOException
*/
@Test
public void testApplyBaseSettings() throws IOException
{
new AdvancedHttpRequest(client, HttpMethod.GET,
"https://www.scm-manager.org").request();
void shouldApplyBaseSettings() throws IOException {
new AdvancedHttpRequest(
client, HttpMethod.GET, "https://www.scm-manager.org"
).request();
verify(connection).setRequestMethod(HttpMethod.GET);
verify(connection).setReadTimeout(DefaultAdvancedHttpClient.TIMEOUT_RAED);
verify(connection).setConnectTimeout(
DefaultAdvancedHttpClient.TIMEOUT_CONNECTION);
verify(connection).setReadTimeout(TIMEOUT_READ);
verify(connection).setConnectTimeout(TIMEOUT_CONNECTION);
verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "0");
}
@Test(expected = ContentTransformerNotFoundException.class)
public void testContentTransformerNotFound(){
client.createTransformer(String.class, "text/plain");
@Test
void shouldThrowContentTransformerNotFound(){
assertThrows(ContentTransformerNotFoundException.class, () -> client.createTransformer(String.class, "text/plain"));
}
@Test
public void testContentTransformer(){
void shouldCreateContentTransformer() {
ContentTransformer transformer = mock(ContentTransformer.class);
when(transformer.isResponsible(String.class, "text/plain")).thenReturn(Boolean.TRUE);
transformers.add(transformer);
ContentTransformer t = client.createTransformer(String.class, "text/plain");
assertSame(transformer, t);
assertThat(t).isSameAs(transformer);
}
/**
* Method description
*
*
* @throws IOException
*/
@Test
public void testApplyContent() throws IOException
{
void shouldApplyContent() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
when(connection.getOutputStream()).thenReturn(baos);
AdvancedHttpRequestWithBody request =
new AdvancedHttpRequestWithBody(client, HttpMethod.PUT,
"https://www.scm-manager.org");
AdvancedHttpRequestWithBody request = new AdvancedHttpRequestWithBody(
client, HttpMethod.PUT, "https://www.scm-manager.org"
);
request.stringContent("test").request();
verify(connection).setDoOutput(true);
verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "4");
assertEquals("test", baos.toString("UTF-8"));
assertThat(baos.toString("UTF-8")).isEqualTo("test");
}
/**
* Method description
*
*
* @throws IOException
*/
@Test
public void testApplyHeaders() throws IOException
{
AdvancedHttpRequest request = new AdvancedHttpRequest(client,
HttpMethod.POST,
"http://www.scm-manager.org");
void shouldApplyHeaders() throws IOException {
AdvancedHttpRequest request = new AdvancedHttpRequest(
client, HttpMethod.POST, "http://www.scm-manager.org"
);
request.header("Header-One", "One").header("Header-Two", "Two").request();
verify(connection).setRequestMethod(HttpMethod.POST);
@@ -135,18 +160,11 @@ public class DefaultAdvancedHttpClientTest
verify(connection).addRequestProperty("Header-Two", "Two");
}
/**
* Method description
*
*
* @throws IOException
*/
@Test
public void testApplyMultipleHeaders() throws IOException
{
AdvancedHttpRequest request = new AdvancedHttpRequest(client,
HttpMethod.POST,
"http://www.scm-manager.org");
void shouldApplyMultipleHeaders() throws IOException {
AdvancedHttpRequest request = new AdvancedHttpRequest(
client, HttpMethod.POST, "http://www.scm-manager.org"
);
request.header("Header-One", "One").header("Header-One", "Two").request();
verify(connection).setRequestMethod(HttpMethod.POST);
@@ -154,118 +172,71 @@ public class DefaultAdvancedHttpClientTest
verify(connection).addRequestProperty("Header-One", "Two");
}
/**
* Method description
*
*
* @throws IOException
*/
@Test
public void testBodyRequestWithoutContent() throws IOException
{
AdvancedHttpRequestWithBody request =
new AdvancedHttpRequestWithBody(client, HttpMethod.PUT,
"https://www.scm-manager.org");
void shouldReturnRequestWithoutContent() throws IOException {
AdvancedHttpRequestWithBody request = new AdvancedHttpRequestWithBody(
client, HttpMethod.PUT, "https://www.scm-manager.org");
request.request();
verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "0");
}
/**
* Method description
*
*
* @throws IOException
*/
@Test
public void testDisableCertificateValidation() throws IOException
{
AdvancedHttpRequest request = new AdvancedHttpRequest(client,
HttpMethod.GET,
"https://www.scm-manager.org");
void shouldDisableCertificateValidation() throws IOException {
AdvancedHttpRequest request = new AdvancedHttpRequest(
client, HttpMethod.GET, "https://www.scm-manager.org"
);
request.disableCertificateValidation(true).request();
verify(connection).setSSLSocketFactory(any(SSLSocketFactory.class));
}
/**
* Method description
*
*
* @throws IOException
*/
@Test
public void testDisableHostnameValidation() throws IOException
{
AdvancedHttpRequest request = new AdvancedHttpRequest(client,
HttpMethod.GET,
"https://www.scm-manager.org");
void shouldDisableHostnameValidation() throws IOException {
AdvancedHttpRequest request = new AdvancedHttpRequest(
client, HttpMethod.GET,"https://www.scm-manager.org"
);
request.disableHostnameValidation(true).request();
verify(connection).setHostnameVerifier(any(TrustAllHostnameVerifier.class));
verify(connection).setHostnameVerifier(any(HostnameVerifier.class));
}
/**
* Method description
*
*
* @throws IOException
*/
@Test
public void testIgnoreProxy() throws IOException
{
void shouldIgnoreProxySettings() throws IOException {
configuration.setProxyServer("proxy.scm-manager.org");
configuration.setProxyPort(8090);
configuration.setEnableProxy(true);
new AdvancedHttpRequest(client, HttpMethod.GET,
"https://www.scm-manager.org").ignoreProxySettings(true).request();
assertFalse(client.proxyConnection);
new AdvancedHttpRequest(
client, HttpMethod.GET, "https://www.scm-manager.org"
).ignoreProxySettings(true).request();
assertThat(proxy).isNull();
}
/**
* Method description
*
*
* @throws IOException
*/
@Test
public void testProxyConnection() throws IOException
{
void shouldUseProxyConnection() throws IOException {
configuration.setProxyServer("proxy.scm-manager.org");
configuration.setProxyPort(8090);
configuration.setEnableProxy(true);
new AdvancedHttpRequest(client, HttpMethod.GET,
"https://www.scm-manager.org").request();
assertTrue(client.proxyConnection);
}
/**
* Method description
*
*
* @throws IOException
*/
@Test
public void testProxyWithAuthentication() throws IOException
{
configuration.setProxyServer("proxy.scm-manager.org");
configuration.setProxyPort(8090);
configuration.setProxyUser("tricia");
configuration.setProxyPassword("tricias secret");
configuration.setEnableProxy(true);
new AdvancedHttpRequest(client, HttpMethod.GET,
"https://www.scm-manager.org").request();
assertTrue(client.proxyConnection);
verify(connection).addRequestProperty(
DefaultAdvancedHttpClient.HEADER_PROXY_AUTHORIZATION,
"Basic dHJpY2lhOnRyaWNpYXMgc2VjcmV0");
new AdvancedHttpRequest(
client, HttpMethod.GET,"https://www.scm-manager.org"
).request();
assertThat(proxy).isNotNull();
}
@Test
public void shouldCreateTracingSpan() throws IOException {
void shouldCreateTracingSpan() throws IOException {
when(connection.getResponseCode()).thenReturn(200);
new AdvancedHttpRequest(client, HttpMethod.GET, "https://www.scm-manager.org").spanKind("spaceships").request();
new AdvancedHttpRequest(
client, HttpMethod.GET, "https://www.scm-manager.org"
).spanKind("spaceships").request();
verify(tracer).span("spaceships");
verify(span).label("url", "https://www.scm-manager.org");
verify(span).label("method", "GET");
@@ -275,10 +246,13 @@ public class DefaultAdvancedHttpClientTest
}
@Test
public void shouldCreateFailedTracingSpan() throws IOException {
void shouldCreateFailedTracingSpan() throws IOException {
when(connection.getResponseCode()).thenReturn(500);
new AdvancedHttpRequest(client, HttpMethod.GET, "https://www.scm-manager.org").request();
new AdvancedHttpRequest(
client, HttpMethod.GET, "https://www.scm-manager.org"
).request();
verify(tracer).span("HTTP Request");
verify(span).label("url", "https://www.scm-manager.org");
verify(span).label("method", "GET");
@@ -288,16 +262,20 @@ public class DefaultAdvancedHttpClientTest
}
@Test
public void shouldCreateFailedTracingSpanOnIOException() throws IOException {
void shouldCreateFailedTracingSpanOnIOException() throws IOException {
when(connection.getResponseCode()).thenThrow(new IOException("failed"));
boolean thrown = false;
try {
new AdvancedHttpRequest(client, HttpMethod.DELETE, "http://failing.host").spanKind("failures").request();
new AdvancedHttpRequest(
client, HttpMethod.DELETE, "http://failing.host"
).spanKind("failures").request();
} catch (IOException ex) {
thrown = true;
}
assertTrue(thrown);
assertThat(thrown).isTrue();
verify(tracer).span("failures");
verify(span).label("url", "http://failing.host");
@@ -309,19 +287,24 @@ public class DefaultAdvancedHttpClientTest
}
@Test
public void shouldNotCreateSpan() throws IOException {
void shouldNotCreateSpan() throws IOException {
when(connection.getResponseCode()).thenReturn(200);
new AdvancedHttpRequest(client, HttpMethod.GET, "https://www.scm-manager.org")
.disableTracing().request();
new AdvancedHttpRequest(
client, HttpMethod.GET, "https://www.scm-manager.org"
).disableTracing().request();
verify(tracer, never()).span(anyString());
}
@Test
public void shouldNotTraceRequestIfAcceptedResponseCode() throws IOException {
void shouldNotTraceRequestIfAcceptedResponseCode() throws IOException {
when(connection.getResponseCode()).thenReturn(400);
new AdvancedHttpRequest(client, HttpMethod.GET, "https://www.scm-manager.org").acceptStatusCodes(400).request();
new AdvancedHttpRequest(
client, HttpMethod.GET, "https://www.scm-manager.org"
).acceptStatusCodes(400).request();
verify(tracer).span("HTTP Request");
verify(span).label("status", 400);
verify(span, never()).failed();
@@ -329,120 +312,16 @@ public class DefaultAdvancedHttpClientTest
}
@Test
public void shouldTraceRequestAsFailedIfAcceptedResponseCodeDoesntMatch() throws IOException {
void shouldTraceRequestAsFailedIfAcceptedResponseCodeDoesntMatch() throws IOException {
when(connection.getResponseCode()).thenReturn(401);
new AdvancedHttpRequest(client, HttpMethod.GET, "https://www.scm-manager.org").acceptStatusCodes(400).request();
new AdvancedHttpRequest(
client, HttpMethod.GET, "https://www.scm-manager.org"
).acceptStatusCodes(400).request();
verify(tracer).span("HTTP Request");
verify(span).label("status", 401);
verify(span).failed();
verify(span).close();
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*/
@Before
public void setUp()
{
configuration = new ScmConfiguration();
transformers = new HashSet<>();
client = new TestingAdvacedHttpClient(configuration, transformers);
when(tracer.span(anyString())).thenReturn(span);
}
//~--- inner classes --------------------------------------------------------
/**
* Class description
*
*
* @version Enter version here..., 15/05/01
* @author Enter your name here...
*/
public class TestingAdvacedHttpClient extends DefaultAdvancedHttpClient
{
/**
* Constructs ...
*
*
* @param configuration
* @param transformers
*/
public TestingAdvacedHttpClient(ScmConfiguration configuration, Set<ContentTransformer> transformers)
{
super(configuration, tracer, transformers, new SSLContextProvider());
}
//~--- methods ------------------------------------------------------------
/**
* Method description
*
*
* @param url
*
* @return
*
* @throws IOException
*/
@Override
protected HttpURLConnection createConnection(URL url) throws IOException
{
return connection;
}
/**
* Method description
*
*
* @param url
* @param address
*
* @return
*
* @throws IOException
*/
@Override
protected HttpURLConnection createProxyConnecton(URL url,
SocketAddress address)
throws IOException
{
proxyConnection = true;
return connection;
}
//~--- fields -------------------------------------------------------------
/** Field description */
private boolean proxyConnection = false;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private TestingAdvacedHttpClient client;
/** Field description */
private ScmConfiguration configuration;
/** Field description */
@Mock
private HttpsURLConnection connection;
/** Field description */
private Set<ContentTransformer> transformers;
@Mock
private Tracer tracer;
@Mock
private Span span;
}

View File

@@ -31,101 +31,73 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteSource;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.config.ScmConfiguration;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
//~--- JDK imports ------------------------------------------------------------
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.net.HttpURLConnectionFactory;
import sonia.scm.trace.Tracer;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.HashSet;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import sonia.scm.net.SSLContextProvider;
import sonia.scm.trace.Tracer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultAdvancedHttpResponseTest
{
@ExtendWith(MockitoExtension.class)
class DefaultAdvancedHttpResponseTest {
@Mock
private HttpURLConnection connection;
private DefaultAdvancedHttpClient client;
@Before
public void setUpClient() {
client = new DefaultAdvancedHttpClient(new ScmConfiguration(), tracer, new HashSet<>(), new SSLContextProvider());
@BeforeEach
void setUpClient() {
client = new DefaultAdvancedHttpClient(mock(HttpURLConnectionFactory.class), mock(Tracer.class), Collections.emptySet());
}
/**
* Method description
*
*
* @throws IOException
*/
@Test
public void testContentAsByteSource() throws IOException
{
ByteArrayInputStream bais =
new ByteArrayInputStream("test".getBytes(Charsets.UTF_8));
void shouldReturnContentAsByteSource() throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream("test".getBytes(Charsets.UTF_8));
when(connection.getInputStream()).thenReturn(bais);
AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(client,
connection, 200, "OK");
AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(client, connection, 200, "OK");
ByteSource content = response.contentAsByteSource();
assertEquals("test", content.asCharSource(Charsets.UTF_8).read());
assertThat(content.asCharSource(Charsets.UTF_8).read()).isEqualTo("test");
}
/**
* Method description
*
*
* @throws IOException
*/
@Test
@SuppressWarnings("unchecked")
public void testContentAsByteSourceWithFailedRequest() throws IOException
{
ByteArrayInputStream bais =
new ByteArrayInputStream("test".getBytes(Charsets.UTF_8));
void shouldReturnContentAsByteSourceEvenForFailedRequests() throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream("test".getBytes(Charsets.UTF_8));
when(connection.getInputStream()).thenThrow(IOException.class);
when(connection.getErrorStream()).thenReturn(bais);
AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(client,
connection, 404, "NOT FOUND");
AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(client, connection, 404, "NOT FOUND");
ByteSource content = response.contentAsByteSource();
assertEquals("test", content.asCharSource(Charsets.UTF_8).read());
assertThat(content.asCharSource(Charsets.UTF_8).read()).isEqualTo("test");
}
/**
* Method description
*
*/
@Test
public void testGetHeaders()
{
void shouldReturnHeaders() {
LinkedHashMap<String, List<String>> map = Maps.newLinkedHashMap();
List<String> test = Lists.newArrayList("One", "Two");
@@ -136,14 +108,7 @@ public class DefaultAdvancedHttpResponseTest
connection, 200, "OK");
Multimap<String, String> headers = response.getHeaders();
assertThat(headers.get("Test"), Matchers.contains("One", "Two"));
assertTrue(headers.get("Test-2").isEmpty());
assertThat(headers.get("Test")).containsOnly("One", "Two");
assertThat(headers.get("Test-2")).isEmpty();
}
/** Field description */
@Mock
private HttpURLConnection connection;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Tracer tracer;
}