SitecatalystHttpClientImpl.java 17.4 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  com.adobe.granite.crypto.CryptoException
 *  com.adobe.granite.crypto.CryptoSupport
 *  com.day.cq.wcm.webservicesupport.Configuration
 *  javax.jcr.RepositoryException
 *  org.apache.commons.httpclient.Header
 *  org.apache.commons.httpclient.HttpClient
 *  org.apache.commons.httpclient.HttpException
 *  org.apache.commons.httpclient.HttpMethod
 *  org.apache.commons.httpclient.URI
 *  org.apache.commons.httpclient.methods.PostMethod
 *  org.apache.commons.httpclient.methods.RequestEntity
 *  org.apache.commons.httpclient.methods.StringRequestEntity
 *  org.apache.felix.scr.annotations.Activate
 *  org.apache.felix.scr.annotations.Component
 *  org.apache.felix.scr.annotations.Deactivate
 *  org.apache.felix.scr.annotations.Properties
 *  org.apache.felix.scr.annotations.Property
 *  org.apache.felix.scr.annotations.Reference
 *  org.apache.felix.scr.annotations.Service
 *  org.apache.sling.commons.osgi.PropertiesUtil
 *  org.osgi.service.component.ComponentContext
 *  org.slf4j.Logger
 *  org.slf4j.LoggerFactory
 */
package com.day.cq.analytics.sitecatalyst.impl;

