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     **************************************************************************************************/