001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/security/owsrequestvalidator/csw/GetRecordsRequestValidator.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.csw;
037    
038    import java.io.IOException;
039    import java.util.ArrayList;
040    import java.util.HashMap;
041    import java.util.List;
042    import java.util.Map;
043    
044    import org.deegree.datatypes.QualifiedName;
045    import org.deegree.datatypes.Types;
046    import org.deegree.framework.log.ILogger;
047    import org.deegree.framework.log.LoggerFactory;
048    import org.deegree.model.feature.Feature;
049    import org.deegree.model.feature.FeatureFactory;
050    import org.deegree.model.feature.FeatureProperty;
051    import org.deegree.model.feature.schema.FeatureType;
052    import org.deegree.model.feature.schema.PropertyType;
053    import org.deegree.model.filterencoding.ComplexFilter;
054    import org.deegree.model.filterencoding.FeatureFilter;
055    import org.deegree.model.filterencoding.Filter;
056    import org.deegree.model.filterencoding.FilterConstructionException;
057    import org.deegree.model.filterencoding.LogicalOperation;
058    import org.deegree.model.filterencoding.OperationDefines;
059    import org.deegree.ogcbase.SortProperty;
060    import org.deegree.ogcwebservices.InvalidParameterValueException;
061    import org.deegree.ogcwebservices.OGCWebServiceRequest;
062    import org.deegree.ogcwebservices.csw.discovery.GetRecords;
063    import org.deegree.ogcwebservices.csw.discovery.Query;
064    import org.deegree.ogcwebservices.csw.discovery.XMLFactory;
065    import org.deegree.portal.standard.security.control.ClientHelper;
066    import org.deegree.security.GeneralSecurityException;
067    import org.deegree.security.UnauthorizedException;
068    import org.deegree.security.drm.SecurityAccess;
069    import org.deegree.security.drm.SecurityAccessManager;
070    import org.deegree.security.drm.model.Right;
071    import org.deegree.security.drm.model.RightSet;
072    import org.deegree.security.drm.model.RightType;
073    import org.deegree.security.drm.model.SecuredObject;
074    import org.deegree.security.drm.model.User;
075    import org.deegree.security.owsproxy.Condition;
076    import org.deegree.security.owsproxy.OperationParameter;
077    import org.deegree.security.owsproxy.Request;
078    import org.deegree.security.owsrequestvalidator.Messages;
079    import org.deegree.security.owsrequestvalidator.Policy;
080    import org.xml.sax.SAXException;
081    
082    /**
083     *
084     *
085     * @version $Revision: 18195 $
086     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
087     * @author last edited by: $Author: mschneider $
088     *
089     * @version 1.0. $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
090     *
091     * @since 2.0
092     */
093    public class GetRecordsRequestValidator extends AbstractCSWRequestValidator {
094    
095        private static final ILogger LOG = LoggerFactory.getLogger( GetRecordsRequestValidator.class );
096    
097        private static final String ELEMENTSETNAME = "elementSetName";
098    
099        private static final String MAXRECORDS = "maxRecords";
100    
101        private static final String OUTPUTFORMAT = "outputFormat";
102    
103        private static final String RESULTTYPE = "resultType";
104    
105        private static final String SORTBY = "sortBy";
106    
107        private static final String TYPENAMES = "typeNames";
108    
109        private static FeatureType grFT = null;
110    
111        private static Map<String, Filter> filterMap = new HashMap<String, Filter>();
112    
113        static {
114            if ( grFT == null ) {
115                grFT = GetRecordsRequestValidator.createFeatureType();
116            }
117        }
118    
119        /**
120         * @param policy
121         */
122        public GetRecordsRequestValidator( Policy policy ) {
123            super( policy );
124        }
125    
126        /**
127         * @param request
128         * @param user
129         */
130        @Override
131        public void validateRequest( OGCWebServiceRequest request, User user )
132                                throws InvalidParameterValueException, UnauthorizedException {
133    
134            userCoupled = false;
135            Request req = policy.getRequest( "CSW", "GetRecords" );
136            // request is valid because no restrictions are made
137            if ( req.isAny() || req.getPreConditions().isAny() ) {
138                return;
139            }
140            Condition condition = req.getPreConditions();
141    
142            GetRecords casreq = (GetRecords) request;
143    
144            validateVersion( condition, casreq.getVersion() );
145    
146            // validateRecordTypes( condition, tn );
147            validateMaxRecords( condition, casreq.getMaxRecords() );
148            validateOutputFormat( condition, casreq.getOutputFormat() );
149            validateResultType( condition, casreq.getResultTypeAsString() );
150            validateElementSetName( condition, casreq.getQuery().getElementSetName() );
151            validateSortBy( condition, casreq.getQuery().getSortProperties() );
152            List<QualifiedName> list = casreq.getQuery().getTypeNamesAsList();
153            validateTypeNames( condition, list );
154    
155            if ( userCoupled ) {
156                validateAgainstRightsDB( casreq, user );
157            }
158    
159            if ( req.getPostConditions() != null ) {
160                addFilter( casreq, req.getPostConditions(), user );
161            }
162    
163        }
164    
165        /**
166         * adds an additional Filter read from parameter 'instanceFilter'to the Filter of the passed
167         * GetFeature request. If parameter 'instanceFilter' is userCoupled the filter will be read from
168         * DRM, if it is not the filter defined within the responsible policy document will be used.
169         *
170         * @param casreq
171         * @param postConditions
172         * @param user
173         * @throws InvalidParameterValueException
174         * @throws UnauthorizedException
175         */
176        private void addFilter( GetRecords casreq, Condition postConditions, User user )
177                                throws InvalidParameterValueException, UnauthorizedException {
178            if ( postConditions.getOperationParameter( "instanceFilter" ) != null ) {
179                Map<String, Filter> localFilterMap;
180                if ( postConditions.getOperationParameter( "instanceFilter" ).isUserCoupled() ) {
181                    // read filterMap from constraints defined in deegree DRM
182                    localFilterMap = readFilterFromDRM( casreq, user );
183                } else {
184                    fillFilterMap( postConditions );
185                    // use filterMap read from policy document
186                    localFilterMap = filterMap;
187                }
188                Query query = casreq.getQuery();
189                Filter filter = null;
190                if ( query.getContraint() == null ) {
191                    // if query does not define a filter just use the matching
192                    // one from the post conditions
193                    filter = localFilterMap.get( casreq.getOutputSchema() );
194                } else if ( query.getContraint() 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) query.getContraint();
199                    filter = localFilterMap.get( casreq.getOutputSchema() );
200                    if ( filter == null ) {
201                        filter = qFilter;
202                    } else {
203                        filter = new ComplexFilter( qFilter, (ComplexFilter) filter, OperationDefines.AND );
204                    }
205                } else if ( query.getContraint() 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 = query.getContraint();
210                }
211                // substitue query by a new one using the re-created filter
212                query = new Query( query.getElementSetName(), query.getElementSetNameTypeNamesList(),
213                                   query.getElementSetNameVariables(), query.getElementNamesAsPropertyPaths(),
214                                   filter, query.getSortProperties(), query.getTypeNamesAsList(),
215                                   query.getDeclaredTypeNameVariables() );
216    
217                casreq.setQuery( query );
218            }
219            if ( LOG.getLevel() != ILogger.LOG_DEBUG ) {
220                try {
221                    XMLFactory.export( casreq ).prettyPrint( System.out );
222                } catch ( Exception e ) {
223                }
224            }
225        }
226    
227        private void fillFilterMap( Condition postConditions ) {
228    //        List<Element> complexValues =
229            postConditions.getOperationParameter( "instanceFilter" ).getComplexValues();
230            /*
231             * TODO try { if ( filterMap.size() == 0 ) { for ( int i = 0; i < complexValues.size(); i++ ) {
232             * Query q = Query.create( complexValues.get( 0 ) ); Filter f = q.getFilter(); QualifiedName
233             * qn = q.getTypeNames()[0]; filterMap.put( qn, f ); } } } catch ( XMLParsingException e ) {
234             * LOG.logError( e.getMessage(), e ); throw new InvalidParameterValueException(
235             * this.getClass().getName(), e.getMessage() ); }
236             */
237        }
238    
239        private Map<String, Filter> readFilterFromDRM( GetRecords casreq, User user )
240                                throws UnauthorizedException, InvalidParameterValueException {
241    
242            Map<String, Filter> map = new HashMap<String, Filter>();
243            try {
244                SecurityAccessManager sam = SecurityAccessManager.getInstance();
245                SecurityAccess access = sam.acquireAccess( user );
246                String ops = casreq.getOutputSchema();
247                SecuredObject secObj = access.getSecuredObjectByName( ops, ClientHelper.TYPE_METADATASCHEMA );
248                RightSet rs = user.getRights( access, secObj );
249                Right right = rs.getRight( secObj, RightType.GETRECORDS_RESPONSE );
250                // a constraint - if available - is constructed as a OGC Filter
251                // one of the filter operations may is 'PropertyIsEqualTo' and
252                // defines a ProperyName == 'instanceFilter'. The Literal of this
253                // operation itself is a complete and valid Filter expression.
254                if ( right != null ) {
255                    ComplexFilter filter = (ComplexFilter) right.getConstraints();
256                    if ( filter != null ) {
257                        List<ComplexFilter> foundFilters = new ArrayList<ComplexFilter>();
258                        // extract filter expression to be used as additional
259                        // filter for a GetFeature request
260                        extractInstanceFilter( filter.getOperation(), foundFilters );
261                        if ( foundFilters.size() == 1 ) {
262                            filter = foundFilters.get( 0 );
263                        } else if ( foundFilters.size() > 1 ) {
264                            List<org.deegree.model.filterencoding.Operation> list = new ArrayList<org.deegree.model.filterencoding.Operation>();
265                            for ( ComplexFilter cf : foundFilters ) {
266                                list.add( cf.getOperation() );
267                            }
268                            LogicalOperation lo = new LogicalOperation( OperationDefines.OR, list );
269                            filter = new ComplexFilter( lo );
270                        }
271                        map.put( ops, filter );
272                    }
273                }
274    
275            } catch ( GeneralSecurityException e ) {
276                LOG.logError( e.getMessage(), e );
277                throw new UnauthorizedException( e.getMessage(), e );
278            } catch ( FilterConstructionException e ) {
279                LOG.logError( e.getMessage(), e );
280                throw new InvalidParameterValueException( e.getMessage(), e );
281            } catch ( SAXException e ) {
282                LOG.logError( e.getMessage(), e );
283                throw new InvalidParameterValueException( e.getMessage(), e );
284            } catch ( IOException e ) {
285                LOG.logError( e.getMessage(), e );
286                throw new InvalidParameterValueException( e.getMessage(), e );
287            }
288    
289            return map;
290        }
291    
292        /**
293         * validates the passed CSW GetRecords request against a User- and Rights-Management DB.
294         *
295         * @param casreq
296         * @param user
297         */
298        private void validateAgainstRightsDB( GetRecords casreq, User user )
299                                throws InvalidParameterValueException, UnauthorizedException {
300    
301            if ( user == null ) {
302                throw new UnauthorizedException( Messages.getString( "RequestValidator.NOACCESS" ) );
303            }
304    
305            // create a feature instance from the parameters of the GetRecords request
306            // to enable comparsion with a filter encoding expression stored in the
307            // assigned rights management system
308            List<FeatureProperty> fp = new ArrayList<FeatureProperty>();
309            fp.add( FeatureFactory.createFeatureProperty( new QualifiedName( "version" ), casreq.getVersion() ) );
310            fp.add( FeatureFactory.createFeatureProperty( new QualifiedName( "maxRecords" ), casreq.getMaxRecords() ) );
311            fp.add( FeatureFactory.createFeatureProperty( new QualifiedName( "outputFormat" ), casreq.getOutputFormat() ) );
312            fp.add( FeatureFactory.createFeatureProperty( new QualifiedName( "resultType" ), casreq.getResultTypeAsString() ) );
313            SortProperty[] sp = casreq.getQuery().getSortProperties();
314            if ( sp != null ) {
315                for ( int i = 0; i < sp.length; i++ ) {
316                    fp.add( FeatureFactory.createFeatureProperty( new QualifiedName( "sortBy" ),
317                                                                  sp[i].getSortProperty().getAsString() ) );
318                }
319            }
320            List<QualifiedName> tp = casreq.getQuery().getTypeNamesAsList();
321            for ( int i = 0; i < tp.size(); i++ ) {
322                fp.add( FeatureFactory.createFeatureProperty( new QualifiedName( "typeNames" ),
323                                                              tp.get( i ).getPrefixedName() ) );
324            }
325            fp.add( FeatureFactory.createFeatureProperty( new QualifiedName( "elementSetName" ),
326                                                          casreq.getQuery().getElementSetName() ) );
327    
328            Feature feature = FeatureFactory.createFeature( "id", grFT, fp );
329            handleUserCoupledRules( user, feature, casreq.getOutputSchema(), ClientHelper.TYPE_METADATASCHEMA,
330                                    RightType.GETRECORDS );
331    
332        }
333    
334        /**
335         * valides if the maxRecords parameter in a GetRecords request is valid against the policy
336         * assigned to Validator.
337         *
338         * @param condition
339         * @param maxRecords
340         * @throws InvalidParameterValueException
341         */
342        private void validateMaxRecords( Condition condition, int maxRecords )
343                                throws InvalidParameterValueException {
344            OperationParameter op = condition.getOperationParameter( MAXRECORDS );
345    
346            // is valid because no restrictions are made
347            if ( op.isAny() )
348                return;
349    
350            int maxF = op.getFirstAsInt();
351    
352            if ( op.isUserCoupled() ) {
353                userCoupled = true;
354            } else {
355                if ( maxRecords > maxF || maxRecords < 0 ) {
356                    String s = Messages.format( "GetRecordsRequestValidator.INVALIDMAXRECORDS", MAXRECORDS );
357                    throw new InvalidParameterValueException( s );
358                }
359            }
360    
361        }
362    
363        /**
364         * valides if the elementSetName parameter in a GetRecords request is valid against the policy
365         * assigned to Validator.
366         *
367         * @param condition
368         * @param elementSetName
369         * @throws InvalidParameterValueException
370         */
371        private void validateElementSetName( Condition condition, String elementSetName )
372                                throws InvalidParameterValueException {
373            OperationParameter op = condition.getOperationParameter( ELEMENTSETNAME );
374    
375            // is valid because no restrictions are made
376            if ( op.isAny() )
377                return;
378    
379            List<String> list = op.getValues();
380    
381            if ( op.isUserCoupled() ) {
382                userCoupled = true;
383            } else {
384                if ( !list.contains( elementSetName ) ) {
385                    String s = Messages.format( "GetRecordsRequestValidator.INVALIDELEMENTSETNAME", elementSetName );
386                    throw new InvalidParameterValueException( s );
387                }
388            }
389    
390        }
391    
392        /**
393         * valides if the metadataFormat parameter in a GetRecords request is valid against the policy
394         * assigned to Validator.
395         *
396         * @param condition
397         * @param outputFormat
398         * @throws InvalidParameterValueException
399         */
400        private void validateOutputFormat( Condition condition, String outputFormat )
401                                throws InvalidParameterValueException {
402            OperationParameter op = condition.getOperationParameter( OUTPUTFORMAT );
403    
404            // is valid because no restrictions are made
405            if ( op.isAny() )
406                return;
407    
408            List<String> list = op.getValues();
409    
410            if ( op.isUserCoupled() ) {
411                userCoupled = true;
412            } else {
413                if ( !list.contains( outputFormat ) ) {
414                    String s = Messages.format( "GetRecordsRequestValidator.INVALIDOUTPUTFORMAT", outputFormat );
415                    throw new InvalidParameterValueException( s );
416                }
417            }
418    
419        }
420    
421        /**
422         * valides if the resultType parameter in a GetRecords request is valid against the policy
423         * assigned to Validator.
424         *
425         * @param condition
426         * @param resultType
427         * @throws InvalidParameterValueException
428         */
429        private void validateResultType( Condition condition, String resultType )
430                                throws InvalidParameterValueException {
431            OperationParameter op = condition.getOperationParameter( RESULTTYPE );
432    
433            // is valid because no restrictions are made
434            if ( op.isAny() )
435                return;
436    
437            List<String> list = op.getValues();
438    
439            if ( op.isUserCoupled() ) {
440                userCoupled = true;
441            } else {
442                if ( !list.contains( resultType ) ) {
443                    String s = Messages.format( "GetRecordsRequestValidator.INVALIDRESULTTYPE", resultType );
444                    throw new InvalidParameterValueException( s );
445                }
446            }
447    
448        }
449    
450        /**
451         * valides if the sortBy parameter in a GetRecords request is valid against the policy assigned
452         * to Validator.
453         *
454         * @param condition
455         * @param sortBy
456         * @throws InvalidParameterValueException
457         */
458        private void validateSortBy( Condition condition, SortProperty[] sortBy )
459                                throws InvalidParameterValueException {
460            OperationParameter op = condition.getOperationParameter( SORTBY );
461    
462            // is valid because no restrictions are made or
463            // nothing to validate
464            if ( op.isAny() || sortBy == null )
465                return;
466    
467            List<String> list = op.getValues();
468    
469            if ( op.isUserCoupled() ) {
470                userCoupled = true;
471            } else {
472                for ( int i = 0; i < sortBy.length; i++ ) {
473    
474                    if ( !list.contains( sortBy[i].getSortProperty().getAsString() ) ) {
475                        String s = Messages.format( "GetRecordsRequestValidator.INVALIDSORTBY", sortBy[i] );
476                        throw new InvalidParameterValueException( s );
477                    }
478                }
479            }
480    
481        }
482    
483        /**
484         * valides if the sortBy parameter in a GetRecords request is valid against the policy assigned
485         * to Validator.
486         *
487         * @param condition
488         * @param typeNames
489         * @throws InvalidParameterValueException
490         */
491        private void validateTypeNames( Condition condition, List<QualifiedName> typeNames )
492                                throws InvalidParameterValueException {
493            OperationParameter op = condition.getOperationParameter( TYPENAMES );
494    
495            // is valid because no restrictions are made
496            if ( op.isAny() )
497                return;
498    
499            List<String> list = op.getValues();
500    
501            if ( op.isUserCoupled() ) {
502                userCoupled = true;
503            } else {
504                for ( int i = 0; i < typeNames.size(); i++ ) {
505                    if ( !list.contains( typeNames.get( i ).getPrefixedName() ) ) {
506                        String s = Messages.format( "GetRecordsRequestValidator.INVALIDTYPENAMES", typeNames.get( i ) );
507                        throw new InvalidParameterValueException( s );
508                    }
509                }
510            }
511    
512        }
513    
514        /**
515         * creates a feature type that matches the parameters of a GetRecords request
516         *
517         * @return created <tt>FeatureType</tt>
518         */
519        private static FeatureType createFeatureType() {
520            PropertyType[] ftps = new PropertyType[7];
521            QualifiedName qn = new QualifiedName( "version" );
522            ftps[0] = FeatureFactory.createSimplePropertyType( qn, Types.VARCHAR, false );
523    
524            qn = new QualifiedName( "maxRecords" );
525            ftps[1] = FeatureFactory.createSimplePropertyType( qn, Types.INTEGER, false );
526    
527            qn = new QualifiedName( "outputFormat" );
528            ftps[2] = FeatureFactory.createSimplePropertyType( qn, Types.VARCHAR, false );
529    
530            qn = new QualifiedName( "resultType" );
531            ftps[3] = FeatureFactory.createSimplePropertyType( qn, Types.VARCHAR, false );
532    
533            qn = new QualifiedName( "sortBy" );
534            ftps[4] = FeatureFactory.createSimplePropertyType( qn, Types.VARCHAR, 0, Integer.MAX_VALUE );
535    
536            qn = new QualifiedName( "typeNames" );
537            ftps[5] = FeatureFactory.createSimplePropertyType( qn, Types.VARCHAR, 0, Integer.MAX_VALUE );
538    
539            qn = new QualifiedName( "elementSetName" );
540            ftps[6] = FeatureFactory.createSimplePropertyType( qn, Types.VARCHAR, false );
541    
542            return FeatureFactory.createFeatureType( "GetRecords", false, ftps );
543        }
544    
545    }