diff options
Diffstat (limited to 'id.server/src/at/gv/egovernment/moa/id/proxy')
15 files changed, 1672 insertions, 0 deletions
diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/ConnectionBuilder.java b/id.server/src/at/gv/egovernment/moa/id/proxy/ConnectionBuilder.java new file mode 100644 index 000000000..8039b67a6 --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/ConnectionBuilder.java @@ -0,0 +1,54 @@ +package at.gv.egovernment.moa.id.proxy; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.Map; + +import javax.net.ssl.SSLSocketFactory; +import javax.servlet.http.HttpServletRequest; + +/** + * Builder for {@link java.net.URLConnection} objects used to forward requests + * to the remote online application. + * + * @author Paul Ivancsics + * @version $Id$ + */ + +public interface ConnectionBuilder { + + /** + * Builds an HttpURLConnection to a {@link java.net.URL} which is derived + * from an {@link HttpServletRequest} URL, by substitution of a + * public URL prefix for the real URL prefix.<br> + * The HttpURLConnection has been created by {@link java.net.URL#openConnection}, but + * it has not yet been connected to by {@link java.net.URLConnection#connect}.<br> + * The field settings of the HttpURLConnection are: + * <ul> + * <li><code>allowUserInteraction = false</code></li> + * <li><code>doInput = true</code></li> + * <li><code>doOutput = true</code></li> + * <li><code>requestMethod = request.getMethod()</code></li> + * <li><code>useCaches = false</code></li> + * </ul> + * + * @param request the incoming request which shall be forwarded + * @param publicURLPrefix the public URL prefix to be substituted by the real URL prefix + * @param realURLPrefix the URL prefix to substitute the public URL prefix + * @param sslSocketFactory factory to be used for creating an SSL socket in case + * of a URL for scheme <code>"https:"</code>; + * <br>if <code>null</code>, the default SSL socket factory would be used + * @param parameters parameters to be forwarded + * @return a URLConnection created by {@link java.net.URL#openConnection}, connecting to + * the requested URL with <code>publicURLPrefix</code> substituted by <code>realURLPrefix</code> + * @throws IOException if an I/O exception occurs during opening the connection + * @see java.net.URL#openConnection() + * @see com.sun.net.ssl.HttpsURLConnection#getDefaultSSLSocketFactory() + */ + public HttpURLConnection buildConnection( + HttpServletRequest request, + String publicURLPrefix, + String realURLPrefix, + SSLSocketFactory sslSocketFactory, + Map parameters) throws IOException; +} diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/ConnectionBuilderFactory.java b/id.server/src/at/gv/egovernment/moa/id/proxy/ConnectionBuilderFactory.java new file mode 100644 index 000000000..7a6c3e575 --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/ConnectionBuilderFactory.java @@ -0,0 +1,68 @@ +package at.gv.egovernment.moa.id.proxy; + +import java.util.HashMap; +import java.util.Map; + +import at.gv.egovernment.moa.id.config.ConfigurationException; +import at.gv.egovernment.moa.id.config.proxy.ProxyConfigurationProvider; +import at.gv.egovernment.moa.id.config.proxy.OAProxyParameter; + +/** + * Factory delivering a {@link ConnectionBuilder} implementation for + * an online application, initialized from configuration data. + * @author Paul Ivancsics + * @version $Id$ + */ +public class ConnectionBuilderFactory { + + /** default connection builder to be used for online application + * where no special implementation of the <code>ConnectionBuilder</code> + * interface is configured + */ + private static ConnectionBuilder defaultConnectionBuilder; + /** mapping from online application public URL prefix to an implementation + * of the <code>ConnectionBuilder</code> interface to be used; + * if no mapping is given for an online application, the + * <code>DefaultConnectionBuilder</code> will be used */ + private static Map connectionBuilderMap; + + /** + * Initializes the <code>ConnectionBuilder</code> map from the configuration data. + * @throws ConfigurationException when the configuration cannot be read, + * or when a class name configured cannot be instantiated + */ + public static void initialize() throws ConfigurationException { + defaultConnectionBuilder = new DefaultConnectionBuilder(); + connectionBuilderMap = new HashMap(); + ProxyConfigurationProvider proxyConf = ProxyConfigurationProvider.getInstance(); + for (int i = 0; i < proxyConf.getOnlineApplicationParameters().length; i++) { + OAProxyParameter oaParam = proxyConf.getOnlineApplicationParameters()[i]; + String publicURLPrefix = oaParam.getPublicURLPrefix(); + String className = oaParam.getConnectionBuilderImpl(); + if (className != null) { + try { + ConnectionBuilder cb = (ConnectionBuilder)Class.forName(className).newInstance(); + connectionBuilderMap.put(publicURLPrefix, cb); + } + catch (Throwable ex) { + throw new ConfigurationException("config.07", new Object[] {publicURLPrefix}, ex); + } + } + } + } + + /** + * Gets the <code>ConnectionBuilder</code> implementation to be used for the given + * online application. + * @param publicURLPrefix public URL prefix of the online application + * @return <code>ConnectionBuilder</code> implementation + */ + public static ConnectionBuilder getConnectionBuilder(String publicURLPrefix) { + ConnectionBuilder cb = (ConnectionBuilder) connectionBuilderMap.get(publicURLPrefix); + if (cb == null) + return defaultConnectionBuilder; + else + return cb; + } + +} diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/DefaultConnectionBuilder.java b/id.server/src/at/gv/egovernment/moa/id/proxy/DefaultConnectionBuilder.java new file mode 100644 index 000000000..48e21f673 --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/DefaultConnectionBuilder.java @@ -0,0 +1,119 @@ +package at.gv.egovernment.moa.id.proxy; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Iterator; +import java.util.Map; + +import javax.net.ssl.SSLSocketFactory; +import javax.servlet.http.HttpServletRequest; + +import at.gv.egovernment.moa.id.config.ConfigurationException; +import at.gv.egovernment.moa.id.config.proxy.ProxyConfigurationProvider; +import at.gv.egovernment.moa.id.util.MOAIDMessageProvider; +import at.gv.egovernment.moa.logging.Logger; +import at.gv.egovernment.moa.util.BoolUtils; + +import com.sun.net.ssl.HostnameVerifier; +import com.sun.net.ssl.HttpsURLConnection; + +/** + * Defaultimplementierung von <code>ConnectionBuilder</code>. + * @author Paul Ivancsics + * @version $Id$ + */ +public class DefaultConnectionBuilder implements ConnectionBuilder { + + /** a boolean to disable the HostnameVerification (default = false)*/ + private static boolean disableHostnameVerification = false; + + /** + * Constructor for DefaultConnectionBuilder. + * @throws ConfigurationException on any config error + */ + public DefaultConnectionBuilder() throws ConfigurationException { + disableHostnameVerification = BoolUtils.valueOf( + ProxyConfigurationProvider.getInstance().getGenericConfigurationParameter( + "ProxyComponent.DisableHostnameVerification")); + if (disableHostnameVerification) + Logger.warn("ProxyComponent.DisableHostnameVerification: " + disableHostnameVerification); + } + + /** + * @see at.gv.egovernment.moa.id.proxy.ConnectionBuilder#buildConnection + */ + public HttpURLConnection buildConnection( + HttpServletRequest req, + String publicURLPrefix, + String realURLPrefix, + SSLSocketFactory sslSocketFactory, + Map parameters) + throws IOException { + + String requestedURL = req.getRequestURL().toString(); + // check whether requested URL starts with publicURLPrefix + if (! requestedURL.startsWith(publicURLPrefix)) + throw new IOException(MOAIDMessageProvider.getInstance().getMessage( + "proxy.01", new Object[] {requestedURL, publicURLPrefix})); + // in case of GET request, append query string to requested URL; + // otherwise, HttpURLConnection would perform a POST request + if ("get".equalsIgnoreCase(req.getMethod()) && ! parameters.isEmpty()) { + requestedURL = appendQueryString(requestedURL, parameters); + } + // build real URL in online application + String realURLString = realURLPrefix + requestedURL.substring(publicURLPrefix.length()); + URL url = new URL(realURLString); + Logger.debug("OA Request: " + req.getMethod() + " " + url.toString()); + + HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + conn.setRequestMethod(req.getMethod()); + conn.setDoInput(true); + conn.setDoOutput(true); + //conn.setUseCaches(false); + conn.setAllowUserInteraction(true); + conn.setInstanceFollowRedirects(false); + if (conn instanceof HttpsURLConnection && sslSocketFactory != null) { + HttpsURLConnection httpsConn = (HttpsURLConnection) conn; + httpsConn.setSSLSocketFactory(sslSocketFactory); + if (disableHostnameVerification) + httpsConn.setHostnameVerifier(new HostnameNonVerifier()); + } + return conn; + } + /** + * @param requestedURL + * @param parameters + * @return + */ + private String appendQueryString(String requestedURL, Map parameters) { + String newURL = requestedURL; + for (Iterator iter = parameters.keySet().iterator(); iter.hasNext();) { + String paramName = (String)iter.next(); + String paramValue = (String)parameters.get(paramName); + String paramString = paramName + "=" + paramValue; + if (newURL.indexOf("?") < 0) + newURL = newURL + "?" + paramString; + else + newURL = newURL + "&" + paramString; + } + return newURL; + } + + /** + * @author Stefan Knirsch + * @version $Id$ + * A private class to change the standard HostName verifier to disable the + * Hostname Verification Check + */ + private class HostnameNonVerifier implements HostnameVerifier { + + /** + * @see com.sun.net.ssl.HostnameVerifier#verify(String, String) + */ + public boolean verify(String arg0, String arg1) { + return true; + } + } + +} diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/DefaultLoginParameterResolver.java b/id.server/src/at/gv/egovernment/moa/id/proxy/DefaultLoginParameterResolver.java new file mode 100644 index 000000000..db3c452bc --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/DefaultLoginParameterResolver.java @@ -0,0 +1,118 @@ +package at.gv.egovernment.moa.id.proxy; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import at.gv.egovernment.moa.id.config.proxy.OAConfiguration; +import at.gv.egovernment.moa.id.data.AuthenticationData; +import at.gv.egovernment.moa.util.Base64Utils; + +/** + * Implementation of interface <code>LoginParameterResolver</code> + * @author Paul Ivancsics + * @version $Id$ + */ +public class DefaultLoginParameterResolver implements LoginParameterResolver { + + /** + * Constructor + */ + public DefaultLoginParameterResolver() { + } + + /** + * @see at.gv.egovernment.moa.id.proxy.LoginParameterResolver#getAuthenticationHeaders(at.gv.egovernment.moa.id.config.proxy.OAConfiguration, at.gv.egovernment.moa.id.auth.data.AuthenticationData, java.lang.String) + */ + public Map getAuthenticationHeaders( + OAConfiguration oaConf, + AuthenticationData authData, + String clientIPAddress) { + + Map result = new HashMap(); + + if (oaConf.getAuthType().equals(OAConfiguration.BASIC_AUTH)) { + String useridPredicate = oaConf.getBasicAuthUserIDMapping(); + String userid = resolveValue(useridPredicate, authData, clientIPAddress); + String passwordPredicate = oaConf.getBasicAuthPasswordMapping(); + String password = resolveValue(passwordPredicate, authData, clientIPAddress); + + try { + String userIDPassword = userid + ":" + password; + String credentials = Base64Utils.encode(userIDPassword.getBytes()); + result.put("Authorization", "Basic " + credentials); + } + catch (IOException ignore) { + } + } + else if (oaConf.getAuthType().equals(OAConfiguration.HEADER_AUTH)) { + for (Iterator iter = oaConf.getHeaderAuthMapping().keySet().iterator(); iter.hasNext();) { + String key = (String) iter.next(); + String predicate = (String) oaConf.getHeaderAuthMapping().get(key); + String resolvedValue = resolveValue(predicate, authData, clientIPAddress); + result.put(key, resolvedValue); + } + } + + return result; + } + + /** + * @see at.gv.egovernment.moa.id.proxy.LoginParameterResolver#getAuthenticationParameters(at.gv.egovernment.moa.id.config.proxy.OAConfiguration, at.gv.egovernment.moa.id.auth.data.AuthenticationData, java.lang.String) + */ + public Map getAuthenticationParameters( + OAConfiguration oaConf, + AuthenticationData authData, + String clientIPAddress) { + + Map result = new HashMap(); + + if (oaConf.getAuthType().equals(OAConfiguration.PARAM_AUTH)) { + for (Iterator iter = oaConf.getParamAuthMapping().keySet().iterator(); iter.hasNext();) { + String key = (String) iter.next(); + String predicate = (String) oaConf.getParamAuthMapping().get(key); + String resolvedValue = resolveValue(predicate, authData, clientIPAddress); + result.put(key, resolvedValue); + } + } + + return result; + } + + /** + * Resolves a login header or parameter value. + * @param predicate header or parameter predicate name from online application configuration + * @param authData authentication data for current login + * @param clientIPAddress client IP address + * @return header or parameter value resolved; <code>null</code> if unknown name is given + */ + private static String resolveValue(String predicate, AuthenticationData authData, String clientIPAddress) { + if (predicate.equals(MOAGivenName)) + return authData.getGivenName(); + else if (predicate.equals(MOAFamilyName)) + return authData.getFamilyName(); + else if (predicate.equals(MOADateOfBirth)) + return authData.getDateOfBirth(); + else if (predicate.equals(MOAVPK)) + return authData.getVPK(); + else if (predicate.equals(MOAPublicAuthority)) + if (authData.isPublicAuthority()) + return "true"; + else + return "false"; + else if (predicate.equals(MOABKZ)) + return authData.getPublicAuthorityCode(); + else if (predicate.equals(MOAQualifiedCertificate)) + if (authData.isQualifiedCertificate()) + return "true"; + else + return "false"; + else if (predicate.equals(MOAZMRZahl)) + return authData.getIdentificationValue(); + else if (predicate.equals(MOAIPAddress)) + return clientIPAddress; + else return null; + } + +} diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/LoginParameterResolver.java b/id.server/src/at/gv/egovernment/moa/id/proxy/LoginParameterResolver.java new file mode 100644 index 000000000..497176a96 --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/LoginParameterResolver.java @@ -0,0 +1,72 @@ +package at.gv.egovernment.moa.id.proxy; + +import java.util.Map; + +import at.gv.egovernment.moa.id.config.proxy.OAConfiguration; +import at.gv.egovernment.moa.id.data.AuthenticationData; + +/** + * Determines authentication parameters and headers to be added to a {@link java.net.URLConnection} + * to the remote online application. + * Utilizes {@link OAConfiguration} and {@link AuthenticationData}. + * + * @author Paul Ivancsics + * @version $Id$ + */ +public interface LoginParameterResolver { + + /** Constants used in <code>MOAIDConfiguration-1.1.xsd</code>, type <code>MOAAuthDataType</code>, + * naming predicates used by the <code>LoginParameterResolver</code>. */ + public static final String MOAGivenName = "MOAGivenName"; + /** Constant used in <code>MOAIDConfiguration-1.1.xsd</code>, type <code>MOAAuthDataType</code> */ + public static final String MOAFamilyName = "MOAFamilyName"; + /** Constant used in <code>MOAIDConfiguration-1.1.xsd</code>, type <code>MOAAuthDataType</code> */ + public static final String MOADateOfBirth = "MOADateOfBirth"; + /** Constant used in <code>MOAIDConfiguration-1.1.xsd</code>, type <code>MOAAuthDataType</code> */ + public static final String MOAVPK = "MOAVPK"; + /** Constant used in <code>MOAIDConfiguration-1.1.xsd</code>, type <code>MOAAuthDataType</code> */ + public static final String MOAPublicAuthority = "MOAPublicAuthority"; + /** Constant used in <code>MOAIDConfiguration-1.1.xsd</code>, type <code>MOAAuthDataType</code> */ + public static final String MOABKZ = "MOABKZ"; + /** Constant used in <code>MOAIDConfiguration-1.1.xsd</code>, type <code>MOAAuthDataType</code> */ + public static final String MOAQualifiedCertificate = "MOAQualifiedCertificate"; + /** Constant used in <code>MOAIDConfiguration-1.1.xsd</code>, type <code>MOAAuthDataType</code> */ + public static final String MOAZMRZahl = "MOAZMRZahl"; + /** Constant used in <code>MOAIDConfiguration-1.1.xsd</code>, type <code>MOAAuthDataType</code> */ + public static final String MOAIPAddress = "MOAIPAddress"; + + /** + * Returns authentication headers to be added to a URLConnection. + * + * @param oaConf configuration data + * @param authData authentication data + * @param clientIPAddress client IP address + * @return A map, the keys being header names and values being corresponding header values. + * <br>In case of authentication type <code>"basic-auth"</code>, header fields + * <code>username</code> and <code>password</code>. + * <br>In case of authentication type <code>"header-auth"</code>, header fields + * derived from parameter mapping and authentication data provided. + * <br>Otherwise, an empty map. + */ + public Map getAuthenticationHeaders ( + OAConfiguration oaConf, + AuthenticationData authData, + String clientIPAddress); + + /** + * Returns request parameters to be added to a URLConnection. + * + * @param oaConf configuration data + * @param authData authentication data + * @param clientIPAddress client IP address + * @return A map, the keys being parameter names and values being corresponding parameter values. + * <br>In case of authentication type <code>"param-auth"</code>, parameters + * derived from parameter mapping and authentication data provided. + * <br>Otherwise, an empty map. + */ + public Map getAuthenticationParameters ( + OAConfiguration oaConf, + AuthenticationData authData, + String clientIPAddress); + +} diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/LoginParameterResolverFactory.java b/id.server/src/at/gv/egovernment/moa/id/proxy/LoginParameterResolverFactory.java new file mode 100644 index 000000000..2ab245923 --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/LoginParameterResolverFactory.java @@ -0,0 +1,68 @@ +package at.gv.egovernment.moa.id.proxy; + +import java.util.HashMap; +import java.util.Map; + +import at.gv.egovernment.moa.id.config.ConfigurationException; +import at.gv.egovernment.moa.id.config.proxy.ProxyConfigurationProvider; +import at.gv.egovernment.moa.id.config.proxy.OAProxyParameter; + +/** + * Factory delivering a {@link LoginParameterResolver} implementation for + * an online application, initialized from configuration data. + * @author Paul Ivancsics + * @version $Id$ + */ +public class LoginParameterResolverFactory { + + /** default login parameter resolver to be used for online application + * where no special implementation of the <code>LoginParameterResolver</code> + * interface is configured + */ + private static LoginParameterResolver defaultLoginParameterResolver; + /** mapping from online application public URL prefix to an implementation + * of the <code>LoginParameterResolver</code> interface to be used; + * if no mapping is given for an online application, the + * <code>DefaultLoginParameterResolver</code> will be used */ + private static Map loginParameterResolverMap; + + /** + * Initializes the <code>LoginParameterResolver</code> map from the configuration data. + * @throws ConfigurationException when the configuration cannot be read, + * or when a class name configured cannot be instantiated + */ + public static void initialize() throws ConfigurationException { + defaultLoginParameterResolver = new DefaultLoginParameterResolver(); + loginParameterResolverMap = new HashMap(); + ProxyConfigurationProvider proxyConf = ProxyConfigurationProvider.getInstance(); + for (int i = 0; i < proxyConf.getOnlineApplicationParameters().length; i++) { + OAProxyParameter oaParam = proxyConf.getOnlineApplicationParameters()[i]; + String publicURLPrefix = oaParam.getPublicURLPrefix(); + String className = oaParam.getLoginParameterResolverImpl(); + if (className != null) { + try { + LoginParameterResolver lpr = (LoginParameterResolver)Class.forName(className).newInstance(); + loginParameterResolverMap.put(publicURLPrefix, lpr); + } + catch (Throwable ex) { + throw new ConfigurationException("config.07", new Object[] {publicURLPrefix}, ex); + } + } + } + } + + /** + * Gets the <code>LoginParameterResolver</code> implementation to be used for the given + * online application. + * @param publicURLPrefix public URL prefix of the online application + * @return <code>LoginParameterResolver</code> implementation + */ + public static LoginParameterResolver getLoginParameterResolver(String publicURLPrefix) { + LoginParameterResolver lpr = (LoginParameterResolver) loginParameterResolverMap.get(publicURLPrefix); + if (lpr == null) + return defaultLoginParameterResolver; + else + return lpr; + } + +} diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/MOAIDProxyInitializer.java b/id.server/src/at/gv/egovernment/moa/id/proxy/MOAIDProxyInitializer.java new file mode 100644 index 000000000..da5d36678 --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/MOAIDProxyInitializer.java @@ -0,0 +1,91 @@ +package at.gv.egovernment.moa.id.proxy; + +import iaik.pki.PKIException; +import iaik.pki.jsse.IAIKX509TrustManager; + +import java.io.IOException; +import java.security.GeneralSecurityException; + +import javax.net.ssl.SSLSocketFactory; + +import at.gv.egovernment.moa.id.config.ConfigurationException; +import at.gv.egovernment.moa.id.config.ConnectionParameter; +import at.gv.egovernment.moa.id.config.proxy.OAProxyParameter; +import at.gv.egovernment.moa.id.config.proxy.ProxyConfigurationProvider; +import at.gv.egovernment.moa.id.iaik.config.LoggerConfigImpl; +import at.gv.egovernment.moa.id.util.AxisSecureSocketFactory; +import at.gv.egovernment.moa.id.util.MOAIDMessageProvider; +import at.gv.egovernment.moa.id.util.SSLUtils; +import at.gv.egovernment.moa.logging.Logger; + +/** + * Web application initializer + * + * @author Paul Ivancsics + * @version $Id$ + */ +public class MOAIDProxyInitializer { + + /** + * Initializes the web application components which need initialization: + * logging, JSSE, MOA-ID Auth configuration, Axis, session cleaner. + */ + public static void initialize() + throws ConfigurationException, IOException, GeneralSecurityException, PKIException { + + Logger.setHierarchy("moa.id.proxy"); + + // Restricts TLS cipher suites + System.setProperty("https.cipherSuites", "SSL_RSA_WITH_RC4_128_SHA,SSL_RSA_WITH_RC4_128_MD5,SSL_RSA_WITH_3DES_EDE_CBC_SHA"); + + // load some jsse classes so that the integrity of the jars can be verified + // before the iaik jce is installed as the security provider + // this workaround is only needed when sun jsse is used in conjunction with + // iaik-jce (on jdk1.3) + ClassLoader cl = MOAIDProxyInitializer.class.getClassLoader(); + try { + cl.loadClass("javax.security.cert.Certificate"); // from jcert.jar + } + catch (ClassNotFoundException e) { + Logger.warn(MOAIDMessageProvider.getInstance().getMessage("init.01", null), e); + } + + // Initializes the SSLSocketFactory store + SSLUtils.initialize(); + + // Initializes IAIKX509TrustManager logging + String log4jConfigURL = System.getProperty("log4j.configuration"); + if (log4jConfigURL != null) { + IAIKX509TrustManager.initLog(new LoggerConfigImpl(log4jConfigURL)); + } + + // Loads the configuration + ProxyConfigurationProvider proxyConf = ProxyConfigurationProvider.reload(); + + // Initializes the Axis secure socket factory for use in calling the MOA-Auth web service, + // using configuration data + ConnectionParameter connParamAuth = proxyConf.getAuthComponentConnectionParameter(); + if (connParamAuth.isHTTPSURL()) { + SSLSocketFactory ssf = SSLUtils.getSSLSocketFactory(proxyConf, connParamAuth); + AxisSecureSocketFactory.initialize(ssf); + } + + // Initializes the Axis secure socket factories for use in calling the online applications, + // using configuration data + OAProxyParameter[] oaParams = proxyConf.getOnlineApplicationParameters(); + for (int i = 0; i < oaParams.length; i++) { + OAProxyParameter oaParam = oaParams[i]; + ConnectionParameter oaConnParam = oaParam.getConnectionParameter(); + if (oaConnParam.isHTTPSURL()) + SSLUtils.getSSLSocketFactory(proxyConf, oaConnParam); + } + + // Initializes the ConnectionBuilderFactory from configuration data + ConnectionBuilderFactory.initialize(); + + // Initializes the LoginParameterResolverFactory from configuration data + LoginParameterResolverFactory.initialize(); + + } + +} diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/WebmailLoginParameterResolver.class b/id.server/src/at/gv/egovernment/moa/id/proxy/WebmailLoginParameterResolver.class Binary files differnew file mode 100644 index 000000000..49200265a --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/WebmailLoginParameterResolver.class diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/builder/SAMLRequestBuilder.java b/id.server/src/at/gv/egovernment/moa/id/proxy/builder/SAMLRequestBuilder.java new file mode 100644 index 000000000..e0e1fde4a --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/builder/SAMLRequestBuilder.java @@ -0,0 +1,55 @@ +package at.gv.egovernment.moa.id.proxy.builder; + +import java.text.MessageFormat; +import java.util.Calendar; + +import org.w3c.dom.Element; + +import at.gv.egovernment.moa.id.BuildException; +import at.gv.egovernment.moa.util.Constants; +import at.gv.egovernment.moa.util.DOMUtils; +import at.gv.egovernment.moa.util.DateTimeUtils; + +/** + * Builder for the <code><samlp:Request></code> used for querying + * the authentication data <code><saml:Assertion></code>. + * + * @author Paul Ivancsics + * @version $Id$ + */ +public class SAMLRequestBuilder implements Constants { + /** samlp-Request template */ + private static final String REQUEST = + "<samlp:Request xmlns:samlp=\"urn:oasis:names:tc:SAML:1.0:protocol\" RequestID=\"{0}\" MajorVersion=\"1\" MinorVersion=\"0\" IssueInstant=\"{1}\">" + + "<samlp:AssertionArtifact>{2}</samlp:AssertionArtifact>" + + "</samlp:Request>"; + + /** + * Constructor for SAMLRequestBuilder. + */ + public SAMLRequestBuilder() { + super(); + } + + /** + * Builds the <code><samlp:Request></code>. + * @param requestID request ID + * @param samlArtifactBase64 SAML artifact, encoded BASE64 + * @return the DOM element + */ + public Element build(String requestID, String samlArtifactBase64) throws BuildException { + try { + String issueInstant = DateTimeUtils.buildDateTime(Calendar.getInstance()); + String request = MessageFormat.format(REQUEST, new Object[] {requestID, issueInstant, samlArtifactBase64}); + Element requestElem = DOMUtils.parseDocument(request, false, ALL_SCHEMA_LOCATIONS, null).getDocumentElement(); + return requestElem; + } + catch (Throwable ex) { + throw new BuildException( + "builder.00", + new Object[] {"samlp:Request", ex.toString()}, + ex); + } + } + +} diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/invoke/GetAuthenticationDataInvoker.java b/id.server/src/at/gv/egovernment/moa/id/proxy/invoke/GetAuthenticationDataInvoker.java new file mode 100644 index 000000000..4e9a72111 --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/invoke/GetAuthenticationDataInvoker.java @@ -0,0 +1,143 @@ +package at.gv.egovernment.moa.id.proxy.invoke; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Vector; + +import javax.xml.namespace.QName; +import javax.xml.rpc.Call; +import javax.xml.rpc.Service; +import javax.xml.rpc.ServiceFactory; + +import org.apache.axis.message.SOAPBodyElement; +import org.w3c.dom.Element; + +import at.gv.egovernment.moa.id.AuthenticationException; +import at.gv.egovernment.moa.id.BuildException; +import at.gv.egovernment.moa.id.ParseException; +import at.gv.egovernment.moa.id.ServiceException; +import at.gv.egovernment.moa.id.config.ConfigurationException; +import at.gv.egovernment.moa.id.config.ConnectionParameter; +import at.gv.egovernment.moa.id.config.proxy.ProxyConfigurationProvider; +import at.gv.egovernment.moa.id.data.AuthenticationData; +import at.gv.egovernment.moa.id.data.SAMLStatus; +import at.gv.egovernment.moa.id.proxy.builder.SAMLRequestBuilder; +import at.gv.egovernment.moa.id.proxy.parser.SAMLResponseParser; +import at.gv.egovernment.moa.id.proxy.servlet.ProxyException; +import at.gv.egovernment.moa.id.util.Random; + +/** + * Invoker of + * <ul> + * <li>either the GetAuthenticationData web service of MOA-ID Auth</li> + * <li>or the API call {@link at.gv.egovernment.moa.id.auth.AuthenticationServer#getAuthenticationData},</li> + * </ul> + * depending of the configuration. + * + * @author Paul Ivancsics + * @version $Id$ + */ +public class GetAuthenticationDataInvoker { + /** Create a new QName object for the webservice endpoint */ + private static final QName SERVICE_QNAME = new QName("GetAuthenticationData"); + + /** invoked object for API call of MOA-ID Auth */ + private static Object apiServer = null; + /** invoked method for API call of MOA-ID Auth */ + private static Method apiMethod = null; + + /** + * Invokes the service passing domain model objects. + * @param samlArtifact SAML artifact + * @return AuthenticationData object + * @throws ServiceException on any exception thrown + */ + /** + * Get authentication data from the MOA-ID Auth component, + * either via API call or via web service call. + * @param samlArtifact SAML artifact to be used as a parameter + * @return AuthenticationData + */ + public AuthenticationData getAuthenticationData(String samlArtifact) + throws ConfigurationException, ProxyException, BuildException, ServiceException, ParseException, AuthenticationException { + + ConnectionParameter authConnParam = + ProxyConfigurationProvider.getInstance().getAuthComponentConnectionParameter(); + if (authConnParam == null) { + try { + if (apiServer == null) { + Class serverClass = Class.forName("at.gv.egovernment.moa.id.auth.AuthenticationServer"); + Method getInstanceMethod = serverClass.getMethod("getInstance", null); + apiServer = getInstanceMethod.invoke(null, null); + apiMethod = serverClass.getMethod( + "getAuthenticationData", new Class[] {String.class}); + } + AuthenticationData authData = (AuthenticationData)apiMethod.invoke(apiServer, new Object[] {samlArtifact}); + return authData; + } + catch (InvocationTargetException ex) { + Throwable targetEx = ex.getTargetException(); + if (targetEx instanceof AuthenticationException) + throw (AuthenticationException) targetEx; + else + throw new ProxyException("proxy.09", new Object[] {targetEx.toString()}); + } + catch (Throwable ex) { + throw new ProxyException("proxy.09", new Object[] {ex.toString()}); + } + } + else { + Element samlpRequest = new SAMLRequestBuilder().build(Random.nextRandom(), samlArtifact); + Element samlpResponse = getAuthenticationData(samlpRequest); + SAMLResponseParser srp = new SAMLResponseParser(samlpResponse); + SAMLStatus status = srp.parseStatusCode(); + if (! "samlp:Success".equals(status.getStatusCode())) { + // on error status throw exception + String code = status.getStatusCode(); + if (status.getSubStatusCode() != null && status.getSubStatusCode().length() > 0) + code += "(" + status.getSubStatusCode() + ")"; + throw new ServiceException("service.02", new Object[] {code, status.getStatusMessage()}); + } + return srp.parseAuthenticationData(); + } + } + + /** + * Invokes the service passing DOM elements. + * @param request request DOM element + * @return response DOM element + * @throws ServiceException on any exception thrown + */ + public Element getAuthenticationData(Element request) throws ServiceException { + try { + Service service = ServiceFactory.newInstance().createService(SERVICE_QNAME); + Call call = service.createCall(); + SOAPBodyElement body = + new SOAPBodyElement(request); + SOAPBodyElement[] params = new SOAPBodyElement[] {body}; + Vector responses; + SOAPBodyElement response; + + String endPoint; + ConnectionParameter authConnParam = + ProxyConfigurationProvider.getInstance().getAuthComponentConnectionParameter(); + + //If the ConnectionParameter do NOT exist, we throw an exception .... + if (authConnParam!=null) { + endPoint = authConnParam.getUrl(); + call.setTargetEndpointAddress(endPoint); + responses = (Vector) call.invoke(SERVICE_QNAME, params); + response = (SOAPBodyElement) responses.get(0); + return response.getAsDOM(); + } + else + { + throw new ServiceException("service.01", null); + } + } + catch (Exception ex) { + throw new ServiceException("service.00", new Object[] {ex.toString()}, ex); + } + } + +} diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/parser/AuthenticationDataAssertionParser.java b/id.server/src/at/gv/egovernment/moa/id/proxy/parser/AuthenticationDataAssertionParser.java new file mode 100644 index 000000000..ce0743b3d --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/parser/AuthenticationDataAssertionParser.java @@ -0,0 +1,145 @@ +package at.gv.egovernment.moa.id.proxy.parser; + +import org.w3c.dom.Element; + +import at.gv.egovernment.moa.id.ParseException; +import at.gv.egovernment.moa.id.data.AuthenticationData; +import at.gv.egovernment.moa.util.BoolUtils; +import at.gv.egovernment.moa.util.Constants; +import at.gv.egovernment.moa.util.DOMUtils; +import at.gv.egovernment.moa.util.XPathUtils; + +/** + * Parser for the <code><saml:Assertion></code> returned by the + * <code>GetAuthenticationData</code> web service. + * @author Paul Ivancsics + * @version $Id$ + */ +public class AuthenticationDataAssertionParser implements Constants { + + /** Prefix for SAML-Xpath-expressions */ + private static String SAML = SAML_PREFIX + ":"; + /** Prefix for PersonData-Xpath-expressions */ + private static String PR = PD_PREFIX + ":"; + /** Prefix for Attribute MajorVersion in an Xpath-expression */ + private static String MAJOR_VERSION_XPATH = + "@MajorVersion"; + /** Prefix for Attribute MinorVersion in an Xpath-expression */ + private static String MINOR_VERSION_XPATH = + "@MinorVersion"; + /** Prefix for Attribute AssertionID in an Xpath-expression */ + private static String ASSERTION_ID_XPATH = + "@AssertionID"; + /** Prefix for Attribute Issuer in an Xpath-expression */ + private static String ISSUER_XPATH = + "@Issuer"; + /** Prefix for Attribute IssueInstant in an Xpath-expression */ + private static String ISSUE_INSTANT_XPATH = + "@IssueInstant"; + /** Prefix for Element AttributeStatement in an Xpath-expression */ + private static String ATTRIBUTESTATEMENT_XPATH = + SAML + "AttributeStatement/"; + /** Prefix for Element NameIdentifier in an Xpath-expression */ + private static String VPK_XPATH = + ATTRIBUTESTATEMENT_XPATH + + SAML + "Subject/" + + SAML + "NameIdentifier"; + /** Prefix for Element Person in an Xpath-expression */ + private static String PERSONDATA_XPATH = + ATTRIBUTESTATEMENT_XPATH + + SAML + "Attribute[@AttributeName=\"PersonData\"]/" + + SAML + "AttributeValue/" + + PR + "Person/"; + /** Prefix for Element Value in an Xpath-expression */ + private static String ZMRZAHL_XPATH = + PERSONDATA_XPATH + + PR + "Identification/" + + PR + "Value"; + /** Prefix for Element GivenName in an Xpath-expression */ + private static String GIVEN_NAME_XPATH = + PERSONDATA_XPATH + + PR + "Name/" + + PR + "GivenName"; + /** Prefix for Element FamilyName in an Xpath-expression */ + private static String FAMILY_NAME_XPATH = + PERSONDATA_XPATH + + PR + "Name/" + + PR + "FamilyName"; + /** Prefix for Element DateOfBirth in an Xpath-expression */ + private static String DATE_OF_BIRTH_XPATH = + PERSONDATA_XPATH + + PR + "DateOfBirth"; + /** Prefix for Element AttributeValue in an Xpath-expression */ + private static String IS_QUALIFIED_CERT_XPATH = + ATTRIBUTESTATEMENT_XPATH + + SAML + "Attribute[@AttributeName=\"isQualifiedCertificate\"]/" + + SAML + "AttributeValue"; + /** Prefix for Element AttributeValue in an Xpath-expression */ + private static String PUBLIC_AUTHORITY_XPATH = + ATTRIBUTESTATEMENT_XPATH + + SAML + "Attribute[@AttributeName=\"isPublicAuthority\"]/" + + SAML + "AttributeValue"; + /** Element samlAssertion represents the SAML:Assertion */ + private Element samlAssertion; + + /** + * Constructor + * @param samlAssertion samlpResponse the <code><samlp:Response></code> as a DOM element + */ + public AuthenticationDataAssertionParser(Element samlAssertion) { + this.samlAssertion = samlAssertion; + } + + /** + * Parses the <code><saml:Assertion></code>. + * @return <code>AuthenticationData</code> object + * @throws ParseException on any error + */ + public AuthenticationData parseAuthenticationData() + throws ParseException { + + try { + AuthenticationData authData = new AuthenticationData(); + //ÄNDERN: NUR der Identification-Teil + authData.setSamlAssertion(DOMUtils.serializeNode(samlAssertion)); + authData.setMajorVersion(new Integer( + XPathUtils.getAttributeValue(samlAssertion, MAJOR_VERSION_XPATH, "-1")).intValue()); + authData.setMinorVersion(new Integer( + XPathUtils.getAttributeValue(samlAssertion, MINOR_VERSION_XPATH, "-1")).intValue()); + authData.setAssertionID( + XPathUtils.getAttributeValue(samlAssertion, ASSERTION_ID_XPATH, "")); + authData.setIssuer( + XPathUtils.getAttributeValue(samlAssertion, ISSUER_XPATH, "")); + authData.setIssueInstant( + XPathUtils.getAttributeValue(samlAssertion, ISSUE_INSTANT_XPATH, "")); + authData.setVPK( + XPathUtils.getElementValue(samlAssertion, VPK_XPATH, "")); + authData.setIdentificationValue( + XPathUtils.getElementValue(samlAssertion, ZMRZAHL_XPATH, "")); + authData.setGivenName( + XPathUtils.getElementValue(samlAssertion, GIVEN_NAME_XPATH, "")); + authData.setFamilyName( + XPathUtils.getElementValue(samlAssertion, FAMILY_NAME_XPATH, "")); + authData.setDateOfBirth( + XPathUtils.getElementValue(samlAssertion, DATE_OF_BIRTH_XPATH, "")); + authData.setQualifiedCertificate(BoolUtils.valueOf( + XPathUtils.getElementValue(samlAssertion, IS_QUALIFIED_CERT_XPATH, ""))); + String publicAuthority = + XPathUtils.getElementValue(samlAssertion, PUBLIC_AUTHORITY_XPATH, null); + if (publicAuthority == null) { + authData.setPublicAuthority(false); + authData.setPublicAuthorityCode(""); + } + else { + authData.setPublicAuthority(true); + if (! publicAuthority.equalsIgnoreCase("true")) + authData.setPublicAuthorityCode(publicAuthority); + } + return authData; + } + catch (Throwable t) { + throw new ParseException("parser.01", new Object[] { t.toString() }, t); + } + } + +} diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/parser/SAMLResponseParser.java b/id.server/src/at/gv/egovernment/moa/id/proxy/parser/SAMLResponseParser.java new file mode 100644 index 000000000..9f77578fd --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/parser/SAMLResponseParser.java @@ -0,0 +1,100 @@ +package at.gv.egovernment.moa.id.proxy.parser; + +import org.w3c.dom.Element; + +import at.gv.egovernment.moa.id.ParseException; +import at.gv.egovernment.moa.id.data.AuthenticationData; +import at.gv.egovernment.moa.id.data.SAMLStatus; +import at.gv.egovernment.moa.util.Constants; +import at.gv.egovernment.moa.util.XPathUtils; + +/** + * Parser for the <code><samlp:Response></code> returned by the + * <code>GetAuthenticationData</code> web service. + * @author Paul Ivancsics + * @version $Id$ + */ +public class SAMLResponseParser implements Constants { + /** Element containing the samlResponse */ + private Element samlResponse; + /** Xpath prefix for reaching SAMLP Namespaces */ + private static String SAMLP = SAMLP_PREFIX + ":"; + /** Xpath prefix for reaching SAML Namespaces */ + private static String SAML = SAML_PREFIX + ":"; + /** Xpath prefix for reaching PersonData Namespaces */ + private static String PR = PD_PREFIX + ":"; + /** Xpath expression for reaching the SAMLP:Response element */ + private static final String ROOT = + "/" + SAMLP + "Response/"; + /** Xpath expression for reaching the SAMLP:Status element */ + private static final String STATUS_XPATH = + ROOT + + SAMLP + "Status/"; + /** Xpath expression for reaching the SAMLP:StatusCode_Value attribute */ + private static final String STATUSCODE_XPATH = + STATUS_XPATH + + SAMLP + "StatusCode/@Value"; + /** Xpath expression for reaching the SAMLP:SubStatusCode_Value attribute */ + private static final String SUBSTATUSCODE_XPATH = + STATUS_XPATH + + SAMLP + "StatusCode/" + + SAMLP + "StatusCode/@Value"; + /** Xpath expression for reaching the SAMLP:StatusMessage element */ + private static final String STATUSMESSAGE_XPATH = + STATUS_XPATH + + SAMLP + "StatusMessage"; + /** Xpath expression for reaching the SAML:Assertion element */ + private static String ASSERTION_XPATH = + ROOT + + SAML + "Assertion"; + + /** + * Constructor + * @param samlResponse the <code><samlp:Response></code> as a DOM element + */ + public SAMLResponseParser(Element samlResponse) { + this.samlResponse = samlResponse; + } + + /** + * Parses the <code><samlp:StatusCode></code> from the <code><samlp:Response></code>. + * @return <code>AuthenticationData</code> object + * @throws ParseException on any parsing error + */ + public SAMLStatus parseStatusCode() + throws ParseException { + + SAMLStatus status = new SAMLStatus(); + try { + status.setStatusCode( + XPathUtils.getAttributeValue(samlResponse, STATUSCODE_XPATH, "")); + status.setSubStatusCode( + XPathUtils.getAttributeValue(samlResponse, SUBSTATUSCODE_XPATH, "")); + status.setStatusMessage( + XPathUtils.getElementValue(samlResponse, STATUSMESSAGE_XPATH, "")); + } + catch (Throwable t) { + throw new ParseException("parser.01", new Object[] { t.toString() }, t); + } + return status; + } + + /** + * Parses the <code><saml:Assertion></code> from the <code><samlp:Response></code>. + * @return <code>AuthenticationData</code> object + * @throws ParseException on any parsing error + */ + public AuthenticationData parseAuthenticationData() + throws ParseException { + + Element samlAssertion; + try { + samlAssertion = (Element)XPathUtils.selectSingleNode(samlResponse, ASSERTION_XPATH); + } + catch (Throwable t) { + throw new ParseException("parser.01", new Object[] { t.toString() }, t); + } + return new AuthenticationDataAssertionParser(samlAssertion).parseAuthenticationData(); + } + +} diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/servlet/ConfigurationServlet.java b/id.server/src/at/gv/egovernment/moa/id/proxy/servlet/ConfigurationServlet.java new file mode 100644 index 000000000..a00c48387 --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/servlet/ConfigurationServlet.java @@ -0,0 +1,73 @@ +package at.gv.egovernment.moa.id.proxy.servlet; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import at.gv.egovernment.moa.id.proxy.MOAIDProxyInitializer; +import at.gv.egovernment.moa.id.util.MOAIDMessageProvider; +import at.gv.egovernment.moa.logging.Logger; + +/** + * Servlet requested for updating the MOA-ID Auth configuration from configuration file + * + * @author Paul Ivancsics + * @version $Id$ + */ +public class ConfigurationServlet extends HttpServlet { + /** The standard String for DTD Doc-type */ + private static final String DOC_TYPE = + "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n"; + + /** + * Handle a HTTP GET request, used to indicated that the MOA + * configuration needs to be updated (reloaded). + * + * @see javax.servlet.http.HttpServlet#doGet(HttpServletRequest, HttpServletResponse) + */ + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + MOAIDMessageProvider msg = MOAIDMessageProvider.getInstance(); + PrintWriter out; + + response.setContentType("text/html"); + out = response.getWriter(); + out.println(DOC_TYPE); + out.println("<head><title>MOA configuration update</title></head>"); + out.println("<body bgcolor=\"#FFFFFF\">"); + try { + MOAIDProxyInitializer.initialize(); + String message = msg.getMessage("config.00", null); + Logger.info(message); + out.println("<p><b>"); + out.println(message); + out.println("</b></p>"); + } catch (Throwable t) { + String errorMessage = msg.getMessage("config.04", null); + Logger.error(errorMessage, t); + out.println("<p><b>"); + out.println(errorMessage); + out.println("</b></p>"); + } + out.println("</body>"); + + out.flush(); + out.close(); + } + + /** + * Do the same as <code>doGet</code>. + * + * @see javax.servlet.http.HttpServlet#doPost(HttpServletRequest, HttpServletResponse) + */ + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doGet(request, response); + } + +} diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/servlet/ProxyException.java b/id.server/src/at/gv/egovernment/moa/id/proxy/servlet/ProxyException.java new file mode 100644 index 000000000..0080c010e --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/servlet/ProxyException.java @@ -0,0 +1,35 @@ +package at.gv.egovernment.moa.id.proxy.servlet; + +import at.gv.egovernment.moa.id.MOAIDException; + +/** + * Exception thrown while proxying a request to the online application + * + * @author Paul Ivancsics + * @version $Id$ + */ +public class ProxyException extends MOAIDException { + + /** + * Constructor for ProxyException. + * @param messageId + * @param parameters + */ + public ProxyException(String messageId, Object[] parameters) { + super(messageId, parameters); + } + + /** + * Constructor for ProxyException. + * @param messageId + * @param parameters + * @param wrapped + */ + public ProxyException( + String messageId, + Object[] parameters, + Throwable wrapped) { + super(messageId, parameters, wrapped); + } + +} diff --git a/id.server/src/at/gv/egovernment/moa/id/proxy/servlet/ProxyServlet.java b/id.server/src/at/gv/egovernment/moa/id/proxy/servlet/ProxyServlet.java new file mode 100644 index 000000000..c52de2ba8 --- /dev/null +++ b/id.server/src/at/gv/egovernment/moa/id/proxy/servlet/ProxyServlet.java @@ -0,0 +1,531 @@ +package at.gv.egovernment.moa.id.proxy.servlet; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URLEncoder; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.net.ssl.SSLSocketFactory; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import at.gv.egovernment.moa.id.AuthenticationException; +import at.gv.egovernment.moa.id.BuildException; +import at.gv.egovernment.moa.id.MOAIDException; +import at.gv.egovernment.moa.id.ParseException; +import at.gv.egovernment.moa.id.ServiceException; +import at.gv.egovernment.moa.id.config.ConfigurationException; +import at.gv.egovernment.moa.id.config.ConnectionParameter; +import at.gv.egovernment.moa.id.config.proxy.ProxyConfigurationProvider; +import at.gv.egovernment.moa.id.config.proxy.OAConfiguration; +import at.gv.egovernment.moa.id.config.proxy.OAProxyParameter; +import at.gv.egovernment.moa.id.data.AuthenticationData; +import at.gv.egovernment.moa.id.data.CookieManager; +import at.gv.egovernment.moa.id.proxy.ConnectionBuilder; +import at.gv.egovernment.moa.id.proxy.ConnectionBuilderFactory; +import at.gv.egovernment.moa.id.proxy.LoginParameterResolver; +import at.gv.egovernment.moa.id.proxy.LoginParameterResolverFactory; +import at.gv.egovernment.moa.id.proxy.MOAIDProxyInitializer; +import at.gv.egovernment.moa.id.proxy.invoke.GetAuthenticationDataInvoker; +import at.gv.egovernment.moa.id.util.MOAIDMessageProvider; +import at.gv.egovernment.moa.id.util.SSLUtils; +import at.gv.egovernment.moa.logging.Logger; +import at.gv.egovernment.moa.util.Base64Utils; + +/** + * Servlet requested for logging in at an online application, + * and then for proxying requests to the online application. + * @author Paul Ivancsics + * @version $Id$ + */ +public class ProxyServlet extends HttpServlet { + /** Name of the Parameter for the Target */ + private static final String PARAM_TARGET = "Target"; + /** Name of the Parameter for the SAMLArtifact */ + private static final String PARAM_SAMLARTIFACT = "SAMLArtifact"; + + /** Name of the Attribute for the PublicURLPrefix */ + private static final String ATT_PUBLIC_URLPREFIX = "PublicURLPrefix"; + /** Name of the Attribute for the RealURLPrefix */ + private static final String ATT_REAL_URLPREFIX = "RealURLPrefix"; + /** Name of the Attribute for the SSLSocketFactory */ + private static final String ATT_SSL_SOCKET_FACTORY = "SSLSocketFactory"; + /** Name of the Attribute for the LoginHeaders */ + private static final String ATT_LOGIN_HEADERS = "LoginHeaders"; + /** Name of the Attribute for the LoginParameters */ + private static final String ATT_LOGIN_PARAMETERS = "LoginParameters"; + + /** + * @see javax.servlet.http.HttpServlet#service(HttpServletRequest, HttpServletResponse) + */ + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + + Logger.debug("getRequestURL:" + req.getRequestURL().toString()); + try { + if (req.getParameter(PARAM_SAMLARTIFACT) != null && req.getParameter(PARAM_TARGET) != null) + login(req, resp); + else + tunnelRequest(req, resp); + } + catch (MOAIDException ex) { + handleError(resp, ex.toString(), ex); + } + catch (Throwable ex) { + handleError(resp, ex.toString(), ex); + } + } + + /** + * Login to online application at first call of servlet for a user session.<br/> + * <ul> + * <li>Acquires authentication data from the MOA-ID Auth component.</li> + * <li>Reads configuration data for the online application.</li> + * <li>Resolves login parameters.</li> + * <li>Sets up an SSLSocketFactory in case of a secure connection to the online application.</li> + * <li>For a stateless online application, stores data in the HttpSession.</li> + * <li>Tunnels the request to the online application.</li> + * </ul> + * @param req + * @param resp + * @throws ConfigurationException when wrong configuration is encountered + * @throws ProxyException when wrong configuration is encountered + * @throws BuildException while building the request for MOA-ID Auth + * @throws ServiceException while invoking MOA-ID Auth + * @throws ParseException while parsing the response from MOA-ID Auth + */ + private void login(HttpServletRequest req, HttpServletResponse resp) throws ConfigurationException, ProxyException, BuildException, ServiceException, ParseException, AuthenticationException { + + String samlArtifact = req.getParameter(PARAM_SAMLARTIFACT); + Logger.debug("moa-id-proxy login " + PARAM_SAMLARTIFACT + ": " + samlArtifact); + // String target = req.getParameter(PARAM_TARGET); parameter given but not processed + + // get authentication data from the MOA-ID Auth component + AuthenticationData authData = new GetAuthenticationDataInvoker().getAuthenticationData(samlArtifact); + + String urlRequested = req.getRequestURL().toString(); + + // read configuration data + ProxyConfigurationProvider proxyConf = ProxyConfigurationProvider.getInstance(); + OAProxyParameter oaParam = proxyConf.getOnlineApplicationParameter(urlRequested); + if (oaParam == null) { + throw new ProxyException("proxy.02", new Object[] { urlRequested }); + } + String publicURLPrefix = oaParam.getPublicURLPrefix(); + Logger.debug("OA: " + publicURLPrefix); + OAConfiguration oaConf = oaParam.getOaConfiguration(); + ConnectionParameter oaConnParam = oaParam.getConnectionParameter(); + String realURLPrefix = oaConnParam.getUrl(); + + // resolve login parameters to be forwarded to online application + LoginParameterResolver lpr = LoginParameterResolverFactory.getLoginParameterResolver(publicURLPrefix); + String clientIPAddress = req.getRemoteAddr(); + Map loginHeaders = null; + Map loginParameters = null; + if (oaConf.getAuthType().equals(OAConfiguration.PARAM_AUTH)) + loginParameters = lpr.getAuthenticationParameters(oaConf, authData, clientIPAddress); + else + loginHeaders = lpr.getAuthenticationHeaders(oaConf, authData, clientIPAddress); + + // setup SSLSocketFactory for communication with the online application + SSLSocketFactory ssf = null; + if (oaConnParam.isHTTPSURL()) { + try { + ssf = SSLUtils.getSSLSocketFactory(proxyConf, oaConnParam); + } + catch (Throwable ex) { + throw new ProxyException("proxy.05", new Object[] { oaConnParam.getUrl(), ex.toString()}, ex); + } + } + + try { + // for stateless online application, store data in HttpSession + String loginType = oaConf.getLoginType(); + Logger.debug("Login type: " + loginType); + if (loginType.equals(OAConfiguration.LOGINTYPE_STATELESS)) { + HttpSession session = req.getSession(); + int sessionTimeOut = oaParam.getSessionTimeOut(); + if (sessionTimeOut == 0) + sessionTimeOut = 60 * 60; // default 1 h + session.setMaxInactiveInterval(sessionTimeOut); + session.setAttribute(ATT_PUBLIC_URLPREFIX, publicURLPrefix); + session.setAttribute(ATT_REAL_URLPREFIX, realURLPrefix); + session.setAttribute(ATT_SSL_SOCKET_FACTORY, ssf); + session.setAttribute(ATT_LOGIN_HEADERS, loginHeaders); + session.setAttribute(ATT_LOGIN_PARAMETERS, loginParameters); + Logger.debug("moa-id-proxy: HTTPSession angelegt"); + } + + // tunnel request to the online application + int respcode = tunnelRequest(req, resp, loginHeaders, loginParameters, publicURLPrefix, realURLPrefix, ssf); + if (respcode == 401) + { + Logger.debug("Got 401, trying again"); + + respcode = tunnelRequest(req, resp, loginHeaders, loginParameters, publicURLPrefix, realURLPrefix, ssf); + if (respcode == 401) + throw new ProxyException("proxy.12", new Object[] { realURLPrefix}); + } + } + catch (ProxyException ex) { + throw new ProxyException("proxy.12", new Object[] { realURLPrefix}); + } + catch (Throwable ex) { + throw new ProxyException("proxy.04", new Object[] { urlRequested, ex.toString()}, ex); + } + } + + /** + * Tunnels a request to the stateless online application using data stored in the HTTP session. + * @param req HTTP request + * @param resp HTTP response + * @throws IOException if an I/O error occurs + */ + private void tunnelRequest(HttpServletRequest req, HttpServletResponse resp) throws ProxyException, IOException { + + Logger.debug("Tunnel request (stateless)"); + HttpSession session = req.getSession(false); + if (session == null) + throw new ProxyException("proxy.07", null); + String publicURLPrefix = (String) session.getAttribute(ATT_PUBLIC_URLPREFIX); + String realURLPrefix = (String) session.getAttribute(ATT_REAL_URLPREFIX); + SSLSocketFactory ssf = (SSLSocketFactory) session.getAttribute(ATT_SSL_SOCKET_FACTORY); + Map loginHeaders = (Map) session.getAttribute(ATT_LOGIN_HEADERS); + Map loginParameters = (Map) session.getAttribute(ATT_LOGIN_PARAMETERS); + if (publicURLPrefix == null || realURLPrefix == null) + throw new ProxyException("proxy.08", new Object[] { req.getRequestURL().toString()}); + + int respcode = tunnelRequest(req, resp, loginHeaders, loginParameters, publicURLPrefix, realURLPrefix, ssf); + if (respcode == 401) + { + Logger.debug("Got 401, trying again"); + respcode = tunnelRequest(req, resp, loginHeaders, loginParameters, publicURLPrefix, realURLPrefix, ssf); + if (respcode == 401) + throw new ProxyException("proxy.12", new Object[] { realURLPrefix}); + } + } + +/** + * Tunnels a request to the online application using given URL mapping and SSLSocketFactory. + * This method returns the ResponseCode of the request to the online application. + * @param req HTTP request + * @param resp HTTP response + * @param loginHeaders header field/values to be inserted for purposes of authentication; + * may be <code>null</code> + * @param loginParameters parameter name/values to be inserted for purposes of authentication; + * may be <code>null</code> + * @param publicURLPrefix prefix of request URL to be substituted for the <code>realURLPrefix</code> + * @param realURLPrefix prefix of online application URL to substitute the <code>publicURLPrefix</code> + * @param ssf SSLSocketFactory to use + * @throws IOException if an I/O error occurs + */ +private int tunnelRequest(HttpServletRequest req, HttpServletResponse resp, Map loginHeaders, Map loginParameters, String publicURLPrefix, String realURLPrefix, SSLSocketFactory ssf) + throws IOException { + + // collect headers from request + Map headers = new HashMap(); + for (Enumeration enum = req.getHeaderNames(); enum.hasMoreElements();) { + String headerKey = (String) enum.nextElement(); + //We ignore any Basic-Auth-Headers from the client + if (headerKey.equalsIgnoreCase("Authorization")) + { Logger.debug("Ignoring authorization-header from browser: " +req.getHeader(headerKey) ); + } + else + headers.put(headerKey, req.getHeader(headerKey)); + } + // collect login headers, possibly overwriting headers from request + if (loginHeaders != null) { + for (Iterator iter = loginHeaders.keySet().iterator(); iter.hasNext();) { + String headerKey = (String) iter.next(); + headers.put(headerKey, loginHeaders.get(headerKey)); + } + } + // collect parameters from request + Map parameters = new HashMap(); + for (Enumeration enum = req.getParameterNames(); enum.hasMoreElements();) { + String paramName = (String) enum.nextElement(); + parameters.put(paramName, req.getParameter(paramName)); + } + // collect login parameters, possibly overwriting parameters from request + if (loginParameters != null) { + for (Iterator iter = loginParameters.keySet().iterator(); iter.hasNext();) { + String paramName = (String) iter.next(); + parameters.put(paramName, loginParameters.get(paramName)); + } + } + + headers.remove("content-length"); + parameters.remove(PARAM_SAMLARTIFACT); + parameters.remove(PARAM_TARGET); + + ConnectionBuilder cb = ConnectionBuilderFactory.getConnectionBuilder(publicURLPrefix); + HttpURLConnection conn = cb.buildConnection(req, publicURLPrefix, realURLPrefix, ssf, parameters); + + //Set Cookies... + + String cookieString = CookieManager.getInstance().getCookie(req.getSession().getId()); + if (cookieString!=null) + { + //If we get Cookies from Client, we put them throgh if they dont exist/conflict with the stored Cookies + for (Iterator iter = headers.keySet().iterator(); iter.hasNext();) { + String headerKey = (String) iter.next(); + String headerValue = (String) headers.get(headerKey); + if (headerKey.equalsIgnoreCase("Cookie")) + CookieManager.getInstance().saveOldCookies(req.getSession().getId(), headerValue); + } + cookieString = CookieManager.getInstance().getCookie(req.getSession().getId()); + headers.put("cookie", cookieString); + } + + // set headers as request properties of URLConnection + for (Iterator iter = headers.keySet().iterator(); iter.hasNext();) { + String headerKey = (String) iter.next(); + String headerValue = (String) headers.get(headerKey); + conn.setRequestProperty(headerKey, headerValue); + Logger.debug("Req header " + headerKey + ": " + headers.get(headerKey)); + if (Logger.isDebugEnabled() && isBasicAuthenticationHeader(headerKey, headerValue)) { + String credentials = headerValue.substring(6); + String userIDPassword = new String(Base64Utils.decode(credentials, false)); + Logger.debug(":UserID:Password: :" + userIDPassword + ":"); + } + } + // Write out parameters into output stream of URLConnection. + // On GET request, do not send parameters in any case, + // otherwise HttpURLConnection would send a POST. + if (!"get".equalsIgnoreCase(req.getMethod()) && !parameters.isEmpty()) { + boolean firstParam = true; + StringWriter sb = new StringWriter(); + for (Iterator iter = parameters.keySet().iterator(); iter.hasNext();) { + String paramname = (String) iter.next(); + String value = URLEncoder.encode((String) parameters.get(paramname)); + if (firstParam) + firstParam = false; + else + sb.write("&"); + sb.write(paramname); + sb.write("="); + sb.write(value); + Logger.debug("Req param " + paramname + ": " + value); + } + PrintWriter reqOut = new PrintWriter(conn.getOutputStream()); + reqOut.write(sb.toString()); + reqOut.flush(); + reqOut.close(); + } + // connect + conn.connect(); + + // Read response status and content type. + // If the connection returns a 401 disconnect and return + // otherwise the attempt to read data from that connection + // will result in an error + + if (conn.getResponseCode()==HttpURLConnection.HTTP_UNAUTHORIZED) + { + Logger.debug("Found 401... searching cookies"); + String headerKey; + + int i = 1; + CookieManager cm = CookieManager.getInstance(); + while ((headerKey = conn.getHeaderFieldKey(i)) != null) { + String headerValue = conn.getHeaderField(i); + if (headerKey.equalsIgnoreCase("set-cookie")) + { cm.saveCookie(req.getSession().getId(), headerValue); + cm.add401(req.getSession().getId(),headerValue); + Logger.debug("Cookie " + headerValue); + Logger.debug("CookieSession " + req.getSession().getId()); + } + i++; + } + + conn.disconnect(); + return conn.getResponseCode(); + } + resp.setStatus(conn.getResponseCode()); + resp.setContentType(conn.getContentType()); + + // Read response headers + // Omit response header "content-length" if response header "Transfer-encoding: chunked" is set. + // Otherwise, the connection will not be kept alive, resulting in subsequent missing requests. + // See JavaDoc of javax.servlet.http.HttpServlet: + // When using HTTP 1.1 chunked encoding (which means that the response has a Transfer-Encoding header), do not set the Content-Length header. + Map respHeaders = new HashMap(); + boolean chunked = false; + String contentLengthKey = null; + String transferEncodingKey = null; + int i = 1; + String headerKey; + while ((headerKey = conn.getHeaderFieldKey(i)) != null) { + String headerValue = conn.getHeaderField(i); + respHeaders.put(headerKey, headerValue); + if (isTransferEncodingChunkedHeader(headerKey, headerValue)) { + chunked = true; + transferEncodingKey = headerKey; + } + CookieManager cm = CookieManager.getInstance(); + if (headerKey.equalsIgnoreCase("set-cookie")) + { cm.saveCookie(req.getSession().getId(), headerValue); + Logger.debug("Cookie " + headerValue); + Logger.debug("CookieSession " + req.getSession().getId()); + } + if ("content-length".equalsIgnoreCase(headerKey)) + contentLengthKey = headerKey; + Logger.debug("Resp header " + headerKey + ": " + headerValue); + i++; + } + if (chunked && contentLengthKey != null) { + respHeaders.remove(transferEncodingKey); + Logger.debug("Resp header " + transferEncodingKey + " REMOVED"); + } + + //Get a Hash-Map of all 401-set-cookies + HashMap cookies401 = CookieManager.getInstance().get401(req.getSession().getId()); + + for (Iterator iter = respHeaders.keySet().iterator(); iter.hasNext();) { + headerKey = (String) iter.next(); + + if (headerKey.equalsIgnoreCase("Set-Cookie")) + { + String headerValue = (String) respHeaders.get(headerKey); + Logger.debug("Found 'Set-Cookie' in ResponseHeaders: " + headerValue); + if(!cookies401.containsKey(headerValue.substring(0, headerValue.indexOf("=")))) + { + // If we dont already have a Set-Cookie-Value for THAT Cookie we create one... + CookieManager.getInstance().add401(req.getSession().getId(), headerValue); + } + } + } + + //write out all Responseheaders != "set-cookie" + for (Iterator iter = respHeaders.keySet().iterator(); iter.hasNext();) { + headerKey = (String) iter.next(); + if (!headerKey.equalsIgnoreCase("Set-Cookie")) + resp.addHeader(headerKey, (String) respHeaders.get(headerKey)); + } + + //write out all Responseheaders = "set-cookie" + cookies401 = CookieManager.getInstance().get401(req.getSession().getId()); + Iterator cookie_i = cookies401.values().iterator(); + while (cookie_i.hasNext()) { + String element = (String) cookie_i.next(); + resp.addHeader("Set-Cookie", element); + } + //Delete all "Set-Cookie" - Values + CookieManager.getInstance().clear401(req.getSession().getId()); + + // read response stream + Logger.debug("Resp from " + conn.getURL().toString() + ": status " + conn.getResponseCode()); + // Load content unless the server lets us know that the content is NOT MODIFIED... + if (conn.getResponseCode()!=HttpURLConnection.HTTP_NOT_MODIFIED) + { + BufferedInputStream respIn = new BufferedInputStream(conn.getInputStream()); + Logger.debug("Got Inputstream"); + BufferedOutputStream respOut = new BufferedOutputStream(resp.getOutputStream()); + Logger.debug("Got Outputstream"); + int ch; + while ((ch = respIn.read()) >= 0) + respOut.write(ch); + respOut.close(); + respIn.close(); + } + else + Logger.debug("Found 304 NOT MODIFIED..."); + conn.disconnect(); + Logger.debug("Request done"); + + + return conn.getResponseCode(); +} +/** + * Determines whether a HTTP header is a basic authentication header of the kind "Authorization: Basic ..." + * + * @param headerKey header name + * @param headerValue header value + * @return true for a basic authentication header + */ +private boolean isBasicAuthenticationHeader(String headerKey, String headerValue) { + if (!"authorization".equalsIgnoreCase(headerKey)) + return false; + if (headerValue.length() < "basic".length()) + return false; + String authenticationSchema = headerValue.substring(0, "basic".length()); + return "basic".equalsIgnoreCase(authenticationSchema); +} +/** + * Determines whether a HTTP header is "Transfer-encoding" header with value containing "chunked" + * + * @param headerKey header name + * @param headerValue header value + * @return true for a "Transfer-encoding: chunked" header + */ +private boolean isTransferEncodingChunkedHeader(String headerKey, String headerValue) { + if (!"transfer-encoding".equalsIgnoreCase(headerKey)) + return false; + return headerValue.indexOf("chunked") >= 0 || headerValue.indexOf("Chunked") >= 0 || headerValue.indexOf("CHUNKED") >= 0; +} + +/** + * Calls the web application initializer. + * + * @see javax.servlet.Servlet#init(ServletConfig) + */ +public void init(ServletConfig servletConfig) throws ServletException { + try { + MOAIDProxyInitializer.initialize(); + Logger.info(MOAIDMessageProvider.getInstance().getMessage("proxy.00", null)); + } + catch (Exception ex) { + Logger.fatal(MOAIDMessageProvider.getInstance().getMessage("proxy.06", null), ex); + throw new ServletException(ex); + } +} +/** + * Handles an error in proxying the request. + * <ul> + * <li>Logs the error.</li> + * <li>Outputs an HTML error page.</li> + * </ul> + * @param resp the HttpServletResponse + * @param errorMessage error message to be used + * @param ex the exception to be logged + */ +private void handleError(HttpServletResponse resp, String errorMessage, Throwable ex) { + Logger.error(errorMessage, ex); + String htmlCode = + "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">" + + "<html><head><title>" + + MOAIDMessageProvider.getInstance().getMessage("proxy.10", null) + + "</title></head><body>" + + "<h1>" + + MOAIDMessageProvider.getInstance().getMessage("proxy.10", null) + + "</h1>" + + "<p>" + + MOAIDMessageProvider.getInstance().getMessage("proxy.11", null) + + "</p>" + + "<p>" + + errorMessage + + "</p>" + + "</body></html>"; + resp.setContentType("text/html"); + try { + OutputStream respOut = resp.getOutputStream(); + respOut.write(htmlCode.getBytes()); + respOut.flush(); + } + catch (IOException ioex) { + Logger.error("", ioex); + } +} + +} |