001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/security/owsrequestvalidator/wfs/TransactionValidator.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.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: 9346 $
099 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
100 * @author last edited by: $Author: apoth $
101 *
102 * @version 1.0. $Revision: 9346 $, $Date: 2007-12-27 17:39:07 +0100 (Do, 27 Dez 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 }