/*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2006 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/deegree/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstr. 19 53177 Bonn Germany E-Mail: poth@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.security.owsproxy; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.URL; import java.security.Principal; import java.util.Enumeration; import java.util.Iterator; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.catalina.Group; import org.apache.catalina.Role; import org.apache.catalina.UserDatabase; import org.apache.catalina.users.MemoryUser; import org.deegree.enterprise.servlet.ServletRequestWrapper; import org.deegree.enterprise.servlet.ServletResponseWrapper; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.framework.util.StringTools; import org.deegree.framework.xml.NamespaceContext; import org.deegree.framework.xml.XMLTools; import org.deegree.graphics.Encoders; import org.deegree.ogcbase.BaseURL; import org.deegree.ogcbase.CommonNamespaces; import org.deegree.ogcwebservices.InvalidParameterValueException; import org.deegree.ogcwebservices.OGCRequestFactory; import org.deegree.ogcwebservices.OGCWebServiceException; import org.deegree.ogcwebservices.OGCWebServiceRequest; import org.deegree.security.SecurityConfigurationException; import org.deegree.security.UnauthorizedException; import org.deegree.security.drm.SecurityAccessManager; import org.deegree.security.drm.model.User; import org.deegree.security.owsrequestvalidator.Policy; import org.deegree.security.owsrequestvalidator.PolicyDocument; import org.deegree.security.owsrequestvalidator.csw.CSWValidator; import org.deegree.security.owsrequestvalidator.wfs.WFSValidator; import org.deegree.security.owsrequestvalidator.wms.WMSValidator; import org.w3c.dom.Document; import org.xml.sax.SAXException; /** * An OWSProxyPolicyFilter can be registered as a ServletFilter to * a web context. It offeres a facade that looks like a OWS but * additionaly enables validating incoming requests and outgoing * responses against rules defined in a policy document and/or a * deegree user and right management system. * @see org.deegree.security.drm.SecurityRegistry * * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a> * @author last edited by: $Author: poth $ * * @version 1.1, $Revision: 1.48 $, $Date: 2006/11/27 09:07:53 $ * * @since 1.1 */ public class OWSProxyServletFilter implements Filter { private static final ILogger LOG = LoggerFactory.getLogger( OWSProxyServletFilter.class ); private static final NamespaceContext nsContext = CommonNamespaces.getNamespaceContext(); private FilterConfig config = null; private OWSProxyPolicyFilter pFilter = null; private Policy policy = null; private String altRequestPage = null; private String altResponsePage = null; private boolean imageExcpeted = false; /** * initialize the filter with parameters from the deployment descriptor * * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) */ public void init( FilterConfig config ) throws ServletException { this.config = config; pFilter = new OWSProxyPolicyFilter(); String proxyURL = "http://127.0.0.1/owsproxy/proxy"; if ( config.getInitParameter( "PROXYURL" ) != null ) { proxyURL = config.getInitParameter( "PROXYURL" ); } Enumeration iterator = config.getInitParameterNames(); try { while ( iterator.hasMoreElements() ) { String paramName = (String) iterator.nextElement(); String paramValue = config.getInitParameter( paramName ); if ( paramName.endsWith( "POLICY" ) ) { paramValue = config.getServletContext().getRealPath( paramValue ); File file = new File( paramValue ); initValidator( proxyURL, paramName, file.toURL() ); } } } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new ServletException( e ); } altRequestPage = config.getInitParameter( "ALTREQUESTPAGE" ); altResponsePage = config.getInitParameter( "ALTRESPONSEPAGE" ); } /** * @param paramName * @param paramValue * @throws ServletException */ private void initValidator( String proxyURL, String paramName, URL paramValue ) throws ServletException { try { PolicyDocument doc = new PolicyDocument( paramValue ); policy = doc.getPolicy(); int pos = paramName.indexOf( ':' ); String service = paramName.substring( 0, pos ); if ( service.equals( "WMS" ) ) { WMSValidator v = new WMSValidator( policy, proxyURL ); pFilter.addValidator( v ); } else if ( service.equals( "WFS" ) ) { pFilter.addValidator( new WFSValidator( policy, proxyURL ) ); } else if ( service.equals( "WCS" ) ) { // TODO } else if ( service.equals( "CSW" ) ) { pFilter.addValidator( new CSWValidator( policy, proxyURL ) ); } } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new ServletException( StringTools.stackTraceToString( e ) ); } } /** * free resources allocated by the filter * * @see javax.servlet.Filter#destroy() */ public void destroy() { config = null; } /** * perform filter * * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, * javax.servlet.ServletResponse, javax.servlet.FilterChain) */ public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException { // encapsulate the servelt request into a wrapper object to ensure // the availability of the InputStream ServletRequestWrapper reqWrap = new ServletRequestWrapper( (HttpServletRequest) request ); // create OGCWebServiceRequest from the ServletRequest // String req = null; // try { // req = getRequestContent( reqWrap ); // } catch ( GeneralSecurityException e ) { // LOG.logError( e.getMessage(), e ); // throw new ServletException( e.getMessage() ); // } OGCWebServiceRequest owsReq = null; if (0 == request.getParameterMap().size()) { response.setContentType( "text/html" ); OutputStream os = response.getOutputStream(); os.write( ("Welcome to the OwsProxyServer application. " + "Please provide GET parameters\n").getBytes() ); os.close(); return; } try { owsReq = OGCRequestFactory.create( reqWrap ); } catch ( OGCWebServiceException e ) { LOG.logError( e.getMessage(), e ); throw new ServletException( e.getMessage() ); } catch ( SAXException e ) { LOG.logError( e.getMessage(), e ); throw new ServletException( e.getMessage() ); } // extract user from the request User user = null; try { user = getUser( reqWrap, owsReq ); } catch ( UnauthorizedException e1 ) { handleResponseMissingAutorization( (HttpServletRequest) request, (HttpServletResponse) response, e1.getMessage() ); return; } catch (InvalidParameterValueException e) { // this may happens if a USER with none assigned valueparameter is used within a request LOG.logError( e.getMessage(), e ); request.setAttribute( "MESSAGE", e.getMessage() ); ServletContext sc = config.getServletContext(); sc.getRequestDispatcher( altResponsePage ).forward( request, response ); return; } UsersOperationParameter.setCurrentUser(user); try { // XXXsyp //pFilter.validateGeneralConditions( (HttpServletRequest) request, reqWrap.getContentLength(), user ); pFilter.validate( owsReq, user ); } catch ( InvalidParameterValueException e ) { handleRequestMissingAutorization( (HttpServletRequest) request, (HttpServletResponse) response, e.getMessage() ); return; } catch ( UnauthorizedException e ) { handleRequestMissingAutorization( (HttpServletRequest) request, (HttpServletResponse) response, e.getMessage() ); return; } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); request.setAttribute( "MESSAGE", e.getMessage() ); ServletContext sc = config.getServletContext(); sc.getRequestDispatcher( altResponsePage ).forward( request, response ); return; } // encapsulate the servelt response into a wrapper object to ensure // the availability of the OutputStream ServletResponseWrapper resWrap = new ServletResponseWrapper( (HttpServletResponse) response ); logHttpRequest( reqWrap ); // forward request to the next filter or servlet chain.doFilter( reqWrap, resWrap ); // get result from performing the request OutputStream os = resWrap.getOutputStream(); byte[] b = ( (ServletResponseWrapper.ProxyServletOutputStream) os ).toByteArray(); if ( !imageExcpeted ) { LOG.logDebug( new String( b ) ); } try { // validate the result of a request performing String mime = resWrap.getContentType(); LOG.logDebug( "mime type raw: " + mime ); if ( mime != null ) { mime = StringTools.toArray( mime, ";", false )[0]; } else { if ( imageExcpeted ) { mime = "image/jpeg"; } else { mime = "text/xml"; } } LOG.logDebug( "mime type: " + mime ); b = pFilter.validate( owsReq, b, mime, user ); } catch ( InvalidParameterValueException ee ) { LOG.logError( ee.getMessage(), ee ); handleResponseMissingAutorization( (HttpServletRequest) request, (HttpServletResponse) response, ee.getMessage() ); return; } catch ( UnauthorizedException e ) { LOG.logError( e.getMessage(), e ); handleResponseMissingAutorization( (HttpServletRequest) request, (HttpServletResponse) response, e.getMessage() ); return; } response.setContentType( resWrap.getContentType() ); // write result back to the client os = response.getOutputStream(); os.write( b ); os.close(); } /** * logs a requests parameters and meta informations * @param reqWrap */ private void logHttpRequest( ServletRequestWrapper reqWrap ) { if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { LOG.logDebug( "getRemoteAddr " + reqWrap.getRemoteAddr() ); LOG.logDebug( "getPort " + reqWrap.getServerPort() ); LOG.logDebug( "getMethod " + reqWrap.getMethod() ); LOG.logDebug( "getQueryString " + reqWrap.getQueryString() ); LOG.logDebug( "getPathInfo " + reqWrap.getPathInfo() ); LOG.logDebug( "getRequestURI " + reqWrap.getRequestURI() ); LOG.logDebug( "getServerName " + reqWrap.getServerName() ); LOG.logDebug( "getServerPort " + reqWrap.getServerPort() ); LOG.logDebug( "getServletPath " + reqWrap.getServletPath() ); } } /** * go to alternative page if autorization to perform the desired request ist missing * * @param message * message indicating the missing right */ private void handleRequestMissingAutorization( HttpServletRequest request, HttpServletResponse response, String message ) throws IOException, ServletException { if ( message == null ) { message = "missing authorization"; } if ( imageExcpeted ) { response.setContentType( "image/jpeg" ); OutputStream os = response.getOutputStream(); BufferedImage bi = new BufferedImage( 500, 500, BufferedImage.TYPE_INT_RGB ); Graphics g = bi.getGraphics(); g.setColor( Color.WHITE ); g.fillRect( 0, 0, 500, 500 ); g.setColor( Color.BLACK ); g.setFont( new Font( "DIALOG", Font.PLAIN, 14 ) ); g.drawString( Messages.getString( "MISSINGAUTHORIZATION" ), 5, 60 ); String[] lines = StringTools.toArray( message, ":|", false ); int y = 100; for ( int i = 0; i < lines.length; i++ ) { g.drawString( lines[i], 5, y ); y = y + 30; } g.dispose(); try { Encoders.encodeJpeg( os, bi ); } catch ( Exception e ) { e.printStackTrace(); } os.close(); } else { request.setAttribute( "MESSAGE", message ); ServletContext sc = config.getServletContext(); sc.getRequestDispatcher( altRequestPage ).forward( request, response ); } } /** * go to alternative page if autorization to deliver the result to a request is missing * * @param message * message indicating the missing right */ private void handleResponseMissingAutorization( HttpServletRequest request, HttpServletResponse response, String message ) throws IOException, ServletException { if ( imageExcpeted ) { response.setContentType( "image/jpeg" ); OutputStream os = response.getOutputStream(); BufferedImage bi = new BufferedImage( 500, 500, BufferedImage.TYPE_INT_RGB ); Graphics g = bi.getGraphics(); g.setColor( Color.WHITE ); g.fillRect( 0, 0, 500, 500 ); g.setColor( Color.BLACK ); g.setFont( new Font( "DIALOG", Font.PLAIN, 14 ) ); String[] lines = StringTools.toArray( message, ":|", false ); int y = 100; for ( int i = 0; i < lines.length; i++ ) { g.drawString( lines[i], 5, y ); y = y + 30; } g.dispose(); try { Encoders.encodeJpeg( os, bi ); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); e.printStackTrace(); } os.write( message.getBytes() ); os.close(); } else { request.setAttribute( "MESSAGE", message ); ServletContext sc = config.getServletContext(); sc.getRequestDispatcher( altResponsePage ).forward( request, response ); } } /** * returns the user from the incomming request. The extraction of the user takes three steps * <ul> * <li>1. get the vendorspecific parameter 'USER' & 'PASSWORD' * <li>2. if 1.) is null get the remote users name (request.getRemoteUser()) * </ul> * * @param request * @return * @throws InvalidParameterValueException */ private User getUser( HttpServletRequest request, OGCWebServiceRequest owsReq ) throws UnauthorizedException, IOException, InvalidParameterValueException { String sessionId = owsReq.getVendorSpecificParameter( "SESSIONID" ); String user = owsReq.getVendorSpecificParameter( "USER" ); String password = null; if ( user != null ) { LOG.logDebug( "get user from user/password parameter" ); return authentificateFromUserPw( owsReq ); } else if ( sessionId == null && user == null && request.getUserPrincipal() != null ) { LOG.logDebug( "get user from UserPrinicipal" ); user = request.getUserPrincipal().getName(); if ( user.indexOf( "\\" ) > 1 ) { String[] us = StringTools.toArray( user, "\\", false ); user = us[us.length-1]; } } else if ( policy.getSecurityConfig() != null && sessionId != null ) { LOG.logDebug( "get user from WAS/sessionID" ); AuthentificationSettings as = policy.getSecurityConfig().getAuthsettings(); BaseURL baseUrl = as.getAuthentificationURL(); String tmp[] = getUserFromWAS( baseUrl.getOnlineResource().toExternalForm(), sessionId ); user = tmp[0]; password = tmp[1]; } else { LOG.logDebug( "get user as source IP address because wether USER, " + "SESSIONID nor Userprincipal are available" ); user = request.getRemoteAddr(); } LOG.logDebug( StringTools.concat( 100, "USER: ", user, '/', password ) ); User usr = null; try { if ( user != null && SecurityAccessManager.isInitialized() ) { SecurityAccessManager sam = SecurityAccessManager.getInstance(); usr = sam.getUserByName( user ); if ( request.getUserPrincipal() == null ) { // a user just must authenticate himself if he is // not identified by its user name being send within // the HTTP header usr.authenticate( password ); } else { // if user is read from UserPrincipal his password must // be read from security management usr.authenticate( sam.getUserByName( user ).getPassword() ); } } // XXXsyp Alternative User usr = new User(0, user, "password", "first", "last", "email", null /* registry */); usr.servletRequest = request; } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new UnauthorizedException( Messages.format( "OWSProxyServletFilter.USERERROR", user ) ); } return usr; } /** * authentificates a user if he is identified by its name and password passed as vendorspecific * parameters with an OGC service request * * @param request * @param owsReq * @return * @throws UnauthorizedException * @throws InvalidParameterValueException * @throws IOException */ private User authentificateFromUserPw( OGCWebServiceRequest owsReq ) throws UnauthorizedException, InvalidParameterValueException { String user = owsReq.getVendorSpecificParameter( "USER" ); String password = owsReq.getVendorSpecificParameter( "PASSWORD" ); LOG.logDebug( "USER: ", user ); LOG.logDebug( "PASSWORD: ", password ); if ( user == null ) { throw new InvalidParameterValueException( Messages.getString( "USERNAMEMISSING" ) ); } User usr = null; try { SecurityAccessManager sam = SecurityAccessManager.getInstance(); usr = sam.getUserByName( user ); usr.authenticate( password ); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); if ( !( user.equals( "anonymous" ) ) ) { throw new UnauthorizedException( Messages.format( "OWSProxyServletFilter.USERERROR", user ) ); } } return usr; } /** * access user informations from a remote WAAS. an array of Strings will be returned. with * <ul> * <li>[0] = user name * <li>[1] = the users password * </ul> * * @param sessionID * @return * @throws UnauthorizedException * @throws SecurityConfigurationException */ private String[] getUserFromWAS( String urlStr, String sessionID ) throws IOException { String[] user = new String[3]; try { StringBuffer sb = new StringBuffer( 200 ); sb.append( urlStr ).append( "?REQUEST=DescribeUser&Service=WAS&" ); sb.append( "SESSIONID=" ).append( sessionID ).append( "&version=1.0.0" ); URL url = new URL( sb.toString() ); InputStreamReader isr = new InputStreamReader( url.openStream() ); Document doc = XMLTools.parse( isr ); user[0] = XMLTools.getNodeAsString( doc, "/User/UserName", nsContext, null ); user[1] = XMLTools.getNodeAsString( doc, "/User/Password", nsContext, null ); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new IOException( Messages.getString( "OWSProxyServletFilter.WASACCESS" ) ); } return user; } /** * creates an <code>AbstractOGCWebServiceRequest</code> from the content contained within the * passed request * * @param request * @return * @throws IOException * @throws OGCWebServiceException */ // public OGCWebServiceRequest createOGCWebServiceRequest( ServletRequest request ) // throws OGCWebServiceException { // // try { // return OGCRequestFactory.create( request ); // } catch ( IOException e ) { // // TODO Auto-generated catch block // e.printStackTrace(); // } catch ( XMLParsingException e ) { // // TODO Auto-generated catch block // e.printStackTrace(); // } catch ( SAXException e ) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // return null; //// try { //// if ( request.startsWith( "<" ) ) { //// Document doc = XMLTools.parse( new StringReader( request ) ); //// return OGCRequestFactory.createFromXML( doc ); //// } //// } catch ( Exception e ) { //// e.printStackTrace(); //// } //// try { //// return createFromKVP( request ); //// } catch ( Exception e ) { //// LOG.logError( e.getMessage(), e ); //// throw new OGCWebServiceException( Messages.format( "OWSProxyServletFilter.KVPREQ", //// request ) ); //// } // // } /** * creates a request object from a KVP encoded OWS request * * @param request * @return * @throws Exception */ // private OGCWebServiceRequest createFromKVP( String request ) // throws Exception { // // OGCWebServiceRequest req = OGCRequestFactory.createFromKVP( request ); // // if ( req instanceof GetMap || req instanceof GetLegendGraphic ) { // imageExcpeted = true; // } // // return req; // } /** * extracts the content of a HTTP request from the encapsulating object * * @param request * @return * @throws IOException * @throws GeneralSecurityException */ // private String getRequestContent( HttpServletRequest request ) // throws IOException, GeneralSecurityException { // String method = request.getMethod(); // if ( method.equalsIgnoreCase( "POST" ) ) { // Reader reader = request.getReader(); // BufferedReader br = new BufferedReader( reader ); // StringBuffer req = new StringBuffer( 10000 ); // String line = null; // while ( ( line = br.readLine() ) != null ) { // req.append( line ); // } // br.close(); // if ( req.length() == 0 ) { // throw new GeneralSecurityException( Messages.getString( "OWSProxyServletFilter.QUERYSTRING2" ) ); // } // return req.toString(); // } // String s = request.getQueryString(); // if ( s == null ) { // throw new GeneralSecurityException( Messages.getString( "OWSProxyServletFilter.QUERYSTRING1" ) ); // } // return URLDecoder.decode( s, CharsetUtility.getSystemCharset() ); // } } /* ******************************************************************** Changes to this class. What the people have been up to: $Log: OWSProxyServletFilter.java,v $ Revision 1.48 2006/11/27 09:07:53 poth JNI integration of proj4 has been removed. The CRS functionality now will be done by native deegree code. Revision 1.47 2006/11/21 17:59:16 poth useless import removed Revision 1.46 2006/10/30 08:07:06 poth bug fix - WFS GetFeature request: FeatureType comparsion Revision 1.45 2006/10/27 11:55:45 poth authentication for users changed to use Remote Address as default Revision 1.44 2006/09/18 11:02:14 poth loglevel for username and password changed to DEBUG Revision 1.43 2006/09/18 10:56:48 poth bug fix - parsing userName from DescribeUser response Revision 1.42 2006/09/15 19:21:43 poth debug level changed for printing user/password Revision 1.41 2006/09/08 15:14:38 schmitz Updated the core of deegree to use the HttpServletRequest methods to create the KVP maps, and not to try to parse as XML every time. Updated the tests to create maps for testing instead of strings. Updated the OWSProxy subsystem to use ServletRequest classes instead of strings for request dispatching. Revision 1.40 2006/08/29 19:20:01 poth bug fix - changed GetUser to DescribeUser request (WAS) Revision 1.39 2006/08/14 13:39:38 poth printing stacktraces removed for authorization errors Revision 1.38 2006/08/08 15:46:49 poth bug fix - double URL decoding of user and password removed Revision 1.37 2006/08/07 15:55:32 poth debug statements added for authenticateFromUserPw Revision 1.36 2006/08/02 14:13:45 poth changed user identification - if a user is null or security manager has not been initialized no validation of user will be performed Revision 1.35 2006/07/23 08:44:53 poth refactoring - moved validators assigned to OWS into specialized packages Revision 1.34 2006/07/21 07:39:05 poth URLdecoding for usernames and passwords added Revision 1.33 2006/07/03 15:36:17 poth bug fix - handling case where a request has no content (avoid NPE) / correction of comments Revision 1.32 2006/06/29 09:08:36 poth *** empty log message *** Revision 1.30 2006/06/22 06:55:13 poth enabled reading user principals having '\' in its name by just using the part after the last '\' Revision 1.29 2006/05/24 16:12:40 poth support for WFS GetFeature validation added ********************************************************************** */