All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.openshift.internal.client.httpclient.UrlConnectionHttpClient Maven / Gradle / Ivy

/******************************************************************************* 
 * Copyright (c) 2013-2014 Red Hat, Inc. 
 * Distributed under license by Red Hat, Inc. All rights reserved. 
 * This program is made available under the terms of the 
 * Eclipse Public License v1.0 which accompanies this distribution, 
 * and is available at http://www.eclipse.org/legal/epl-v10.html 
 * 
 * Contributors: 
 * Red Hat, Inc. - initial API and implementation 
 ******************************************************************************/
package com.openshift.internal.client.httpclient;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.ProtocolException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.openshift.client.HttpMethod;
import com.openshift.client.IHttpClient;
import com.openshift.client.utils.Base64Coder;
import com.openshift.client.utils.SSLUtils;
import com.openshift.internal.client.httpclient.request.IMediaType;
import com.openshift.internal.client.httpclient.request.Parameter;
import com.openshift.internal.client.httpclient.request.ParameterValueMap;
import com.openshift.internal.client.utils.StreamUtils;
import com.openshift.internal.client.utils.StringUtils;

/**
 * @author Andre Dietisheim
 * @author Nicolas Spano
 * @author Corey Daley
 * @author Sean Kavanagh
 */
public class UrlConnectionHttpClient implements IHttpClient {

	private static final Logger LOGGER = LoggerFactory.getLogger(UrlConnectionHttpClient.class);

	protected String userAgent;
	protected String username;
	protected String password;
	protected String authKey;
	protected String authIV;
	protected String token;
	protected String acceptedMediaType;
	protected String acceptedVersion;
	protected ISSLCertificateCallback sslAuthorizationCallback;
	protected Integer configTimeout;
	private String excludedSSLCipherRegex;

	public UrlConnectionHttpClient(
			String username, String password, String userAgent, String acceptedMediaType, String version) {
		this(username, password, userAgent, acceptedMediaType, version, null, null);
	}

	public UrlConnectionHttpClient(
			String username, String password, String userAgent, String acceptedMediaType, String version, String authKey, String authIV) {
		this(username, password, userAgent, acceptedMediaType, version, authKey, authIV, null,null, null, null);
	}

	public UrlConnectionHttpClient(String username, String password, String userAgent, String acceptedMediaType,
			String version, String authKey, String authIV, String token, ISSLCertificateCallback callback, Integer configTimeout, String excludedSSLCipherRegex) {
		// TODO: separate auth strategies in UrlConnectionHttpClient
		this.username = username;
		this.password = password;
		this.userAgent = userAgent;
		this.acceptedMediaType = acceptedMediaType;
		this.acceptedVersion = version;
		this.authKey = authKey;
		this.authIV = authIV;
		this.token = token;
		this.sslAuthorizationCallback = callback;
		this.configTimeout = configTimeout;
		this.excludedSSLCipherRegex = excludedSSLCipherRegex;
	}

	@Override
	public String get(URL url, int timeout) throws HttpClientException, SocketTimeoutException {
		return request(HttpMethod.GET, url, null, timeout);
	}

	@Override
	public String head(URL url, int timeout) throws HttpClientException, SocketTimeoutException {
		return request(HttpMethod.HEAD, url, null, timeout);
	}

	@Override
	public String put(URL url, IMediaType mediaType, int timeout, Parameter... parameters)
			throws HttpClientException, SocketTimeoutException, EncodingException {
		return request(HttpMethod.PUT, url, mediaType, timeout, parameters);
	}

	@Override
	public String post(URL url, IMediaType mediaType, int timeout, Parameter... parameters)
			throws HttpClientException, SocketTimeoutException, EncodingException {
		return request(HttpMethod.POST, url, mediaType, timeout, parameters);
	}

	@Override
	public String patch(URL url, IMediaType mediaType, int timeout, Parameter... parameters)
			throws HttpClientException, SocketTimeoutException, EncodingException {
		return request(HttpMethod.PATCH, url, mediaType, timeout, parameters);
	}

	@Override
	public String delete(URL url, IMediaType mediaType, int timeout, Parameter... parameters)
			throws HttpClientException, SocketTimeoutException, EncodingException {
		return request(HttpMethod.DELETE, url, mediaType, timeout, parameters);
	}

	@Override
	public String delete(URL url, int timeout)
			throws HttpClientException, SocketTimeoutException, EncodingException {
		return delete(url, null, timeout);
	}

