001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/security/owsrequestvalidator/wfs/GetFeatureRequestValidator.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2001-2006 by: 006 EXSE, Department of Geography, University of Bonn 007 http://www.giub.uni-bonn.de/deegree/ 008 lat/lon GmbH 009 http://www.lat-lon.de 010 011 This library is free software; you can redistribute it and/or 012 modify it under the terms of the GNU Lesser General Public 013 License as published by the Free Software Foundation; either 014 version 2.1 of the License, or (at your option) any later version. 015 016 This library is distributed in the hope that it will be useful, 017 but WITHOUT ANY WARRANTY; without even the implied warranty of 018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 019 Lesser General Public License for more details. 020 021 You should have received a copy of the GNU Lesser General Public 022 License along with this library; if not, write to the Free Software 023 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 024 025 Contact: 026 027 Andreas Poth 028 lat/lon GmbH 029 Aennchenstr. 19 030 53177 Bonn 031 Germany 032 E-Mail: poth@lat-lon.de 033 034 Prof. Dr. Klaus Greve 035 Department of Geography 036 University of Bonn 037 Meckenheimer Allee 166 038 53115 Bonn 039 Germany 040 E-Mail: greve@giub.uni-bonn.de 041 042 ---------------------------------------------------------------------------*/ 043 package org.deegree.security.owsrequestvalidator.wfs; 044 045 import java.io.IOException; 046 import java.util.HashMap; 047 import java.util.List; 048 import java.util.Map; 049 050 import org.deegree.datatypes.QualifiedName; 051 import org.deegree.datatypes.Types; 052 import org.deegree.framework.log.ILogger; 053 import org.deegree.framework.log.LoggerFactory; 054 import org.deegree.framework.xml.XMLParsingException; 055 import org.deegree.i18n.Messages; 056 import org.deegree.model.feature.Feature; 057 import org.deegree.model.feature.FeatureFactory; 058 import org.deegree.model.feature.FeatureProperty; 059 import org.deegree.model.feature.schema.FeatureType; 060 import org.deegree.model.feature.schema.PropertyType; 061 import org.deegree.model.filterencoding.ComplexFilter; 062 import org.deegree.model.filterencoding.FeatureFilter; 063 import org.deegree.model.filterencoding.Filter; 064 import org.deegree.model.filterencoding.FilterConstructionException; 065 import org.deegree.model.filterencoding.OperationDefines; 066 import org.deegree.ogcwebservices.InvalidParameterValueException; 067 import org.deegree.ogcwebservices.OGCWebServiceRequest; 068 import org.deegree.ogcwebservices.wfs.XMLFactory; 069 import org.deegree.ogcwebservices.wfs.operation.GetFeature; 070 import org.deegree.ogcwebservices.wfs.operation.Query; 071 import org.deegree.portal.standard.security.control.ClientHelper; 072 import org.deegree.security.GeneralSecurityException; 073 import org.deegree.security.UnauthorizedException; 074 import org.deegree.security.drm.SecurityAccess; 075 import org.deegree.security.drm.SecurityAccessManager; 076 import org.deegree.security.drm.model.Right; 077 import org.deegree.security.drm.model.RightSet; 078 import org.deegree.security.drm.model.RightType; 079 import org.deegree.security.drm.model.SecuredObject; 080 import org.deegree.security.drm.model.User; 081 import org.deegree.security.owsproxy.Condition; 082 import org.deegree.security.owsproxy.OperationParameter; 083 import org.deegree.security.owsproxy.Request; 084 import org.deegree.security.owsrequestvalidator.Policy; 085 import org.w3c.dom.Element; 086 import org.xml.sax.SAXException; 087 088 /** 089 * 090 * 091 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a> 092 * @author last edited by: $Author: mschneider $ 093 * 094 * @version 1.1, $Revision: 6625 $, $Date: 2007-04-17 18:05:27 +0200 (Di, 17 Apr 2007) $ 095 * 096 * @since 1.1 097 */ 098 class GetFeatureRequestValidator extends AbstractWFSRequestValidator { 099 100 private static final ILogger LOG = LoggerFactory.getLogger( GetFeatureRequestValidator.class ); 101 102 // known condition parameter 103 private static final String FORMAT = "format"; 104 105 private static final String MAXFEATURES = "maxFeatures"; 106 107 private static Map<QualifiedName, Filter> filterMap = new HashMap<QualifiedName, Filter>(); 108 109 private static FeatureType gfFT = null; 110 111 static { 112 if ( gfFT == null ) { 113 gfFT = GetFeatureRequestValidator.createFeatureType(); 114 } 115 } 116 117 /** 118 * @param policy 119 */ 120 public GetFeatureRequestValidator( Policy policy ) { 121 super( policy ); 122 } 123 124 /** 125 * validates if the passed request is valid against the policy assigned to the validator. If the 126 * passed user is not <tt>null</tt> user coupled parameters will be validated against a users 127 * and rights management system. 128 */ 129 public void validateRequest( OGCWebServiceRequest request, User user ) 130 throws InvalidParameterValueException, UnauthorizedException { 131 132 userCoupled = false; 133 Request req = policy.getRequest( "WFS", "GetFeature" ); 134 // request is valid because no restrictions are made 135 if ( req.isAny() ) 136 return; 137 Condition condition = req.getPreConditions(); 138 139 GetFeature wfsreq = (GetFeature) request; 140 141 validateVersion( condition, wfsreq.getVersion() ); 142 143 Query[] queries = wfsreq.getQuery(); 144 String[] ft = new String[queries.length]; 145 for ( int i = 0; i < ft.length; i++ ) { 146 ft[i] = queries[i].getTypeNames()[0].getFormattedString(); 147 } 148 149 validateFeatureTypes( condition, ft ); 150 validateFormat( condition, wfsreq.getOutputFormat() ); 151 validateMaxFeatures( condition, wfsreq.getMaxFeatures() ); 152 153 if ( userCoupled ) { 154 validateAgainstRightsDB( wfsreq, user ); 155 } 156 157 if ( req.getPostConditions() != null ) { 158 addFilter( wfsreq, req.getPostConditions(), user ); 159 } 160 161 } 162 163 /** 164 * adds an additional Filter read from parameter 'instanceFilter'to the Filter of the passed 165 * GetFeature request. If parameter 'instanceFilter' is userCoupled the filter will be read from 166 * DRM, if it is not the filter defined within the responsible policy document will be used. 167 * 168 * @param wfsreq 169 * @param postConditions 170 * @param user 171 * @throws InvalidParameterValueException 172 * @throws UnauthorizedException 173 */ 174 private void addFilter( GetFeature wfsreq, Condition postConditions, User user ) 175 throws InvalidParameterValueException, UnauthorizedException { 176 if ( postConditions.getOperationParameter( "instanceFilter" ) != null ) { 177 Map<QualifiedName, Filter> localFilterMap; 178 if ( postConditions.getOperationParameter( "instanceFilter" ).isUserCoupled() ) { 179 // read filterMap from constraints defined in deegree DRM 180 localFilterMap = readFilterFromDRM( wfsreq, user ); 181 } else { 182 fillFilterMap( postConditions ); 183 // use filterMap read from policy document 184 localFilterMap = filterMap; 185 } 186 Query[] queries = wfsreq.getQuery(); 187 for ( int i = 0; i < queries.length; i++ ) { 188 Filter filter = null; 189 if ( queries[i].getFilter() == null ) { 190 // if query does not define a filter just use the matching 191 // one from the post conditions 192 filter = localFilterMap.get( queries[i].getTypeNames()[0] ); 193 } else if ( queries[i].getFilter() instanceof ComplexFilter ) { 194 // create a new Filter that is a combination of the 195 // original filter and the one defined in the GetFeatures 196 // PostConditions coupled by a logical 'And' 197 ComplexFilter qFilter = (ComplexFilter) queries[i].getFilter(); 198 filter = localFilterMap.get( queries[i].getTypeNames()[0] ); 199 if ( filter == null ) { 200 filter = qFilter; 201 } else { 202 filter = new ComplexFilter( qFilter, (ComplexFilter) filter, OperationDefines.AND ); 203 } 204 } else if ( queries[i].getFilter() instanceof FeatureFilter ) { 205 // just take original filter if it is as feature filter 206 // because feature filter and complex filters can not 207 // be combined 208 filter = queries[i].getFilter(); 209 } 210 // substitue query by a new one using the re-created filter 211 queries[i] = Query.create( queries[i].getPropertyNames(), queries[i].getFunctions(), 212 queries[i].getSortProperties(), queries[i].getHandle(), 213 queries[i].getFeatureVersion(), queries[i].getTypeNames(), 214 queries[i].getAliases(), queries[i].getSrsName(), filter, 215 queries[i].getMaxFeatures(), queries[i].getStartPosition(), 216 queries[i].getResultType() ); 217 } 218 wfsreq.setQueries( queries ); 219 } 220 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 221 try { 222 XMLFactory.export( wfsreq ).prettyPrint( System.out ); 223 } catch ( Exception e ) { 224 } 225 } 226 } 227 228 /** 229 * 230 * @param wfsreq 231 * @param user 232 * @return 233 * @throws UnauthorizedException 234 * @throws InvalidParameterValueException 235 */ 236 private Map<QualifiedName, Filter> readFilterFromDRM( GetFeature wfsreq, User user ) 237 throws UnauthorizedException, InvalidParameterValueException { 238 239 Map<QualifiedName, Filter> map = new HashMap<QualifiedName, Filter>(); 240 try { 241 SecurityAccessManager sam = SecurityAccessManager.getInstance(); 242 SecurityAccess access = sam.acquireAccess( user ); 243 Query[] queries = wfsreq.getQuery(); 244 for ( int i = 0; i < queries.length; i++ ) { 245 QualifiedName qn = queries[i].getTypeNames()[0]; 246 SecuredObject secObj = access.getSecuredObjectByName( qn.getFormattedString(), 247 ClientHelper.TYPE_FEATURETYPE ); 248 249 RightSet rs = user.getRights( access, secObj ); 250 Right right = rs.getRight( secObj, RightType.GETFEATURE_RESPONSE ); 251 // a constraint - if available - is constructed as a OGC Filter 252 // one of the filter operations may is 'PropertyIsEqualTo' and 253 // defines a ProperyName == 'instanceFilter'. The Literal of this 254 // operation itself is a complete and valid Filter expression. 255 if ( right != null ) { 256 ComplexFilter filter = (ComplexFilter) right.getConstraints(); 257 if ( filter != null ) { 258 // extract filter expression to be used as additional 259 // filter for a GetFeature request 260 filter = extractInstanceFilter( filter.getOperation() ); 261 if ( filter != null ) { 262 map.put( qn, filter ); 263 } 264 } 265 } 266 } 267 } catch ( GeneralSecurityException e ) { 268 LOG.logError( e.getMessage(), e ); 269 throw new UnauthorizedException( e.getMessage(), e ); 270 } catch ( FilterConstructionException e ) { 271 LOG.logError( e.getMessage(), e ); 272 throw new InvalidParameterValueException( e.getMessage(), e ); 273 } catch ( SAXException e ) { 274 LOG.logError( e.getMessage(), e ); 275 throw new InvalidParameterValueException( e.getMessage(), e ); 276 } catch ( IOException e ) { 277 LOG.logError( e.getMessage(), e ); 278 throw new InvalidParameterValueException( e.getMessage(), e ); 279 } 280 281 return map; 282 } 283 284 private void fillFilterMap( Condition postConditions ) 285 throws InvalidParameterValueException { 286 List<Element> complexValues = postConditions.getOperationParameter( "instanceFilter" ).getComplexValues(); 287 try { 288 if ( filterMap.size() == 0 ) { 289 for ( int i = 0; i < complexValues.size(); i++ ) { 290 Query q = Query.create( complexValues.get( 0 ) ); 291 Filter f = q.getFilter(); 292 QualifiedName qn = q.getTypeNames()[0]; 293 filterMap.put( qn, f ); 294 } 295 } 296 } catch ( XMLParsingException e ) { 297 LOG.logError( e.getMessage(), e ); 298 throw new InvalidParameterValueException( this.getClass().getName(), e.getMessage() ); 299 } 300 } 301 302 /** 303 * valides if the format you in a GetFeature request is valid against the policy assigned to 304 * Validator. If the passed user is not <tt>null</tt> and the format parameter is user coupled 305 * the format will be validated against a users and rights management system. 306 * 307 * @param condition 308 * @param format 309 * @throws InvalidParameterValueException 310 */ 311 private void validateFormat( Condition condition, String format ) 312 throws InvalidParameterValueException { 313 OperationParameter op = condition.getOperationParameter( FORMAT ); 314 315 // version is valid because no restrictions are made 316 if ( op.isAny() ) 317 return; 318 319 List validLayers = op.getValues(); 320 if ( op.isUserCoupled() ) { 321 userCoupled = true; 322 } else { 323 if ( !validLayers.contains( format ) ) { 324 String s = Messages.getMessage( "OWSPROXY_DESCRIBEFEATURETYPE_FORMAT", format ); 325 throw new InvalidParameterValueException( s ); 326 } 327 } 328 329 } 330 331 /** 332 * valides if the format you in a GetFeature request is valid against the policy assigned to 333 * Validator. If the passed user is not <tt>null</tt> and the maxFeatures parameter is user 334 * coupled the maxFeatures will be validated against a users and rights management system. 335 * 336 * @param condition 337 * @param maxFeatures 338 * @throws InvalidParameterValueException 339 */ 340 private void validateMaxFeatures( Condition condition, int maxFeatures ) 341 throws InvalidParameterValueException { 342 OperationParameter op = condition.getOperationParameter( MAXFEATURES ); 343 344 // version is valid because no restrictions are made 345 if ( op.isAny() ) 346 return; 347 348 int maxF = Integer.parseInt( op.getValues().get( 0 ) ); 349 350 if ( op.isUserCoupled() ) { 351 userCoupled = true; 352 } else { 353 if ( maxFeatures > maxF || maxFeatures < 0 ) { 354 String s = Messages.getMessage( "OWSPROXY_GETFEATURE_MAXFEATURE", maxFeatures ); 355 throw new InvalidParameterValueException( s ); 356 } 357 } 358 359 } 360 361 /** 362 * validates the passed WMS GetMap request against a User- and Rights-Management DB. 363 * 364 * @param wmsreq 365 * @param user 366 * @throws InvalidParameterValueException 367 */ 368 private void validateAgainstRightsDB( GetFeature wfsreq, User user ) 369 throws InvalidParameterValueException, UnauthorizedException { 370 371 if ( user == null ) { 372 throw new UnauthorizedException( "no access to anonymous user" ); 373 } 374 375 // create feature that describes the map request 376 FeatureProperty[] fps = new FeatureProperty[3]; 377 fps[0] = FeatureFactory.createFeatureProperty( new QualifiedName( "version" ), wfsreq.getVersion() ); 378 Integer mxf = new Integer( wfsreq.getMaxFeatures() ); 379 // The database can handle "features as a key", this feature is build from the request's 380 // features 381 fps[1] = FeatureFactory.createFeatureProperty( new QualifiedName( "maxfeatures" ), mxf ); 382 fps[2] = FeatureFactory.createFeatureProperty( new QualifiedName( "outputformat" ), wfsreq.getOutputFormat() ); 383 384 Feature feature = FeatureFactory.createFeature( "id", gfFT, fps ); 385 Query[] queries = wfsreq.getQuery(); 386 for ( int i = 0; i < queries.length; i++ ) { 387 StringBuffer sb = new StringBuffer( 200 ); 388 sb.append( '{' ).append( queries[i].getTypeNames()[0].getNamespace().toASCIIString() ); 389 sb.append( "}:" ).append( queries[i].getTypeNames()[0].getLocalName() ); 390 handleUserCoupledRules( user, // the user who posted the request 391 feature, // This is the Database feature 392 sb.toString(), // the Qualified name of the users 393 // Featurerequest 394 ClientHelper.TYPE_FEATURETYPE, // a primary key in the db. 395 RightType.GETFEATURE );// We're requesting a featuretype. 396 } 397 398 } 399 400 /** 401 * creates a feature type that matches the parameters of a GetLagendGraphic request 402 * 403 * @return created <tt>FeatureType</tt> 404 */ 405 private static FeatureType createFeatureType() { 406 PropertyType[] ftps = new PropertyType[3]; 407 ftps[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "version" ), Types.VARCHAR, false ); 408 ftps[1] = FeatureFactory.createSimplePropertyType( new QualifiedName( "maxfeatures" ), Types.INTEGER, false ); 409 ftps[2] = FeatureFactory.createSimplePropertyType( new QualifiedName( "outputformat" ), Types.VARCHAR, false ); 410 411 return FeatureFactory.createFeatureType( "GetFeature", false, ftps ); 412 } 413 }