package com.hybris.hyeclipse.hac.manager; import java.io.IOException; import java.net.ConnectException; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.stream.Collectors; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.auth.AuthenticationException; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.HttpResponseException; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustStrategy; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.ssl.SSLContexts; import org.eclipse.jface.preference.IPreferenceStore; import org.jsoup.Jsoup; import org.jsoup.helper.StringUtil; import org.jsoup.nodes.Document; import com.hybris.hyeclipse.commons.utils.ConsoleUtils; import com.hybris.hyeclipse.commons.utils.CharactersConstants; import com.hybris.hyeclipse.hac.Activator; import com.hybris.hyeclipse.hac.preferences.HACPreferenceConstants; /** * Abstract class to communicate with hAC web page. */ public abstract class AbstractHACCommunicationManager { /** * HAC authentication properties */ protected interface Authentication { static final String LOCATION_ERROR = "error"; static final String LOCATION_HEADER = "Location"; interface Parameters { static final String USERNAME = "j_username"; static final String PASSWORD = "j_password"; } interface Path { static final String LOGIN = "/j_spring_security_check"; static final String LOGOUT = "/j_spring_security_logout"; } } /** * HAC communication meta data properties */ protected interface Meta { static final String X_CSRF_TOKEN = "X-CSRF-Token"; static final String CSRF_META_TAG_CONTENT = "content"; static final String CSRF_META_TAG = "meta[name='_csrf']"; static final int NOT_FOUND_STATUS_CODE = 404; } /** * Error messages for communication with HAC */ protected interface ErrorMessage { static final String SERVER_RESPONSE = "Server response: "; static final String INVALID_HAC_URL = "HAC URL is invalid"; static final String WRONG_CREDENTIALS = " Wrong login credentials."; static final String CANNOT_CREATE_SSL_SOCKET = "Cannot create SSL socket"; static final String UNKNOWN_HOST_EXCEPTION_MESSAGE_FORMAT = "Host: $1%s is unreachable"; static final String CSRF_RESPONSE_CANNOT_BE_BLANK = "HAC authentication response cannot be empty."; static final String CSRF_TOKEN_CANNOT_BE_OBTAINED = "Cannot obtain CSRF authentication token from HAC."; } /** * Maximum time in seconds to wait for response */ private int timeout; /** * hAC user name */ private String username; /** * hAC user's password */ private String password; /** * csrf token received from hAC page */ private String csrfToken; /** * hAC URL */ private String endpointUrl; /* HTTP communication properties */ private HttpClientContext context; private final HttpClient httpClient; public AbstractHACCommunicationManager() { httpClient = getSSLAcceptingClient(); context = HttpClientContext.create(); context.setCookieStore(new BasicCookieStore()); } /** * Check whether HAC is up * * @return true if HAC is online, false otherwise */ public boolean checkHacHealth() { final String response = sendAuthenticatedGetRequest(CharactersConstants.EMPTY_STRING); return !StringUtil.isBlank(response); } /** * Creates {@link HttpClient} that trusts any SSL certificate * * @return prepared HTTP client */ protected HttpClient getSSLAcceptingClient() { final TrustStrategy trustAllStrategy = (final X509Certificate[] chain, final String authType) -> true; try { final SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, trustAllStrategy).build(); sslContext.init(null, getTrustManager(), new SecureRandom()); final SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier()); return HttpClients.custom().setSSLSocketFactory(connectionSocketFactory).build(); } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException error) { ConsoleUtils.printError(error.getMessage()); throw new IllegalStateException(ErrorMessage.CANNOT_CREATE_SSL_SOCKET, error); } } /** * Creates {@link TrustManager} that trusts any SSL certificate * * @return prepared TrustManager */ protected TrustManager[] getTrustManager() { final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted(final java.security.cert.X509Certificate[] certs, final String authType) { } @Override public void checkServerTrusted(final java.security.cert.X509Certificate[] certs, final String authType) { } } }; return trustAllCerts; } /** * Send HTTP GET request to {@link #endpointUrl}, updates {@link #csrfToken} * token * * @return true if {@link #endpointUrl} is accessible * @throws IOException * @throws ClientProtocolException * @throws AuthenticationException */ protected void fetchCsrfTokenFromHac() throws ClientProtocolException, IOException, AuthenticationException { final HttpGet getRequest = new HttpGet(getEndpointUrl()); try { final HttpResponse response = httpClient.execute(getRequest, getContext()); final String responseString = new BasicResponseHandler().handleResponse(response); csrfToken = getCsrfToken(responseString); if (StringUtil.isBlank(csrfToken)) { throw new AuthenticationException(ErrorMessage.CSRF_TOKEN_CANNOT_BE_OBTAINED); } } catch (UnknownHostException error) { final String errorMessage = error.getMessage(); final Matcher matcher = HACPreferenceConstants.HOST_REGEXP_PATTERN.matcher(getEndpointUrl()); if (matcher.find() && matcher.group(1).equals(errorMessage)) { throw new UnknownHostException( String.format(ErrorMessage.UNKNOWN_HOST_EXCEPTION_MESSAGE_FORMAT, matcher.group(1))); } throw error; } } /** * Retrieves csrf token from response body * * @param responseBody * response body of GET method * @return csrf token * @throws AuthenticationException */ protected String getCsrfToken(String responseBody) throws AuthenticationException { if (StringUtil.isBlank(responseBody)) { throw new AuthenticationException(ErrorMessage.CSRF_RESPONSE_CANNOT_BE_BLANK); } final Document document = Jsoup.parse(responseBody); return document.select(Meta.CSRF_META_TAG).attr(Meta.CSRF_META_TAG_CONTENT); } /** * Send HTTP POST request to {@link #endpointUrl}, logins to HAC * * @throws IOException * @throws ClientProtocolException */ protected void loginToHac() throws ClientProtocolException, IOException { final HttpPost postRequest = new HttpPost(endpointUrl + Authentication.Path.LOGIN); final Map<String, String> parameters = new HashMap<>(); parameters.put(Authentication.Parameters.USERNAME, username); parameters.put(Authentication.Parameters.PASSWORD, password); postRequest.addHeader(Meta.X_CSRF_TOKEN, csrfToken); postRequest.setEntity(new UrlEncodedFormEntity(createParametersList(parameters))); final HttpResponse response = httpClient.execute(postRequest, context); final Header[] locationHeaders = response.getHeaders(Authentication.LOCATION_HEADER); if (Meta.NOT_FOUND_STATUS_CODE == response.getStatusLine().getStatusCode()) { throw new IOException(ErrorMessage.INVALID_HAC_URL); } else if (locationHeaders.length > 0 && locationHeaders[0].getValue().contains(Authentication.LOCATION_ERROR)) { throw new IOException(ErrorMessage.WRONG_CREDENTIALS); } } /** * Send HTTP POST request to {@link #endpointUrl}, logouts from HAC * * @throws IOException * @throws ClientProtocolException */ protected void logoutFromHac() throws ClientProtocolException, IOException { final HttpPost post = new HttpPost(endpointUrl + Authentication.Path.LOGOUT); post.addHeader(Meta.X_CSRF_TOKEN, csrfToken); httpClient.execute(post, context); } /** * Creates a parameters list from provided map * * @param parametersMap * parameters map to convert * @return List of parameters */ protected List<NameValuePair> createParametersList(final Map<String, String> parametersMap) { return parametersMap.entrySet() .stream() .map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue())) .collect(Collectors.toList()); } /** * Updates the {@link #endpointUrl}, {@link #timeout}, {@link #username} and * {@link #password} basing on the data provided to preference page */ protected void updateLoginVariables() { final IPreferenceStore store = Activator.getDefault().getPreferenceStore(); username = store.getString(HACPreferenceConstants.P_USERNAME); password = store.getString(HACPreferenceConstants.P_PASSWORD); timeout = store.getInt(HACPreferenceConstants.P_TIMEOUT) * 1000; endpointUrl = store.getString(HACPreferenceConstants.P_HOSTNAME_URL); } /** * Send post request to the {@link #getEndpointUrl()} with suffix as a url * parameter. * * @param url * suffix to the {@link #getEndpointUrl()}. * @param parameters * map of parameters that will be attached to the request * @return response of the request * @throws HttpResponseException * @throws IOException */ protected String sendPostRequest(final String url, final Map<String, String> parameters) throws HttpResponseException, IOException { final HttpPost postRequest = new HttpPost(getEndpointUrl() + url); final RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(getTimeout()).build(); postRequest.setConfig(requestConfig); postRequest.addHeader(getxCsrfToken(), getCsrfToken()); postRequest.setEntity(new UrlEncodedFormEntity(createParametersList(parameters))); final HttpResponse response = getHttpClient().execute(postRequest, getContext()); final String responseBody = new BasicResponseHandler().handleResponse(response); return responseBody; } /** * Send get request to the {@link #getEndpointUrl()} with suffx as a URL * parameter. * * @param url * suffix to the {@link #getEndpointUrl()} * @return response of the request * @throws HttpResponseException * @throws IOException */ protected String sendGetRequest(final String url) throws HttpResponseException, IOException { final HttpGet getRequest = new HttpGet(getEndpointUrl() + url); final RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(getTimeout()).build(); getRequest.setConfig(requestConfig); getRequest.addHeader(getxCsrfToken(), getCsrfToken()); final HttpResponse response = getHttpClient().execute(getRequest, getContext()); final String responseBody = new BasicResponseHandler().handleResponse(response); return responseBody; } /** * Send post request to the {@link #getEndpointUrl()} with suffix as a url * parameter. * * @param url * suffix to the {@link #getEndpointUrl()}. * @param parameters * map of parameters that will be attached to the request * @return response of the request */ protected String sendAuthenticatedPostRequest(final String url, final Map<String, String> parameters) { String response = null; updateLoginVariables(); try { fetchCsrfTokenFromHac(); loginToHac(); fetchCsrfTokenFromHac(); response = sendPostRequest(url, parameters); logoutFromHac(); } catch (ConnectException | IllegalArgumentException error) { ConsoleUtils.printError(error.getMessage()); } catch (final IOException | AuthenticationException error) { ConsoleUtils.printError(ErrorMessage.SERVER_RESPONSE + error.getMessage()); } return response; } /** * Send get request to the {@link #getEndpointUrl()} with suffx as a URL * parameter. * * @param url * suffix to the {@link #getEndpointUrl()} * @return response of the request */ protected String sendAuthenticatedGetRequest(final String url) { String response = null; updateLoginVariables(); try { fetchCsrfTokenFromHac(); loginToHac(); fetchCsrfTokenFromHac(); response = sendGetRequest(url); logoutFromHac(); } catch (ConnectException | IllegalArgumentException error) { ConsoleUtils.printError(error.getMessage()); } catch (final IOException | AuthenticationException error) { ConsoleUtils.printError(ErrorMessage.SERVER_RESPONSE + error.getMessage()); } return response; } protected static String getxCsrfToken() { return Meta.X_CSRF_TOKEN; } protected HttpClient getHttpClient() { return httpClient; } protected HttpClientContext getContext() { return context; } protected String getEndpointUrl() { return endpointUrl; } protected String getUsername() { return username; } protected String getPassword() { return password; } protected String getCsrfToken() { return csrfToken; } protected int getTimeout() { return timeout; } protected String getEncoding() { return CharactersConstants.UTF_8_ENCODING; } }