	protected String request(HttpMethod httpMethod, URL url, IMediaType requestMediaType, int timeout,
			Parameter... parameters)
			throws SocketTimeoutException, HttpClientException {
		return request(httpMethod, url, requestMediaType, timeout, new ParameterValueMap(parameters));
	}

	protected String request(HttpMethod httpMethod, URL url, IMediaType requestMediaType, int timeout,
			ParameterValueMap parameters)
			throws SocketTimeoutException, HttpClientException {
		HttpURLConnection connection = null;
		try {
			connection = createConnection(
					url, username, password, authKey, authIV, token, userAgent, acceptedVersion, acceptedMediaType, sslAuthorizationCallback, timeout);
			// PATCH not yet supported by JVM
			setRequestMethod(httpMethod, connection);
			if (!parameters.isEmpty()) {
				connection.setDoOutput(true);
				setRequestMediaType(requestMediaType, connection);
				requestMediaType.writeTo(parameters, connection.getOutputStream());
			}
			return StreamUtils.readToString(connection.getInputStream(), StreamUtils.UTF_8);
		} catch (SocketTimeoutException e) {
			throw e;
		} catch (IOException e) {
			throw createException(e, connection);
		} finally {
			disconnect(connection);
		}
	}

	private void setRequestMethod(HttpMethod httpMethod, HttpURLConnection connection) throws ProtocolException {
		if (httpMethod == HttpMethod.PATCH) {
			httpMethod = HttpMethod.POST;
			connection.setRequestProperty("X-Http-Method-Override", "PATCH");
		}
		connection.setRequestMethod(httpMethod.toString());
	}
	
	private void disconnect(HttpURLConnection connection) {
		if (connection != null) {
			connection.disconnect();
		}
	}

	private HttpClientException createException(IOException ioe, HttpURLConnection connection)
			throws SocketTimeoutException {
		try {
			int responseCode = connection.getResponseCode();
			String errorMessage = createErrorMessage(ioe, connection);
			switch (responseCode) {
			case STATUS_INTERNAL_SERVER_ERROR:
				return new InternalServerErrorException(errorMessage, ioe);
			case STATUS_BAD_REQUEST:
				return new BadRequestException(errorMessage, ioe);
			case STATUS_UNAUTHORIZED:
				return new UnauthorizedException(errorMessage, ioe);
			case STATUS_NOT_FOUND:
				return new NotFoundException(errorMessage, ioe);
			default:
				return new HttpClientException(errorMessage, ioe);
			}
		} catch (SocketTimeoutException e) {
			throw e;
		} catch (IOException e) {
			return new HttpClientException(e);
		}
	}

	protected String createErrorMessage(IOException ioe, HttpURLConnection connection) throws IOException {
		String errorMessage = StreamUtils.readToString(connection.getErrorStream());
		if (!StringUtils.isEmpty(errorMessage)) {
			return errorMessage;
		}
		StringBuilder builder = new StringBuilder("Connection to ")
				.append(connection.getURL());
		String reason = connection.getResponseMessage();
		if (!StringUtils.isEmpty(reason)) {
			builder.append(": ").append(reason);
		}
		return builder.toString();
	}

	private boolean isHttps(URL url) {
		return "https".equals(url.getProtocol());
	}

	protected HttpURLConnection createConnection(URL url, String username, String password, String authKey,
			String authIV, String token, String userAgent, String acceptedVersion, String acceptedMediaType,
			ISSLCertificateCallback callback, int timeout)
			throws IOException {
		LOGGER.trace(
                "creating connection to {} using username \"{}\" and password \"{}\" or token \"{}\"",
				new Object[] { url, username, password, token });
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();
		if (isHttps(url)) {
			HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
			SSLContext sslContext = setSSLCallback(sslAuthorizationCallback, url, httpsConnection);
			setFilteredCiphers(excludedSSLCipherRegex, sslContext, httpsConnection);
		}
		setAuthorization(username, password, authKey, authIV, token, connection);
		connection.setUseCaches(false);
		connection.setDoInput(true);
		connection.setAllowUserInteraction(false);
		setConnectTimeout(NO_TIMEOUT, connection);
		setReadTimeout(timeout, connection);
		// wont work when switching http->https
		// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571
		connection.setInstanceFollowRedirects(true);
		setUserAgent(userAgent, authKey, connection);
		setAcceptHeader(acceptedVersion, acceptedMediaType, connection);

		return connection;
	}

	private void setUserAgent(String userAgent, String authKey, HttpURLConnection connection) {
		if (!StringUtils.isEmpty(userAgent)) {
			connection.setRequestProperty(PROPERTY_USER_AGENT, userAgent);
		}
	}

