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;
|
|
|
|
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;
|
|
|
|
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-10-07 13:46:15 +02:00
|
|
|
import ch.ethz.seb.sebserver.gbl.api.ProxyData;
|
|
|
|
import ch.ethz.seb.sebserver.gbl.api.ProxyData.ProxyAuthType;
|
2019-10-07 13:18:16 +02:00
|
|
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
|
|
|
|
|
|
|
@Service
|
|
|
|
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");
|
|
|
|
|
|
|
|
private final Environment environment;
|
|
|
|
|
|
|
|
public ClientHttpRequestFactoryService(final Environment environment) {
|
|
|
|
this.environment = environment;
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
log.info("Initialize ClientHttpRequestFactory with insecure ClientHttpRequestFactory for development");
|
|
|
|
|
2019-10-07 13:54:45 +02:00
|
|
|
if (proxy != null && proxy.proxyAuthType != null && proxy.proxyAuthType != ProxyAuthType.NONE) {
|
2019-10-07 13:18:16 +02:00
|
|
|
|
|
|
|
log.info("Initialize ClientHttpRequestFactory with proxy auth: {} : {}",
|
|
|
|
proxy.proxyAuthType,
|
|
|
|
proxy.proxyName);
|
|
|
|
|
|
|
|
final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
|
|
|
|
factory.setHttpClient(this.createProxiedClient(proxy, null));
|
2019-10-17 16:53:33 +02:00
|
|
|
factory.setBufferRequestBody(false);
|
2019-10-07 13:18:16 +02:00
|
|
|
return factory;
|
|
|
|
|
|
|
|
} else {
|
2019-10-17 16:53:33 +02:00
|
|
|
final HttpComponentsClientHttpRequestFactory devClientHttpRequestFactory =
|
|
|
|
new HttpComponentsClientHttpRequestFactory();
|
|
|
|
|
|
|
|
devClientHttpRequestFactory.setBufferRequestBody(false);
|
|
|
|
|
|
|
|
// final HttpClient httpClient = devClientHttpRequestFactory.getHttpClient();
|
|
|
|
// httpClient.setInstanceFollowRedirects(false);
|
|
|
|
// httpClient.setOutputStreaming(false);
|
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 {
|
|
|
|
|
|
|
|
log.info("Initialize with secure ClientHttpRequestFactory for production");
|
|
|
|
|
|
|
|
final String truststoreFilePath = this.environment
|
|
|
|
.getProperty("server.ssl.trust-store", "");
|
|
|
|
|
|
|
|
SSLContext sslContext = null;
|
|
|
|
if (StringUtils.isBlank(truststoreFilePath)) {
|
|
|
|
|
|
|
|
log.info("Securing outgoing calls without trust-store by trusting all certificates");
|
|
|
|
|
|
|
|
sslContext = org.apache.http.ssl.SSLContexts
|
|
|
|
.custom()
|
|
|
|
.loadTrustMaterial(null, new TrustAllStrategy())
|
|
|
|
.build();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
log.info("Securing with defined trust-store");
|
|
|
|
|
|
|
|
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-10-08 14:21:46 +02:00
|
|
|
if (proxy != null &&
|
|
|
|
proxy.proxyAuthType != null &&
|
|
|
|
proxy.proxyAuthType != ProxyAuthType.NONE) {
|
2019-10-07 13:18:16 +02:00
|
|
|
|
|
|
|
log.info("Initialize ClientHttpRequestFactory with proxy auth: {} : {}",
|
|
|
|
proxy.proxyAuthType,
|
|
|
|
proxy.proxyName);
|
|
|
|
|
|
|
|
final HttpClient client = createProxiedClient(proxy, sslContext);
|
|
|
|
return new HttpComponentsClientHttpRequestFactory(client);
|
|
|
|
} else {
|
|
|
|
|
|
|
|
final HttpClient client = HttpClients.custom()
|
|
|
|
.setSSLContext(sslContext)
|
|
|
|
.build();
|
|
|
|
return new HttpComponentsClientHttpRequestFactory(client);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO set connection and read timeout!? configurable!?
|
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
|
|
|
|
|
|
|
final CredentialsProvider credsProvider = new BasicCredentialsProvider();
|
|
|
|
credsProvider.setCredentials(
|
|
|
|
AuthScope.ANY,
|
|
|
|
new UsernamePasswordCredentials(
|
2019-10-07 13:46:15 +02:00
|
|
|
proxy.getProxyAuthUsernameAsString(),
|
|
|
|
proxy.getProxyAuthSecretAsString()));
|
2019-10-07 13:18:16 +02:00
|
|
|
|
|
|
|
final HttpClientBuilder clientBuilder = HttpClients
|
|
|
|
.custom()
|
|
|
|
.useSystemProperties()
|
|
|
|
.setProxy(new HttpHost(proxy.proxyName,
|
|
|
|
proxy.proxyPort))
|
|
|
|
.setDefaultCredentialsProvider(credsProvider)
|
|
|
|
.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy());
|
|
|
|
|
|
|
|
if (sslContext != null) {
|
|
|
|
clientBuilder.setSSLContext(sslContext);
|
|
|
|
}
|
|
|
|
|
|
|
|
return clientBuilder.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO set connection and read timeout!? configurable!?
|
2019-10-17 16:53:33 +02:00
|
|
|
// private static class DevClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory {
|
|
|
|
//
|
|
|
|
// @Override
|
|
|
|
// protected void prepareConnection(
|
|
|
|
// final HttpURLConnection connection,
|
|
|
|
// final String httpMethod) throws IOException {
|
|
|
|
//
|
|
|
|
// super.prepareConnection(connection, httpMethod);
|
|
|
|
// super.setBufferRequestBody(false);
|
|
|
|
// connection.setInstanceFollowRedirects(false);
|
|
|
|
// }
|
|
|
|
// }
|
2019-10-07 13:18:16 +02:00
|
|
|
|
|
|
|
}
|