aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-core/src/main
diff options
context:
space:
mode:
authorWojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com>2023-06-15 07:32:29 +0200
committersonartech <sonartech@sonarsource.com>2023-06-20 13:10:17 +0000
commite8305b73e2ebc8fa5c555a88bf9a9f94f4a135c2 (patch)
tree2dc40ae7f6639a36a1fbc88cea92fee992f20693 /sonar-core/src/main
parent1235c887b45a26e03824a1d49aa4a0bf7c0ad119 (diff)
downloadsonarqube-e8305b73e2ebc8fa5c555a88bf9a9f94f4a135c2.tar.gz
sonarqube-e8305b73e2ebc8fa5c555a88bf9a9f94f4a135c2.zip
SONAR-19597 Security fix (SSF-420)
Diffstat (limited to 'sonar-core/src/main')
-rw-r--r--sonar-core/src/main/java/org/sonar/core/util/DefaultHttpDownloader.java248
-rw-r--r--sonar-core/src/main/java/org/sonar/core/util/HttpsTrust.java99
2 files changed, 41 insertions, 306 deletions
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 85c56de88be..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,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);
}
}
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
- }
- }
-}