    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }