Java tutorial
/******************************************************************************* * Copyright 2014 Federal Chancellery Austria * MOA-ID has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. ******************************************************************************/ /* * Copyright 2003 Federal Chancellery Austria * MOA-ID has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. */ 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.StringWriter; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; 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 org.apache.commons.lang.StringEscapeUtils; import at.gv.egovernment.moa.id.auth.exception.AuthenticationException; import at.gv.egovernment.moa.id.auth.exception.BuildException; import at.gv.egovernment.moa.id.auth.exception.MOAIDException; import at.gv.egovernment.moa.id.auth.exception.ParseException; import at.gv.egovernment.moa.id.auth.exception.ServiceException; import at.gv.egovernment.moa.id.auth.servlet.RedirectServlet; import at.gv.egovernment.moa.id.config.ConfigurationException; import at.gv.egovernment.moa.id.config.legacy.ConnectionParameter; import at.gv.egovernment.moa.id.config.proxy.OAConfiguration; import at.gv.egovernment.moa.id.config.proxy.OAProxyParameter; import at.gv.egovernment.moa.id.config.proxy.ProxyConfigurationProvider; import at.gv.egovernment.moa.id.data.AuthenticationData; import at.gv.egovernment.moa.id.protocols.saml1.SAML1AuthenticationData; 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; import at.gv.egovernment.moa.util.MiscUtil; import at.gv.egovernment.moa.util.URLEncoder; /** * 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 { /** * */ private static final long serialVersionUID = 6838184868735988125L; /** 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 Parameter for the ErrorMessage */ private static final String PARAM_ERRORMASSAGE = "error"; /** Name of the Attribute for marking the session as authenticated*/ private static final String ATT_AUTHDATAFETCHED = "AuthDataFetched"; /** 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 = "<html><head><title>Ein Fehler ist aufgetreten</title></head><body><h1>Fehler bei der Anmeldung</h1><p>Bei der Anmeldung ist ein Fehler aufgetreten.</p><p>Fehler bei der Anmeldung. <br>Prüfen Sie bitte ihre Berechtigung.<br><b>Abbruch durch den Benutzer.</b><br></p></body></html>"; /** * @see javax.servlet.http.HttpServlet#service(HttpServletRequest, HttpServletResponse) */ protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Logger.debug("getRequestURL:" + req.getRequestURL().toString()); String artifact = req.getParameter(PARAM_SAMLARTIFACT); artifact = StringEscapeUtils.escapeHtml(artifact); try { if (artifact != null) { // check if SAML Artifact was already used in this session (in case of page reload) HttpSession session = req.getSession(); if (null != session && artifact.equals(session.getAttribute(ATT_SAML_ARTIFACT))) { if (session.getAttribute(ATT_BROWSERREQU) == null) { tunnelRequest(req, resp); } else { login(req, resp); //login after browser login dialog } } else // it is the first time that the SAML Artifact was used login(req, resp); } else tunnelRequest(req, resp); } catch (MOAIDException ex) { handleError(ex.getMessage(), ex, req, resp); } catch (Throwable ex) { handleError(ex.getMessage(), ex, req, resp); } } /** * 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 { HttpSession session = req.getSession(); String samlArtifact = ""; Map loginHeaders = null; Map loginParameters = null; String publicURLPrefix = ""; String realURLPrefix = ""; SSLSocketFactory ssf = null; String urlRequested = req.getRequestURL().toString(); OAConfiguration oaConf = null; String loginType = ""; String binding = ""; if (session.getAttribute(ATT_BROWSERREQU) == null) { // read configuration data ProxyConfigurationProvider proxyConf = ProxyConfigurationProvider.getInstance(); OAProxyParameter oaParam = proxyConf.getOnlineApplicationParameter(urlRequested); if (oaParam == null) { throw new ProxyException("proxy.02", new Object[] { urlRequested }); } 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 // boolean targetprovided = req.getParameter(PARAM_TARGET) != null; // get authentication data from the MOA-ID Auth component SAML1AuthenticationData authData; try { authData = new GetAuthenticationDataInvoker().getAuthenticationData(samlArtifact); } catch (ServiceException ex) { throw new ProxyException("proxy.14", new Object[] { ex.getMessage() }, ex); } catch (ProxyException ex) { throw new ProxyException("proxy.14", new Object[] { ex.getMessage() }, ex); } catch (MOAIDException ex) { String errorURL = oaParam.getErrorRedirctURL(); if (MiscUtil.isNotEmpty(errorURL)) { generateErrorAndRedirct(resp, errorURL, ex.getMessage()); return; } else { Logger.info("No ErrorRedirectURL defined. The error is shown on MOA-ID Proxy errorpage."); throw new ProxyException("proxy.14", new Object[] { ex.getMessage() }, ex); } } session.setAttribute(ATT_AUTHDATAFETCHED, "true"); publicURLPrefix = oaParam.getPublicURLPrefix(); Logger.debug("OA: " + publicURLPrefix); oaConf = oaParam.getOaConfiguration(); ConnectionParameter oaConnParam = oaParam.getConnectionParameter(); realURLPrefix = oaConnParam.getUrl(); // resolve login parameters to be forwarded to online application LoginParameterResolver lpr = LoginParameterResolverFactory.getLoginParameterResolver(publicURLPrefix); String clientIPAddress = req.getRemoteAddr(); boolean businessService = oaParam.getBusinessService(); try { if (oaConf.getAuthType().equals(OAConfiguration.PARAM_AUTH)) { loginParameters = lpr.getAuthenticationParameters(oaConf, authData, clientIPAddress, businessService, publicURLPrefix); } else { loginHeaders = lpr.getAuthenticationHeaders(oaConf, authData, clientIPAddress, businessService, publicURLPrefix); for (Iterator iter = loginHeaders.keySet().iterator(); iter.hasNext();) { //extract user-defined bindingValue String headerKey = (String) iter.next(); String headerKeyValue = (String) loginHeaders.get(headerKey); if (headerKey.equalsIgnoreCase("binding")) { binding = (String) loginHeaders.get(headerKey); } for (int i = 1; i <= 3; i++) { if (headerKey.equalsIgnoreCase("param" + i)) { int sep = headerKeyValue.indexOf("="); if (sep > -1) { if (sep > 0) { String value = ""; if (headerKeyValue.length() > sep + 1) value = headerKeyValue.substring(sep + 1); if (loginParameters == null) loginParameters = new HashMap(); loginParameters.put(headerKeyValue.substring(0, sep), value); } } else { loginParameters.put(headerKey, ""); } } } } loginHeaders.remove("binding"); loginHeaders.remove("param1"); loginHeaders.remove("param2"); loginHeaders.remove("param3"); } } catch (LoginParameterResolverException ex) { String errorURL = oaParam.getErrorRedirctURL(); if (MiscUtil.isNotEmpty(errorURL)) { generateErrorAndRedirct(resp, errorURL, MOAIDMessageProvider.getInstance() .getMessage("proxy.13", new Object[] { publicURLPrefix })); return; } else throw new ProxyException("proxy.13", new Object[] { publicURLPrefix }); } catch (NotAllowedException e) { String errorURL = oaParam.getErrorRedirctURL(); if (MiscUtil.isNotEmpty(errorURL)) { generateErrorAndRedirct(resp, errorURL, MOAIDMessageProvider.getInstance().getMessage("proxy.15", new Object[] {})); return; } else throw new ProxyException("proxy.15", new Object[] {}); } // setup SSLSocketFactory for communication with the online application if (oaConnParam.isHTTPSURL()) { try { ssf = SSLUtils.getSSLSocketFactory(proxyConf, oaConnParam); } catch (Throwable ex) { throw new ProxyException("proxy.05", new Object[] { oaConnParam.getUrl(), ex.toString() }, ex); } } // for stateless online application, store data in HttpSession loginType = oaConf.getLoginType(); if ("".equalsIgnoreCase(binding)) { binding = oaConf.getBinding(); if ("".equalsIgnoreCase(binding)) binding = "full"; } Logger.debug("Login type: " + loginType); if (loginType.equals(OAConfiguration.LOGINTYPE_STATELESS)) { 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); session.setAttribute(ATT_SAML_ARTIFACT, samlArtifact); session.setAttribute(ATT_OA_CONF, oaConf); session.setAttribute(ATT_OA_LOGINTYPE, loginType); session.setAttribute(ATT_OA_USER_BINDING, binding); session.removeAttribute(ATT_BROWSERREQU); session.removeAttribute(ATT_OA_AUTHORIZATION_HEADER); session.removeAttribute(ATT_OA_LOGINTRY); Logger.debug("moa-id-proxy: HTTPSession " + session.getId() + " angelegt"); } } else { loginHeaders = (Map) session.getAttribute(ATT_LOGIN_HEADERS); publicURLPrefix = (String) session.getAttribute(ATT_PUBLIC_URLPREFIX); realURLPrefix = (String) session.getAttribute(ATT_REAL_URLPREFIX); ssf = (SSLSocketFactory) session.getAttribute(ATT_SSL_SOCKET_FACTORY); loginHeaders = (Map) session.getAttribute(ATT_LOGIN_HEADERS); loginParameters = (Map) session.getAttribute(ATT_LOGIN_PARAMETERS); samlArtifact = (String) session.getAttribute(ATT_SAML_ARTIFACT); oaConf = (OAConfiguration) session.getAttribute(ATT_OA_CONF); loginType = (String) session.getAttribute(ATT_OA_LOGINTYPE); binding = (String) session.getAttribute(ATT_OA_USER_BINDING); session.removeAttribute(ATT_BROWSERREQU); Logger.debug("moa-id-proxy: HTTPSession " + session.getId() + " aufgenommen"); } try { int respcode = 0; // tunnel request to the online application respcode = tunnelRequest(req, resp, loginHeaders, loginParameters, publicURLPrefix, realURLPrefix, ssf, binding); if (respcode == 401) { if (OAConfiguration.BINDUNG_FULL.equals(binding) && oaConf.getLoginType().equals(OAConfiguration.LOGINTYPE_STATELESS)) { 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); //A session is automatically created when forwarded 1st time to errorpage-proxy.jsp (with the handleError method) //additional check if publicURLPrefix is OK, if not throw an Exception if (publicURLPrefix == null) throw new ProxyException("proxy.07", null); 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); String binding = (String) session.getAttribute(ATT_OA_USER_BINDING); 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, binding); if (respcode == -401) // #tries to login exceeded throw new ProxyException("proxy.16", new Object[] { realURLPrefix, Integer.toString(MAX_OA_LOGINTRY) }); } /** * 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, String binding) throws IOException { String originBinding = binding; String browserUserID = ""; String browserPassword = ""; //URL url = new URL(realURLPrefix); //String realURLHost = url.getHost(); 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); //deactivate following line for security //if (INTERNAL_DEBUG) Logger.debug("Analyzing authorization-header from browser: " + headerKeyValue + "gives UN:PW=" + browserUserID + ":" + browserPassword ); } if (headerKeyValue.substring(0, 9).equalsIgnoreCase("Negotiate")) { //deactivate following line for security //if (INTERNAL_DEBUG) Logger.debug("Analyzing authorization-header from browser: Found NTLM Aut.: " + headerKeyValue + "gives UN:PW=" + browserUserID + ":" + browserPassword ); } } else { /* Headers MUST NOT be repaced according to our Spec. if (headerKey.equalsIgnoreCase("Host")) { headerKeyValue = realURLHost; //headerKeyValue= realURLPrefix.substring(hoststartpos); if (INTERNAL_DEBUG) Logger.debug("replaced:" + headerKey + "=" + headerKeyValue); } */ headers.put(headerKey, headerKeyValue); } } // collect login headers, possibly overwriting headers from request String authorizationvalue = ""; if (req.getSession().getAttribute(ATT_OA_AUTHORIZATION_HEADER) == null) { if (OAConfiguration.BINDUNG_NOMATCH.equals(binding)) { int loginTry = getLoginTry(req); Logger.debug("Binding: mode = " + OAConfiguration.BINDUNG_NOMATCH + "(try #" + Integer.toString(loginTry) + ")"); if (loginTry == 1) { binding = OAConfiguration.BINDUNG_FULL; } else { binding = OAConfiguration.BINDUNG_USERNAME; } } /* Soll auch bei anderen bindings zuerst ein passwort probiert werden knnen: //if we have the first Login-Try and we have Binding to Username and a predefined Password we try this one first // full binding will be covered by next block if (loginTry==1 && !OAConfiguration.BINDUNG_FULL.equals(binding)) { //1st try: if we have a password, try this one first for (Iterator iter = loginHeaders.keySet().iterator(); iter.hasNext();) { String headerKey = (String) iter.next(); String headerKeyValue = (String) loginHeaders.get(headerKey); if (isBasicAuthenticationHeader(headerKey, headerKeyValue)) { String credentials = headerKeyValue.substring(6); byte [] bplaintextcredentials = Base64Utils.decode(credentials, true); String plaintextcredentials = new String(bplaintextcredentials); String password = plaintextcredentials.substring(plaintextcredentials.indexOf(":")+1); if (password!=null && !password.equals("")) { Logger.debug("Binding: found predefined password. Trying full binding first"); binding = OAConfiguration.BINDUNG_FULL; break; } } } } */ //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); //deactivate following line for security //if (INTERNAL_DEBUG && 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 if (INTERNAL_DEBUG) Logger.debug("Connect Request"); conn.connect(); if (INTERNAL_DEBUG) Logger.debug("Connect Response"); // check login tries if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) { int loginTry = getLoginTry(req); 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(originBinding)) { Logger.debug("Found 401 UNAUTHORIZED, leaving..."); cb.disconnect(conn); return conn.getResponseCode(); } resp.setStatus(conn.getResponseCode()); //Issue by Gregor Karlinger - content type was annotated twice //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); if (headerKey.equalsIgnoreCase("WWW-Authenticate")) { int start = headerValue.indexOf("Basic realm=\""); boolean requestsBasicAuth = headerValue.substring(start).startsWith("Basic realm=\""); if (requestsBasicAuth) { headerValue = "Basic realm=\"" + publicURLPrefix + "\""; if (OAConfiguration.BINDUNG_USERNAME.equals(originBinding) || OAConfiguration.BINDUNG_NOMATCH.equals(originBinding)) headerValue = "Basic realm=\"Bitte Passwort eingeben\""; else if ("none".equals(originBinding)) { headerValue = "Basic realm=\"Bitte Benutzername und Passwort eingeben\""; } } } // // 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(originBinding) || OAConfiguration.BINDUNG_NOMATCH.equals(originBinding)) { // headerValue = "Basic realm=\"Bitte Passwort eingeben\""; // } else if (OAConfiguration.BINDUNG_NONE.equals(originBinding)) { // 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(originBinding) && 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) || "content-length".equalsIgnoreCase(headerKey)) { respHeaders.remove(respHeader); Logger.debug("Resp header " + headerKey + " REMOVED"); } i++; } 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(); } /** * Gets the current amount of the login try at the online application * * @param req the HttpServletRequest * @return the number off the current login try */ private int getLoginTry(HttpServletRequest req) { String oa_loginTry = (String) req.getSession().getAttribute(ATT_OA_LOGINTRY); int loginTry = 1; if (oa_loginTry != null) loginTry = Integer.parseInt(oa_loginTry) + 1; return loginTry; } /** * 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. <br> * <ul> * <li>Logs the error</li> * <li>Places error message and exception thrown into the request * as request attributes (to be used by <code>"/errorpage-proxy.jsp"</code>)</li> * <li>Sets HTTP status 500 (internal server error)</li> * </ul> * * @param errorMessage error message * @param exceptionThrown exception thrown * @param req servlet request * @param resp servlet response */ protected void handleError(String errorMessage, Throwable exceptionThrown, HttpServletRequest req, HttpServletResponse resp) { if (null != errorMessage) { Logger.error(errorMessage); req.setAttribute("ErrorMessage", errorMessage); } if (null != exceptionThrown) { if (null == errorMessage) errorMessage = exceptionThrown.getMessage(); Logger.error(errorMessage, exceptionThrown); //req.setAttribute("ExceptionThrown", exceptionThrown); } if (Logger.isDebugEnabled()) { req.setAttribute("LogLevel", "debug"); } //forward this to errorpage-proxy.jsp where the HTML error page is generated ServletContext context = getServletContext(); RequestDispatcher dispatcher = context.getRequestDispatcher("/errorpage-proxy.jsp"); try { dispatcher.forward(req, resp); } catch (ServletException e) { Logger.error(e); } catch (IOException e) { Logger.error(e); } } // * taken from iaik.utils.util.copyStream: /** * Reads all data (until EOF is reached) from the given source to the * destination stream. If the destination stream is null, all data is dropped. * It uses the given buffer to read data and forward it. If the buffer is * null, this method allocates a buffer. * * @param source The stream providing the data. * @param destination The stream that takes the data. If this is null, all * data from source will be read and discarded. * @param buffer The buffer to use for forwarding. If it is null, the method * allocates a buffer. * @exception IOException If reading from the source or writing to the * destination fails. */ private static void copyStream(InputStream source, OutputStream destination, byte[] buffer, String method) throws IOException { if (source == null) { throw new NullPointerException("Argument \"source\" must not be null."); } if (buffer == null) { buffer = new byte[4096]; } if (destination != null) { int bytesRead; while ((bytesRead = source.read(buffer)) >= 0) { destination.write(buffer, 0, bytesRead); //if (method.equalsIgnoreCase("POST")) Logger.debug(buffer.toString()); } } else { while (source.read(buffer) >= 0) ; } } private static void generateErrorAndRedirct(HttpServletResponse resp, String errorURL, String message) { try { errorURL = addURLParameter(errorURL, PARAM_ERRORMASSAGE, URLEncoder.encode(message, "UTF-8")); } catch (UnsupportedEncodingException e) { errorURL = addURLParameter(errorURL, PARAM_ERRORMASSAGE, "Fehlermeldung%20konnte%20nicht%20%C3%BCbertragen%20werden."); } errorURL = resp.encodeRedirectURL(errorURL); resp.setContentType("text/html"); resp.setStatus(302); resp.addHeader("Location", errorURL); } protected static String addURLParameter(String url, String paramname, String paramvalue) { String param = paramname + "=" + paramvalue; if (url.indexOf("?") < 0) return url + "?" + param; else return url + "&" + param; } }