From 781a8e146ed4eaf672c739067cdf3da6750a90d9 Mon Sep 17 00:00:00 2001 From: Wojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com> Date: Thu, 15 Jun 2023 07:32:29 +0200 Subject: [PATCH] SONAR-19579 Fix SSF-420 (#8573) --- sonar-core/build.gradle | 2 + .../core/util/DefaultHttpDownloaderIT.java | 125 +++------ .../core/util/DefaultHttpDownloader.java | 250 +++--------------- .../java/org/sonar/core/util/HttpsTrust.java | 99 ------- .../org/sonar/core/util/HttpsTrustTest.java | 101 ------- 5 files changed, 81 insertions(+), 496 deletions(-) delete mode 100644 sonar-core/src/main/java/org/sonar/core/util/HttpsTrust.java delete mode 100644 sonar-core/src/test/java/org/sonar/core/util/HttpsTrustTest.java diff --git a/sonar-core/build.gradle b/sonar-core/build.gradle index 7c85982378f..8f8abccb27a 100644 --- a/sonar-core/build.gradle +++ b/sonar-core/build.gradle @@ -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' diff --git a/sonar-core/src/it/java/org/sonar/core/util/DefaultHttpDownloaderIT.java b/sonar-core/src/it/java/org/sonar/core/util/DefaultHttpDownloaderIT.java index c185bbbdc0d..63e0afc5520 100644 --- a/sonar-core/src/it/java/org/sonar/core/util/DefaultHttpDownloaderIT.java +++ b/sonar-core/src/it/java/org/sonar/core/util/DefaultHttpDownloaderIT.java @@ -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,10 +53,7 @@ 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 DefaultHttpDownloaderIT { @@ -67,7 +61,6 @@ public class DefaultHttpDownloaderIT { @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); - @Rule public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60)); @@ -81,27 +74,28 @@ public class DefaultHttpDownloaderIT { 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 DefaultHttpDownloaderIT { 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 DefaultHttpDownloaderIT { String url = "http://10.255.255.1"; assertThatThrownBy(() -> { - DefaultHttpDownloader downloader = new DefaultHttpDownloader(new MapSettings().asConfig(), 10, 10); + 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 DefaultHttpDownloaderIT { @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() { @Override public boolean matches(Object ex) { @@ -190,7 +189,7 @@ public class DefaultHttpDownloaderIT { 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 DefaultHttpDownloaderIT { 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 DefaultHttpDownloaderIT { 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)); - } - } } diff --git a/sonar-core/src/main/java/org/sonar/core/util/DefaultHttpDownloader.java b/sonar-core/src/main/java/org/sonar/core/util/DefaultHttpDownloader.java index a61aeb5dc5a..8db9479e61d 100644 --- a/sonar-core/src/main/java/org/sonar/core/util/DefaultHttpDownloader.java +++ b/sonar-core/src/main/java/org/sonar/core/util/DefaultHttpDownloader.java @@ -19,36 +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.slf4j.Logger; -import org.slf4j.LoggerFactory; 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.sonarqube.ws.client.OkHttpClientBuilder; import static org.apache.commons.io.FileUtils.copyInputStreamToFile; import static org.sonar.core.util.FileUtils.deleteQuietly; @@ -60,43 +49,44 @@ import static org.sonar.core.util.FileUtils.deleteQuietly; */ public class DefaultHttpDownloader extends HttpDownloader { - private static final Logger LOG = LoggerFactory.getLogger(DefaultHttpDownloader.class); - 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 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 @@ -111,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); } @@ -125,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); } @@ -147,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 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 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 proxies = proxySelector.select(uri); - if (proxies.size() == 1 && proxies.get(0).type().equals(Proxy.Type.DIRECT)) { - return "no proxy"; - } - - List 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 { - LOG.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); } } diff --git a/sonar-core/src/main/java/org/sonar/core/util/HttpsTrust.java b/sonar-core/src/main/java/org/sonar/core/util/HttpsTrust.java deleted file mode 100644 index 2fb2b235c92..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/util/HttpsTrust.java +++ /dev/null @@ -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 - } - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/util/HttpsTrustTest.java b/sonar-core/src/test/java/org/sonar/core/util/HttpsTrustTest.java deleted file mode 100644 index 85e1f57894f..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/util/HttpsTrustTest.java +++ /dev/null @@ -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(); - } -} -- 2.39.5