001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/security/owsrequestvalidator/wfs/GetFeatureRequestValidator.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.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: mschneider $
093     * 
094     * @version 1.1, $Revision: 6625 $, $Date: 2007-04-17 18:05:27 +0200 (Di, 17 Apr 2007) $
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                Map<QualifiedName, Filter> localFilterMap;
178                if ( postConditions.getOperationParameter( "instanceFilter" ).isUserCoupled() ) {
179                    // read filterMap from constraints defined in deegree DRM
180                    localFilterMap = readFilterFromDRM( wfsreq, user );
181                } else {
182                    fillFilterMap( postConditions );
183                    // use filterMap read from policy document
184                    localFilterMap = filterMap;
185                }
186                Query[] queries = wfsreq.getQuery();
187                for ( int i = 0; i < queries.length; i++ ) {
188                    Filter filter = null;
189                    if ( queries[i].getFilter() == null ) {
190                        // if query does not define a filter just use the matching
191                        // one from the post conditions
192                        filter = localFilterMap.get( queries[i].getTypeNames()[0] );
193                    } else if ( queries[i].getFilter() instanceof ComplexFilter ) {
194                        // create a new Filter that is a combination of the
195                        // original filter and the one defined in the GetFeatures
196                        // PostConditions coupled by a logical 'And'
197                        ComplexFilter qFilter = (ComplexFilter) queries[i].getFilter();
198                        filter = localFilterMap.get( queries[i].getTypeNames()[0] );
199                        if ( filter == null ) {
200                            filter = qFilter;
201                        } else {
202                            filter = new ComplexFilter( qFilter, (ComplexFilter) filter, OperationDefines.AND );
203                        }
204                    } else if ( queries[i].getFilter() instanceof FeatureFilter ) {
205                        // just take original filter if it is as feature filter
206                        // because feature filter and complex filters can not
207                        // be combined
208                        filter = queries[i].getFilter();
209                    }
210                    // substitue query by a new one using the re-created filter
211                    queries[i] = Query.create( queries[i].getPropertyNames(), queries[i].getFunctions(),
212                                               queries[i].getSortProperties(), queries[i].getHandle(),
213                                               queries[i].getFeatureVersion(), queries[i].getTypeNames(),
214                                               queries[i].getAliases(), queries[i].getSrsName(), filter,
215                                               queries[i].getMaxFeatures(), queries[i].getStartPosition(),
216                                               queries[i].getResultType() );
217                }
218                wfsreq.setQueries( queries );
219            }
220            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
221                try {
222                    XMLFactory.export( wfsreq ).prettyPrint( System.out );
223                } catch ( Exception e ) {
224                }
225            }
226        }
227    
228        /**
229         * 
230         * @param wfsreq
231         * @param user
232         * @return
233         * @throws UnauthorizedException
234         * @throws InvalidParameterValueException
235         */
236        private Map<QualifiedName, Filter> readFilterFromDRM( GetFeature wfsreq, User user )
237                                throws UnauthorizedException, InvalidParameterValueException {
238    
239            Map<QualifiedName, Filter> map = new HashMap<QualifiedName, Filter>();
240            try {
241                SecurityAccessManager sam = SecurityAccessManager.getInstance();
242                SecurityAccess access = sam.acquireAccess( user );
243                Query[] queries = wfsreq.getQuery();
244                for ( int i = 0; i < queries.length; i++ ) {
245                    QualifiedName qn = queries[i].getTypeNames()[0];
246                    SecuredObject secObj = access.getSecuredObjectByName( qn.getFormattedString(),
247                                                                          ClientHelper.TYPE_FEATURETYPE );
248    
249                    RightSet rs = user.getRights( access, secObj );
250                    Right right = rs.getRight( secObj, RightType.GETFEATURE_RESPONSE );
251                    // a constraint - if available - is constructed as a OGC Filter
252                    // one of the filter operations may is 'PropertyIsEqualTo' and
253                    // defines a ProperyName == 'instanceFilter'. The Literal of this
254                    // operation itself is a complete and valid Filter expression.
255                    if ( right != null ) {
256                        ComplexFilter filter = (ComplexFilter) right.getConstraints();
257                        if ( filter != null ) {
258                            // extract filter expression to be used as additional
259                            // filter for a GetFeature request
260                            filter = extractInstanceFilter( filter.getOperation() );
261                            if ( filter != null ) {
262                                map.put( qn, filter );
263                            }
264                        }
265                    }
266                }
267            } catch ( GeneralSecurityException e ) {
268                LOG.logError( e.getMessage(), e );
269                throw new UnauthorizedException( e.getMessage(), e );
270            } catch ( FilterConstructionException e ) {
271                LOG.logError( e.getMessage(), e );
272                throw new InvalidParameterValueException( e.getMessage(), e );
273            } catch ( SAXException e ) {
274                LOG.logError( e.getMessage(), e );
275                throw new InvalidParameterValueException( e.getMessage(), e );
276            } catch ( IOException e ) {
277                LOG.logError( e.getMessage(), e );
278                throw new InvalidParameterValueException( e.getMessage(), e );
279            }
280    
281            return map;
282        }
283    
284        private void fillFilterMap( Condition postConditions )
285                                throws InvalidParameterValueException {
286            List<Element> complexValues = postConditions.getOperationParameter( "instanceFilter" ).getComplexValues();
287            try {
288                if ( filterMap.size() == 0 ) {
289                    for ( int i = 0; i < complexValues.size(); i++ ) {
290                        Query q = Query.create( complexValues.get( 0 ) );
291                        Filter f = q.getFilter();
292                        QualifiedName qn = q.getTypeNames()[0];
293                        filterMap.put( qn, f );
294                    }
295                }
296            } catch ( XMLParsingException e ) {
297                LOG.logError( e.getMessage(), e );
298                throw new InvalidParameterValueException( this.getClass().getName(), e.getMessage() );
299            }
300        }
301    
302        /**
303         * valides if the format you in a GetFeature request is valid against the policy assigned to
304         * Validator. If the passed user is not <tt>null</tt> and the format parameter is user coupled
305         * the format will be validated against a users and rights management system.
306         * 
307         * @param condition
308         * @param format
309         * @throws InvalidParameterValueException
310         */
311        private void validateFormat( Condition condition, String format )
312                                throws InvalidParameterValueException {
313            OperationParameter op = condition.getOperationParameter( FORMAT );
314    
315            // version is valid because no restrictions are made
316            if ( op.isAny() )
317                return;
318    
319            List validLayers = op.getValues();
320            if ( op.isUserCoupled() ) {
321                userCoupled = true;
322            } else {
323                if ( !validLayers.contains( format ) ) {
324                    String s = Messages.getMessage( "OWSPROXY_DESCRIBEFEATURETYPE_FORMAT", format );
325                    throw new InvalidParameterValueException( s );
326                }
327            }
328    
329        }
330    
331        /**
332         * valides if the format you in a GetFeature request is valid against the policy assigned to
333         * Validator. If the passed user is not <tt>null</tt> and the maxFeatures parameter is user
334         * coupled the maxFeatures will be validated against a users and rights management system.
335         * 
336         * @param condition
337         * @param maxFeatures
338         * @throws InvalidParameterValueException
339         */
340        private void validateMaxFeatures( Condition condition, int maxFeatures )
341                                throws InvalidParameterValueException {
342            OperationParameter op = condition.getOperationParameter( MAXFEATURES );
343    
344            // version is valid because no restrictions are made
345            if ( op.isAny() )
346                return;
347    
348            int maxF = Integer.parseInt( op.getValues().get( 0 ) );
349    
350            if ( op.isUserCoupled() ) {
351                userCoupled = true;
352            } else {
353                if ( maxFeatures > maxF || maxFeatures < 0 ) {
354                    String s = Messages.getMessage( "OWSPROXY_GETFEATURE_MAXFEATURE", maxFeatures );
355                    throw new InvalidParameterValueException( s );
356                }
357            }
358    
359        }
360    
361        /**
362         * validates the passed WMS GetMap request against a User- and Rights-Management DB.
363         * 
364         * @param wmsreq
365         * @param user
366         * @throws InvalidParameterValueException
367         */
368        private void validateAgainstRightsDB( GetFeature wfsreq, User user )
369                                throws InvalidParameterValueException, UnauthorizedException {
370    
371            if ( user == null ) {
372                throw new UnauthorizedException( "no access to anonymous user" );
373            }
374    
375            // create feature that describes the map request
376            FeatureProperty[] fps = new FeatureProperty[3];
377            fps[0] = FeatureFactory.createFeatureProperty( new QualifiedName( "version" ), wfsreq.getVersion() );
378            Integer mxf = new Integer( wfsreq.getMaxFeatures() );
379            // The database can handle "features as a key", this feature is build from the request's
380            // features
381            fps[1] = FeatureFactory.createFeatureProperty( new QualifiedName( "maxfeatures" ), mxf );
382            fps[2] = FeatureFactory.createFeatureProperty( new QualifiedName( "outputformat" ), wfsreq.getOutputFormat() );
383    
384            Feature feature = FeatureFactory.createFeature( "id", gfFT, fps );
385            Query[] queries = wfsreq.getQuery();
386            for ( int i = 0; i < queries.length; i++ ) {
387                StringBuffer sb = new StringBuffer( 200 );
388                sb.append( '{' ).append( queries[i].getTypeNames()[0].getNamespace().toASCIIString() );
389                sb.append( "}:" ).append( queries[i].getTypeNames()[0].getLocalName() );
390                handleUserCoupledRules( user, // the user who posted the request
391                                        feature, // This is the Database feature
392                                        sb.toString(), // the Qualified name of the users
393                                                        // Featurerequest
394                                        ClientHelper.TYPE_FEATURETYPE, // a primary key in the db.
395                                        RightType.GETFEATURE );// We're requesting a featuretype.
396            }
397    
398        }
399    
400        /**
401         * creates a feature type that matches the parameters of a GetLagendGraphic request
402         * 
403         * @return created <tt>FeatureType</tt>
404         */
405        private static FeatureType createFeatureType() {
406            PropertyType[] ftps = new PropertyType[3];
407            ftps[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "version" ), Types.VARCHAR, false );
408            ftps[1] = FeatureFactory.createSimplePropertyType( new QualifiedName( "maxfeatures" ), Types.INTEGER, false );
409            ftps[2] = FeatureFactory.createSimplePropertyType( new QualifiedName( "outputformat" ), Types.VARCHAR, false );
410    
411            return FeatureFactory.createFeatureType( "GetFeature", false, ftps );
412        }
413    }