001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/security/owsrequestvalidator/wfs/GetFeatureRequestValidator.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2001-2008 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: apoth $ 093 * 094 * @version 1.1, $Revision: 9683 $, $Date: 2008-01-23 10:37:26 +0100 (Mi, 23 Jan 2008) $ 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 && !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 } else { 183 fillFilterMap( postConditions ); 184 // use filterMap read from policy document 185 localFilterMap = filterMap; 186 } 187 Query[] queries = wfsreq.getQuery(); 188 for ( int i = 0; i < queries.length; i++ ) { 189 Filter filter = null; 190 if ( queries[i].getFilter() == null ) { 191 // if query does not define a filter just use the matching 192 // one from the post conditions 193 filter = localFilterMap.get( queries[i].getTypeNames()[0] ); 194 } else if ( queries[i].getFilter() instanceof ComplexFilter ) { 195 // create a new Filter that is a combination of the 196 // original filter and the one defined in the GetFeatures 197 // PostConditions coupled by a logical 'And' 198 ComplexFilter qFilter = (ComplexFilter) queries[i].getFilter(); 199 filter = localFilterMap.get( queries[i].getTypeNames()[0] ); 200 if ( filter == null ) { 201 filter = qFilter; 202 } else { 203 filter = new ComplexFilter( qFilter, (ComplexFilter) filter, OperationDefines.AND ); 204 } 205 } else if ( queries[i].getFilter() instanceof FeatureFilter ) { 206 // just take original filter if it is as feature filter 207 // because feature filter and complex filters can not 208 // be combined 209 filter = queries[i].getFilter(); 210 } 211 // substitue query by a new one using the re-created filter 212 queries[i] = Query.create( queries[i].getPropertyNames(), queries[i].getFunctions(), 213 queries[i].getSortProperties(), queries[i].getHandle(), 214 queries[i].getFeatureVersion(), queries[i].getTypeNames(), 215 queries[i].getAliases(), queries[i].getSrsName(), filter, 216 queries[i].getMaxFeatures(), queries[i].getStartPosition(), 217 queries[i].getResultType() ); 218 } 219 wfsreq.setQueries( queries ); 220 } 221 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 222 try { 223 XMLFactory.export( wfsreq ).prettyPrint( System.out ); 224 } catch ( Exception e ) { 225 } 226 } 227 } 228 229 /** 230 * 231 * @param wfsreq 232 * @param user 233 * @return 234 * @throws UnauthorizedException 235 * @throws InvalidParameterValueException 236 */ 237 private Map<QualifiedName, Filter> readFilterFromDRM( GetFeature wfsreq, User user ) 238 throws UnauthorizedException, InvalidParameterValueException { 239 240 Map<QualifiedName, Filter> map = new HashMap<QualifiedName, Filter>(); 241 try { 242 SecurityAccessManager sam = SecurityAccessManager.getInstance(); 243 SecurityAccess access = sam.acquireAccess( user ); 244 Query[] queries = wfsreq.getQuery(); 245 for ( int i = 0; i < queries.length; i++ ) { 246 QualifiedName qn = queries[i].getTypeNames()[0]; 247 SecuredObject secObj = access.getSecuredObjectByName( qn.getFormattedString(), 248 ClientHelper.TYPE_FEATURETYPE ); 249 250 RightSet rs = user.getRights( access, secObj ); 251 Right right = rs.getRight( secObj, RightType.GETFEATURE_RESPONSE ); 252 // a constraint - if available - is constructed as a OGC Filter 253 // one of the filter operations may is 'PropertyIsEqualTo' and 254 // defines a ProperyName == 'instanceFilter'. The Literal of this 255 // operation itself is a complete and valid Filter expression. 256 257 if ( right != null ) { 258 ComplexFilter filter = (ComplexFilter) right.getConstraints(); 259 if ( filter != null ) { 260 // extract filter expression to be used as additional 261 // filter for a GetFeature request 262 filter = extractInstanceFilter( filter.getOperation() ); 263 if ( filter != null ) { 264 map.put( qn, filter ); 265 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 266 LOG.logDebug( "instance filter for right GETFEATURE_RESPONSE", filter.toXML() ); 267 } 268 } else { 269 LOG.logDebug( "no instance filter defined for right GETFEATURE_RESPONSE and feature type: " 270 + qn ); 271 } 272 } else { 273 LOG.logDebug( "no constraint defined for right GETFEATURE_RESPONSE and feature type: " + qn ); 274 } 275 } else { 276 LOG.logDebug( "right GETFEATURE_RESPONSE not defined for current user and feature type: " + qn ); 277 } 278 } 279 } catch ( GeneralSecurityException e ) { 280 LOG.logError( e.getMessage(), e ); 281 throw new UnauthorizedException( e.getMessage(), e ); 282 } catch ( FilterConstructionException e ) { 283 LOG.logError( e.getMessage(), e ); 284 throw new InvalidParameterValueException( e.getMessage(), e ); 285 } catch ( SAXException e ) { 286 LOG.logError( e.getMessage(), e ); 287 throw new InvalidParameterValueException( e.getMessage(), e ); 288 } catch ( IOException e ) { 289 LOG.logError( e.getMessage(), e ); 290 throw new InvalidParameterValueException( e.getMessage(), e ); 291 } 292 293 return map; 294 } 295 296 private void fillFilterMap( Condition postConditions ) 297 throws InvalidParameterValueException { 298 List<Element> complexValues = postConditions.getOperationParameter( "instanceFilter" ).getComplexValues(); 299 try { 300 if ( filterMap.size() == 0 ) { 301 for ( int i = 0; i < complexValues.size(); i++ ) { 302 Query q = Query.create( complexValues.get( 0 ) ); 303 Filter f = q.getFilter(); 304 QualifiedName qn = q.getTypeNames()[0]; 305 filterMap.put( qn, f ); 306 } 307 } 308 } catch ( XMLParsingException e ) { 309 LOG.logError( e.getMessage(), e ); 310 throw new InvalidParameterValueException( this.getClass().getName(), e.getMessage() ); 311 } 312 } 313 314 /** 315 * valides if the format you in a GetFeature request is valid against the policy assigned to 316 * Validator. If the passed user is not <tt>null</tt> and the format parameter is user coupled 317 * the format will be validated against a users and rights management system. 318 * 319 * @param condition 320 * @param format 321 * @throws InvalidParameterValueException 322 */ 323 private void validateFormat( Condition condition, String format ) 324 throws InvalidParameterValueException { 325 OperationParameter op = condition.getOperationParameter( FORMAT ); 326 327 // version is valid because no restrictions are made 328 if ( op.isAny() ) 329 return; 330 331 List validLayers = op.getValues(); 332 if ( op.isUserCoupled() ) { 333 userCoupled = true; 334 } else { 335 if ( !validLayers.contains( format ) ) { 336 String s = Messages.getMessage( "OWSPROXY_DESCRIBEFEATURETYPE_FORMAT", format ); 337 throw new InvalidParameterValueException( s ); 338 } 339 } 340 341 } 342 343 /** 344 * valides if the format you in a GetFeature request is valid against the policy assigned to 345 * Validator. If the passed user is not <tt>null</tt> and the maxFeatures parameter is user 346 * coupled the maxFeatures will be validated against a users and rights management system. 347 * 348 * @param condition 349 * @param maxFeatures 350 * @throws InvalidParameterValueException 351 */ 352 private void validateMaxFeatures( Condition condition, int maxFeatures ) 353 throws InvalidParameterValueException { 354 OperationParameter op = condition.getOperationParameter( MAXFEATURES ); 355 356 // version is valid because no restrictions are made 357 if ( op.isAny() ) 358 return; 359 360 int maxF = Integer.parseInt( op.getValues().get( 0 ) ); 361 362 if ( op.isUserCoupled() ) { 363 userCoupled = true; 364 } else { 365 if ( maxFeatures > maxF || maxFeatures < 0 ) { 366 String s = Messages.getMessage( "OWSPROXY_GETFEATURE_MAXFEATURE", maxFeatures ); 367 throw new InvalidParameterValueException( s ); 368 } 369 } 370 371 } 372 373 /** 374 * validates the passed WMS GetMap request against a User- and Rights-Management DB. 375 * 376 * @param wmsreq 377 * @param user 378 * @throws InvalidParameterValueException 379 */ 380 private void validateAgainstRightsDB( GetFeature wfsreq, User user ) 381 throws InvalidParameterValueException, UnauthorizedException { 382 383 if ( user == null ) { 384 throw new UnauthorizedException( "no access to anonymous user" ); 385 } 386 387 // create feature that describes the map request 388 FeatureProperty[] fps = new FeatureProperty[3]; 389 fps[0] = FeatureFactory.createFeatureProperty( new QualifiedName( "version" ), wfsreq.getVersion() ); 390 Integer mxf = new Integer( wfsreq.getMaxFeatures() ); 391 // The database can handle "features as a key", this feature is build from the request's 392 // features 393 fps[1] = FeatureFactory.createFeatureProperty( new QualifiedName( "maxfeatures" ), mxf ); 394 fps[2] = FeatureFactory.createFeatureProperty( new QualifiedName( "outputformat" ), wfsreq.getOutputFormat() ); 395 396 Feature feature = FeatureFactory.createFeature( "id", gfFT, fps ); 397 Query[] queries = wfsreq.getQuery(); 398 for ( int i = 0; i < queries.length; i++ ) { 399 StringBuffer sb = new StringBuffer( 200 ); 400 sb.append( '{' ).append( queries[i].getTypeNames()[0].getNamespace().toASCIIString() ); 401 sb.append( "}:" ).append( queries[i].getTypeNames()[0].getLocalName() ); 402 handleUserCoupledRules( user, // the user who posted the request 403 feature, // This is the Database feature 404 sb.toString(), // the Qualified name of the users 405 // Featurerequest 406 ClientHelper.TYPE_FEATURETYPE, // a primary key in the db. 407 RightType.GETFEATURE );// We're requesting a featuretype. 408 } 409 410 } 411 412 /** 413 * creates a feature type that matches the parameters of a GetLagendGraphic request 414 * 415 * @return created <tt>FeatureType</tt> 416 */ 417 private static FeatureType createFeatureType() { 418 PropertyType[] ftps = new PropertyType[3]; 419 ftps[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "version" ), Types.VARCHAR, false ); 420 ftps[1] = FeatureFactory.createSimplePropertyType( new QualifiedName( "maxfeatures" ), Types.INTEGER, false ); 421 ftps[2] = FeatureFactory.createSimplePropertyType( new QualifiedName( "outputformat" ), Types.VARCHAR, false ); 422 423 return FeatureFactory.createFeatureType( "GetFeature", false, ftps ); 424 } 425 }