# ProxyConfigBuilder.java

    /*
     * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
     *
     * WSO2 Inc. licenses this file to you under the Apache License,
     * Version 2.0 (the "License"); you may not use this file except
     * in compliance with the License.
     * You may obtain a copy of the License at
     *
     *    http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing,
     * software distributed under the License is distributed on an
     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     * KIND, either express or implied.  See the License for the
     * specific language governing permissions and limitations
     * under the License.
     */
    package org.apache.synapse.transport.nhttp.config;

    import org.apache.axiom.om.OMElement;
    import org.apache.axis2.AxisFault;
    import org.apache.axis2.description.Parameter;
    import org.apache.axis2.description.TransportOutDescription;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.apache.http.HttpHost;
    import org.apache.http.auth.UsernamePasswordCredentials;
    import org.apache.synapse.transport.http.conn.ProxyConfig;
    import org.apache.synapse.transport.http.conn.ProxyProfileConfig;
    import org.apache.synapse.transport.passthru.PassThroughConstants;
    import org.wso2.securevault.SecretResolver;
    import org.wso2.securevault.SecretResolverFactory;
    import org.wso2.securevault.SecureVaultException;
    import org.wso2.securevault.commons.MiscellaneousUtil;

    import javax.xml.namespace.QName;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Set;

    public class ProxyConfigBuilder {

        private HttpHost proxy;
        private UsernamePasswordCredentials proxyCredentials;
        private String[] proxyBypass;
        private String name;

        private static final QName Q_PROFILE = new QName("profile");
        private static final QName Q_TARGET_HOSTS = new QName("targetHosts");
        private static final QName Q_PROXY_HOST = new QName("proxyHost");
        private static final QName Q_PROXY_PORT = new QName("proxyPort");
        private static final QName Q_PROXY_USER = new QName("proxyUserName");
        private static final QName Q_PROXY_PASSWORD = new QName("proxyPassword");
        private static final QName Q_BYPASS = new QName("bypass");

        private static final Log log = LogFactory.getLog(ProxyConfigBuilder.class);

        /**
         * Tries to read the axis2.xml transport sender's proxy configuration
         * @param transportOut axis2 transport out description
         * @return ProxyConfig
         * @throws AxisFault
         */
        public ProxyConfig build(TransportOutDescription transportOut) throws AxisFault {
            name = transportOut.getName();
            Map<String, ProxyProfileConfig> proxyProfileConfigMap = getProxyProfiles(transportOut);

            // if proxy profile is not configured, we read the proxy configured using http.proxyHost
            // if proxy profile is configured we only read profile related configuration
            if (proxyProfileConfigMap == null) {

                String proxyHost = null;
                int proxyPort = -1;
                Parameter proxyHostParam = transportOut.getParameter(PassThroughConstants.HTTP_PROXY_HOST);
                if (proxyHostParam != null) {
                    proxyHost = (String) proxyHostParam.getValue();
                    Parameter proxyPortParam = transportOut.getParameter(PassThroughConstants.HTTP_PROXY_PORT);
                    if (proxyPortParam != null) {
                        proxyPort = Integer.parseInt((String) proxyPortParam.getValue());
                    }
                }
                if (proxyHost == null) {
                    proxyHost = System.getProperty(PassThroughConstants.HTTP_PROXY_HOST);
                    if (proxyHost != null) {
                        String s = System.getProperty(PassThroughConstants.HTTP_PROXY_PORT);
                        if (s != null) {
                            proxyPort = Integer.parseInt(s);
                        }
                    }
                }
                if (proxyHost != null) {
                    proxy = new HttpHost(proxyHost, proxyPort >= 0 ? proxyPort : 80);

                    String bypassListStr = null;
                    Parameter bypassListParam = transportOut.getParameter(PassThroughConstants.HTTP_NON_PROXY_HOST);

                    if (bypassListParam == null) {
                        bypassListStr = System.getProperty(PassThroughConstants.HTTP_NON_PROXY_HOST);
                    } else {
                        bypassListStr = (String) bypassListParam.getValue();
                    }
                    if (bypassListStr != null) {
                        proxyBypass = bypassListStr.split("\\|");
                    }

                    Parameter proxyUsernameParam = transportOut.getParameter(PassThroughConstants.HTTP_PROXY_USERNAME);
                    Parameter proxyPasswordParam = transportOut.getParameter(PassThroughConstants.HTTP_PROXY_PASSWORD);
                    if (proxyUsernameParam != null) {
                        String proxyPassword = "";
                        if (proxyPasswordParam != null) {
                            OMElement passwordElement = proxyPasswordParam.getParameterElement();
                            SecretResolver secretResolver =SecretResolverFactory.create(passwordElement, true);
                            proxyPassword = getSecureVaultValue(secretResolver, passwordElement);
                        }
                        proxyCredentials = new UsernamePasswordCredentials((String) proxyUsernameParam.getValue(), proxyPassword);
                    }
                }
            }
            return new ProxyConfig(proxy, proxyCredentials, proxyBypass, proxyProfileConfigMap);
        }

        /**
         * Looks for a transport parameter named proxyProfiles and initializes a map of ProxyProfileConfig.
         * The syntax for defining a proxy profiles is as follows.
         * {@code
         * <parameter name="proxyProfiles">
         *      <profile>
         *          <targetHosts>example.com, *.sample.com</targetHosts>
         *          <proxyHost>localhost</proxyHost>
         *          <proxyPort>3128</proxyPort>
         *          <proxyUserName>squidUser</proxyUserName>
         *          <proxyPassword>password</proxyPassword>
         *          <bypass>xxx.sample.com</bypass>
         *      </profile>
         *      <profile>
         *          <targetHosts>localhost</targetHosts>
         *          <proxyHost>localhost</proxyHost>
         *          <proxyPort>7443</proxyPort>
         *      </profile>
         *      <profile>
         *          <targetHosts>*</targetHosts>
         *          <proxyHost>localhost</proxyHost>
         *          <proxyPort>7443</proxyPort>
         *          <bypass>test.com, direct.com</bypass>
         *      </profile>
         * </parameter>
         * }
         *
         * @param transportOut transport out description
         * @return map of <code>ProxyProfileConfig<code/> if configured in axis2.xml; otherwise null
         * @throws AxisFault if proxy profile is not properly configured
         */
        private Map<String, ProxyProfileConfig> getProxyProfiles(TransportOutDescription transportOut) throws AxisFault {
            Parameter proxyProfilesParam = transportOut.getParameter("proxyProfiles");
            if (proxyProfilesParam == null) {
                return null;
            }
            OMElement proxyProfilesElt = proxyProfilesParam.getParameterElement();
            SecretResolver secretResolver = SecretResolverFactory.create(proxyProfilesElt, true);
            if (log.isDebugEnabled()) {
                log.debug(name + " Loading proxy profiles for the HTTP/S sender");
            }

            OMElement proxyProfilesParamEle = proxyProfilesParam.getParameterElement();
            Iterator<?> profiles = proxyProfilesParamEle.getChildrenWithName(Q_PROFILE);
            Map<String, ProxyProfileConfig> proxyProfileMap = new HashMap<String, ProxyProfileConfig>();

            while (profiles.hasNext()) {
                OMElement profile = (OMElement) profiles.next();
                OMElement targetHostsEle = profile.getFirstChildWithName(Q_TARGET_HOSTS);
                if (targetHostsEle == null || targetHostsEle.getText().isEmpty()) {
                    String msg = "Each proxy profile must define at least one host " +
                            "or a wildcard matcher under the targetHosts element";
                    log.error(name + " " + msg);
                    throw new AxisFault(msg);
                }

                HttpHost proxy = getHttpProxy(profile, targetHostsEle.getText());

                UsernamePasswordCredentials proxyCredentials = getUsernamePasswordCredentials(secretResolver, profile);

                Set<String> proxyBypass = getProxyBypass(profile);

                ProxyProfileConfig proxyProfileConfig = new ProxyProfileConfig(proxy, proxyCredentials, proxyBypass);

                String[] targetHosts = targetHostsEle.getText().split(",");

                for (String endpoint : targetHosts) {
                    endpoint = endpoint.trim();
                    if (!proxyProfileMap.containsKey(endpoint)) {
                        proxyProfileMap.put(endpoint, proxyProfileConfig);
                    } else {
                        log.warn(name + " Multiple proxy profiles were found for the endpoint: " +
                                endpoint + ". Ignoring the excessive profiles.");
                    }
                }
            }

            if (proxyProfileMap.size() > 0) {
                log.info(name + " Proxy profiles initialized for " + proxyProfileMap.size() + " targetHosts");
                return proxyProfileMap;
            }
            return null;
        }

        /**
         * Extracts the proxy server detail from given profile
         * @param profile profile element
         * @param targetHosts targetHosts given in the profile
         * @return proxyServer(HttpHost) configured in the given profile element
         * @throws AxisFault, if host or port element is not configured properly
         */
        private HttpHost getHttpProxy(OMElement profile, String targetHosts) throws AxisFault {
            String proxyHost;
            String proxyPortStr;
            OMElement proxyHostEle = profile.getFirstChildWithName(Q_PROXY_HOST);
            if (proxyHostEle != null) {
                proxyHost = proxyHostEle.getText();
                OMElement proxyPortEle = profile.getFirstChildWithName(Q_PROXY_PORT);
                if (proxyPortEle != null) {
                    proxyPortStr = proxyPortEle.getText();
                } else {
                    throw new AxisFault("Proxy Port didn't configure correctly in proxy profile [" + targetHosts + "]");
                }
            } else {
                throw new AxisFault("Proxy Host didn't configure correctly in proxy profile [" + targetHosts + "]");
            }

            int proxyPort = Integer.parseInt(proxyPortStr);
            return new HttpHost(proxyHost, proxyPort >= 0 ? proxyPort : 80);
        }

        /**
         * Extracts the credential from given profile
         * @param profile profile element
         * @return usernamePasswordCredentials if username, password is configured, null otherwise
         */
        private UsernamePasswordCredentials getUsernamePasswordCredentials(SecretResolver secretResolver, OMElement profile) {
            UsernamePasswordCredentials proxyCredentials = null;
            OMElement proxyUserNameEle = profile.getFirstChildWithName(Q_PROXY_USER);
            if (proxyUserNameEle != null) {
                String proxyUserName = proxyUserNameEle.getText();
                OMElement proxyPasswordEle = profile.getFirstChildWithName(Q_PROXY_PASSWORD);
                String proxyPassword;
                if (proxyPasswordEle != null) {
                    proxyPassword = getSecureVaultValue(secretResolver, proxyPasswordEle);
                } else {
                    proxyPassword = "";
                }

                proxyCredentials = new UsernamePasswordCredentials(proxyUserName,
                        proxyPassword != null ? proxyPassword : "");
            }
            return proxyCredentials;
        }

        /**
         * Extracts the proxyBypass hosts from given profile
         * @param profile profile element
         * @return Set of String containing bypass hosts; empty set if bypass hosts is not configured
         */
        private Set<String> getProxyBypass(OMElement profile) {
            Set<String> bypassSet = new HashSet<String>();
            OMElement bypassEle = profile.getFirstChildWithName(Q_BYPASS);
            if (bypassEle != null && !bypassEle.getText().isEmpty()) {
                String[] bypassHosts = bypassEle.getText().split(",");

                for (String bypassHost : bypassHosts) {
                    bypassSet.add(bypassHost.trim());
                }
            }
            return bypassSet;
        }
        private String getSecureVaultValue(SecretResolver secretResolver, OMElement paramElement) {
            String value = null;
            if (paramElement != null) {
                if (secretResolver == null) {
                    throw new SecureVaultException("Cannot resolve secret password because axis2 secret resolver " +
                            "is null");
                }
                value = MiscellaneousUtil.resolve(paramElement, secretResolver);
            }
            return value;
        }
    }