Browse Source

SONAR-19597 Security fix (SSF-420)

tags/9.9.2.77730
Wojtek Wajerowicz 11 months ago
parent
commit
e8305b73e2

+ 2
- 0
sonar-core/build.gradle View File

@@ -11,6 +11,7 @@ dependencies {
api 'ch.qos.logback:logback-core'
api 'com.google.guava:guava'
api 'com.google.protobuf:protobuf-java'
api 'com.squareup.okhttp3:okhttp'
api 'commons-codec:commons-codec'
api 'commons-io:commons-io'
api 'commons-lang:commons-lang'
@@ -22,6 +23,7 @@ dependencies {
api 'org.sonarsource.update-center:sonar-update-center-common'
api 'org.springframework:spring-context'
api project(':sonar-plugin-api-impl')
api project(':sonar-ws')

compileOnlyApi 'com.google.code.findbugs:jsr305'
compileOnlyApi 'com.google.code.gson:gson'

+ 41
- 207
sonar-core/src/main/java/org/sonar/core/util/DefaultHttpDownloader.java View File

@@ -19,35 +19,25 @@
*/
package org.sonar.core.util;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import java.util.zip.GZIPInputStream;
import javax.annotation.Nullable;
import javax.inject.Inject;
import org.apache.commons.codec.binary.Base64;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.io.IOUtils;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Configuration;
import org.sonar.api.platform.Server;
import org.sonar.api.utils.HttpDownloader;
import org.sonar.api.utils.SonarException;
import org.sonar.api.utils.log.Loggers;
import org.sonarqube.ws.client.OkHttpClientBuilder;

import static org.apache.commons.io.FileUtils.copyInputStreamToFile;
import static org.sonar.core.util.FileUtils.deleteQuietly;
@@ -59,42 +49,44 @@ import static org.sonar.core.util.FileUtils.deleteQuietly;
*/
public class DefaultHttpDownloader extends HttpDownloader {

private final BaseHttpDownloader downloader;
private final Integer readTimeout;
private final Integer connectTimeout;
private final OkHttpClient client;

@Inject
public DefaultHttpDownloader(Server server, Configuration config) {
this(server, config, null);
}

public DefaultHttpDownloader(Server server, Configuration config, @Nullable Integer readTimeout) {
this(server, config, null, readTimeout);
this(server, config, null, null);
}

public DefaultHttpDownloader(Server server, Configuration config, @Nullable Integer connectTimeout, @Nullable Integer readTimeout) {
this.readTimeout = readTimeout;
this.connectTimeout = connectTimeout;
downloader = new BaseHttpDownloader(new AuthenticatorFacade(), config, server.getVersion());
}

public DefaultHttpDownloader(Configuration config) {
this(config, null);
client = buildHttpClient(server, config, connectTimeout, readTimeout);
}

public DefaultHttpDownloader(Configuration config, @Nullable Integer readTimeout) {
this(config, null, readTimeout);
private static OkHttpClient buildHttpClient(Server server, Configuration config, @Nullable Integer connectTimeout,
@Nullable Integer readTimeout) {
OkHttpClientBuilder clientBuilder = new OkHttpClientBuilder()
.setFollowRedirects(true)
.setUserAgent(getUserAgent(server, config));
if (connectTimeout != null) {
clientBuilder
.setConnectTimeoutMs(connectTimeout);
}
if (readTimeout != null) {
clientBuilder
.setReadTimeoutMs(readTimeout);
}
return clientBuilder.build();
}

public DefaultHttpDownloader(Configuration config, @Nullable Integer connectTimeout, @Nullable Integer readTimeout) {
this.readTimeout = readTimeout;
this.connectTimeout = connectTimeout;
downloader = new BaseHttpDownloader(new AuthenticatorFacade(), config, null);
private static String getUserAgent(Server server, Configuration config) {
Optional<String> serverId = config.get(CoreProperties.SERVER_ID);
if (serverId.isEmpty()) {
return String.format("SonarQube %s #", server.getVersion());
}
return String.format("SonarQube %s # %s", server.getVersion(), serverId.get());
}

@Override
protected String description(URI uri) {
return String.format("%s (%s)", uri.toString(), getProxySynthesis(uri));
return uri.toString();
}

@Override
@@ -109,8 +101,8 @@ public class DefaultHttpDownloader extends HttpDownloader {

@Override
protected String readString(URI uri, Charset charset) {
try {
return IOUtils.toString(downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout).getInput(), charset);
try (Response response = executeCall(uri)) {
return IOUtils.toString(response.body().byteStream(), charset);
} catch (IOException e) {
throw failToDownload(uri, e);
}
@@ -123,21 +115,18 @@ public class DefaultHttpDownloader extends HttpDownloader {

@Override
public byte[] download(URI uri) {
try {
return ByteStreams.toByteArray(downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout).getInput());
try (Response response = executeCall(uri)) {
return ByteStreams.toByteArray(response.body().byteStream());
} catch (IOException e) {
throw failToDownload(uri, e);
}
}

public String getProxySynthesis(URI uri) {
return BaseHttpDownloader.getProxySynthesis(uri);
}

@Override
public InputStream openStream(URI uri) {
try {
return downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout).getInput();
Response response = executeCall(uri);
return response.body().byteStream();
} catch (IOException e) {
throw failToDownload(uri, e);
}
@@ -145,176 +134,21 @@ public class DefaultHttpDownloader extends HttpDownloader {

@Override
public void download(URI uri, File toFile) {
try {
copyInputStreamToFile(downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout).getInput(), toFile);
try (Response response = executeCall(uri)) {
copyInputStreamToFile(response.body().byteStream(), toFile);
} catch (IOException e) {
deleteQuietly(toFile);
throw failToDownload(uri, e);
}
}

private SonarException failToDownload(URI uri, IOException e) {
throw new SonarException(String.format("Fail to download: %s (%s)", uri, getProxySynthesis(uri)), e);
}

/**
* Facade to allow unit tests to verify calls to {@link Authenticator#setDefault(Authenticator)}.
*/
static class AuthenticatorFacade {
void setDefaultAuthenticator(Authenticator authenticator) {
Authenticator.setDefault(authenticator);
}
private Response executeCall(URI uri) throws IOException {
Request request = new Request.Builder().url(uri.toURL()).get().build();
return client.newCall(request).execute();
}

static class BaseHttpDownloader {

private static final String GET = "GET";
private static final String HTTP_PROXY_USER = "http.proxyUser";
private static final String HTTP_PROXY_PASSWORD = "http.proxyPassword";

private String userAgent;

BaseHttpDownloader(AuthenticatorFacade system, Configuration config, @Nullable String userAgent) {
initProxy(system, config);
initUserAgent(userAgent, config);
}

private static void initProxy(AuthenticatorFacade system, Configuration config) {
// register credentials
Optional<String> login = config.get(HTTP_PROXY_USER);
if (login.isPresent()) {
system.setDefaultAuthenticator(new ProxyAuthenticator(login.get(), config.get(HTTP_PROXY_PASSWORD).orElse(null)));
}
}

private void initUserAgent(@Nullable String sonarVersion, Configuration settings) {
Optional<String> serverId = settings.get(CoreProperties.SERVER_ID);
userAgent = sonarVersion == null ? "SonarQube" : String.format("SonarQube %s # %s", sonarVersion, serverId.orElse(""));
System.setProperty("http.agent", userAgent);
}

private static String getProxySynthesis(URI uri) {
return getProxySynthesis(uri, ProxySelector.getDefault());
}

@VisibleForTesting
static String getProxySynthesis(URI uri, ProxySelector proxySelector) {
List<Proxy> proxies = proxySelector.select(uri);
if (proxies.size() == 1 && proxies.get(0).type().equals(Proxy.Type.DIRECT)) {
return "no proxy";
}

List<String> descriptions = Lists.newArrayList();
for (Proxy proxy : proxies) {
if (proxy.type() != Proxy.Type.DIRECT) {
descriptions.add(proxy.type() + " proxy: " + proxy.address());
}
}

return Joiner.on(", ").join(descriptions);
}

public HttpInputSupplier newInputSupplier(URI uri, @Nullable Integer connectTimeoutMillis, @Nullable Integer readTimeoutMillis) {
return newInputSupplier(uri, GET, connectTimeoutMillis, readTimeoutMillis);
}

public HttpInputSupplier newInputSupplier(URI uri, String requestMethod, @Nullable Integer connectTimeoutMillis, @Nullable Integer readTimeoutMillis) {
return newInputSupplier(uri, requestMethod, null, null, connectTimeoutMillis, readTimeoutMillis);
}

public HttpInputSupplier newInputSupplier(URI uri, String requestMethod, String login, String password, @Nullable Integer connectTimeoutMillis,
@Nullable Integer readTimeoutMillis) {
int read = readTimeoutMillis != null ? readTimeoutMillis : DEFAULT_READ_TIMEOUT_IN_MILLISECONDS;
int connect = connectTimeoutMillis != null ? connectTimeoutMillis : DEFAULT_CONNECT_TIMEOUT_IN_MILLISECONDS;
return new HttpInputSupplier(uri, requestMethod, userAgent, login, password, connect, read);
}

private static class HttpInputSupplier {
private final String login;
private final String password;
private final URI uri;
private final String userAgent;
private final int connectTimeoutMillis;
private final int readTimeoutMillis;
private final String requestMethod;

HttpInputSupplier(URI uri, String requestMethod, String userAgent, String login, String password, int connectTimeoutMillis, int readTimeoutMillis) {
this.uri = uri;
this.requestMethod = requestMethod;
this.userAgent = userAgent;
this.login = login;
this.password = password;
this.readTimeoutMillis = readTimeoutMillis;
this.connectTimeoutMillis = connectTimeoutMillis;
}

/**
* @throws IOException any I/O error, not limited to the network connection
* @throws HttpException if HTTP response code > 400
*/
public InputStream getInput() throws IOException {
Loggers.get(getClass()).debug("Download: " + uri + " (" + getProxySynthesis(uri, ProxySelector.getDefault()) + ")");
HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
connection.setRequestMethod(requestMethod);
HttpsTrust.INSTANCE.trust(connection);

// allow both GZip and Deflate (ZLib) encodings
connection.setRequestProperty("Accept-Encoding", "gzip");
if (!Strings.isNullOrEmpty(login)) {
String encoded = Base64.encodeBase64String((login + ":" + password).getBytes(StandardCharsets.UTF_8));
connection.setRequestProperty("Authorization", "Basic " + encoded);
}
connection.setConnectTimeout(connectTimeoutMillis);
connection.setReadTimeout(readTimeoutMillis);
connection.setUseCaches(true);
connection.setInstanceFollowRedirects(true);
connection.setRequestProperty("User-Agent", userAgent);

// establish connection, get response headers
connection.connect();

// obtain the encoding returned by the server
String encoding = connection.getContentEncoding();

int responseCode = connection.getResponseCode();
if (responseCode >= 400) {
InputStream errorResponse = null;
try {
errorResponse = connection.getErrorStream();
if (errorResponse != null) {
String errorResponseContent = IOUtils.toString(errorResponse);
throw new HttpException(uri, responseCode, errorResponseContent);
}
throw new HttpException(uri, responseCode);

} finally {
IOUtils.closeQuietly(errorResponse);
}
}

InputStream resultingInputStream;
// create the appropriate stream wrapper based on the encoding type
if ("gzip".equalsIgnoreCase(encoding)) {
resultingInputStream = new GZIPInputStream(connection.getInputStream());
} else {
resultingInputStream = connection.getInputStream();
}
return resultingInputStream;
}
}
}

static class ProxyAuthenticator extends Authenticator {
private final PasswordAuthentication auth;

ProxyAuthenticator(String user, @Nullable String password) {
auth = new PasswordAuthentication(user, password == null ? new char[0] : password.toCharArray());
}

@Override
protected PasswordAuthentication getPasswordAuthentication() {
return auth;
}
private static SonarException failToDownload(URI uri, IOException e) {
throw new SonarException(String.format("Fail to download: %s", uri), e);
}

}

+ 0
- 99
sonar-core/src/main/java/org/sonar/core/util/HttpsTrust.java View File

@@ -1,99 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.core.util;

import java.net.HttpURLConnection;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
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 javax.net.ssl.X509TrustManager;

/**
* @since 4.0
*/
class HttpsTrust {

static final HttpsTrust INSTANCE = new HttpsTrust(new Ssl());

static class Ssl {
SSLSocketFactory newFactory(TrustManager... managers) throws NoSuchAlgorithmException, KeyManagementException {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, managers, new SecureRandom());
return context.getSocketFactory();
}
}

private final SSLSocketFactory socketFactory;
private final HostnameVerifier hostnameVerifier;

HttpsTrust(Ssl context) {
this.socketFactory = createSocketFactory(context);
this.hostnameVerifier = createHostnameVerifier();
}

void trust(HttpURLConnection connection) {
if (connection instanceof HttpsURLConnection) {
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
httpsConnection.setSSLSocketFactory(socketFactory);
httpsConnection.setHostnameVerifier(hostnameVerifier);
}
}

/**
* Trust all certificates
*/
private static SSLSocketFactory createSocketFactory(Ssl context) {
try {
return context.newFactory(new AlwaysTrustManager());
} catch (Exception e) {
throw new IllegalStateException("Fail to build SSL factory", e);
}
}

/**
* Trust all hosts
*/
private static HostnameVerifier createHostnameVerifier() {
return (hostname, session) -> true;
}

static class AlwaysTrustManager implements X509TrustManager {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
// Do not check
}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
// Do not check
}
}
}

sonar-core/src/test/java/org/sonar/core/util/DefaultHttpDownloaderTest.java → sonar-core/src/test/java/org/sonar/core/util/DefaultHttpDownloaderIT.java View File

@@ -22,16 +22,13 @@ package org.sonar.core.util;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Properties;
import java.util.zip.GZIPOutputStream;
import org.hamcrest.BaseMatcher;
@@ -56,18 +53,14 @@ import org.sonar.api.utils.SonarException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class DefaultHttpDownloaderTest {
public class DefaultHttpDownloaderIT {

@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();


@Rule
public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));

@@ -81,27 +74,28 @@ public class DefaultHttpDownloaderTest {
try {
if (req.getPath().getPath().contains("/redirect/")) {
resp.setCode(303);
resp.setValue("Location", "/");
} else {
if (req.getPath().getPath().contains("/timeout/")) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
resp.setValue("Location", "/redirected");
} else if (req.getPath().getPath().contains("/timeout/")) {
try {
Thread.sleep(500);
writeDefaultResponse(req, resp);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
if (req.getPath().getPath().contains("/gzip/")) {
if (!"gzip".equals(req.getValue("Accept-Encoding"))) {
throw new IllegalStateException("Should accept gzip");
}
resp.setValue("Content-Encoding", "gzip");
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(resp.getOutputStream());
gzipOutputStream.write("GZIP response".getBytes());
gzipOutputStream.close();
} else {
resp.getPrintStream().append("agent=" + req.getValues("User-Agent").get(0));
} else if (req.getPath().getPath().contains("/gzip/")) {
if (!"gzip".equals(req.getValue("Accept-Encoding"))) {
throw new IllegalStateException("Should accept gzip");
}
resp.setValue("Content-Encoding", "gzip");
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(resp.getOutputStream());
gzipOutputStream.write("GZIP response".getBytes());
gzipOutputStream.close();
} else if (req.getPath().getPath().contains("/redirected")) {
resp.getPrintStream().append("redirected");
} else {
writeDefaultResponse(req, resp);
}

} catch (IOException e) {
throw new IllegalStateException(e);
} finally {
@@ -117,6 +111,10 @@ public class DefaultHttpDownloaderTest {
baseUrl = String.format("http://%s:%d", ((InetSocketAddress) address).getAddress().getHostAddress(), ((InetSocketAddress) address).getPort());
}

private static PrintStream writeDefaultResponse(Request req, Response resp) throws IOException {
return resp.getPrintStream().append("agent=" + req.getValues("User-Agent").get(0));
}

@AfterClass
public static void stopServer() throws IOException {
if (null != socketConnection) {
@@ -130,7 +128,7 @@ public class DefaultHttpDownloaderTest {
String url = "http://10.255.255.1";

assertThatThrownBy(() -> {
DefaultHttpDownloader downloader = new DefaultHttpDownloader(new MapSettings().asConfig(), 10, 50000);
DefaultHttpDownloader downloader = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig(), 10, 10);
downloader.openStream(new URI(url));
})
.isInstanceOf(SonarException.class)
@@ -148,31 +146,32 @@ public class DefaultHttpDownloaderTest {

@Test
public void downloadBytes() throws URISyntaxException {
byte[] bytes = new DefaultHttpDownloader(new MapSettings().asConfig()).readBytes(new URI(baseUrl));
byte[] bytes = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readBytes(new URI(baseUrl));
assertThat(bytes.length).isGreaterThan(10);
}

@Test
public void readString() throws URISyntaxException {
String text = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl), StandardCharsets.UTF_8);
String text = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readString(new URI(baseUrl), StandardCharsets.UTF_8);
assertThat(text.length()).isGreaterThan(10);
}

@Test
public void readGzipString() throws URISyntaxException {
String text = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl + "/gzip/"), StandardCharsets.UTF_8);
String text = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readString(new URI(baseUrl + "/gzip/"), StandardCharsets.UTF_8);
assertThat(text).isEqualTo("GZIP response");
}

@Test
public void readStringWithDefaultTimeout() throws URISyntaxException {
String text = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8);
String text = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8);
assertThat(text.length()).isGreaterThan(10);
}

@Test
public void readStringWithTimeout() throws URISyntaxException {
assertThatThrownBy(() -> new DefaultHttpDownloader(new MapSettings().asConfig(), 50).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8))
assertThatThrownBy(
() -> new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig(), null, 50).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8))
.isEqualToComparingFieldByField(new BaseMatcher<Exception>() {
@Override
public boolean matches(Object ex) {
@@ -190,7 +189,7 @@ public class DefaultHttpDownloaderTest {
File toDir = temporaryFolder.newFolder();
File toFile = new File(toDir, "downloadToFile.txt");

new DefaultHttpDownloader(new MapSettings().asConfig()).download(new URI(baseUrl), toFile);
new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).download(new URI(baseUrl), toFile);
assertThat(toFile).exists();
assertThat(toFile.length()).isGreaterThan(10L);
}
@@ -201,8 +200,7 @@ public class DefaultHttpDownloaderTest {
File toFile = new File(toDir, "downloadToFile.txt");

try {
int port = new InetSocketAddress("localhost", 0).getPort();
new DefaultHttpDownloader(new MapSettings().asConfig()).download(new URI("http://localhost:" + port), toFile);
new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).download(new URI("http://localhost:1"), toFile);
} catch (SonarException e) {
assertThat(toFile).doesNotExist();
}
@@ -236,68 +234,21 @@ public class DefaultHttpDownloaderTest {
assertThat(props.getProperty("agent")).isEqualTo("SonarQube 2.2 #");
}

@Test
public void userAgent_is_static_value_when_server_is_not_provided() throws URISyntaxException, IOException {
InputStream stream = new DefaultHttpDownloader(new MapSettings().asConfig()).openStream(new URI(baseUrl));
Properties props = new Properties();
props.load(stream);
stream.close();

assertThat(props.getProperty("agent")).isEqualTo("SonarQube");
}

@Test
public void followRedirect() throws URISyntaxException {
String content = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl + "/redirect/"), StandardCharsets.UTF_8);
assertThat(content).contains("agent");
}

@Test
public void shouldGetDirectProxySynthesis() throws URISyntaxException {
ProxySelector proxySelector = mock(ProxySelector.class);
when(proxySelector.select(any(URI.class))).thenReturn(Arrays.asList(Proxy.NO_PROXY));
assertThat(DefaultHttpDownloader.BaseHttpDownloader.getProxySynthesis(new URI("http://an_url"), proxySelector)).isEqualTo("no proxy");
}

@Test
public void shouldGetProxySynthesis() throws URISyntaxException {
ProxySelector proxySelector = mock(ProxySelector.class);
when(proxySelector.select(any(URI.class))).thenReturn(Arrays.asList(new FakeProxy()));
assertThat(DefaultHttpDownloader.BaseHttpDownloader.getProxySynthesis(new URI("http://an_url"), proxySelector)).isEqualTo("HTTP proxy: /123.45.67.89:4040");
String content = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readString(new URI(baseUrl + "/redirect/"), StandardCharsets.UTF_8);
assertThat(content).isEqualTo("redirected");
}

@Test
public void supported_schemes() {
assertThat(new DefaultHttpDownloader(new MapSettings().asConfig()).getSupportedSchemes()).contains("http");
assertThat(new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).getSupportedSchemes()).contains("http");
}

@Test
public void uri_description() throws URISyntaxException {
String description = new DefaultHttpDownloader(new MapSettings().asConfig()).description(new URI("http://sonarsource.org"));
assertThat(description).matches("http://sonarsource.org \\(.*\\)");
}

@Test
public void configure_http_proxy_credentials() {
DefaultHttpDownloader.AuthenticatorFacade system = mock(DefaultHttpDownloader.AuthenticatorFacade.class);
MapSettings settings = new MapSettings();
settings.setProperty("https.proxyHost", "1.2.3.4");
settings.setProperty("http.proxyUser", "the_login");
settings.setProperty("http.proxyPassword", "the_passwd");

new DefaultHttpDownloader.BaseHttpDownloader(system, settings.asConfig(), null);

verify(system).setDefaultAuthenticator(argThat(authenticator -> {
DefaultHttpDownloader.ProxyAuthenticator a = (DefaultHttpDownloader.ProxyAuthenticator) authenticator;
PasswordAuthentication authentication = a.getPasswordAuthentication();
return authentication.getUserName().equals("the_login") &&
new String(authentication.getPassword()).equals("the_passwd");
}));
String description = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).description(new URI("http://sonarsource.org"));
assertThat(description).isEqualTo("http://sonarsource.org");
}

private static class FakeProxy extends Proxy {
FakeProxy() {
super(Type.HTTP, new InetSocketAddress("123.45.67.89", 4040));
}
}
}

+ 0
- 101
sonar-core/src/test/java/org/sonar/core/util/HttpsTrustTest.java View File

@@ -1,101 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.core.util;

import java.io.IOException;
import java.net.URL;
import java.security.KeyManagementException;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.TrustManager;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class HttpsTrustTest {
@Test
public void trustAllHosts() throws Exception {
HttpsURLConnection connection = newHttpsConnection();
HttpsTrust.INSTANCE.trust(connection);

assertThat(connection.getHostnameVerifier()).isNotNull();
assertThat(connection.getHostnameVerifier().verify("foo", null)).isTrue();
}

@Test
public void singleHostnameVerifier() throws Exception {
HttpsURLConnection connection1 = newHttpsConnection();
HttpsTrust.INSTANCE.trust(connection1);
HttpsURLConnection connection2 = newHttpsConnection();
HttpsTrust.INSTANCE.trust(connection2);

assertThat(connection1.getHostnameVerifier()).isSameAs(connection2.getHostnameVerifier());
}

@Test
public void trustAllCerts() throws Exception {
HttpsURLConnection connection1 = newHttpsConnection();
HttpsTrust.INSTANCE.trust(connection1);

assertThat(connection1.getSSLSocketFactory()).isNotNull();
assertThat(connection1.getSSLSocketFactory().getDefaultCipherSuites()).isNotEmpty();
}

@Test
public void singleSslFactory() throws Exception {
HttpsURLConnection connection1 = newHttpsConnection();
HttpsTrust.INSTANCE.trust(connection1);
HttpsURLConnection connection2 = newHttpsConnection();
HttpsTrust.INSTANCE.trust(connection2);

assertThat(connection1.getSSLSocketFactory()).isSameAs(connection2.getSSLSocketFactory());
}

@Test
public void testAlwaysTrustManager() {
HttpsTrust.AlwaysTrustManager manager = new HttpsTrust.AlwaysTrustManager();
assertThat(manager.getAcceptedIssuers()).isEmpty();
// does nothing
manager.checkClientTrusted(null, null);
manager.checkServerTrusted(null, null);
}

@Test
public void failOnError() throws Exception {
HttpsTrust.Ssl context = mock(HttpsTrust.Ssl.class);
KeyManagementException cause = new KeyManagementException("foo");
when(context.newFactory(any(TrustManager.class))).thenThrow(cause);

try {
new HttpsTrust(context);
fail();
} catch (IllegalStateException e) {
assertThat(e.getMessage()).isEqualTo("Fail to build SSL factory");
assertThat(e.getCause()).isSameAs(cause);
}
}

private HttpsURLConnection newHttpsConnection() throws IOException {
return (HttpsURLConnection) new URL("https://localhost").openConnection();
}
}

Loading…
Cancel
Save