001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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 }