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