001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/security/owsrequestvalidator/wfs/GetFeatureRequestValidator.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.GETFEATURE;
040    
041    import java.io.IOException;
042    import java.util.HashMap;
043    import java.util.List;
044    import java.util.Map;
045    
046    import org.deegree.datatypes.QualifiedName;
047    import org.deegree.datatypes.Types;
048    import org.deegree.framework.log.ILogger;
049    import org.deegree.framework.log.LoggerFactory;
050    import org.deegree.framework.xml.XMLParsingException;
051    import org.deegree.i18n.Messages;
052    import org.deegree.model.feature.Feature;
053    import org.deegree.model.feature.FeatureFactory;
054    import org.deegree.model.feature.FeatureProperty;
055    import org.deegree.model.feature.schema.FeatureType;
056    import org.deegree.model.feature.schema.PropertyType;
057    import org.deegree.model.filterencoding.ComplexFilter;
058    import org.deegree.model.filterencoding.FeatureFilter;
059    import org.deegree.model.filterencoding.Filter;
060    import org.deegree.model.filterencoding.FilterConstructionException;
061    import org.deegree.model.filterencoding.OperationDefines;
062    import org.deegree.ogcwebservices.InvalidParameterValueException;
063    import org.deegree.ogcwebservices.OGCWebServiceRequest;
064    import org.deegree.ogcwebservices.wfs.XMLFactory;
065    import org.deegree.ogcwebservices.wfs.operation.GetFeature;
066    import org.deegree.ogcwebservices.wfs.operation.Query;
067    import org.deegree.portal.standard.security.control.ClientHelper;
068    import org.deegree.security.GeneralSecurityException;
069    import org.deegree.security.UnauthorizedException;
070    import org.deegree.security.drm.SecurityAccess;
071    import org.deegree.security.drm.SecurityAccessManager;
072    import org.deegree.security.drm.model.Right;
073    import org.deegree.security.drm.model.RightSet;
074    import org.deegree.security.drm.model.RightType;
075    import org.deegree.security.drm.model.SecuredObject;
076    import org.deegree.security.drm.model.User;
077    import org.deegree.security.owsproxy.Condition;
078    import org.deegree.security.owsproxy.OperationParameter;
079    import org.deegree.security.owsproxy.Request;
080    import org.deegree.security.owsrequestvalidator.Policy;
081    import org.w3c.dom.Element;
082    import org.xml.sax.SAXException;
083    
084    /**
085     *
086     *
087     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
088     * @author last edited by: $Author: mschneider $
089     *
090     * @version 1.1, $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18 Jun 2009) $
091     *
092     * @since 1.1
093     */
094    class GetFeatureRequestValidator extends AbstractWFSRequestValidator {
095    
096        private static final ILogger LOG = LoggerFactory.getLogger( GetFeatureRequestValidator.class );
097    
098        // known condition parameter
099        private static final String FORMAT = "format";
100    
101        private static final String MAXFEATURES = "maxFeatures";
102    
103        private static Map<QualifiedName, Filter> filterMap = new HashMap<QualifiedName, Filter>();
104    
105        private static FeatureType gfFT = null;
106    
107        static {
108            if ( gfFT == null ) {
109                gfFT = GetFeatureRequestValidator.createFeatureType();
110            }
111        }
112    
113        /**
114         * @param policy
115         */
116        public GetFeatureRequestValidator( Policy policy ) {
117            super( policy );
118        }
119    
120        /**
121         * validates if the passed request is valid against the policy assigned to the validator. If the passed user is not
122         * <tt>null</tt> user coupled parameters will be validated against a users and rights management system.
123         */
124        @Override
125        public void validateRequest( OGCWebServiceRequest request, User user )
126                                throws InvalidParameterValueException, UnauthorizedException {
127    
128            userCoupled = false;
129            Request req = policy.getRequest( "WFS", "GetFeature" );
130            // request is valid because no restrictions are made
131            if ( req.isAny() || req.getPreConditions().isAny() ) {
132                return;
133            }
134            Condition condition = req.getPreConditions();
135    
136            GetFeature wfsreq = (GetFeature) request;
137    
138            validateVersion( condition, wfsreq.getVersion() );
139    
140            Query[] queries = wfsreq.getQuery();
141            String[] ft = new String[queries.length];
142            for ( int i = 0; i < ft.length; i++ ) {
143                ft[i] = queries[i].getTypeNames()[0].getFormattedString();
144            }
145    
146            validateFeatureTypes( condition, ft );
147            validateFormat( condition, wfsreq.getOutputFormat() );
148            validateMaxFeatures( condition, wfsreq.getMaxFeatures() );
149    
150            if ( userCoupled ) {
151                validateAgainstRightsDB( wfsreq, user );
152            }
153    
154            if ( req.getPostConditions() != null ) {
155                addFilter( wfsreq, req.getPostConditions(), user );
156            }
157    
158        }
159    
160        /**
161         * adds an additional Filter read from parameter 'instanceFilter'to the Filter of the passed GetFeature request. If
162         * parameter 'instanceFilter' is userCoupled the filter will be read from DRM, if it is not the filter defined
163         * within the responsible policy document will be used.
164         *
165         * @param wfsreq
166         * @param postConditions
167         * @param user
168         * @throws InvalidParameterValueException
169         * @throws UnauthorizedException
170         */
171        private void addFilter( GetFeature wfsreq, Condition postConditions, User user )
172                                throws InvalidParameterValueException, UnauthorizedException {
173            if ( LOG.isDebug() ) {
174                LOG.logDebug( "Feature type", wfsreq.getQuery()[0].getTypeNames()[0] );
175            }
176            if ( postConditions.getOperationParameter( "instanceFilter" ) != null
177                 && !postConditions.getOperationParameter( "instanceFilter" ).isAny() ) {
178                Map<QualifiedName, Filter> localFilterMap;
179                if ( postConditions.getOperationParameter( "instanceFilter" ).isUserCoupled() ) {
180                    // read filterMap from constraints defined in deegree DRM
181                    localFilterMap = readFilterFromDRM( wfsreq, user );
182                    LOG.logDebug( "Filter map from DRM", localFilterMap );
183                } else {
184                    fillFilterMap( postConditions );
185                    // use filterMap read from policy document
186                    localFilterMap = filterMap;
187                }
188                Query[] queries = wfsreq.getQuery();
189                for ( int i = 0; i < queries.length; i++ ) {
190                    Filter filter = null;
191                    if ( queries[i].getFilter() == null ) {
192                        // if query does not define a filter just use the matching
193                        // one from the post conditions
194                        filter = localFilterMap.get( queries[i].getTypeNames()[0] );
195                    } else if ( queries[i].getFilter() instanceof ComplexFilter ) {
196                        // create a new Filter that is a combination of the
197                        // original filter and the one defined in the GetFeatures
198                        // PostConditions coupled by a logical 'And'
199                        ComplexFilter qFilter = (ComplexFilter) queries[i].getFilter();
200                        filter = localFilterMap.get( queries[i].getTypeNames()[0] );
201                        if ( filter == null ) {
202                            filter = qFilter;
203                        } else {
204                            filter = new ComplexFilter( qFilter, (ComplexFilter) filter, OperationDefines.AND );
205                        }
206                    } else if ( queries[i].getFilter() instanceof FeatureFilter ) {
207                        // just take original filter if it is as feature filter
208                        // because feature filter and complex filters can not
209                        // be combined
210                        filter = queries[i].getFilter();
211                    }
212    
213                    if ( LOG.isDebug() ) {
214                        LOG.logDebug( "Filter", filter == null ? " is null" : filter.to110XML() );
215                    }
216    
217                    // substitue query by a new one using the re-created filter
218                    queries[i] = Query.create( queries[i].getPropertyNames(), queries[i].getFunctions(),
219                                               queries[i].getSortProperties(), queries[i].getHandle(),
220                                               queries[i].getFeatureVersion(), queries[i].getTypeNames(),
221                                               queries[i].getAliases(), queries[i].getSrsName(), filter,
222                                               queries[i].getMaxFeatures(), queries[i].getStartPosition(),
223                                               queries[i].getResultType() );
224                }
225                wfsreq.setQueries( queries );
226            }
227            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
228                try {
229                    XMLFactory.export( wfsreq ).prettyPrint( System.out );
230                } catch ( Exception e ) {
231                    // nottin
232                }
233            }
234        }
235    
236        /**
237         *
238         * @param wfsreq
239         * @param user
240         * @return a map with the filters
241         * @throws UnauthorizedException
242         * @throws InvalidParameterValueException
243         */
244        private Map<QualifiedName, Filter> readFilterFromDRM( GetFeature wfsreq, User user )
245                                throws UnauthorizedException, InvalidParameterValueException {
246    
247            Map<QualifiedName, Filter> map = new HashMap<QualifiedName, Filter>();
248            try {
249                SecurityAccessManager sam = SecurityAccessManager.getInstance();
250                SecurityAccess access = sam.acquireAccess( user );
251                Query[] queries = wfsreq.getQuery();
252                for ( int i = 0; i < queries.length; i++ ) {
253                    QualifiedName qn = queries[i].getTypeNames()[0];
254                    SecuredObject secObj = access.getSecuredObjectByName( qn.getFormattedString(),
255                                                                          ClientHelper.TYPE_FEATURETYPE );
256    
257                    RightSet rs = user.getRights( access, secObj );
258                    Right right = rs.getRight( secObj, RightType.GETFEATURE_RESPONSE );
259                    // a constraint - if available - is constructed as a OGC Filter
260                    // one of the filter operations may is 'PropertyIsEqualTo' and
261                    // defines a ProperyName == 'instanceFilter'. The Literal of this
262                    // operation itself is a complete and valid Filter expression.
263    
264                    if ( right != null ) {
265                        ComplexFilter filter = (ComplexFilter) right.getConstraints();
266                        if ( filter != null ) {
267                            // extract filter expression to be used as additional
268                            // filter for a GetFeature request
269                            LOG.logDebug( "Filter before extraction", filter.toXML() );
270                            filter = extractInstanceFilter( filter.getOperation() );
271                            if ( filter != null ) {
272                                map.put( qn, filter );
273                                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
274                                    LOG.logDebug( "instance filter for right GETFEATURE_RESPONSE", filter.toXML() );
275                                }
276                            } else {
277                                LOG.logDebug( "no instance filter defined for right GETFEATURE_RESPONSE and feature type: "
278                                              + qn );
279                            }
280                        } else {
281                            LOG.logDebug( "no constraint defined for right GETFEATURE_RESPONSE and feature type: " + qn );
282                        }
283                    } else {
284                        LOG.logDebug( "right GETFEATURE_RESPONSE not defined for current user and feature type: " + qn );
285                    }
286                }
287            } catch ( GeneralSecurityException e ) {
288                LOG.logError( e.getMessage(), e );
289                throw new UnauthorizedException( e.getMessage(), e );
290            } catch ( FilterConstructionException e ) {
291                LOG.logError( e.getMessage(), e );
292                throw new InvalidParameterValueException( e.getMessage(), e );
293            } catch ( SAXException e ) {
294                LOG.logError( e.getMessage(), e );
295                throw new InvalidParameterValueException( e.getMessage(), e );
296            } catch ( IOException e ) {
297                LOG.logError( e.getMessage(), e );
298                throw new InvalidParameterValueException( e.getMessage(), e );
299            }
300    
301            return map;
302        }
303    
304        private void fillFilterMap( Condition postConditions )
305                                throws InvalidParameterValueException {
306            List<Element> complexValues = postConditions.getOperationParameter( "instanceFilter" ).getComplexValues();
307            try {
308                if ( filterMap.size() == 0 ) {
309                    for ( int i = 0; i < complexValues.size(); i++ ) {
310                        Query q = Query.create( complexValues.get( i ) );
311                        Filter f = q.getFilter();
312                        QualifiedName qn = q.getTypeNames()[0];
313                        filterMap.put( qn, f );
314                    }
315                }
316            } catch ( XMLParsingException e ) {
317                LOG.logError( e.getMessage(), e );
318                throw new InvalidParameterValueException( this.getClass().getName(), e.getMessage() );
319            }
320        }
321    
322        /**
323         * valides if the format you in a GetFeature request is valid against the policy assigned to Validator. If the
324         * passed user is not <tt>null</tt> and the format parameter is user coupled the format will be validated against a
325         * users and rights management system.
326         *
327         * @param condition
328         * @param format
329         * @throws InvalidParameterValueException
330         */
331        private void validateFormat( Condition condition, String format )
332                                throws InvalidParameterValueException {
333            OperationParameter op = condition.getOperationParameter( FORMAT );
334    
335            // version is valid because no restrictions are made
336            if ( op.isAny() )
337                return;
338    
339            List<String> validLayers = op.getValues();
340            if ( op.isUserCoupled() ) {
341                userCoupled = true;
342            } else {
343                if ( !validLayers.contains( format ) ) {
344                    String s = Messages.getMessage( "OWSPROXY_DESCRIBEFEATURETYPE_FORMAT", format );
345                    throw new InvalidParameterValueException( s );
346                }
347            }
348    
349        }
350    
351        /**
352         * valides if the format you in a GetFeature request is valid against the policy assigned to Validator. If the
353         * passed user is not <tt>null</tt> and the maxFeatures parameter is user coupled the maxFeatures will be validated
354         * against a users and rights management system.
355         *
356         * @param condition
357         * @param maxFeatures
358         * @throws InvalidParameterValueException
359         */
360        private void validateMaxFeatures( Condition condition, int maxFeatures )
361                                throws InvalidParameterValueException {
362            OperationParameter op = condition.getOperationParameter( MAXFEATURES );
363    
364            // version is valid because no restrictions are made
365            if ( op.isAny() )
366                return;
367    
368            int maxF = Integer.parseInt( op.getValues().get( 0 ) );
369    
370            if ( op.isUserCoupled() ) {
371                userCoupled = true;
372            } else {
373                if ( maxFeatures > maxF || maxFeatures < 0 ) {
374                    String s = Messages.getMessage( "OWSPROXY_GETFEATURE_MAXFEATURE", maxFeatures );
375                    throw new InvalidParameterValueException( s );
376                }
377            }
378    
379        }
380    
381        /**
382         * validates the passed WMS GetMap request against a User- and Rights-Management DB.
383         *
384         * @param wfsreq
385         * @param user
386         * @throws InvalidParameterValueException
387         */
388        private void validateAgainstRightsDB( GetFeature wfsreq, User user )
389                                throws InvalidParameterValueException, UnauthorizedException {
390    
391            if ( user == null ) {
392                throw new UnauthorizedException( "no access to anonymous user" );
393            }
394    
395            // create feature that describes the map request
396            FeatureProperty[] fps = new FeatureProperty[3];
397            fps[0] = FeatureFactory.createFeatureProperty( new QualifiedName( "version" ), wfsreq.getVersion() );
398            Integer mxf = new Integer( wfsreq.getMaxFeatures() );
399            // The database can handle "features as a key", this feature is build from the request's
400            // features
401            fps[1] = FeatureFactory.createFeatureProperty( new QualifiedName( "maxfeatures" ), mxf );
402            fps[2] = FeatureFactory.createFeatureProperty( new QualifiedName( "outputformat" ), wfsreq.getOutputFormat() );
403    
404            Feature feature = FeatureFactory.createFeature( "id", gfFT, fps );
405            Query[] queries = wfsreq.getQuery();
406            for ( int i = 0; i < queries.length; i++ ) {
407                StringBuffer sb = new StringBuffer( 200 );
408                sb.append( '{' ).append( queries[i].getTypeNames()[0].getNamespace().toASCIIString() );
409                sb.append( "}:" ).append( queries[i].getTypeNames()[0].getLocalName() );
410                if ( securityConfig.getProxiedUrl() == null ) {
411                    handleUserCoupledRules( user, // the user who posted the request
412                                            feature, // This is the Database feature
413                                            sb.toString(), // the Qualified name of the users Featurerequest
414                                            TYPE_FEATURETYPE, // a primary key in the db.
415                                            GETFEATURE );// We're requesting a featuretype.
416                } else {
417                    handleUserCoupledRules( user, // the user who posted the request
418                                            feature, // This is the Database feature
419                                            "[" + securityConfig.getProxiedUrl() + "]:" + sb, // the Qualified name of the
420                                            // users Featurerequest
421                                            TYPE_FEATURETYPE, // a primary key in the db.
422                                            GETFEATURE );// We're requesting a featuretype.
423                }
424            }
425    
426        }
427    
428        /**
429         * creates a feature type that matches the parameters of a GetLagendGraphic request
430         *
431         * @return created <tt>FeatureType</tt>
432         */
433        private static FeatureType createFeatureType() {
434            PropertyType[] ftps = new PropertyType[3];
435            ftps[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "version" ), Types.VARCHAR, false );
436            ftps[1] = FeatureFactory.createSimplePropertyType( new QualifiedName( "maxfeatures" ), Types.INTEGER, false );
437            ftps[2] = FeatureFactory.createSimplePropertyType( new QualifiedName( "outputformat" ), Types.VARCHAR, false );
438    
439            return FeatureFactory.createFeatureType( "GetFeature", false, ftps );
440        }
441    }