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