/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.esri.gpt.framework.context; import com.esri.gpt.framework.collection.StringAttributeMap; import com.esri.gpt.framework.security.codec.Base64; import com.esri.gpt.framework.security.credentials.Credentials; import com.esri.gpt.framework.security.credentials.CredentialsDeniedException; import com.esri.gpt.framework.security.credentials.UsernamePasswordCredentials; import com.esri.gpt.framework.security.identity.IdentityAdapter; import com.esri.gpt.framework.security.identity.IdentityException; import com.esri.gpt.framework.security.identity.NotAuthorizedException; import com.esri.gpt.framework.security.principal.User; import com.esri.gpt.framework.util.Val; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.sql.SQLException; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Super-class for HttpServlet end-points. */ public abstract class BaseServlet extends HttpServlet { // class variables ============================================================= private static Logger LOGGER = Logger.getLogger(BaseServlet.class.getName()); // instance variables ========================================================== // constructors ================================================================ // properties ================================================================== // methods ===================================================================== /** * Authenticate credentials found within and HTTP request. * @param context the active request context * @param credentials the credentials to authenticate * @throws CredentialsDeniedException if credentials are denied * @throws IdentityException if a system error occurs preventing authentication * @throws SQLException if a database communication exception occurs */ protected void authenticate(RequestContext context, Credentials credentials) throws CredentialsDeniedException, IdentityException, SQLException { getLogger().finer("Authenticating user..."); IdentityAdapter idAdapter = context.newIdentityAdapter(); User user = context.getUser(); user.reset(); user.setCredentials(credentials); try { idAdapter.authenticate(user); } catch(CredentialsDeniedException e) { if (credentials instanceof UsernamePasswordCredentials) { String sUser = ((UsernamePasswordCredentials)credentials).getUsername(); getLogger().finer("Authentication failed for: "+sUser); } else { getLogger().finer("Authentication failed."); } throw e; } } /** * Handles a GET request. * <p/> * The default behavior is the execute the doPost method. * @param request the servlet request * @param response the servlet response */ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } /** * Handles a POST request. * <p/> * The default behavior: * <li>set the character encoding (UTF-8) if it is null</li> * <li>instantiate a RequestContext</li> * <li>authenticate credentials if found within the header</li> * <li>invoke the abstract "execute" method</li> * <li>release the request context</li> * @param request the servlet request * @param response the servlet response */ @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { RequestContext context = null; try { getLogger().finer("Query string="+request.getQueryString()); logHeader(request); String sEncoding = request.getCharacterEncoding(); if ((sEncoding == null) || (sEncoding.trim().length() == 0)) { request.setCharacterEncoding("UTF-8"); } context = RequestContext.extract(request); StringAttributeMap params = context.getCatalogConfiguration().getParameters(); String autoAuthenticate = Val.chkStr(params.getValue("BaseServlet.autoAuthenticate")); if (!autoAuthenticate.equalsIgnoreCase("false")) { Credentials credentials = getCredentials(request); if (credentials != null) { authenticate(context,credentials); } } execute(request,response,context); } catch (CredentialsDeniedException e) { String sRealm = this.getRealm(context); response.setHeader("WWW-Authenticate","Basic realm=\""+sRealm+"\""); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); } catch (NotAuthorizedException e) { String sRealm = this.getRealm(context); response.setHeader("WWW-Authenticate","Basic realm=\""+sRealm+"\""); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); } catch (Throwable t) { String sErr = "Exception occured while processing servlet request."; getLogger().log(Level.SEVERE,sErr,t); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } finally { if (context != null) context.onExecutionPhaseCompleted(); } } /** * Logs the header for an incoming request. * @param request the HTTP request */ private void logHeader(HttpServletRequest request) { if (LOGGER.isLoggable(Level.FINEST)) { StringBuffer sb = new StringBuffer(); sb.append("HTTP Header ======================================"); java.util.Enumeration enNames = request.getHeaderNames(); while (enNames.hasMoreElements()) { Object o = enNames.nextElement(); if ((o != null) && (o instanceof String)) { String sName = (String)o; String sValue = request.getHeader(sName); sb.append("\n "+sName+"="+sValue); } } LOGGER.finest(sb.toString()); } } /** * Processes the HTTP request. * @param request the HTTP request * @param response HTTP response * @param context request context * @throws Exception if an exception occurs */ protected abstract void execute(HttpServletRequest request, HttpServletResponse response, RequestContext context) throws Exception; /** * Looks for username:password credentials within the Authorization * header parameter of the HTTP request. * @param request the servlet request * @return the credentials (null if none were located) * @throws IOException if an IO exception occurs * @throws CredentialsDeniedException if empty or non-basic credentials were located */ protected UsernamePasswordCredentials getCredentials(HttpServletRequest request) throws IOException, CredentialsDeniedException { UsernamePasswordCredentials creds = null; String sAuthorization = request.getHeader("Authorization"); getLogger().finer("Authorization header="+sAuthorization); if (sAuthorization != null) { creds = new UsernamePasswordCredentials(); if (sAuthorization.startsWith("Basic ")) { // look for a Basic encoded username:password // (ignore Digest we can't handle it at the moment, // requires password retrieval from LDAP) sAuthorization = sAuthorization.substring(6); if (sAuthorization.length() > 0) { String sDecoded = Base64.decode(sAuthorization,"UTF-8"); int nIdx = sDecoded.indexOf(':'); if (nIdx > 0) { creds.setUsername(sDecoded.substring(0,nIdx)); creds.setPassword(sDecoded.substring(nIdx+1)); } } } getLogger().finer("Authorization username="+creds.getUsername()); if ((creds.getUsername().length() == 0) || (creds.getPassword().length() == 0)) { throw new CredentialsDeniedException("Invalid credentials."); } } return creds; } /** * Gets the logger. * @return the logger */ protected Logger getLogger() { return LOGGER; } /** * Gets a request parameter value. * @param request the HTTP request * @param name the parameter name * @return ther parameter value */ protected String getParameterValue(HttpServletRequest request, String name) { Map<String, String[]> parMap = request.getParameterMap(); for (Map.Entry<String, String[]> e : parMap.entrySet()) { if (e.getKey().equalsIgnoreCase(name)) { if (e.getValue().length > 0) { return Val.chkStr(e.getValue()[0]); } else { return ""; } } } return ""; } /** * Gets the identity store realm (used as an identifier during HTTP 401 * credential challenge/response). * @param context the active request context * @return the identity store realm */ protected String getRealm(RequestContext context) { String realm = Val.chkStr(context.getIdentityConfiguration().getRealm()); if (realm.length() == 0) { realm = "Geoportal"; } return realm; } /** * Fully reads the characters from the request input stream. * @param request the HTTP servlet request * @return the characters read * @throws IOException if an exception occurs */ protected String readInputCharacters(HttpServletRequest request) throws IOException { StringBuffer sb = new StringBuffer(); InputStream is = null; InputStreamReader ir = null; BufferedReader br = null; try { //if (request.getContentLength() > 0) { char cbuf[] = new char[2048]; int n = 0; int nLen = cbuf.length; String sEncoding = request.getCharacterEncoding(); if ((sEncoding == null) || (sEncoding.trim().length() == 0)) { sEncoding = "UTF-8"; } is = request.getInputStream(); ir = new InputStreamReader(is,sEncoding); br = new BufferedReader(ir); while ((n = br.read(cbuf,0,nLen)) > 0) { sb.append(cbuf,0,n); } //} } finally { try {if (br != null) br.close();} catch (Exception ef) {} try {if (ir != null) ir.close();} catch (Exception ef) {} try {if (is != null) is.close();} catch (Exception ef) {} } return sb.toString(); } /** * Writes characters to the response stream. * @param response the servlet response * @param content the content to write * @param charset the response character encoding * @param contentType the response content type * @throws IOException if an IO exception occurs */ protected void writeCharacterResponse(HttpServletResponse response, String content, String charset, String contentType) throws IOException { PrintWriter writer = null; try { if (content.length() > 0) { response.setCharacterEncoding(charset); response.setContentType(contentType); writer = response.getWriter(); writer.write(content); writer.flush(); } } finally { try { if (writer != null) { writer.flush(); writer.close(); } } catch (Exception ef) { getLogger().log(Level.SEVERE,"Error closing PrintWriter.",ef); } } } /** * Convience method for writeCharacterResponse. * <br/>charset="UTF-8" * <br/>contentType="text/html; charset=UTF-8" * @param response the servlet response * @param content the content to write * @throws IOException if an IO exception occurs */ protected void writeHtmlResponse(HttpServletResponse response, String content) throws IOException { writeCharacterResponse(response,content,"UTF-8","text/html; charset=UTF-8"); } /** * Convience method for writeCharacterResponse. * <br/>charset="UTF-8" * <br/>contentType="text/xml; charset=UTF-8" * @param response the servlet response * @param content the content to write * @throws IOException if an IO exception occurs */ protected void writeXmlResponse(HttpServletResponse response, String content) throws IOException { writeCharacterResponse(response,content,"UTF-8","text/xml; charset=UTF-8"); } }