import com.adobe.granite.crypto.CryptoException;
import com.adobe.granite.crypto.CryptoSupport;
import com.day.cq.analytics.sitecatalyst.SitecatalystException;
import com.day.cq.analytics.sitecatalyst.SitecatalystHttpClient;
import com.day.cq.analytics.sitecatalyst.util.AuthenticationHelper;
import com.day.cq.analytics.sitecatalyst.util.HttpClientUtils;
import com.day.cq.wcm.webservicesupport.Configuration;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Dictionary;
import java.util.List;
import java.util.TimeZone;
import javax.jcr.RepositoryException;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(metatype=1, label="Adobe AEM Analytics HTTP Client", description="HTTP Client for Analytics API")
@Service
@Properties(value={@Property(name="service.description", value={"SiteCatalyst HTTP Client"})})
public class SitecatalystHttpClientImpl
implements SitecatalystHttpClient {
    private final Logger log;
    private static final String CFG_DATACENTER_URLS = "cq.analytics.sitecatalyst.service.datacenter.url";
    private static final String CFG_DEV_HOSTNAME_PATTERNS = "devhostnamepatterns";
    private static final String CFG_APPKEY = "applicationkey";
    private static final String CFG_TOKENBYPASSSECRET = "tokenbypasssecret";
    private static final String CFG_PROXYUSER = "proxyuser";
    private static final String CFG_PROXYUSERSECRET = "proxyusersecret";
    private static final Integer PRIME = 78593;
    @Reference
    private CryptoSupport cryptoSupport;
    @Property(name="cq.analytics.sitecatalyst.service.datacenter.url", label="Data center URL", description="Default data center URL", value={"https://api.omniture.com/admin/1.4/rest/"})
    private List<String> dataCenterURLs;
    @Property(name="devhostnamepatterns", label="Hostname patterns", description="Development machines hostname regular expression patterns", value={".*(\\.dev|\\.ut1)+\\.omniture\\.com$"}, cardinality=1024)
    private List<String> devHostnames;
    @Property(name="applicationkey", label="Application key", description="SiteCatalyst application key", value={"a1729166-7b52-2914-f15b-3834a2e118aa"}, propertyPrivate=1)
    private String appKey;
    @Property(name="tokenbypasssecret", label="Token bypass secret", description="SiteCatalyst secret to bypass token count", value={"MmD2*Oafei"}, propertyPrivate=1)
    private String tokenBypassSecret;
    @Property(name="proxyuser", label="Proxy user", description="SiteCatalyst proxy user (i.e. Company:Username)", propertyPrivate=1)
    private String proxyUser;
    @Property(name="proxyusersecret", label="Proxy user secret", description="SiteCatalyst secret for proxy user", value={"MmD2*Oafei"}, propertyPrivate=1)
    private String proxyUserSecret;
    private HttpClient httpClient;

    public SitecatalystHttpClientImpl() {
        this.log = LoggerFactory.getLogger(this.getClass());
        this.dataCenterURLs = new ArrayList<String>();
        this.devHostnames = new ArrayList<String>();
    }

    @Override
    public String executeProxyUser(String method, String data) throws SitecatalystException {
        return this.execute(null, method, data, true, null, null, null);
    }

    @Override
    public String execute(String method, String data, String company, String username, String secret) throws SitecatalystException {
        return this.execute(null, method, data, false, company, username, secret);
    }

    @Override
    public String execute(String datacenter, String method, String data, String company, String username, String secret) throws SitecatalystException {
        return this.execute(datacenter, method, data, false, company, username, secret);
    }

    @Override
    public String execute(String method, String data, Configuration configuration) throws SitecatalystException {
        HttpMethod httpMethod = null;
        try {
            String company = (String)configuration.getInherited("company", (Object)null);
            String username = (String)configuration.getInherited("username", (Object)null);
            String secret = (String)configuration.getInherited("secret", (Object)null);
            String server = (String)configuration.getInherited("server", (Object)null);
            if (server == null) {
                this.log.warn("Server property is missing in configuration {}", (Object)configuration.getPath());
                if (!this.dataCenterURLs.isEmpty()) {
                    server = this.dataCenterURLs.get(0);
                    this.log.debug("Using default datacenter URL {}", (Object)server);
                }
            }
            server = this.upgradeServerToAPI14(server);
            PostMethod request = new PostMethod(server + "?method=" + method);
            request.setRequestEntity((RequestEntity)new StringRequestEntity(data, "application/json", "UTF-8"));
            httpMethod = this.execute((HttpMethod)request, false, company, username, secret);
            String string = this.consumeResponse(httpMethod);
            return string;
        }
        catch (IOException e) {
            this.log.warn("I/O Error while connecting: {}", (Object)e.getMessage());
            throw new SitecatalystException("not online", e);
        }
        catch (CryptoException e) {
            this.log.warn("Internal error while connecting: {}", (Object)e.getMessage());
            throw new SitecatalystException("Internal Error", (Throwable)e);
        }
        finally {
            if (httpMethod != null) {
                httpMethod.releaseConnection();
            }
        }
    }

    private String upgradeServerToAPI14(String oldServer) {
        if (oldServer != null) {
            String upgradedServer = oldServer.replaceAll("1\\.[0-3]", "1.4");
            if (!upgradedServer.equals(oldServer)) {
                this.log.info("Analytics servers using pre-1.4 API version are not supported anymore. Converted server URL to use 1.4 API Version: " + oldServer + " -> " + upgradedServer);
            }
            return upgradedServer;
        }
        return null;
    }

    @Override
    public String execute(java.net.URI uri, String data, Configuration configuration) throws SitecatalystException {
        HttpMethod httpMethod = null;
        try {
            String uriS = this.upgradeServerToAPI14(uri.toString());
            PostMethod request = new PostMethod(uriS);
            request.setRequestEntity((RequestEntity)new StringRequestEntity(data, "application/xml", "UTF-8"));
            httpMethod = this.execute((HttpMethod)request, false, (String)configuration.getInherited("company", (Object)""), (String)configuration.getInherited("username", (Object)""), (String)configuration.getInherited("secret", (Object)""));
            String string = this.consumeResponse(httpMethod);
            return string;
        }
        catch (Exception e) {
            throw new SitecatalystException(e.getMessage(), e);
        }
        finally {
            if (httpMethod != null) {
                httpMethod.releaseConnection();
            }
        }
    }

    private String execute(String datacenter, String method, String data, boolean isProxyUser, String company, String username, String secret) throws SitecatalystException {
        HttpMethod httpMethod = null;
        try {
            if (datacenter == null && this.dataCenterURLs.size() > 0) {
                this.log.debug("No data center URL provided, using service default.");
                datacenter = this.dataCenterURLs.get(0);
            }
            if (datacenter == null) {
                throw new SitecatalystException("No valid end point given");
            }
            datacenter = this.upgradeServerToAPI14(datacenter);
            PostMethod request = new PostMethod(datacenter + "?method=" + method);
            request.setRequestEntity((RequestEntity)new StringRequestEntity(data, "application/json", "UTF-8"));
            httpMethod = this.execute((HttpMethod)request, isProxyUser, company, username, secret);
            Header contentType = httpMethod.getResponseHeader("Content-Type");
            if (contentType != null && !contentType.getValue().contains("application/json")) {
                throw new SitecatalystException("Response is of unsupported content-type, please check end point.");
            }
            String string = this.consumeResponse(httpMethod);
            return string;
        }
        catch (IOException e) {
            this.log.error("I/O Error while connecting: {}", (Object)e.getMessage());
            throw new SitecatalystException("not online", e);
        }
        catch (Exception e) {
            this.log.error("Internal error while connecting: {}", (Object)e.getMessage());
            throw new SitecatalystException(e.getMessage(), e);
        }
        finally {
            if (httpMethod != null) {
                httpMethod.releaseConnection();
            }
        }
    }

    private HttpMethod execute(HttpMethod method, boolean isProxyUser, String companyname, String username, String secret) throws HttpException, IOException, CryptoException {
        URI uri = method.getURI();
        String hostName = uri.getHost();
        if (uri.getScheme() != null && uri.getScheme().equals("https") && this.isDev(hostName)) {
            HttpClientUtils.allowSelfSigned(this.httpClient, hostName, uri.getPort());
            URI relUri = new URI(null, null, uri.getPath(), uri.getQuery(), uri.getFragment());
            method.setURI(relUri);
        } else {
            method.setURI(uri);
        }
        Date now = new Date();
        byte[] nonce = this.generateNonce();
        String created = this.generateTimestamp(now);
        String securityHeader = null;
        if (isProxyUser) {
            if (companyname == null || username == null) {
                String[] serviceUser = this.proxyUser.split("/");
                if (serviceUser != null && serviceUser.length == 2) {
                    companyname = serviceUser[0];
                    username = serviceUser[1];
                } else {
                    this.log.warn("Method call with isProxyUser=true and no proxy user configured.");
                }
            }
            securityHeader = this.getAppKeySecurityHeader(nonce, username, companyname, created);
        } else if (companyname != null && username != null && secret != null) {
            String wsUsername = username + ":" + companyname;
            if (this.cryptoSupport.isProtected(secret)) {
                secret = this.cryptoSupport.unprotect(secret);
            }
            securityHeader = this.getSecurityHeader(nonce, wsUsername, secret, created);
        }
        if (securityHeader != null) {
            method.addRequestHeader("X-WSSE", securityHeader);
        }
        this.httpClient.executeMethod(method);
        return method;
    }

    private String consumeResponse(HttpMethod method) throws IOException {
        String line;
        StringBuffer sb = new StringBuffer();
        BufferedReader responseReader = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
        while ((line = responseReader.readLine()) != null) {
            sb.append(line);
        }
        return sb.toString();
    }

    private String getSecurityHeader(byte[] nonce, String username, String secret, String created) throws UnsupportedEncodingException {
        String password64 = AuthenticationHelper.getBase64Digest(nonce, created.getBytes(), secret.getBytes());
        StringBuffer header = new StringBuffer();
        header.append("UsernameToken Username=\"");
        header.append(username);
        header.append("\", ");
        header.append("PasswordDigest=\"");
        header.append(password64.trim());
        header.append("\", ");
        header.append("Nonce=\"");
        header.append(AuthenticationHelper.base64Encode(nonce).trim());
        header.append("\", ");
        header.append("Created=\"");
        header.append(created);
        header.append("\", ");
        header.append("appkey=\"");
        header.append(this.appKey);
        header.append("\", ");
        header.append("appdigest=\"");
        header.append(AuthenticationHelper.getBase64DigestKey(nonce, this.tokenBypassSecret.getBytes()));
        header.append("\", ");
        header.append("appnonce=\"");
        header.append(AuthenticationHelper.base64Encode(nonce).trim());
        header.append("\"");
        return header.toString();
    }

    private String getAppKeySecurityHeader(byte[] nonce, String username, String companyname, String created) throws UnsupportedEncodingException {
        StringBuffer header = new StringBuffer();
        header.append("UsernameToken ");
        header.append("Created=\"");
        header.append(created);
        header.append("\", ");
        header.append("appkey=\"");
        header.append(this.appKey);
        header.append("\", ");
        header.append("appdigest=\"");
        header.append(AuthenticationHelper.getBase64DigestKey(nonce, this.proxyUserSecret.getBytes()));
        header.append("\", ");
        header.append("appnonce=\"");
        header.append(AuthenticationHelper.base64Encode(nonce).trim());
        header.append("\", ");
        header.append("proxyuser=\"");
        header.append(username);
        header.append("\", ");
        header.append("proxycompany=\"");
        header.append(companyname);
        header.append("\"");
        return header.toString();
    }

    private boolean isDev(String hostName) {
        for (String pattern : this.devHostnames) {
            if (!hostName.matches(pattern)) continue;
            return true;
        }
        return false;
    }

    private byte[] generateNonce() {
        String nonce = String.valueOf(Math.random() * (double)PRIME.intValue());
        return nonce.getBytes();
    }

    private String generateTimestamp(Date date) {
        SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
        return dateFormatter.format(date);
    }

    @Activate
    protected void activate(ComponentContext ctx) throws RepositoryException {
        Dictionary props = ctx.getProperties();
        String[] dc = PropertiesUtil.toStringArray(props.get("cq.analytics.sitecatalyst.service.datacenter.url"));
        this.dataCenterURLs.addAll(Arrays.asList(dc));
        String[] hostnames = PropertiesUtil.toStringArray(props.get("devhostnamepatterns"));
        this.devHostnames.addAll(Arrays.asList(hostnames));
        this.appKey = PropertiesUtil.toString(props.get("applicationkey"), (String)"");
        this.tokenBypassSecret = PropertiesUtil.toString(props.get("tokenbypasssecret"), (String)"");
        this.proxyUser = PropertiesUtil.toString(props.get("proxyuser"), (String)"");
        this.proxyUserSecret = PropertiesUtil.toString(props.get("proxyusersecret"), (String)"");
        this.httpClient = HttpClientUtils.newMultiThreaded();
    }

    @Deactivate
    protected void deactivate(ComponentContext ctx) {
        HttpClientUtils.shutdown(this.httpClient);
    }

    protected void bindCryptoSupport(CryptoSupport cryptoSupport) {
        this.cryptoSupport = cryptoSupport;
    }

    protected void unbindCryptoSupport(CryptoSupport cryptoSupport) {
        if (this.cryptoSupport == cryptoSupport) {
            this.cryptoSupport = null;
        }
    }
}