/*---------------- 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.owsrequestvalidator; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.Properties; import javax.xml.transform.OutputKeys; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.framework.util.MimeTypeMapper; import org.deegree.framework.util.StringTools; import org.deegree.framework.xml.XMLFragment; import org.deegree.model.metadata.iso19115.Linkage; import org.deegree.model.metadata.iso19115.OnlineResource; import org.deegree.ogcwebservices.InvalidParameterValueException; import org.deegree.ogcwebservices.OGCWebServiceRequest; import org.deegree.ogcwebservices.csw.capabilities.CatalogueCapabilities; import org.deegree.ogcwebservices.csw.capabilities.CatalogueCapabilitiesDocument; import org.deegree.ogcwebservices.getcapabilities.InvalidCapabilitiesException; import org.deegree.ogcwebservices.wfs.capabilities.FeatureTypeList; import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities; import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilitiesDocument; import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType; import org.deegree.ogcwebservices.wms.XMLFactory; import org.deegree.ogcwebservices.wms.capabilities.Layer; import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilities; import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilitiesDocument; import org.deegree.owscommon_new.DCP; import org.deegree.owscommon_new.HTTP; import org.deegree.owscommon_new.Operation; import org.deegree.security.GeneralSecurityException; import org.deegree.security.UnauthorizedException; import org.deegree.security.drm.SecurityAccess; import org.deegree.security.drm.SecurityAccessManager; import org.deegree.security.drm.model.RightType; import org.deegree.security.drm.model.SecuredObject; import org.deegree.security.drm.model.User; import org.deegree.security.owsproxy.Condition; import org.deegree.security.owsproxy.OperationParameter; import org.deegree.security.owsproxy.Request; import org.deegree.security.owsrequestvalidator.wms.GetMapRequestValidator; import org.w3c.dom.Document; /** * * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a> * @author last edited by: $Author: poth $ * * @version 1.1, $Revision: 1.35 $, $Date: 2006/11/01 11:09:59 $ * * @since 1.1 * */ public class GetCapabilitiesResponseValidator extends ResponseValidator { private static final ILogger LOG = LoggerFactory.getLogger( GetCapabilitiesResponseValidator.class ); private static final String INVALIDSERVICE = Messages.getString( "GetCapabilitiesResponseValidator.INVALIDSERVICE" ); private String proxyURL = null; /** * @param policy */ public GetCapabilitiesResponseValidator( Policy policy, String proxyURL ) { super( policy ); this.proxyURL = proxyURL; } /** * validates the passed object as a response to a OWS request. The validity of the * response may is assigned to specific user rights. If the passed user is <>null * this will be evaluated. <br> * the reponse may contain three valid kinds of objects: * <ul> * <li>a serialized image * <li>a xml encoded exception * <li>a svg-encoded vector image * </ul> * Each of these types can be identified by the mime-type of the response that is also * passed to the method. <br> * If something basic went wrong it is possible that not further specified kind of * object is passed as response. In this case the method will throw an * <tt>InvalidParameterValueException</tt> to avoid sending bad responses to the * client. * * @param service service which produced the response (WMS, WFS ...) * @param response * @param mime mime-type of the response * @param user * @see GetMapRequestValidator#validateRequest(OGCWebServiceRequest, String) */ public byte[] validateResponse( String service, byte[] response, String mime, User user ) throws InvalidParameterValueException, UnauthorizedException { Request req = policy.getRequest( service, "GetCapabilities" ); if ( req == null ) { throw new InvalidParameterValueException( INVALIDSERVICE + service ); } // request is valid because no restrictions are made if ( req.isAny() ) return response; if ( MimeTypeMapper.isKnownOGCType( mime ) ) { // if the mime-type is a known OGC mime-type it must be an XML // document. probably it is a capabilities document but it also // could be an response = validateXML( service, response, user ); } else if ( mime.equals( "text/xml" ) ) { // if the mime-type isn't an image type but 'text/xml' // it could be an exception response = validateXML( service, response, user ); } else { throw new InvalidParameterValueException( UNKNOWNMIMETYPE + mime ); } return response; } /** * splits document string into 'core' capabilities document and xml header * @param xml * @return * @throws InvalidParameterValueException */ private String[] clearCapabilities( byte[] xml ) throws InvalidParameterValueException { InputStreamReader isr = new InputStreamReader( new ByteArrayInputStream( xml ) ); StringBuffer sb = new StringBuffer( 50000 ); int c = 0; try { while ( ( c = isr.read() ) > -1 ) { sb.append( (char) c ); } isr.close(); } catch ( IOException e ) { throw new InvalidParameterValueException( Messages.format( "GetCapabilitiesResponseValidator.CAPAREAD", e.getMessage() ) ); } int pos = sb.indexOf("<WMT_MS_Capabilities"); if (pos < 0 ) { pos = sb.indexOf("WFS_Capabilities"); } if (pos < 0 ) { pos = sb.indexOf("Capabilities"); } pos = pos +2; char ch = '$'; while ( ch != '<' ) { pos--; ch = sb.charAt( pos ); } String[]o = new String[2]; if ( pos > 0 ) { o[0] = sb.substring(0, pos); } else { o[0] = ""; } if ( pos > -1 ) { o[1] = sb.substring(pos); } else { o[0] = "ERROR"; o[1] = sb.toString(); } return o; } /** * validates the passed xml to be valid against the policy * * @param service service which produced the response (WMS, WFS ...) * @param xml * @param mime * @param user * @throws InvalidParameterValueException */ private byte[] validateXML( String service, byte[] xml, User user ) throws InvalidParameterValueException, UnauthorizedException { String[] st = clearCapabilities( xml ); if ( st[0].equals( "ERROR" ) ) { String s = Messages.format( "GetCapabilitiesResponseValidator.NOCAPADOC", st[1] ); throw new InvalidParameterValueException( s ); } Document doc = null; try { XMLFragment frag = new XMLFragment(); frag.load( new ByteArrayInputStream( st[1].getBytes() ), XMLFragment.DEFAULT_URL ); doc = frag.getRootElement().getOwnerDocument(); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); String s = Messages.getString( "GetCapabilitiesResponseValidator.ALLCAPAPARSE" ); throw new InvalidParameterValueException( s ); } String root = doc.getDocumentElement().getNodeName(); if ( root.equalsIgnoreCase( "Exception" ) ) { // if the xml contains a exception the reponse is valid! } else if ( "WMS".equals( service ) ) { xml = validateWMSCapabilities( doc, user ); } else if ( "WFS".equals( service ) ) { xml = validateWFSCapabilities( doc, user ); } else if ( "WCS".equals( service ) ) { // TODO } else if ( "CSW".equals( service ) ) { xml = validateCSWCapabilities( doc ); } StringBuffer sb = new StringBuffer( xml.length + st[0].length() ); sb.append( st[0] ); String s = new String( xml ); int p = s.indexOf( "?>" ); if ( p > -1 ) { sb.append( s.substring( p + 2, s.length() ) ); } else { sb.append( s ); } return sb.toString().getBytes(); } /** * validates the passed xml to be valid against the policy * * @param xml * @param user * @throws InvalidParameterValueException */ private byte[] validateWMSCapabilities( Document doc, User user ) throws InvalidParameterValueException, UnauthorizedException { WMSCapabilitiesDocument cdoc = new WMSCapabilitiesDocument(); cdoc.setRootElement( doc.getDocumentElement() ); WMSCapabilities capa = null; try { capa = (WMSCapabilities) cdoc.parseCapabilities(); } catch ( InvalidCapabilitiesException e ) { LOG.logError( e.getMessage(), e ); throw new InvalidParameterValueException( Messages.format("GetCapabilitiesResponseValidator.WMSCAPAPARSE", e.getMessage()) ,e ); } capa = filterWMSLayers( capa, user ); List<Operation> ops = capa.getOperationMetadata().getOperations(); for ( Operation operation : ops ) { setNewOnlineResource( operation ); } ByteArrayOutputStream bos = new ByteArrayOutputStream( 50000 ); byte[] b; try { cdoc = XMLFactory.export( capa ); // must be UTF-8 because the WMS Capabilities template uses // UTF-8 encoding Properties properties = new Properties(); properties.setProperty( OutputKeys.ENCODING, "UTF-8" ); cdoc.write( bos, properties ); b = bos.toByteArray(); bos.close(); String s = StringTools.replace( new String( b ), "xmlns:xlink=\"http://www.w3.org/1999/xlink\"", "", false ); b = s.getBytes(); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new InvalidParameterValueException( Messages.format( "GetCapabilitiesResponseValidator.WMSCAPAEXPORT", e.getMessage() ) ); } return b; } /** * * @param op */ private void setNewOnlineResource( Operation op ) { if ( op.getDCP() != null ) { List<DCP> dcps = op.getDCP(); for ( DCP dcp : dcps ) { HTTP http = (HTTP) dcp; List<OnlineResource> links = http.getLinks(); try { int size = links.size(); links.clear(); OnlineResource proxy = new OnlineResource( new Linkage( new URL( proxyURL ) ) ); for( int i = 0; i < size; ++i ) links.add( proxy ); } catch ( MalformedURLException e1 ) { LOG.logError( e1.getLocalizedMessage(), e1 ); } } } } /** * Sets the proxy online resource in the old owscommon Operation class. To be removed soon! * * @param op */ private void setNewOnlineResourceInOldOperation( org.deegree.ogcwebservices.getcapabilities.Operation op ) { if ( op.getDCPs() != null ) { for ( int i = 0; i < op.getDCPs().length; i++ ) { org.deegree.ogcwebservices.getcapabilities.HTTP http = (org.deegree.ogcwebservices.getcapabilities.HTTP) op.getDCPs()[i].getProtocol(); try { if ( http.getGetOnlineResources().length > 0 ) { URL urls[] = new URL[http.getGetOnlineResources().length]; for( int k = 0; k< http.getGetOnlineResources().length; ++k ) urls[k] = new URL( proxyURL ); http.setGetOnlineResources( urls ); } if ( http.getPostOnlineResources().length > 0 ) { URL urls[] = new URL[http.getPostOnlineResources().length]; for( int k = 0; k< http.getPostOnlineResources().length; ++k ) urls[k] = new URL( proxyURL ); http.setPostOnlineResources( urls ); } } catch ( MalformedURLException e1 ) { e1.printStackTrace(); } } } } /** * validates the passed xml to be valid against the policy * * @param xml * @param user * @throws InvalidParameterValueException * @throws UnauthorizedException */ private byte[] validateWFSCapabilities( Document doc, User user ) throws InvalidParameterValueException, UnauthorizedException { WFSCapabilities capa = null; try { WFSCapabilitiesDocument capaDoc = new WFSCapabilitiesDocument(); capaDoc.setRootElement( doc.getDocumentElement() ); capa = (WFSCapabilities) capaDoc.parseCapabilities(); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new InvalidParameterValueException( Messages.format( "GetCapabilitiesResponseValidator.INVALIDWFSCAPA", e.getMessage() ) ); } capa = filterWFSFeatureType( capa, user ); WFSCapabilitiesDocument capaDoc = null; try { capaDoc = org.deegree.ogcwebservices.wfs.XMLFactory.export( capa ); } catch ( Exception e ) { throw new InvalidParameterValueException( e ); } ByteArrayOutputStream bos = new ByteArrayOutputStream( 20000 ); PrintWriter pr = new PrintWriter( bos ); capaDoc.write( pr ); return bos.toByteArray(); } /** * validates the passed xml to be valid against the policy * * @param doc * @return * @throws InvalidParameterValueException */ private byte[] validateCSWCapabilities( Document doc ) throws InvalidParameterValueException { CatalogueCapabilities capa = null; try { CatalogueCapabilitiesDocument capaDoc = new CatalogueCapabilitiesDocument(); capaDoc.setRootElement( doc.getDocumentElement() ); capa = (CatalogueCapabilities) capaDoc.parseCapabilities(); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new InvalidParameterValueException( Messages.format( "GetCapabilitiesResponseValidator.INVALIDWFSCAPA", e.getMessage() ) ); } org.deegree.ogcwebservices.getcapabilities.Operation[] ops = capa.getOperationsMetadata().getOperations(); for ( int i = 0; i < ops.length; i++ ) { setNewOnlineResourceInOldOperation( ops[i] ); } CatalogueCapabilitiesDocument capaDoc = null; try { capaDoc = org.deegree.ogcwebservices.csw.XMLFactory.export( capa, null ); } catch ( Exception e ) { throw new InvalidParameterValueException( e ); } ByteArrayOutputStream bos = new ByteArrayOutputStream( 20000 ); PrintWriter pr = new PrintWriter( bos ); capaDoc.write( pr ); return bos.toByteArray(); } /** * filters the wms capabilities to rturn just the valid layers * @param capa * @param user * @throws InvalidParameterValueException */ private WMSCapabilities filterWMSLayers( WMSCapabilities capa, User user ) throws UnauthorizedException { Request req = policy.getRequest( "WMS", "GetCapabilities" ); Condition con = req.getPostConditions(); OperationParameter op = con.getOperationParameter( "layers" ); if ( op.isAny() ) return capa; Layer layer = capa.getLayer(); if ( op.isUserCoupled() && user != null ) { try { SecurityAccessManager sam = SecurityAccessManager.getInstance(); SecurityAccess access = sam.acquireAccess( user ); // call recursive method to remove all 'named' layers not // included in the list from the capabilities layer = removeWMSLayer( layer, user, access ); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new UnauthorizedException( Messages.format( "GetCapabilitiesResponseValidator.INVALIDUSER", user ) ); } } else { // get list of valid wms layers List list = op.getValues(); // call recursive method to remove all 'named' layers not // included in the list from the capabilities layer = removeWMSLayer( layer, list ); } capa.setLayer( layer ); return capa; } /** * recursive method that removes all 'named' layers (layers that has * a name in addtion to a title) from the layer tree thats root node * (layer) is passed to the method and that not present in the passed * <tt>List</tt> * * @param layer * @param validLayers */ private Layer removeWMSLayer( Layer layer, List validLayers ) { Layer[] layers = layer.getLayer(); for ( int i = 0; i < layers.length; i++ ) { if ( layers[i].getName() != null && !validLayers.contains( layers[i].getName() ) ) { layer.removeLayer( layers[i].getName() ); } else { removeWMSLayer( layers[i], validLayers ); if ( layers[i].getLayer().length == 0 && layers[i].getName() == null ) { layer.removeLayerByTitle( layers[i].getTitle() ); } } } return layer; } /** * recursive method that removes all 'named' layers (layers that has * a name in addition to a title) from the layer tree thats root node * (layer) is passed to the method and the passed user doesn't have * a GetMap right on. * * @param layer layer to validate * @param user user whose rights are considered * @param access object to access DRM registry * */ private Layer removeWMSLayer( Layer layer, User user, SecurityAccess access ) throws GeneralSecurityException { Layer[] layers = layer.getLayer(); for ( int i = 0; i < layers.length; i++ ) { if ( layers[i].getName() != null ) { SecuredObject secObj = null; try { // must be in try-catch block because an exception will be thrown // if no SecuredObject with the passed layer exists secObj = access.getSecuredObjectByName( layers[i].getName(), "Layer" ); } catch ( Exception e ) { } if ( secObj == null || !user.hasRight( access, RightType.GETMAP, secObj ) ) { // remove the layer from the capabilities if it's not known // by the DRM registry or if the user doesn't have a GetMap // right on it layer.removeLayer( layers[i].getName() ); } } else { removeWMSLayer( layers[i], user, access ); if ( layers[i].getLayer().length == 0 && layers[i].getName() == null ) { layer.removeLayerByTitle( layers[i].getTitle() ); } } } return layer; } /** * @param capa * @param user * @return * @throws InvalidParameterValueException * @throws UnauthorizedException */ private WFSCapabilities filterWFSFeatureType( WFSCapabilities capa, User user ) throws UnauthorizedException { Request req = policy.getRequest( "WFS", "GetCapabilities" ); Condition con = req.getPostConditions(); OperationParameter op = con.getOperationParameter( "featureTypes" ); if ( op.isAny() ) return capa; if ( op.isUserCoupled() && user != null ) { try { SecurityAccessManager sam = SecurityAccessManager.getInstance(); SecurityAccess access = sam.acquireAccess( user ); FeatureTypeList ftl = capa.getFeatureTypeList(); WFSFeatureType[] ft = ftl.getFeatureTypes(); StringBuffer sb = new StringBuffer( 200 ); for ( int i = 0; i < ft.length; i++ ) { SecuredObject secObj = null; try { // must be in try-catch block because an exception will be thrown // if no SecuredObject with the passed layer exists sb.delete( 0, sb.length() ); sb.append( '{' ).append( ft[i].getName().getNamespace().toASCIIString() ); sb.append( "}:" ).append( ft[i].getName().getLocalName() ); secObj = access.getSecuredObjectByName( sb.toString(), "Featuretype" ); } catch ( Exception e ) {} if ( secObj == null || !user.hasRight( access, RightType.GETFEATURE, secObj ) ) { ftl.removeFeatureType( ft[i] ); } } } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new UnauthorizedException( Messages.format( "GetCapabilitiesResponseValidator.INVALIDUSER", user ) ); } } else { // get list of valid wms layers List<String> list = op.getValues(); FeatureTypeList ftl = capa.getFeatureTypeList(); WFSFeatureType[] ft = ftl.getFeatureTypes(); StringBuffer sb = new StringBuffer( 200 ); for ( int i = 0; i < ft.length; i++ ) { sb.delete( 0, sb.length() ); sb.append( '{' ).append( ft[i].getName().getNamespace().toASCIIString() ); sb.append( "}:" ).append( ft[i].getName().getLocalName() ); if ( !list.contains( sb.toString() ) ) { ftl.removeFeatureType( ft[i] ); } } } return capa; } } /* ******************************************************************** Changes to this class. What the people have been up to: $Log: GetCapabilitiesResponseValidator.java,v $ Revision 1.35 2006/11/01 11:09:59 poth bug fix parsing capabilities dokument Revision 1.34 2006/10/30 08:07:06 poth bug fix - WFS GetFeature request: FeatureType comparsion Revision 1.33 2006/08/23 07:10:22 schmitz Renamed the owscommon_neu package to owscommon_new. Revision 1.32 2006/08/22 10:25:01 schmitz Updated the WMS to use the new OWS common package. Updated the rest of deegree to use the new data classes returned by the updated WMS methods/capabilities. Revision 1.31 2006/08/10 07:17:52 poth bug fix - removing Arrays.asList calls for transforming op.geValues because accoring to refactoring this method it already returns a list Revision 1.30 2006/08/02 21:17:01 poth implementation completed - substitute Online resources for CSW operations Revision 1.29 2006/08/02 18:51:40 poth bug fixes Revision 1.28 2006/07/31 07:14:50 poth bug fix - wms capabilities header Revision 1.27 2006/07/29 08:53:40 poth support for CSW completed Revision 1.26 2006/07/23 08:44:53 poth refactoring - moved validators assigned to OWS into specialized packages Revision 1.25 2006/06/12 08:04:08 bezema updated the setNewOnlineResource method to handle arbitrary length of proxy urls Revision 1.24 2006/05/30 16:27:11 poth bug fix; reading GetCapabilities section from policy document Revision 1.23 2006/05/25 09:53:31 poth adapated to changed/simplified policy xml-schema Revision 1.22 2006/05/23 06:54:47 poth error messages corrected ********************************************************************** */