package com.statscollector.gerrit.dao;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.statscollector.application.config.AbstractWebConfig;
import com.statscollector.application.dao.AbstractWebDao;
import com.statscollector.gerrit.config.GerritConfig;
/**
* I'm a DAO (actually more of a http client) that accesses Gerrit data using
* simple http requests.
*
* @author JCannon
*
*/
@Repository
public class GerritDao extends AbstractWebDao {
private static final String HTTP_SCHEME = "http";
private static final String ALL_CHANGES_REST_URL = "/a/changes/";
private final static String QUERY = "q";
private static final String DETAIL_URL_END = "/detail";
private static final String BASE_STATUS_STRING = "status:";
private static final Object UNAUTHORIZED = "Unauthorized";
private static final String AUTHENTICATION_HEADER = "WWW-Authenticate";
private static final String NONCE_KEY = "nonce";
private static final String REALM_KEY = "Digest realm";
@Autowired
private GerritConfig gerritConfig;
final static Logger LOGGER = Logger.getLogger(GerritDao.class);
public String testConnection(final CredentialsProvider credentialsProvider) throws Exception {
URIBuilder baseURIBuilder = setupBaseURI(ALL_CHANGES_REST_URL);
baseURIBuilder.setParameter(QUERY, BASE_STATUS_STRING + "merged");
URI uri = baseURIBuilder.build();
return makeHttpRequest(credentialsProvider, uri);
}
/**
* I Return a String containing all changes for all projects, that have the
* provided status, I require a username and password passed in as a
* credentials provider, see:
*
* https://hc.apache.org/httpcomponents-client-ga/httpclient
* /examples/org/apache/http/examples/client/ClientAuthentication.java
*
* For an example
*
* @param credsProvider
* @param changeStatus
* @return result
* @throws Exception
*/
public String getAllChanges(final CredentialsProvider credsProvider, final String changeStatus) throws Exception {
URIBuilder baseURIBuilder = setupBaseURI(ALL_CHANGES_REST_URL);
baseURIBuilder.setParameter(QUERY, BASE_STATUS_STRING + changeStatus);
URI uri = baseURIBuilder.build();
return makeHttpRequest(credsProvider, uri);
}
public String getDetails(final CredentialsProvider credsProvider, final String changeId) throws Exception {
URIBuilder baseURIBuilder = setupBaseURI(ALL_CHANGES_REST_URL + changeId + DETAIL_URL_END);
URI uri = baseURIBuilder.build();
return makeHttpRequest(credsProvider, uri);
}
/**
* I return a URIBuilder with the default http scheme and host and the
* provided request url
*
* @param status
* @param requestUrl
* @return
* @throws URISyntaxException
*/
private URIBuilder setupBaseURI(final String requestUrl) throws URISyntaxException {
return new URIBuilder().setScheme(HTTP_SCHEME).setHost(getTargetHost()).setPath(requestUrl);
}
private String makeHttpRequest(final CredentialsProvider credsProvider, final URI uri) throws Exception {
try (CloseableHttpClient httpclient = HttpClients.custom().build()) {
HttpGet httpGet = new HttpGet(uri);
try (CloseableHttpResponse response = httpclient.execute(httpGet)) {
HttpEntity httpEntity = response.getEntity();
String resultString = EntityUtils.toString(httpEntity);
if(resultString.equals(UNAUTHORIZED)) {
resultString = processResponseWithDigestAuthentication(credsProvider, getConfig().getHost(),
getConfig().getHostPort(), response, uri);
// LOGGER.info("INFO: AUTH FAILED RETURN STRING WAS: " + resultString);
}
EntityUtils.consume(httpEntity);
return resultString;
} catch(Exception e) {
LOGGER.error("ERROR THROWN PROCESSING HTTP REQUEST: " + e.getMessage());
throw e;
}
} catch(Exception e) {
LOGGER.error("ERROR THROWN PROCESSING HTTP REQUEST: " + e.getMessage());
throw e;
}
}
private String processResponseWithDigestAuthentication(final CredentialsProvider credsProvider, final String host,
final int port, final CloseableHttpResponse notAuthorizedResponse, final URI uri) throws Exception {
HttpHost httpHost = new HttpHost(host, port, "http");
Map<String, String> authenticationElementsMap = getAuthenticationElements(notAuthorizedResponse);
String nonceValue = authenticationElementsMap.get(NONCE_KEY);
String realmValue = authenticationElementsMap.get(REALM_KEY);
DigestScheme digestScheme = new DigestScheme();
digestScheme.overrideParamter(REALM_KEY, realmValue);
digestScheme.overrideParamter(NONCE_KEY, nonceValue);
if(realmValue.isEmpty()) {
LOGGER.error("WARNING NO REALM VALUE");
}
BasicAuthCache authCache = new BasicAuthCache();
authCache.put(httpHost, digestScheme);
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
context.setAuthCache(authCache);
HttpGet httpGet = new HttpGet(uri);
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
try (CloseableHttpResponse response = httpclient.execute(httpHost, httpGet, context)) {
HttpEntity httpEntity = response.getEntity();
String resultString = EntityUtils.toString(httpEntity);
if(resultString.equals(UNAUTHORIZED)) {
LOGGER.error("Failed To Authenticate With Digest Auth, Giving Up !, Result String Was: "
+ resultString);
return "Failed to authenticate Digest";
}
EntityUtils.consume(httpEntity);
return resultString;
} catch(Exception e) {
LOGGER.error("ERROR THROWN PROCESSING HTTP REQUEST: " + e.getMessage());
throw e;
}
} catch(Exception e) {
LOGGER.error("ERROR THROWN PROCESSING HTTP REQUEST: " + e.getMessage());
throw e;
}
}
private Map<String, String> getAuthenticationElements(final CloseableHttpResponse response) {
Header[] authenticationHeaders = response.getHeaders(AUTHENTICATION_HEADER);
if(authenticationHeaders.length != 1) {
throw new RuntimeException("Cannot handle multiple http authentication headers!");
}
Header authenticationHeader = authenticationHeaders[0];
HeaderElement[] authenticationElements = authenticationHeader.getElements();
Map<String, String> authenticationElementsMap = new HashMap<>();
for(HeaderElement headerElement : authenticationElements) {
authenticationElementsMap.put(headerElement.getName(), headerElement.getValue());
}
return authenticationElementsMap;
}
public void setGerritConfig(final GerritConfig gerritConfig) {
this.gerritConfig = gerritConfig;
}
@Override
protected AbstractWebConfig getConfig() {
return gerritConfig;
}
}