	private void setAcceptHeader(String acceptedVersion, String acceptedMediaType, HttpURLConnection connection) {
		if (StringUtils.isEmpty(acceptedMediaType)) {
			throw new HttpClientException(MessageFormat.format(
					"Accepted media type (ex. {0}) is not defined", MEDIATYPE_APPLICATION_JSON));
		}

		StringBuilder builder = new StringBuilder(acceptedMediaType);
		if (acceptedVersion != null) {
			builder.append(SEMICOLON).append(SPACE)
					.append(VERSION).append(EQUALS).append(acceptedVersion);
		}

		connection.setRequestProperty(PROPERTY_ACCEPT, builder.toString());
	}

	private void setAuthorization(String username, String password, String authKey, String authIV, String token,
			HttpURLConnection connection) {
		if (username == null || username.trim().length() == 0
				|| password == null || password.trim().length() == 0) {
			if (authKey != null && authIV != null) {
				connection.setRequestProperty(PROPERTY_AUTHKEY, authKey);
				connection.setRequestProperty(PROPERTY_AUTHIV, authIV);
			}
			else if(token != null){
                		connection.setRequestProperty(PROPERTY_AUTHORIZATION,
				new StringBuilder().append(AUTHORIZATION_BEARER).append(SPACE).append(token).toString());
			}
		} else {
			String credentials = Base64Coder.encode(
					new StringBuilder().append(username).append(COLON).append(password).toString().getBytes());
			connection.setRequestProperty(PROPERTY_AUTHORIZATION,
					new StringBuilder().append(AUTHORIZATION_BASIC).append(SPACE).append(credentials).toString());
		}
	}

	private SSLContext setSSLCallback(ISSLCertificateCallback sslAuthorizationCallback, URL url, HttpsURLConnection connection) {
		X509TrustManager trustManager = null;
		if (sslAuthorizationCallback != null) {
			connection.setHostnameVerifier(new CallbackHostnameVerifier());
			trustManager = createCallbackTrustManager(sslAuthorizationCallback, connection);
		}

		try {
			SSLContext sslContext = SSLUtils.getSSLContext(trustManager);
			connection.setSSLSocketFactory(sslContext.getSocketFactory());
			return sslContext;
		} catch (GeneralSecurityException e) {
			LOGGER.warn("Could not install trust manager callback", e);;
			return null;
		}
	}

	/**
	 * Returns the callback trustmanager or null if it could not be created.
	 * 
	 * @see ISSLCertificateCallback
	 */
	private X509TrustManager createCallbackTrustManager(ISSLCertificateCallback sslAuthorizationCallback,HttpsURLConnection connection) {
		X509TrustManager trustManager = null;
		try {
			trustManager = getCurrentTrustManager();
			if (trustManager == null) {
				LOGGER.warn("Could not install trust manager callback, no trustmanager was found.", trustManager);
			} else {
				trustManager = new CallbackTrustManager(trustManager, sslAuthorizationCallback);
			}
		} catch (GeneralSecurityException e) {
			LOGGER.warn("Could not install trust manager callback.", e);;
		}
		return trustManager;
	}
		
	/**
	 * Sets a ssl socket factory that sets a filtered list of ciphers based on
	 * the #excludedSSLCipherRegex to the given connection.
	 * 
	 * @param sslContext
	 * 
	 * @param sslContext
	 *            the ssl context that shall be used
	 * @param url
	 *            the url we are connecting to
	 * @param connection
	 *            the connection that the cipher filter shall be applied to
	 */
	protected SSLContext setFilteredCiphers(String excludedSSLCipherRegex, SSLContext sslContext, HttpsURLConnection connection) {
		if (excludedSSLCipherRegex != null) {
			connection.setSSLSocketFactory(
					new EnabledCiphersSSLSocketFactory(
							SSLUtils.filterCiphers(
									excludedSSLCipherRegex, getSupportedCiphers(sslContext)), sslContext
									.getSocketFactory()));
		}
		return sslContext;
	}

	protected String[] getSupportedCiphers(SSLContext sslContext) {
		return sslContext.getSupportedSSLParameters().getCipherSuites();
	}

	private void setConnectTimeout(int timeout, URLConnection connection) {
		if (getTimeout(timeout) != NO_TIMEOUT) {
			connection.setConnectTimeout(getTimeout(timeout));
		}
	}

	private void setReadTimeout(int timeout, URLConnection connection) {
		if (getTimeout(timeout) != NO_TIMEOUT) {
			connection.setReadTimeout(getTimeout(timeout));
		}
	}

