package at.gv.egovernment.moa.id.proxy.servlet; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Vector; import javax.net.ssl.SSLSocketFactory; import javax.servlet.RequestDispatcher; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; 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.LoginParameterResolverException; import at.gv.egovernment.moa.id.proxy.LoginParameterResolverFactory; import at.gv.egovernment.moa.id.proxy.MOAIDProxyInitializer; import at.gv.egovernment.moa.id.proxy.NotAllowedException; 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"; /** Name of the Attribute for the SAMLARTIFACT */ private static final String ATT_SAML_ARTIFACT = "SamlArtifact"; /** Name of the Attribute for the state of the browser request for login dialog*/ private static final String ATT_BROWSERREQU = "BrowserLoginRequest"; /** Name of the Attribute for the state of the browser request for login dialog*/ private static final String ATT_OA_CONF = "oaConf"; /** Name of the Attribute for the Logintype of the OnlineApplication*/ private static final String ATT_OA_LOGINTYPE = "LoginType"; /** Name of the Attribute for the number of the try to login into the OnlineApplication*/ private static final String ATT_OA_LOGINTRY = "LoginTry"; /** Maximum permitted login tries */ private static final int MAX_OA_LOGINTRY = 3; /** Name of the Attribute for authorization value for further connections*/ private static final String ATT_OA_AUTHORIZATION_HEADER = "authorizationkey"; /** Name of the Attribute for user binding */ private static final String ATT_OA_USER_BINDING = "UserBinding"; /** For extended internal debug messages */ private static final boolean INTERNAL_DEBUG = false; /** Message to be given if browser login failed */ private static final String RET_401_MSG = "
Bei der Anmeldung ist ein Fehler aufgetreten.
Fehler bei der Anmeldung.
Prüfen Sie bitte ihre Berechtigung.
Abbruch durch den Benutzer.
null
* @param loginParameters parameter name/values to be inserted for purposes of authentication;
* may be null
* @param publicURLPrefix prefix of request URL to be substituted for the realURLPrefix
* @param realURLPrefix prefix of online application URL to substitute the publicURLPrefix
* @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, String binding)
throws IOException {
String browserUserID = "";
String browserPassword = "";
if (INTERNAL_DEBUG && !binding.equals("")) Logger.debug("Binding: " + binding);
// collect headers from request
Map headers = new HashMap();
for (Enumeration enu = req.getHeaderNames(); enu.hasMoreElements();) {
String headerKey = (String) enu.nextElement();
String headerKeyValue = req.getHeader(headerKey);
if (INTERNAL_DEBUG) Logger.debug("Incoming:" + headerKey + "=" + headerKeyValue);
//Analyze Basic-Auth-Headers from the client
if (headerKey.equalsIgnoreCase("Authorization")) {
if (headerKeyValue.substring(0,6).equalsIgnoreCase("Basic ")) {
String credentials = headerKeyValue.substring(6);
byte [] bplaintextcredentials = Base64Utils. decode(credentials, true);
String plaintextcredentials = new String(bplaintextcredentials);
browserUserID = plaintextcredentials.substring(0,plaintextcredentials.indexOf(":"));
browserPassword = plaintextcredentials.substring(plaintextcredentials.indexOf(":")+1);
//Logger.debug("Analyzing authorization-header from browser: " + headerKeyValue + "gives UN:PW=" + browserUserID + ":" + browserPassword );
}
if (headerKeyValue.substring(0,9).equalsIgnoreCase("Negotiate")) {
//Logger.debug("Analyzing authorization-header from browser: Found NTLM Aut.: " + headerKeyValue + "gives UN:PW=" + browserUserID + ":" + browserPassword );
}
}
else
{
headers.put(headerKey, headerKeyValue);
}
}
// collect login headers, possibly overwriting headers from request
String authorizationvalue="";
if (req.getSession().getAttribute(ATT_OA_AUTHORIZATION_HEADER)==null) {
//we have a connection with not having logged on
if (loginHeaders != null && (browserPassword.length()!=0 || browserUserID.length()!=0 || OAConfiguration.BINDUNG_FULL.equals(binding))) {
for (Iterator iter = loginHeaders.keySet().iterator(); iter.hasNext();) {
String headerKey = (String) iter.next();
String headerKeyValue = (String) loginHeaders.get(headerKey);
//customize loginheaders if necessary
if (isBasicAuthenticationHeader(headerKey, headerKeyValue))
{
if ( OAConfiguration.BINDUNG_FULL.equals(binding)) {
authorizationvalue = headerKeyValue;
Logger.debug("Binding: full binding to user established");
} else {
String credentials = headerKeyValue.substring(6);
byte [] bplaintextcredentials = Base64Utils.decode(credentials, true);
String plaintextcredentials = new String(bplaintextcredentials);
String userID = plaintextcredentials.substring(0,plaintextcredentials.indexOf(":"));
String password = plaintextcredentials.substring(plaintextcredentials.indexOf(":")+1);
String userIDPassword = ":";
if (OAConfiguration.BINDUNG_USERNAME.equals(binding)) {
Logger.debug("Binding: Access with necessary binding to user");
userIDPassword = userID + ":" + browserPassword;
} else if (OAConfiguration.BINDUNG_NONE.equals(binding)) {
Logger.debug("Binding: Access without binding to user");
//If first time
if (browserUserID.length()==0) browserUserID = userID;
if (browserPassword.length()==0) browserPassword = password;
userIDPassword = browserUserID + ":" + browserPassword;
} else {
userIDPassword = userID + ":" + password;
}
credentials = Base64Utils.encode(userIDPassword.getBytes());
authorizationvalue = "Basic " + credentials;
headerKeyValue = authorizationvalue;
}
}
headers.put(headerKey, headerKeyValue);
}
}
}else{
//if OA needs Authorization header in each further request
authorizationvalue = (String) req.getSession().getAttribute(ATT_OA_AUTHORIZATION_HEADER);
if (loginHeaders != null) headers.put("Authorization", authorizationvalue);
}
Vector parameters = new Vector();
for (Enumeration enu = req.getParameterNames(); enu.hasMoreElements();) {
String paramName = (String) enu.nextElement();
if (!(paramName.equals(PARAM_SAMLARTIFACT) || paramName.equals(PARAM_TARGET))) {
if (INTERNAL_DEBUG) Logger.debug("Req Parameter-put: " + paramName + ":" + req.getParameter(paramName));
String parameter[] = new String[2];
parameter[0]= paramName;
parameter[1]= req.getParameter(paramName);
parameters.add(parameter);
}
}
// collect login parameters, possibly overwriting parameters from request
if (loginParameters != null) {
for (Iterator iter = loginParameters.keySet().iterator(); iter.hasNext();) {
String paramName = (String) iter.next();
if (!(paramName.equals(PARAM_SAMLARTIFACT) || paramName.equals(PARAM_TARGET))) {
if (INTERNAL_DEBUG) Logger.debug("Req Login-Parameter-put: " + paramName + ":" + loginParameters.get(paramName));
String parameter[] = new String[2];
parameter[0]= paramName;
parameter[1]= (String) loginParameters.get(paramName);
parameters.add(parameter);
}
}
}
ConnectionBuilder cb = ConnectionBuilderFactory.getConnectionBuilder(publicURLPrefix);
HttpURLConnection conn = cb.buildConnection(req, publicURLPrefix, realURLPrefix, ssf, parameters);
// 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);
String LogStr = "Req header " + headerKey + ": " + headers.get(headerKey);
if (isBasicAuthenticationHeader(headerKey, headerValue)) {
String credentials = headerValue.substring(6);
byte [] bplaintextcredentials = Base64Utils. decode(credentials, true);
String plaintextcredentials = new String(bplaintextcredentials);
String uid = plaintextcredentials.substring(0,plaintextcredentials.indexOf(":"));
String pwd = plaintextcredentials.substring(plaintextcredentials.indexOf(":")+1);
//Sollte AuthorizationInfo vom HTTPClient benutzt werden: cb.addBasicAuthorization(publicURLPrefix, uid, pwd);
//if (Logger.isDebugEnabled()) LogStr = LogStr + " >UserID:Password< >" + uid + ":" + pwd + "<";
}
conn.setRequestProperty(headerKey, headerValue);
if (INTERNAL_DEBUG) Logger.debug(LogStr);
}
StringWriter sb = new StringWriter();
// 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;
String parameter[] = new String[2];
for (Iterator iter = parameters.iterator(); iter.hasNext();) {
parameter = (String[]) iter.next();
String paramName = parameter[0];
String paramValue = parameter[1];
if (firstParam)
firstParam = false;
else
sb.write("&");
sb.write(paramName);
sb.write("=");
sb.write(paramValue);
if (INTERNAL_DEBUG) Logger.debug("Req param " + paramName + ": " + paramValue);
}
}
// For WebDAV and POST: copy content
if (!"get".equalsIgnoreCase(req.getMethod())) {
if (INTERNAL_DEBUG && !"post".equalsIgnoreCase(req.getMethod())) Logger.debug("---- WEBDAV ---- copying content");
try {
OutputStream out = conn.getOutputStream();
InputStream in = req.getInputStream();
if (!parameters.isEmpty()) out.write(sb.toString().getBytes()); //Parameter nicht mehr mittels Printwriter schreiben
copyStream(in, out, null, req.getMethod());
out.flush();
out.close();
} catch (IOException e) {
if (!"post".equalsIgnoreCase(req.getMethod()))
Logger.debug("---- WEBDAV ---- streamcopy problem");
else
Logger.debug("---- POST ---- streamcopy problem");
}
}
// connect
conn.connect();
// check login tries
if (conn.getResponseCode()==HttpURLConnection.HTTP_UNAUTHORIZED) {
String oa_loginTry = (String) req.getSession().getAttribute(ATT_OA_LOGINTRY);
int loginTry = 1;
if (oa_loginTry!=null) loginTry = Integer.parseInt(oa_loginTry)+1;
req.getSession().setAttribute(ATT_OA_LOGINTRY, Integer.toString(loginTry));
if (loginTry > MAX_OA_LOGINTRY) {
Logger.debug("Found 401 UNAUTHORIZED, maximum tries exceeded; leaving...");
cb.disconnect(conn);
return -401;
}
}
if (conn.getResponseCode()==HttpURLConnection.HTTP_UNAUTHORIZED && OAConfiguration.BINDUNG_FULL.equals(binding)) {
Logger.debug("Found 401 UNAUTHORIZED, leaving...");
cb.disconnect(conn);
return conn.getResponseCode();
}
resp.setStatus(conn.getResponseCode());
resp.setContentType(conn.getContentType());
if (loginHeaders != null && (conn.getResponseCode()==HttpURLConnection.HTTP_OK || conn.getResponseCode()==HttpURLConnection.HTTP_MOVED_TEMP) && req.getSession().getAttribute(ATT_OA_AUTHORIZATION_HEADER)==null) {
req.getSession().setAttribute(ATT_OA_AUTHORIZATION_HEADER, authorizationvalue);
Logger.debug("Login OK. Saving authorization header to remember in further requests");
}
// 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.
Vector respHeaders = new Vector();
boolean chunked = false;
String contentLengthKey = null;
String transferEncodingKey = null;
int i = 1;
String headerKey;
String loginType = (String) req.getSession().getAttribute(ATT_OA_LOGINTYPE);
while ((headerKey = conn.getHeaderFieldKey(i)) != null) {
String headerValue = conn.getHeaderField(i);
// Überschrift im Browser-Passworteingabedialog setzen (sonst ist der reale host eingetragen)
if (headerKey.equalsIgnoreCase("WWW-Authenticate") && headerValue.startsWith("Basic realm=\"")) {
headerValue = "Basic realm=\"" + publicURLPrefix + "\"";
if (OAConfiguration.BINDUNG_USERNAME.equals(binding)) headerValue = "Basic realm=\"Bitte Passwort eingeben\"";
if (OAConfiguration.BINDUNG_NONE.equals(binding)) headerValue = "Basic realm=\"Bitte Benutzername und Passwort eingeben\"";
}
String respHeader[] = new String[2];
if ((conn.getResponseCode()==HttpURLConnection.HTTP_UNAUTHORIZED) && headerKey.equalsIgnoreCase("content-length")) {
//alter the unauthorized message with template for login
//TODO: supply a special login form on unauthorized messages with bindings!=full
headerValue = Integer.toString(RET_401_MSG.length());
}
respHeader[0]= headerKey;
respHeader[1]= headerValue;
if (!(OAConfiguration.BINDUNG_FULL.equals(binding) && OAConfiguration.LOGINTYPE_STATELESS.equals(loginType) && headerKey.equalsIgnoreCase("WWW-Authenticate") && headerValue.startsWith("Basic realm=\""))) {
respHeaders.add(respHeader);
if (INTERNAL_DEBUG) Logger.debug("Resp header " + headerKey + ": " + headerValue);
} else {
Logger.debug("Resp header ---REMOVED--- " + headerKey + ": " + headerValue);
}
if (isTransferEncodingChunkedHeader(headerKey, headerValue)) {
chunked = true;
transferEncodingKey = headerKey;
}
if ("content-length".equalsIgnoreCase(headerKey))
contentLengthKey = headerKey;
i++;
}
if (chunked && contentLengthKey != null) {
respHeaders.remove(transferEncodingKey);
Logger.debug("Resp header " + transferEncodingKey + " REMOVED");
}
String headerValue;
String respHeader[] = new String[2];
//write out all Responseheaders
for (Iterator iter = respHeaders.iterator(); iter.hasNext();) {
respHeader = (String[]) iter.next();
headerKey = respHeader[0];
headerValue = respHeader[1];
resp.addHeader(headerKey, headerValue);
}
//Logger.debug(">>>> Copy Content");
//Logger.debug(" from ()" + conn.getURL());
//Logger.debug(" to (" + req.getRemoteAddr() + ":"+ ") " +req.getRequestURL());
// 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");
byte [] buffer = new byte[4096];
if (respOut != null) {
int bytesRead;
while ((bytesRead = respIn.read(buffer)) >= 0) {
if (conn.getResponseCode()!=HttpURLConnection.HTTP_UNAUTHORIZED) respOut.write(buffer, 0, bytesRead);
}
} else {
while (respIn.read(buffer) >= 0);
}
/*
int ch;
StringBuffer strBuf = new StringBuffer("");
while ((ch = respIn.read()) >= 0) {
if (conn.getResponseCode()!=HttpURLConnection.HTTP_UNAUTHORIZED) respOut.write(ch);
strBuf.append((char)ch);
}
Logger.debug("Resp Content:");
if (strBuf.toString().length()>500)
Logger.debug(strBuf.toString().substring(0,500));
else
Logger.debug(strBuf.toString());
*/
if (conn.getResponseCode()==HttpURLConnection.HTTP_UNAUTHORIZED) {
respOut.write(RET_401_MSG.getBytes());
}
respOut.flush();
respOut.close();
respIn.close();
if (conn.getResponseCode()==HttpURLConnection.HTTP_UNAUTHORIZED) {
Logger.debug("Found 401 UNAUTHORIZED...");
cb.disconnect(conn);
return conn.getResponseCode();
}
} else {
//if (conn.getResponseCode()==HttpURLConnection.HTTP_NOT_MODIFIED)
Logger.debug("Found 304 NOT MODIFIED...");
}
cb.disconnect(conn);
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 basic authentication header of the kind "Authorization: Basic ..."
* is included in a HTTP request
* @param req HTTP request
* @return true for a basic authentication header provided
*/
private boolean isBasicAuthenticationHeaderProvided(HttpServletRequest req) {
for (Enumeration enu = req.getHeaderNames(); enu.hasMoreElements();) {
String headerKey = (String) enu.nextElement();
String headerValue = req.getHeader(headerKey);
if (isBasicAuthenticationHeader(headerKey, headerValue))
return true;
}
return false;
}
/**
* 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 {
super.init(servletConfig);
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. "/errorpage-proxy.jsp"
)