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