/*
* 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.http.conn;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;
import java.util.HashSet;
import org.apache.axis2.AxisFault;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpHost;
import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.auth.UsernamePasswordCredentials;
public class ProxyConfig {
private static final Log log = LogFactory.getLog(ProxyConfig.class);
private final HttpHost proxy;
private final UsernamePasswordCredentials creds;
private final Set<String> proxyBypass;
// The set of known hosts to bypass proxy
private Set<String> knownDirectHosts = new HashSet<String>();
// The set of known hosts to go via proxy
private Set<String> knownProxyHosts = new HashSet<String>();
// Map to hold the known proxy profile configuration
private Map<String, ProxyProfileConfig> knownProxyConfigMap = new HashMap<String,ProxyProfileConfig>();
// Map to hold the custom proxy profile details
private Map<String, ProxyProfileConfig> proxyProfileMap = new HashMap<String, ProxyProfileConfig>();
public ProxyConfig(
final HttpHost proxy,
final UsernamePasswordCredentials creds,
final String[] proxyBypass,
final Map<String, ProxyProfileConfig> proxyProfileMap) {
super();
this.proxy = proxy;
this.creds = creds;
if (proxyBypass != null) {
this.proxyBypass = new LinkedHashSet<String>(proxyBypass.length);
for (String s: proxyBypass) {
this.proxyBypass.add(s.trim().toLowerCase(Locale.US));
}
} else {
this.proxyBypass = Collections.<String>emptySet();
}
if (proxyProfileMap != null) {
this.proxyProfileMap = proxyProfileMap;
} else {
this.proxyProfileMap = Collections.emptyMap();
}
}
public HttpHost getProxy() {
return proxy;
}
public UsernamePasswordCredentials getCreds() {
return creds;
}
public Set<String> getProxyBypass() {
return proxyBypass;
}
/**
* Selects the configured proxy server
* @param target request endpoint
* @return proxy host based on the proxy profile or http.proxyHost,
* null when no proxy is configured or if the target is matched with proxy bypass
*/
public HttpHost selectProxy(final HttpHost target) {
if (isProxyProfileConfigured()) {
return getProxyForTargetHost(target.getHostName());
}
if (this.proxy != null) {
if (knownProxyHosts.contains(target.getHostName().toLowerCase(Locale.US))) {
return this.proxy;
} else if (knownDirectHosts.contains(target.getHostName().toLowerCase(Locale.US))) {
return null;
} else {
// we are encountering this host for the first time
if (isBypass(target.getHostName().toLowerCase(Locale.US))) {
return null;
} else {
return this.proxy;
}
}
}
return this.proxy;
}
/**
* checks weather the proxy profile map is empty
*
* @return true if proxy profile map is not empty, false otherwise
*/
public boolean isProxyProfileConfigured() {
return !this.proxyProfileMap.isEmpty();
}
/**
* select the appropriate proxy for the given targetHost
*
* @param targetHost targeted end point
* @return proxy mapped for the end point, if not returns null
*/
private HttpHost getProxyForTargetHost(String targetHost) {
HttpHost proxy = null;
ProxyProfileConfig proxyProfileForTargetHost = getProxyProfileForTargetHost(targetHost);
if (proxyProfileForTargetHost != null) {
proxy = proxyProfileForTargetHost.getProxy();
}
return proxy;
}
/**
* Selects the appropriate proxyProfileConfiguration for the given targetHost
*
* First, checks in the knownProxyConfigMap and returns the proxyProfile. If the profile is not in the
* knowProxyConfigMap checks the knowDirectHosts and returns null (since the targetHost is not associated with
* any proxy).
*
* If the request hits the ESB for the first time, checks whether the default profile is configured. If so, a flag
* is set true. Then the targetHost is matched against the proxyProfileMaps key set
* i.e check any of the key patten is matching with the targetHost. If the targetHost is matched against a key then
* calls getProxyProfileConfig(String, String) and returns the proxyProfileConfig.
*
* If the targetHost is not matched against the proxyProfileMap key set and default profile flag is set true
* then calls getProxyProfileConfig(String, String) and returns the defaultProfile.
*
* @param targetHost request's targeted host
* @return ProxyProfileConfig for the given targetHost
*/
private ProxyProfileConfig getProxyProfileForTargetHost(String targetHost) {
if (knownProxyConfigMap.containsKey(targetHost)) {
return knownProxyConfigMap.get(targetHost);
}
if (knownDirectHosts.contains(targetHost)) {
return null;
}
boolean defaultProfile = false;
for (String key : proxyProfileMap.keySet()) {
if ("*".equals(key)) {
log.debug("Default proxy profile found");
defaultProfile = true;
continue;
}
if (targetHost.matches(key)) {
return getProxyProfileConfig(targetHost, key);
}
}
if (defaultProfile) {
return getProxyProfileConfig(targetHost, "*");
}
return null;
}
/**
* Selects the proxyProfile for the given key and gets the bypass set. Matches the targetHost against the
* bypass set. If it is matched then adds the targetHost to the knownDirectHosts List and returns null.
* Otherwise (i.e the targetHost is not matched in the bypass proxy) puts the proxyProfile against the targetHost
* into the knownProxyConfigMap and returns the proxyProfileConfig
*
* @param targetHost request's targeted host
* @param key proxyProfileMap's key, if default profile then the key is "*"
* @return proxyProfileConfig
*/
private ProxyProfileConfig getProxyProfileConfig(String targetHost, String key) {
ProxyProfileConfig proxyProfileConfig = proxyProfileMap.get(key);
Set<String> proxyByPass = proxyProfileConfig.getProxyByPass();
for (String bypass : proxyByPass) {
if (targetHost.matches(bypass)) {
knownDirectHosts.add(targetHost);
return null;
}
}
knownProxyConfigMap.put(targetHost, proxyProfileConfig);
return proxyProfileConfig;
}
/**
* select the proxy credential for the targetHost
*
* @param targetHost targeted host
* @return proxy credential for the given end point, if not returns null
*/
public UsernamePasswordCredentials getCredentialsForTargetHost(String targetHost) {
UsernamePasswordCredentials credentials = null;
ProxyProfileConfig proxyProfileForTargetHost = getProxyProfileForTargetHost(targetHost);
if (proxyProfileForTargetHost != null) {
credentials = proxyProfileForTargetHost.getCredentials();
}
return credentials;
}
/**
* returns appropriate log message based on the proxy configuration
* whether loading proxy profile or single proxy server or no proxy configured
*
* @return log message
*/
public String logProxyConfig() {
if (isProxyProfileConfigured()) {
return "HTTP Sender using proxy profile";
}
if (this.proxy != null) {
return "HTTP Sender using Proxy " + getProxy() + " and bypassing " + getProxyBypass();
} else {
return "No proxy configuration found";
}
}
/**
* checks whether proxy configured (either proxy profile or single server)
*
* @return true when either proxy profile is configure or default proxy server is configure, false otherwise
*/
private boolean isProxyConfigured() {
return proxy != null || isProxyProfileConfigured();
}
/**
* checks the proxy configuration and profile configuration whether proxy is configured with credential
*
* @return true when at least one proxy has configured with credential, false otherwise
*/
private boolean isProxyHasCredential() {
if (!isProxyConfigured()) {
return false;
}
if (!isProxyProfileConfigured()) {
return getCreds() != null;
}
for (Map.Entry<String, ProxyProfileConfig> proxyProfile : this.proxyProfileMap.entrySet()) {
if (proxyProfile.getValue().getCredentials() != null) {
return true;
}
}
return false;
}
/**
* returns DefaultProxyAuthenticator if single proxy server is configured
* ProfileProxyAuthenticator if proxy profile is configured
*
* @return ProxyAuthenticator, if proxy is not configured null
* @throws AxisFault
*/
public ProxyAuthenticator createProxyAuthenticator() throws AxisFault {
if (!isProxyHasCredential()) {
return null;
}
ProxyAuthenticator proxyAuthenticator;
try {
if (isProxyProfileConfigured()) {
proxyAuthenticator = new ProfileProxyAuthenticator(this);
} else {
proxyAuthenticator = new DefaultProxyAuthenticator(getCreds());
}
} catch (MalformedChallengeException e) {
throw new AxisFault("Error while creating proxy authenticator", e);
}
return proxyAuthenticator;
}
@Override
public String toString() {
return "[proxy=" + proxy + ", proxyCredential=" + creds + ", proxyBypass=" + proxyBypass +
", proxyProfileMap=" + proxyProfileMap + "]";
}
private boolean isBypass(String hostName) {
for (String entry : this.proxyBypass) {
if (hostName.matches(entry)) {
knownDirectHosts.add(hostName);
return true;
}
}
knownProxyHosts.add(hostName);
return false;
}
}