001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/security/owsrequestvalidator/wfs/TransactionValidator.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.DELETE; 040 import static org.deegree.security.drm.model.RightType.INSERT; 041 import static org.deegree.security.drm.model.RightType.UPDATE; 042 043 import java.io.IOException; 044 import java.util.ArrayList; 045 import java.util.HashMap; 046 import java.util.List; 047 import java.util.Map; 048 049 import org.deegree.datatypes.QualifiedName; 050 import org.deegree.datatypes.Types; 051 import org.deegree.framework.log.ILogger; 052 import org.deegree.framework.log.LoggerFactory; 053 import org.deegree.framework.util.StringTools; 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.Query; 070 import org.deegree.ogcwebservices.wfs.operation.transaction.Delete; 071 import org.deegree.ogcwebservices.wfs.operation.transaction.Insert; 072 import org.deegree.ogcwebservices.wfs.operation.transaction.Transaction; 073 import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionOperation; 074 import org.deegree.ogcwebservices.wfs.operation.transaction.Update; 075 import org.deegree.portal.standard.security.control.ClientHelper; 076 import org.deegree.security.GeneralSecurityException; 077 import org.deegree.security.UnauthorizedException; 078 import org.deegree.security.drm.SecurityAccess; 079 import org.deegree.security.drm.SecurityAccessManager; 080 import org.deegree.security.drm.model.Right; 081 import org.deegree.security.drm.model.RightSet; 082 import org.deegree.security.drm.model.RightType; 083 import org.deegree.security.drm.model.SecuredObject; 084 import org.deegree.security.drm.model.User; 085 import org.deegree.security.owsproxy.Condition; 086 import org.deegree.security.owsproxy.OperationParameter; 087 import org.deegree.security.owsproxy.Request; 088 import org.deegree.security.owsrequestvalidator.Policy; 089 import org.w3c.dom.Element; 090 import org.xml.sax.SAXException; 091 092 /** 093 * Validator for OGC CSW Transaction requests. It will validated values of:<br> 094 * <ul> 095 * <li>service version</li> 096 * <li>operation</li> 097 * <li>type names</li> 098 * <li>metadata standard</li> 099 * </ul> 100 * 101 * @version $Revision: 18195 $ 102 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 103 * @author last edited by: $Author: mschneider $ 104 * 105 * @version 1.0. $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18 Jun 2009) $ 106 * 107 * @since 2.0 108 */ 109 public class TransactionValidator extends AbstractWFSRequestValidator { 110 111 private static final ILogger LOG = LoggerFactory.getLogger( TransactionValidator.class ); 112 113 private final static String TYPENAME = "typeName"; 114 115 private static Map<QualifiedName, Filter> filterMap = new HashMap<QualifiedName, Filter>(); 116 117 private static FeatureType insertFT = null; 118 119 private static FeatureType updateFT = null; 120 121 private static FeatureType deleteFT = null; 122 123 static { 124 if ( insertFT == null ) { 125 insertFT = TransactionValidator.createInsertFeatureType(); 126 } 127 if ( updateFT == null ) { 128 updateFT = TransactionValidator.createUpdateFeatureType(); 129 } 130 if ( deleteFT == null ) { 131 deleteFT = TransactionValidator.createDeleteFeatureType(); 132 } 133 } 134 135 /** 136 * 137 * @param policy 138 */ 139 public TransactionValidator( Policy policy ) { 140 super( policy ); 141 } 142 143 @Override 144 public void validateRequest( OGCWebServiceRequest request, User user ) 145 throws InvalidParameterValueException, UnauthorizedException { 146 147 userCoupled = false; 148 149 Transaction wfsreq = (Transaction) request; 150 151 List<TransactionOperation> ops = wfsreq.getOperations(); 152 for ( int i = 0; i < ops.size(); i++ ) { 153 userCoupled = false; 154 if ( ops.get( i ) instanceof Insert ) { 155 Request req = policy.getRequest( "WFS", "WFS_Insert" ); 156 if ( !req.isAny() && !req.getPreConditions().isAny() ) { 157 Condition condition = req.getPreConditions(); 158 validateOperation( condition, (Insert) ops.get( i ) ); 159 } 160 if ( userCoupled ) { 161 validateAgainstRightsDB( (Insert) ops.get( i ), user ); 162 } 163 } else if ( ops.get( i ) instanceof Update ) { 164 Request req = policy.getRequest( "WFS", "WFS_Update" ); 165 if ( !req.isAny() && !req.getPreConditions().isAny() ) { 166 Condition condition = req.getPreConditions(); 167 validateOperation( condition, (Update) ops.get( i ) ); 168 } 169 if ( userCoupled ) { 170 validateAgainstRightsDB( (Update) ops.get( i ), user ); 171 } 172 if ( req.getPostConditions() != null ) { 173 addFilter( ops.get( i ), req.getPostConditions(), user ); 174 } 175 } else if ( ops.get( i ) instanceof Delete ) { 176 Request req = policy.getRequest( "WFS", "WFS_Delete" ); 177 if ( !req.isAny() && !req.getPreConditions().isAny() ) { 178 Condition condition = req.getPreConditions(); 179 validateOperation( condition, (Delete) ops.get( i ) ); 180 } 181 if ( userCoupled ) { 182 validateAgainstRightsDB( (Delete) ops.get( i ), user ); 183 } 184 if ( req.getPostConditions() != null ) { 185 addFilter( ops.get( i ), req.getPostConditions(), user ); 186 } 187 } 188 } 189 190 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 191 try { 192 XMLFactory.export( wfsreq ).prettyPrint( System.out ); 193 } catch ( Exception e ) { 194 // nottin 195 } 196 } 197 198 } 199 200 /** 201 * adds a filter to the passed opertaion. If the condition is userCoupled the filter will be read from the DRM 202 * otherwise it is read from the current WFS policy file 203 * 204 * @param operation 205 * @param postConditions 206 * @param user 207 * @throws InvalidParameterValueException 208 * @throws UnauthorizedException 209 */ 210 private void addFilter( TransactionOperation operation, Condition postConditions, User user ) 211 throws InvalidParameterValueException, UnauthorizedException { 212 if ( postConditions.getOperationParameter( "instanceFilter" ) != null ) { 213 Filter opFilter = null; 214 if ( operation instanceof Update ) { 215 opFilter = ( (Update) operation ).getFilter(); 216 } else { 217 opFilter = ( (Delete) operation ).getFilter(); 218 } 219 Filter filter = null; 220 if ( postConditions.getOperationParameter( "instanceFilter" ).isUserCoupled() ) { 221 // read filterMap from constraints defined in deegree DRM 222 filter = readFilterFromDRM( operation, user ); 223 } else { 224 fillFilterMap( postConditions ); 225 // use filterMap read from policy document 226 filter = filterMap.get( operation.getAffectedFeatureTypes().get( 0 ) ); 227 } 228 229 if ( opFilter instanceof ComplexFilter ) { 230 // create a new Filter that is a combination of the 231 // original filter and the one defined in the GetFeatures 232 // PostConditions coupled by a logical 'And' 233 ComplexFilter qFilter = (ComplexFilter) opFilter; 234 if ( filter == null ) { 235 filter = qFilter; 236 } else { 237 filter = new ComplexFilter( qFilter, (ComplexFilter) filter, OperationDefines.AND ); 238 } 239 } else if ( opFilter instanceof FeatureFilter ) { 240 // just take original filter if it is as feature filter 241 // because feature filter and complex filters can not 242 // be combined 243 filter = opFilter; 244 } 245 if ( operation instanceof Update ) { 246 ( (Update) operation ).setFilter( filter ); 247 } else { 248 ( (Delete) operation ).setFilter( filter ); 249 } 250 } 251 } 252 253 /** 254 * reads a filter m 255 * 256 * @param operation 257 * @param user 258 * @return the defined filter for the given operation or <code>null</code> if no such filter was found. 259 * @throws UnauthorizedException 260 * @throws InvalidParameterValueException 261 */ 262 private Filter readFilterFromDRM( TransactionOperation operation, User user ) 263 throws UnauthorizedException, InvalidParameterValueException { 264 Filter f = null; 265 try { 266 SecurityAccessManager sam = SecurityAccessManager.getInstance(); 267 SecurityAccess access = sam.acquireAccess( user ); 268 269 QualifiedName qn = operation.getAffectedFeatureTypes().get( 0 ); 270 SecuredObject secObj = access.getSecuredObjectByName( qn.getFormattedString(), 271 ClientHelper.TYPE_FEATURETYPE ); 272 273 RightSet rs = user.getRights( access, secObj ); 274 Right right = null; 275 if ( operation instanceof Update ) { 276 right = rs.getRight( secObj, RightType.UPDATE_RESPONSE ); 277 } else { 278 right = rs.getRight( secObj, RightType.DELETE_RESPONSE ); 279 } 280 281 // a constraint - if available - is constructed as a OGC Filter 282 // one of the filter operations may is 'PropertyIsEqualTo' and 283 // defines a ProperyName == 'instanceFilter'. The Literal of this 284 // operation itself is a complete and valid Filter expression. 285 if ( right != null ) { 286 ComplexFilter filter = (ComplexFilter) right.getConstraints(); 287 if ( filter != null ) { 288 // extract filter expression to be used as additional 289 // filter for a GetFeature request 290 filter = extractInstanceFilter( filter.getOperation() ); 291 if ( filter != null ) { 292 f = filter; 293 } 294 } 295 } 296 297 } catch ( GeneralSecurityException e ) { 298 LOG.logError( e.getMessage(), e ); 299 throw new UnauthorizedException( e.getMessage(), e ); 300 } catch ( FilterConstructionException e ) { 301 LOG.logError( e.getMessage(), e ); 302 throw new InvalidParameterValueException( e.getMessage(), e ); 303 } catch ( SAXException e ) { 304 LOG.logError( e.getMessage(), e ); 305 throw new InvalidParameterValueException( e.getMessage(), e ); 306 } catch ( IOException e ) { 307 LOG.logError( e.getMessage(), e ); 308 throw new InvalidParameterValueException( e.getMessage(), e ); 309 } 310 return f; 311 } 312 313 private void fillFilterMap( Condition postConditions ) 314 throws InvalidParameterValueException { 315 List<Element> complexValues = postConditions.getOperationParameter( "instanceFilter" ).getComplexValues(); 316 try { 317 if ( filterMap.size() == 0 ) { 318 for ( int i = 0; i < complexValues.size(); i++ ) { 319 Query q = Query.create( complexValues.get( 0 ) ); 320 Filter f = q.getFilter(); 321 QualifiedName qn = q.getTypeNames()[0]; 322 filterMap.put( qn, f ); 323 } 324 } 325 } catch ( XMLParsingException e ) { 326 LOG.logError( e.getMessage(), e ); 327 throw new InvalidParameterValueException( this.getClass().getName(), e.getMessage() ); 328 } 329 } 330 331 /** 332 * 333 * @param condition 334 * @param insert 335 * @throws InvalidParameterValueException 336 */ 337 private void validateOperation( Condition condition, Insert insert ) 338 throws InvalidParameterValueException { 339 340 OperationParameter op = condition.getOperationParameter( TYPENAME ); 341 342 // version is valid because no restrictions are made 343 if ( op.isAny() ) { 344 return; 345 } 346 347 if ( op.isUserCoupled() ) { 348 userCoupled = true; 349 } else { 350 List<String> vals = op.getValues(); 351 List<QualifiedName> fts = insert.getAffectedFeatureTypes(); 352 for ( int i = 0; i < fts.size(); i++ ) { 353 String qn = fts.get( i ).getFormattedString(); 354 if ( !vals.contains( qn ) ) { 355 String s = Messages.getMessage( "OWSPROXY_NOT_ALLOWED_FEATURETYPE", "insert", qn ); 356 throw new InvalidParameterValueException( s ); 357 } 358 } 359 } 360 } 361 362 /** 363 * 364 * @param condition 365 * @param delete 366 * @throws InvalidParameterValueException 367 */ 368 private void validateOperation( Condition condition, Delete delete ) 369 throws InvalidParameterValueException { 370 OperationParameter op = condition.getOperationParameter( TYPENAME ); 371 372 // version is valid because no restrictions are made 373 if ( op.isAny() ) { 374 return; 375 } 376 377 if ( op.isUserCoupled() ) { 378 userCoupled = true; 379 } else { 380 List<String> vals = op.getValues(); 381 List<QualifiedName> fts = delete.getAffectedFeatureTypes(); 382 for ( int i = 0; i < fts.size(); i++ ) { 383 String qn = fts.get( i ).getFormattedString(); 384 if ( !vals.contains( qn ) ) { 385 String s = Messages.getMessage( "OWSPROXY_NOT_ALLOWED_FEATURETYPE", "delete", qn ); 386 throw new InvalidParameterValueException( s ); 387 } 388 } 389 } 390 } 391 392 /** 393 * 394 * @param condition 395 * @param update 396 * @throws InvalidParameterValueException 397 */ 398 private void validateOperation( Condition condition, Update update ) 399 throws InvalidParameterValueException { 400 401 OperationParameter op = condition.getOperationParameter( TYPENAME ); 402 403 // version is valid because no restrictions are made 404 if ( op.isAny() ) { 405 return; 406 } 407 408 if ( op.isUserCoupled() ) { 409 userCoupled = true; 410 } else { 411 List<String> vals = op.getValues(); 412 List<QualifiedName> fts = update.getAffectedFeatureTypes(); 413 for ( int i = 0; i < fts.size(); i++ ) { 414 String qn = fts.get( i ).getFormattedString(); 415 if ( !vals.contains( qn ) ) { 416 String s = Messages.getMessage( "OWSPROXY_NOT_ALLOWED_FEATURETYPE", "update", qn ); 417 throw new InvalidParameterValueException( s ); 418 } 419 } 420 } 421 } 422 423 /** 424 * validates a Transcation.Delete request against the underlying users and rights management system 425 * 426 * @param delete 427 * @param user 428 * @throws InvalidParameterValueException 429 * @throws UnauthorizedException 430 */ 431 private void validateAgainstRightsDB( Delete delete, User user ) 432 throws InvalidParameterValueException, UnauthorizedException { 433 if ( user == null ) { 434 throw new UnauthorizedException( Messages.getMessage( "OWSPROXY_NO_ANONYMOUS_ACCESS" ) ); 435 } 436 437 List<QualifiedName> fts = delete.getAffectedFeatureTypes(); 438 for ( int i = 0; i < fts.size(); i++ ) { 439 String name = fts.get( i ).getLocalName(); 440 String ns = fts.get( i ).getNamespace().toASCIIString(); 441 String qn = StringTools.concat( 200, '{', ns, "}:", name ); 442 443 // create a feature instance from the parameters of the GetFeature request 444 // to enable comparsion with a filter encoding expression stored in the 445 // assigned rights management system 446 List<FeatureProperty> fps = new ArrayList<FeatureProperty>(); 447 QualifiedName tn = new QualifiedName( "typeName" ); 448 FeatureProperty fp = FeatureFactory.createFeatureProperty( tn, qn ); 449 fps.add( fp ); 450 Feature feature = FeatureFactory.createFeature( "id", deleteFT, fps ); 451 452 if ( securityConfig.getProxiedUrl() == null ) { 453 handleUserCoupledRules( user, // the user who posted the request 454 feature, // This is the Database feature 455 qn, // the Qualified name of the users Featurerequest 456 TYPE_FEATURETYPE, // a primary key in the db. 457 DELETE );// We're requesting a featuretype. 458 } else { 459 handleUserCoupledRules( user, // the user who posted the request 460 feature, // This is the Database feature 461 "[" + securityConfig.getProxiedUrl() + "]:" + qn, // the Qualified name of the 462 // users Featurerequest 463 TYPE_FEATURETYPE, // a primary key in the db. 464 DELETE );// We're requesting a featuretype. 465 } 466 } 467 468 } 469 470 /** 471 * validates a Transcation.Update request against the underlying users and rights management system 472 * 473 * @param update 474 * @param user 475 * @throws UnauthorizedException 476 * @throws InvalidParameterValueException 477 */ 478 private void validateAgainstRightsDB( Update update, User user ) 479 throws InvalidParameterValueException, UnauthorizedException { 480 481 if ( user == null ) { 482 throw new UnauthorizedException( Messages.getMessage( "OWSPROXY_NO_ANONYMOUS_ACCESS" ) ); 483 } 484 485 List<QualifiedName> fts = update.getAffectedFeatureTypes(); 486 for ( int i = 0; i < fts.size(); i++ ) { 487 String name = fts.get( i ).getLocalName(); 488 String ns = fts.get( i ).getNamespace().toASCIIString(); 489 String qn = StringTools.concat( 200, '{', ns, "}:", name ); 490 491 // create a feature instance from the parameters of the GetFeature request 492 // to enable comparsion with a filter encoding expression stored in the 493 // assigned rights management system 494 List<FeatureProperty> fps = new ArrayList<FeatureProperty>(); 495 QualifiedName tn = new QualifiedName( "typeName" ); 496 FeatureProperty fp = FeatureFactory.createFeatureProperty( tn, qn ); 497 fps.add( fp ); 498 Feature feature = FeatureFactory.createFeature( "id", updateFT, fps ); 499 500 if ( securityConfig.getProxiedUrl() == null ) { 501 handleUserCoupledRules( user, // the user who posted the request 502 feature, // This is the Database feature 503 qn, // the Qualified name of the users Featurerequest 504 TYPE_FEATURETYPE, // a primary key in the db. 505 UPDATE );// We're requesting a featuretype. 506 } else { 507 handleUserCoupledRules( user, // the user who posted the request 508 feature, // This is the Database feature 509 "[" + securityConfig.getProxiedUrl() + "]:" + qn, // the Qualified name of the 510 // users Featurerequest 511 TYPE_FEATURETYPE, // a primary key in the db. 512 UPDATE );// We're requesting a featuretype. 513 } 514 } 515 } 516 517 /** 518 * validates the passed insert operation against the deegree user/rights management system 519 * 520 * @param insert 521 * @param user 522 * @throws InvalidParameterValueException 523 * @throws UnauthorizedException 524 */ 525 private void validateAgainstRightsDB( Insert insert, User user ) 526 throws InvalidParameterValueException, UnauthorizedException { 527 528 if ( user == null ) { 529 throw new UnauthorizedException( Messages.getMessage( "OWSPROXY_NO_ANONYMOUS_ACCESS" ) ); 530 } 531 532 List<QualifiedName> fts = insert.getAffectedFeatureTypes(); 533 for ( int i = 0; i < fts.size(); i++ ) { 534 String name = fts.get( i ).getLocalName(); 535 String ns = fts.get( i ).getNamespace().toASCIIString(); 536 String qn = StringTools.concat( 200, '{', ns, "}:", name ); 537 // create a feature instance from the parameters of the GetRecords request 538 // to enable comparsion with a filter encoding expression stored in the 539 // assigned rights management system 540 List<FeatureProperty> fps = new ArrayList<FeatureProperty>(); 541 QualifiedName tn = new QualifiedName( "typeName" ); 542 FeatureProperty fp = FeatureFactory.createFeatureProperty( tn, qn ); 543 fps.add( fp ); 544 Feature feature = FeatureFactory.createFeature( "id", insertFT, fps ); 545 546 if ( securityConfig.getProxiedUrl() == null ) { 547 handleUserCoupledRules( user, // the user who posted the request 548 feature, // This is the Database feature 549 qn, // the Qualified name of the users Featurerequest 550 TYPE_FEATURETYPE, // a primary key in the db. 551 INSERT );// We're requesting a featuretype. 552 } else { 553 handleUserCoupledRules( user, // the user who posted the request 554 feature, // This is the Database feature 555 "[" + securityConfig.getProxiedUrl() + "]:" + qn, // the Qualified name of the 556 // users Featurerequest 557 TYPE_FEATURETYPE, // a primary key in the db. 558 INSERT );// We're requesting a featuretype. 559 } 560 } 561 562 } 563 564 /** 565 * creates a feature type that matches the parameters of a Insert operation 566 * 567 * @return created <tt>FeatureType</tt> 568 */ 569 private static FeatureType createInsertFeatureType() { 570 PropertyType[] ftps = new PropertyType[1]; 571 ftps[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "typeName" ), Types.VARCHAR, false ); 572 573 return FeatureFactory.createFeatureType( "WFS_Insert", false, ftps ); 574 } 575 576 /** 577 * creates a feature type that matches the parameters of a Update operation 578 * 579 * @return created <tt>FeatureType</tt> 580 */ 581 private static FeatureType createUpdateFeatureType() { 582 PropertyType[] ftps = new PropertyType[2]; 583 ftps[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "typeName" ), Types.VARCHAR, false ); 584 585 return FeatureFactory.createFeatureType( "WFS_Update", false, ftps ); 586 } 587 588 /** 589 * creates a feature type that matches the parameters of a Delete operation 590 * 591 * @return created <tt>FeatureType</tt> 592 */ 593 private static FeatureType createDeleteFeatureType() { 594 PropertyType[] ftps = new PropertyType[1]; 595 ftps[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "typeName" ), Types.VARCHAR, false ); 596 597 return FeatureFactory.createFeatureType( "WFS_Delete", false, ftps ); 598 } 599 600 }