	private int getTimeout(int timeout) {
			if (timeout == NO_TIMEOUT) {
				if (configTimeout != null) {
					timeout = this.configTimeout;
				}
			}
		return timeout;
	}

	private void setRequestMediaType(IMediaType mediaType, HttpURLConnection connection) {
		if (mediaType == null
				|| StringUtils.isEmpty(mediaType.getType())) {
			throw new HttpClientException(
					MessageFormat.format("Request media type (ex. {0}) is not defined",
							MEDIATYPE_APPLICATION_FORMURLENCODED));
		}
		connection.setRequestProperty(PROPERTY_CONTENT_TYPE, mediaType.getType());	
	}
	
	private X509TrustManager getCurrentTrustManager() throws NoSuchAlgorithmException, KeyStoreException {
		TrustManagerFactory trustManagerFactory = 
				TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		trustManagerFactory.init((KeyStore) null);

		X509TrustManager x509TrustManager = null;
		for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
			if (trustManager instanceof X509TrustManager) {
				x509TrustManager = (X509TrustManager) trustManager;
				break;
			}
		}
		return x509TrustManager;
	}
	
	@Override
	public void setUserAgent(String userAgent) {
		this.userAgent = userAgent;
	}

	@Override
	public void setAcceptVersion(String version) {
		this.acceptedVersion = version;
	}

	@Override
	public void setAcceptedMediaType(String acceptedMediaType) {
		this.acceptedMediaType = acceptedMediaType;
	}
	
	public class CallbackTrustManager implements X509TrustManager {

		private X509TrustManager trustManager;
		private ISSLCertificateCallback callback;

		private CallbackTrustManager(X509TrustManager currentTrustManager, ISSLCertificateCallback callback) 
				throws NoSuchAlgorithmException, KeyStoreException {
			this.trustManager = currentTrustManager; 
			this.callback = callback;
		}
		
		public X509Certificate[] getAcceptedIssuers() {
			return trustManager.getAcceptedIssuers();
		}

		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			try {
				trustManager.checkServerTrusted(chain, authType);
			} catch (CertificateException e) {
				if (!callback.allowCertificate(chain)) {
					throw e;
				}
			}
		}

		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			trustManager.checkServerTrusted(chain, authType);
		}
	}

	private class CallbackHostnameVerifier implements HostnameVerifier {

		@Override
		public boolean verify(String hostname, SSLSession session) {
			return sslAuthorizationCallback.allowHostname(hostname, session);
		}
	}
	
	/**
	 * SSL socket factory that wraps a given socket factory and sets given ciphers
	 * to the socket that the wrapped factory creates.
	 * 
	 * @see http://stackoverflow.com/questions/6851461/java-why-does-ssl-handshake-give-could-not-generate-dh-keypair-exception/16686994#16686994
	 */
	private static class EnabledCiphersSSLSocketFactory extends SSLSocketFactory {
		
		private String[] enabledCiphers;
		private SSLSocketFactory socketFactory;

		EnabledCiphersSSLSocketFactory(String[] enabledCiphers, SSLSocketFactory socketFactory) {
			this.enabledCiphers = enabledCiphers;
			this.socketFactory = socketFactory;
		}

		@Override
		public Socket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException {
			return setEnabledCiphers((SSLSocket) socketFactory.createSocket(host, port, localHost, localPort));
		}
		
		@Override
		public Socket createSocket(String host, int port, InetAddress localHost, int localPort) 
				throws IOException, UnknownHostException {
			return setEnabledCiphers((SSLSocket) socketFactory.createSocket(host, port, localHost, localPort));
		}
		
		@Override
		public Socket createSocket(InetAddress host, int port) throws IOException {
			return setEnabledCiphers((SSLSocket) socketFactory.createSocket(host, port));
		}
		
		@Override
		public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
			return setEnabledCiphers((SSLSocket) socketFactory.createSocket(host, port));
		}
		
		@Override
		public String[] getSupportedCipherSuites() {
			if (enabledCiphers == null) {
				return socketFactory.getSupportedCipherSuites();
			} else {
				return enabledCiphers;
			}
		}
		
		@Override
		public String[] getDefaultCipherSuites() {
			return socketFactory.getDefaultCipherSuites();
		}
		
		@Override
		public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
			 return setEnabledCiphers((SSLSocket) socketFactory.createSocket(socket, host, port, autoClose));
		}
		
		private SSLSocket setEnabledCiphers(SSLSocket socket) {
			if (enabledCiphers == null) {
				return socket;
			}
			socket.setEnabledCipherSuites(enabledCiphers);
			return socket;
		}
	}
	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy