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