2019-10-07 13:18:16 +02:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
|
|
|
*
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package ch.ethz.seb.sebserver;
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileNotFoundException;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.security.KeyManagementException;
|
|
|
|
import java.security.KeyStoreException;
|
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
import java.security.cert.CertificateException;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
import javax.net.ssl.SSLContext;
|
|
|
|
|
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
|
import org.apache.http.HttpHost;
|
|
|
|
import org.apache.http.auth.AuthScope;
|
|
|
|
import org.apache.http.auth.UsernamePasswordCredentials;
|
|
|
|
import org.apache.http.client.CredentialsProvider;
|
|
|
|
import org.apache.http.client.HttpClient;
|
2019-11-13 13:26:28 +01:00
|
|
|
import org.apache.http.client.config.RequestConfig;
|
2019-10-07 13:18:16 +02:00
|
|
|
import org.apache.http.conn.ssl.TrustAllStrategy;
|
|
|
|
import org.apache.http.impl.client.BasicCredentialsProvider;
|
|
|
|
import org.apache.http.impl.client.HttpClientBuilder;
|
|
|
|
import org.apache.http.impl.client.HttpClients;
|
|
|
|
import org.apache.http.impl.client.ProxyAuthenticationStrategy;
|
|
|
|
import org.apache.http.ssl.SSLContextBuilder;
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
2019-11-27 10:38:04 +01:00
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
2019-12-11 12:40:49 +01:00
|
|
|
import org.springframework.context.annotation.Lazy;
|
2019-10-07 13:18:16 +02:00
|
|
|
import org.springframework.core.env.Environment;
|
|
|
|
import org.springframework.http.client.ClientHttpRequestFactory;
|
|
|
|
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import org.springframework.util.CollectionUtils;
|
|
|
|
import org.springframework.util.ResourceUtils;
|
|
|
|
|
2019-12-11 12:40:49 +01:00
|
|
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
|
|
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
2019-10-07 13:18:16 +02:00
|
|
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
2019-11-13 13:26:28 +01:00
|
|
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
|
|
|
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
|
|
|
|
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ProxyData;
|
2019-10-07 13:18:16 +02:00
|
|
|
|
2019-12-11 12:40:49 +01:00
|
|
|
@Lazy
|
2019-10-07 13:18:16 +02:00
|
|
|
@Service
|
2019-12-11 12:40:49 +01:00
|
|
|
@WebServiceProfile
|
|
|
|
@GuiProfile
|
2019-10-07 13:18:16 +02:00
|
|
|
public class ClientHttpRequestFactoryService {
|
|
|
|
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(ClientHttpRequestFactoryService.class);
|
|
|
|
|
|
|
|
private static final Collection<String> DEV_PROFILES = Arrays.asList("dev-gui", "test", "demo", "dev-ws");
|
|
|
|
private static final Collection<String> PROD_PROFILES = Arrays.asList("prod-gui", "prod-ws");
|
|
|
|
|
2019-11-27 10:38:04 +01:00
|
|
|
private final int connectTimeout;
|
|
|
|
private final int connectionRequestTimeout;
|
|
|
|
private final int readTimeout;
|
|
|
|
|
2019-10-07 13:18:16 +02:00
|
|
|
private final Environment environment;
|
2019-11-13 13:26:28 +01:00
|
|
|
private final ClientCredentialService clientCredentialService;
|
|
|
|
|
|
|
|
public ClientHttpRequestFactoryService(
|
|
|
|
final Environment environment,
|
2019-11-27 10:38:04 +01:00
|
|
|
final ClientCredentialService clientCredentialService,
|
|
|
|
@Value("${sebserver.http.client.connect-timeout:15000}") final int connectTimeout,
|
2019-11-27 12:08:48 +01:00
|
|
|
@Value("${sebserver.http.client.connection-request-timeout:20000}") final int connectionRequestTimeout,
|
2019-11-27 10:38:04 +01:00
|
|
|
@Value("${sebserver.http.client.read-timeout:10000}") final int readTimeout) {
|
2019-10-07 13:18:16 +02:00
|
|
|
|
|
|
|
this.environment = environment;
|
2019-11-13 13:26:28 +01:00
|
|
|
this.clientCredentialService = clientCredentialService;
|
2019-11-27 10:38:04 +01:00
|
|
|
this.connectTimeout = connectTimeout;
|
|
|
|
this.connectionRequestTimeout = connectionRequestTimeout;
|
|
|
|
this.readTimeout = readTimeout;
|
2019-10-07 13:18:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public Result<ClientHttpRequestFactory> getClientHttpRequestFactory() {
|
|
|
|
return getClientHttpRequestFactory(null);
|
|
|
|
}
|
|
|
|
|
2019-10-07 13:46:15 +02:00
|
|
|
public Result<ClientHttpRequestFactory> getClientHttpRequestFactory(final ProxyData proxy) {
|
2019-10-07 13:18:16 +02:00
|
|
|
return Result.tryCatch(() -> {
|
|
|
|
final List<String> activeProfiles = Arrays.asList(this.environment.getActiveProfiles());
|
|
|
|
if (CollectionUtils.containsAny(activeProfiles, DEV_PROFILES)) {
|
|
|
|
return clientHttpRequestFactory(proxy);
|
|
|
|
} else if (CollectionUtils.containsAny(activeProfiles, PROD_PROFILES)) {
|
|
|
|
return clientHttpRequestFactoryTLS(proxy);
|
|
|
|
} else {
|
|
|
|
throw new IllegalStateException("Unknown or invalid Spring profile setup: " + activeProfiles);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/** A ClientHttpRequestFactory for development profile with no TSL SSL protocol and
|
|
|
|
* not following redirects on redirect responses.
|
|
|
|
*
|
|
|
|
* @return ClientHttpRequestFactory bean for development profiles */
|
2019-10-07 13:46:15 +02:00
|
|
|
private ClientHttpRequestFactory clientHttpRequestFactory(final ProxyData proxy) {
|
2019-10-07 13:18:16 +02:00
|
|
|
|
2019-10-28 14:47:38 +01:00
|
|
|
if (log.isDebugEnabled()) {
|
|
|
|
log.debug("Initialize ClientHttpRequestFactory with insecure ClientHttpRequestFactory for development");
|
|
|
|
}
|
2019-10-07 13:18:16 +02:00
|
|
|
|
2019-11-13 13:26:28 +01:00
|
|
|
if (proxy != null) {
|
2019-10-07 13:18:16 +02:00
|
|
|
|
2019-10-28 14:47:38 +01:00
|
|
|
if (log.isDebugEnabled()) {
|
2019-11-13 13:26:28 +01:00
|
|
|
log.debug("Initialize ClientHttpRequestFactory with proxy: {}", proxy);
|
2019-10-28 14:47:38 +01:00
|
|
|
}
|
2019-10-07 13:18:16 +02:00
|
|
|
|
2019-11-13 13:26:28 +01:00
|
|
|
final HttpComponentsClientHttpRequestFactory factory =
|
|
|
|
new HttpComponentsClientHttpRequestFactory();
|
2019-10-07 13:18:16 +02:00
|
|
|
factory.setHttpClient(this.createProxiedClient(proxy, null));
|
2019-10-17 16:53:33 +02:00
|
|
|
factory.setBufferRequestBody(false);
|
2019-11-27 10:38:04 +01:00
|
|
|
factory.setConnectionRequestTimeout(this.connectionRequestTimeout);
|
|
|
|
factory.setConnectTimeout(this.connectTimeout);
|
|
|
|
factory.setReadTimeout(this.readTimeout);
|
2019-10-07 13:18:16 +02:00
|
|
|
return factory;
|
|
|
|
|
|
|
|
} else {
|
2019-11-13 13:26:28 +01:00
|
|
|
|
2019-10-17 16:53:33 +02:00
|
|
|
final HttpComponentsClientHttpRequestFactory devClientHttpRequestFactory =
|
|
|
|
new HttpComponentsClientHttpRequestFactory();
|
|
|
|
|
|
|
|
devClientHttpRequestFactory.setBufferRequestBody(false);
|
2019-11-27 10:38:04 +01:00
|
|
|
devClientHttpRequestFactory.setConnectionRequestTimeout(this.connectionRequestTimeout);
|
|
|
|
devClientHttpRequestFactory.setConnectTimeout(this.connectTimeout);
|
|
|
|
devClientHttpRequestFactory.setReadTimeout(this.readTimeout);
|
2019-10-07 13:18:16 +02:00
|
|
|
return devClientHttpRequestFactory;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** A ClientHttpRequestFactory used in production with TSL SSL configuration.
|
|
|
|
*
|
|
|
|
* @return ClientHttpRequestFactory with TLS / SSL configuration
|
|
|
|
* @throws IOException
|
|
|
|
* @throws FileNotFoundException
|
|
|
|
* @throws CertificateException
|
|
|
|
* @throws KeyStoreException
|
|
|
|
* @throws NoSuchAlgorithmException
|
|
|
|
* @throws KeyManagementException */
|
2019-10-07 13:46:15 +02:00
|
|
|
private ClientHttpRequestFactory clientHttpRequestFactoryTLS(final ProxyData proxy) throws KeyManagementException,
|
2019-10-07 13:18:16 +02:00
|
|
|
NoSuchAlgorithmException, KeyStoreException, CertificateException, FileNotFoundException, IOException {
|
|
|
|
|
2019-10-28 14:47:38 +01:00
|
|
|
if (log.isDebugEnabled()) {
|
|
|
|
log.debug("Initialize with secure ClientHttpRequestFactory for production");
|
|
|
|
}
|
2019-10-07 13:18:16 +02:00
|
|
|
|
|
|
|
final String truststoreFilePath = this.environment
|
|
|
|
.getProperty("server.ssl.trust-store", "");
|
|
|
|
|
|
|
|
SSLContext sslContext = null;
|
|
|
|
if (StringUtils.isBlank(truststoreFilePath)) {
|
|
|
|
|
2019-10-28 14:47:38 +01:00
|
|
|
if (log.isDebugEnabled()) {
|
|
|
|
log.debug("Securing outgoing calls without trust-store by trusting all certificates");
|
|
|
|
}
|
2019-10-07 13:18:16 +02:00
|
|
|
|
|
|
|
sslContext = org.apache.http.ssl.SSLContexts
|
|
|
|
.custom()
|
|
|
|
.loadTrustMaterial(null, new TrustAllStrategy())
|
|
|
|
.build();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2019-10-28 14:47:38 +01:00
|
|
|
if (log.isDebugEnabled()) {
|
|
|
|
log.debug("Securing with defined trust-store");
|
|
|
|
}
|
2019-10-07 13:18:16 +02:00
|
|
|
|
|
|
|
final File trustStoreFile = ResourceUtils.getFile("file:" + truststoreFilePath);
|
|
|
|
|
|
|
|
final char[] password = this.environment
|
|
|
|
.getProperty("server.ssl.trust-store-password", "")
|
|
|
|
.toCharArray();
|
|
|
|
|
|
|
|
if (password.length < 3) {
|
|
|
|
log.error("Missing or incorrect trust-store password: " + String.valueOf(password));
|
|
|
|
throw new IllegalArgumentException("Missing or incorrect trust-store password");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the specified trust-store also on javax.net.ssl level
|
|
|
|
System.setProperty("javax.net.ssl.trustStore", truststoreFilePath);
|
|
|
|
System.setProperty("javax.net.ssl.trustStorePassword", String.valueOf(password));
|
|
|
|
|
|
|
|
sslContext = SSLContextBuilder
|
|
|
|
.create()
|
|
|
|
.loadTrustMaterial(trustStoreFile, password)
|
2019-10-08 14:21:46 +02:00
|
|
|
.setKeyStoreType(this.environment.getProperty(
|
|
|
|
"server.ssl.key-store-type",
|
|
|
|
"pkcs12"))
|
2019-10-07 13:18:16 +02:00
|
|
|
.build();
|
|
|
|
}
|
|
|
|
|
2019-11-13 13:26:28 +01:00
|
|
|
if (proxy != null) {
|
2019-10-07 13:18:16 +02:00
|
|
|
|
2019-10-28 14:47:38 +01:00
|
|
|
if (log.isDebugEnabled()) {
|
2019-11-13 13:26:28 +01:00
|
|
|
log.debug("Initialize ClientHttpRequestFactory with proxy: {}", proxy);
|
2019-10-28 14:47:38 +01:00
|
|
|
}
|
2019-10-07 13:18:16 +02:00
|
|
|
|
|
|
|
final HttpClient client = createProxiedClient(proxy, sslContext);
|
|
|
|
return new HttpComponentsClientHttpRequestFactory(client);
|
|
|
|
} else {
|
|
|
|
|
|
|
|
final HttpClient client = HttpClients.custom()
|
|
|
|
.setSSLContext(sslContext)
|
|
|
|
.build();
|
2019-11-27 10:38:04 +01:00
|
|
|
final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(client);
|
|
|
|
factory.setConnectionRequestTimeout(this.connectionRequestTimeout);
|
|
|
|
factory.setConnectTimeout(this.connectTimeout);
|
|
|
|
factory.setReadTimeout(this.readTimeout);
|
|
|
|
return factory;
|
2019-10-07 13:18:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-07 13:46:15 +02:00
|
|
|
private HttpClient createProxiedClient(final ProxyData proxy, final SSLContext sslContext) {
|
2019-10-07 13:18:16 +02:00
|
|
|
|
2019-11-13 13:26:28 +01:00
|
|
|
final HttpHost httpHost = new HttpHost(
|
|
|
|
proxy.proxyName,
|
|
|
|
proxy.proxyPort);
|
2019-10-07 13:18:16 +02:00
|
|
|
|
|
|
|
final HttpClientBuilder clientBuilder = HttpClients
|
|
|
|
.custom()
|
|
|
|
.useSystemProperties()
|
2019-11-13 13:26:28 +01:00
|
|
|
.setProxy(httpHost)
|
|
|
|
|
|
|
|
.setDefaultRequestConfig(RequestConfig
|
|
|
|
.custom()
|
|
|
|
.setRedirectsEnabled(true)
|
|
|
|
.setCircularRedirectsAllowed(true)
|
|
|
|
.build())
|
2019-10-07 13:18:16 +02:00
|
|
|
.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy());
|
|
|
|
|
2019-11-13 13:26:28 +01:00
|
|
|
if (proxy.clientCredentials != null && StringUtils.isNotBlank(proxy.clientCredentials.clientId)) {
|
|
|
|
final CredentialsProvider credsProvider = new BasicCredentialsProvider();
|
|
|
|
final String plainClientId = proxy.clientCredentials.clientIdAsString();
|
|
|
|
final String plainClientSecret = Utils.toString(this.clientCredentialService
|
|
|
|
.getPlainClientSecret(proxy.clientCredentials));
|
|
|
|
|
|
|
|
credsProvider.setCredentials(
|
|
|
|
AuthScope.ANY,
|
|
|
|
new UsernamePasswordCredentials(plainClientId, plainClientSecret));
|
|
|
|
|
|
|
|
clientBuilder.setDefaultCredentialsProvider(credsProvider);
|
|
|
|
}
|
|
|
|
|
2019-10-07 13:18:16 +02:00
|
|
|
if (sslContext != null) {
|
|
|
|
clientBuilder.setSSLContext(sslContext);
|
|
|
|
}
|
|
|
|
|
|
|
|
return clientBuilder.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|