[FEATURE][needs-docs] Add OGC filters to WMS

Implement https://github.com/qgis/QGIS-Enhancement-Proposals/issues/104
This commit is contained in:
Patrick Valsecchi 2017-10-26 11:29:54 +02:00
parent d31f60baa5
commit b8f708ff1b
22 changed files with 593 additions and 108 deletions

View File

@ -0,0 +1,60 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/server/qgsfeaturefilter.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsFeatureFilter : QgsFeatureFilterProvider
{
%Docstring
A feature filter provider allowing to set filter expressions on a per-layer basis.
.. versionadded:: 3.0
*
%End
%TypeHeaderCode
#include "qgsfeaturefilter.h"
%End
public:
QgsFeatureFilter();
%Docstring
Constructor
%End
void filterFeatures( const QgsVectorLayer *layer, QgsFeatureRequest &filterFeatures ) const;
%Docstring
Filter the features of the layer
\param layer the layer to control
\param filterFeatures the request to fill
%End
QgsFeatureFilterProvider *clone() const /Factory/;
%Docstring
Return a clone of the object
:return: A clone
:rtype: QgsFeatureFilterProvider
%End
void setFilter( const QgsVectorLayer *layer, const QgsExpression &expression );
%Docstring
Set a filter for the given layer.
\param layer the layer to filter
\param expression the filter expression
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/server/qgsfeaturefilter.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,60 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/server/qgsfeaturefilterprovidergroup.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsFeatureFilterProviderGroup : QgsFeatureFilterProvider
{
%Docstring
A filter filter provider grouping several filter providers.
.. versionadded:: 3.0
*
%End
%TypeHeaderCode
#include "qgsfeaturefilterprovidergroup.h"
%End
public:
QgsFeatureFilterProviderGroup();
%Docstring
Constructor
%End
void filterFeatures( const QgsVectorLayer *layer, QgsFeatureRequest &filterFeatures ) const;
%Docstring
Filter the features of the layer
\param layer the layer to control
\param filterFeatures the request to fill
%End
QgsFeatureFilterProvider *clone() const /Factory/;
%Docstring
Return a clone of the object
:return: A clone
:rtype: QgsFeatureFilterProvider
%End
QgsFeatureFilterProviderGroup &addProvider( const QgsFeatureFilterProvider *provider );
%Docstring
Add another filter provider to the group
\param provider The provider to add
:return: itself
:rtype: QgsFeatureFilterProviderGroup
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/server/qgsfeaturefilterprovidergroup.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -17,6 +17,8 @@
%Include qgsservice.sip
%Include qgsservicemodule.sip
%Include qgsserviceregistry.sip
%Include qgsfeaturefilterprovidergroup.sip
%Include qgsfeaturefilter.sip
%If ( HAVE_SERVER_PYTHON_PLUGINS )
%Include qgsserverfilter.sip
%End

View File

@ -428,7 +428,7 @@ class CORE_EXPORT QgsFeatureRequest
*
* \since QGIS 2.12
*/
QgsFeatureRequest &disableFilter() { mFilter = FilterNone; return *this; }
QgsFeatureRequest &disableFilter() { mFilter = FilterNone; mFilterExpression.reset(); return *this; }
/**
* Adds a new OrderByClause, appending it as the least important one.

View File

@ -44,6 +44,8 @@ SET(QGIS_SERVER_SRCS
qgsservicemodule.cpp
qgsservicenativeloader.cpp
qgsserviceregistry.cpp
qgsfeaturefilterprovidergroup.cpp
qgsfeaturefilter.cpp
)
SET (QGIS_SERVER_HDRS

View File

@ -0,0 +1,42 @@
/***************************************************************************
qgsfeaturefilter.cpp
--------------------
begin : 26-10-2017
copyright : (C) 2017 by Patrick Valsecchi
email : patrick dot valsecchi at camptocamp dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsfeaturefilter.h"
#include "qgsfeaturerequest.h"
#include "qgsvectorlayer.h"
#include "qgsexpression.h"
void QgsFeatureFilter::filterFeatures( const QgsVectorLayer *layer, QgsFeatureRequest &filterFeatures ) const
{
const QString expr = mFilters[layer->id()];
if ( !expr.isEmpty() )
{
filterFeatures.setFilterExpression( expr );
}
}
QgsFeatureFilterProvider *QgsFeatureFilter::clone() const
{
auto result = new QgsFeatureFilter();
result->mFilters = mFilters;
return result;
}
void QgsFeatureFilter::setFilter( const QgsVectorLayer *layer, const QgsExpression &filter )
{
mFilters[layer->id()] = filter.dump();
}

View File

@ -0,0 +1,64 @@
/***************************************************************************
qgsfeaturefilter.h
------------------
begin : 26-10-2017
copyright : (C) 2017 by Patrick Valsecchi
email : patrick dot valsecchi at camptocamp dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSFEATUREFILTER_H
#define QGSFEATUREFILTER_H
#include "qgsfeaturefilterprovider.h"
#include "qgis_server.h"
#include <QMap>
class QgsExpression;
/**
* \ingroup server
* \class QgsFeatureFilter
* \brief A feature filter provider allowing to set filter expressions on a per-layer basis.
* \since QGIS 3.0
**/
class SERVER_EXPORT QgsFeatureFilter : public QgsFeatureFilterProvider
{
public:
//! Constructor
QgsFeatureFilter() {}
/**
* Filter the features of the layer
* \param layer the layer to control
* \param filterFeatures the request to fill
*/
void filterFeatures( const QgsVectorLayer *layer, QgsFeatureRequest &filterFeatures ) const;
/**
* Return a clone of the object
* \returns A clone
*/
QgsFeatureFilterProvider *clone() const SIP_FACTORY;
/**
* Set a filter for the given layer.
* \param layer the layer to filter
* \param expression the filter expression
*/
void setFilter( const QgsVectorLayer *layer, const QgsExpression &expression );
private:
QMap<QString, QString> mFilters;
};
#endif

View File

@ -0,0 +1,49 @@
/***************************************************************************
qgsfeaturefilterprovidergroup.cpp
--------------------------------
begin : 26-10-2017
copyright : (C) 2017 by Patrick Valsecchi
email : patrick dot valsecchi at camptocamp dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsfeaturefilterprovidergroup.h"
#include "qgsfeaturerequest.h"
void QgsFeatureFilterProviderGroup::filterFeatures( const QgsVectorLayer *layer, QgsFeatureRequest &filterFeatures ) const
{
filterFeatures.disableFilter();
for ( const QgsFeatureFilterProvider *provider : mProviders )
{
QgsFeatureRequest temp;
provider->filterFeatures( layer, temp );
if ( temp.filterExpression() )
{
filterFeatures.combineFilterExpression( temp.filterExpression()->dump() );
}
}
}
QgsFeatureFilterProvider *QgsFeatureFilterProviderGroup::clone() const
{
auto result = new QgsFeatureFilterProviderGroup();
result->mProviders = mProviders;
return result;
}
QgsFeatureFilterProviderGroup &QgsFeatureFilterProviderGroup::addProvider( const QgsFeatureFilterProvider *provider )
{
if ( provider )
{
mProviders.append( provider );
}
return *this;
}

View File

@ -0,0 +1,62 @@
/***************************************************************************
qgsfeaturefilterprovidergroup.h
-------------------------------
begin : 26-10-2017
copyright : (C) 2017 by Patrick Valsecchi
email : patrick dot valsecchi at camptocamp dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSFEATUREFILTERPROVIDERGROUP_H
#define QGSFEATUREFILTERPROVIDERGROUP_H
#include "qgsfeaturefilterprovider.h"
#include "qgis_server.h"
#include <QList>
/**
* \ingroup server
* \class QgsFeatureFilterProviderGroup
* \brief A filter filter provider grouping several filter providers.
* \since QGIS 3.0
**/
class SERVER_EXPORT QgsFeatureFilterProviderGroup : public QgsFeatureFilterProvider
{
public:
//! Constructor
QgsFeatureFilterProviderGroup() {}
/**
* Filter the features of the layer
* \param layer the layer to control
* \param filterFeatures the request to fill
*/
void filterFeatures( const QgsVectorLayer *layer, QgsFeatureRequest &filterFeatures ) const;
/**
* Return a clone of the object
* \returns A clone
*/
QgsFeatureFilterProvider *clone() const SIP_FACTORY;
/**
* Add another filter provider to the group
* \param provider The provider to add
* \return itself
*/
QgsFeatureFilterProviderGroup &addProvider( const QgsFeatureFilterProvider *provider );
private:
QList<const QgsFeatureFilterProvider *> mProviders;
};
#endif

View File

@ -27,14 +27,14 @@ namespace QgsWms
QgsMapRendererJobProxy::QgsMapRendererJobProxy(
bool parallelRendering
, int maxThreads
, QgsAccessControl *accessControl
, QgsFeatureFilterProvider *featureFilterProvider
)
:
mParallelRendering( parallelRendering )
, mAccessControl( accessControl )
, mFeatureFilterProvider( featureFilterProvider )
{
#ifndef HAVE_SERVER_PYTHON_PLUGINS
Q_UNUSED( mAccessControl );
Q_UNUSED( mFeatureFilterProvider );
#endif
if ( mParallelRendering )
{
@ -53,7 +53,7 @@ namespace QgsWms
{
QgsMapRendererParallelJob renderJob( mapSettings );
#ifdef HAVE_SERVER_PYTHON_PLUGINS
renderJob.setFeatureFilterProvider( mAccessControl );
renderJob.setFeatureFilterProvider( mFeatureFilterProvider );
#endif
renderJob.start();
renderJob.waitForFinished();
@ -65,7 +65,7 @@ namespace QgsWms
mPainter.reset( new QPainter( image ) );
QgsMapRendererCustomPainterJob renderJob( mapSettings, mPainter.get() );
#ifdef HAVE_SERVER_PYTHON_PLUGINS
renderJob.setFeatureFilterProvider( mAccessControl );
renderJob.setFeatureFilterProvider( mFeatureFilterProvider );
#endif
renderJob.renderSynchronously();
}

View File

@ -19,7 +19,8 @@
#define QGSMAPRENDERERJOBPROXY_H
#include "qgsmapsettings.h"
#include "qgsaccesscontrol.h"
class QgsFeatureFilterProvider;
namespace QgsWms
{
@ -36,12 +37,12 @@ namespace QgsWms
/**
* Constructor.
* \param accessControl Does not take ownership of QgsAccessControl
* \param featureFilterProvider Does not take ownership of QgsFeatureFilterProvider
*/
QgsMapRendererJobProxy(
bool parallelRendering
, int maxThreads
, QgsAccessControl *accessControl
, QgsFeatureFilterProvider *featureFilterProvider
);
/**
@ -59,7 +60,7 @@ namespace QgsWms
private:
bool mParallelRendering;
QgsAccessControl *mAccessControl = nullptr;
QgsFeatureFilterProvider *mFeatureFilterProvider = nullptr;
std::unique_ptr<QPainter> mPainter;
};

View File

@ -488,7 +488,7 @@ namespace QgsWms
mRequestParameters = parameters;
const QMetaEnum metaEnum( QMetaEnum::fromType<ParameterName>() );
static QRegExp composerParamRegExp( "^MAP\\d+:" );
static QRegExp composerParamRegExp( QStringLiteral( "^MAP\\d+:" ) );
foreach ( QString key, parameters.keys() )
{
@ -529,7 +529,7 @@ namespace QgsWms
}
else //maybe an external wms parameter?
{
int separator = key.indexOf( ":" );
int separator = key.indexOf( QStringLiteral( ":" ) );
if ( separator >= 1 )
{
QString id = key.left( separator );
@ -545,7 +545,7 @@ namespace QgsWms
{
const QMetaEnum metaEnum( QMetaEnum::fromType<ParameterName>() );
log( "WMS Request parameters:" );
log( QStringLiteral( "WMS Request parameters:" ) );
for ( auto parameter : mParameters.toStdMap() )
{
const QString value = parameter.second.mValue.toString();
@ -553,13 +553,13 @@ namespace QgsWms
if ( ! value.isEmpty() )
{
const QString name = metaEnum.valueToKey( parameter.first );
log( " - " + name + " : " + value );
log( QStringLiteral( " - " ) + name + QStringLiteral( " : " ) + value );
}
}
for ( auto map : mComposerParameters.toStdMap() )
{
const int mapId = map.first;
log( " - MAP" + QString::number( mapId ) );
log( QStringLiteral( " - MAP" ) + QString::number( mapId ) );
for ( auto param : mComposerParameters[map.first].toStdMap() )
{
const QString value = param.second.mValue.toString();
@ -567,13 +567,13 @@ namespace QgsWms
if ( ! value.isEmpty() )
{
const QString name = metaEnum.valueToKey( param.first );
log( " - MAP" + QString::number( mapId ) + ":" + name + " : " + value );
log( QStringLiteral( " - MAP" ) + QString::number( mapId ) + QStringLiteral( ":" ) + name + QStringLiteral( " : " ) + value );
}
}
}
if ( !version().isEmpty() )
log( " - VERSION : " + version() );
log( QStringLiteral( " - VERSION : " ) + version() );
}
void QgsWmsParameters::save( const Parameter &parameter )
@ -694,8 +694,8 @@ namespace QgsWms
{
// VERSION parameter is not managed with other parameters because
// there's a conflict with qgis VERSION defined in qgsconfig.h
if ( mRequestParameters.contains( "VERSION" ) )
return mRequestParameters["VERSION"];
if ( mRequestParameters.contains( QStringLiteral( "VERSION" ) ) )
return mRequestParameters[QStringLiteral( "VERSION" )];
else
return QString();
}
@ -736,7 +736,7 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p ).toString();
QString msg = n + " ('" + valStr + "') cannot be converted into a double";
QString msg = n + QStringLiteral( " ('" ) + valStr + QStringLiteral( "') cannot be converted into a double" );
raiseError( msg );
}
@ -751,7 +751,8 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p, mapId ).toString();
QString msg = "MAP" + QString::number( mapId ) + ":" + n + " ('" + valStr + "') cannot be converted into a double";
QString msg = QStringLiteral( "MAP" ) + QString::number( mapId ) + QStringLiteral( ":" ) + n +
QStringLiteral( " ('" ) + valStr + QStringLiteral( "') cannot be converted into a double" );
raiseError( msg );
}
@ -802,7 +803,7 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p ).toString();
QString msg = n + " ('" + valStr + "') cannot be converted into int";
QString msg = n + QStringLiteral( " ('" ) + valStr + QStringLiteral( "') cannot be converted into int" );
raiseError( msg );
}
@ -817,7 +818,8 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p, mapId ).toString();
QString msg = "MAP" + QString::number( mapId ) + ":" + n + " ('" + valStr + "') cannot be converted into int";
QString msg = QStringLiteral( "MAP" ) + QString::number( mapId ) + QStringLiteral( ":" ) + n +
QStringLiteral( " ('" ) + valStr + QStringLiteral( "') cannot be converted into int" );
raiseError( msg );
}
@ -833,8 +835,8 @@ namespace QgsWms
if ( !cStr.isEmpty() )
{
// support hexadecimal notation to define colors
if ( cStr.startsWith( "0x", Qt::CaseInsensitive ) )
cStr.replace( 0, 2, "#" );
if ( cStr.startsWith( QStringLiteral( "0x" ), Qt::CaseInsensitive ) )
cStr.replace( 0, 2, QStringLiteral( "#" ) );
c = QColor( cStr );
@ -852,7 +854,7 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p ).toString();
QString msg = n + " ('" + valStr + "') cannot be converted into a color";
QString msg = n + QStringLiteral( " ('" ) + valStr + QStringLiteral( "') cannot be converted into a color" );
raiseError( msg );
}
@ -867,7 +869,8 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p, mapId ).toString();
QString msg = "MAP" + QString::number( mapId ) + ":" + n + " ('" + valStr + "') cannot be converted into a color";
QString msg = QStringLiteral( "MAP" ) + QString::number( mapId ) + QStringLiteral( ":" ) + n +
QStringLiteral( " ('" ) + valStr + QStringLiteral( "') cannot be converted into a color" );
raiseError( msg );
}
@ -926,7 +929,7 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p ).toString();
QString msg = n + " ('" + valStr + "') cannot be converted into a rectangle";
QString msg = n + QStringLiteral( " ('" ) + valStr + QStringLiteral( "') cannot be converted into a rectangle" );
raiseError( msg );
}
@ -941,7 +944,8 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p, mapId ).toString();
QString msg = "MAP" + QString::number( mapId ) + ":" + n + " ('" + valStr + "') cannot be converted into a rectangle";
QString msg = QStringLiteral( "MAP" ) + QString::number( mapId ) + QStringLiteral( ":" ) + n +
QStringLiteral( " ('" ) + valStr + QStringLiteral( "') cannot be converted into a rectangle" );
raiseError( msg );
}
@ -990,7 +994,7 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p ).toString();
QString msg = n + " ('" + valStr + "') cannot be converted into a list of int";
QString msg = n + QStringLiteral( " ('" ) + valStr + QStringLiteral( "') cannot be converted into a list of int" );
raiseError( msg );
}
@ -1005,7 +1009,8 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p, mapId ).toString();
QString msg = "MAP" + QString::number( mapId ) + ":" + n + " ('" + valStr + "') cannot be converted into a list of int";
QString msg = QStringLiteral( "MAP" ) + QString::number( mapId ) + QStringLiteral( ":" ) + n +
QStringLiteral( " ('" ) + valStr + QStringLiteral( "') cannot be converted into a list of int" );
raiseError( msg );
}
@ -1044,7 +1049,8 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p ).toString();
QString msg = n + " ('" + valStr + "') cannot be converted into a list of float";
QString msg = n + QStringLiteral( " ('" ) + valStr +
QStringLiteral( "') cannot be converted into a list of float" );
raiseError( msg );
}
@ -1059,7 +1065,8 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p ).toString();
QString msg = "MAP" + QString::number( mapId ) + ":" + n + " ('" + valStr + "') cannot be converted into a list of float";
QString msg = QStringLiteral( "MAP" ) + QString::number( mapId ) + QStringLiteral( ":" ) + n +
QStringLiteral( " ('" ) + valStr + QStringLiteral( "') cannot be converted into a list of float" );
raiseError( msg );
}
@ -1097,7 +1104,8 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p ).toString();
QString msg = n + " ('" + valStr + "') cannot be converted into a list of colors";
QString msg = n + QStringLiteral( " ('" ) + valStr +
QStringLiteral( "') cannot be converted into a list of colors" );
raiseError( msg );
}
@ -1112,7 +1120,8 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p ).toString();
QString msg = "MAP" + QString::number( mapId ) + ":" + n + " ('" + valStr + "') cannot be converted into a list of colors";
QString msg = QStringLiteral( "MAP" ) + QString::number( mapId ) + QStringLiteral( ":" ) + n +
QStringLiteral( " ('" ) + valStr + QStringLiteral( "') cannot be converted into a list of colors" );
raiseError( msg );
}
@ -1150,7 +1159,8 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p ).toString();
QString msg = n + " ('" + valStr + "') cannot be converted into a list of geometries";
QString msg = n + QStringLiteral( " ('" ) + valStr +
QStringLiteral( "') cannot be converted into a list of geometries" );
raiseError( msg );
}
@ -1165,7 +1175,8 @@ namespace QgsWms
{
QString n = name( p );
QString valStr = value( p, mapId ).toString();
QString msg = "MAP" + QString::number( mapId ) + ":" + n + " ('" + valStr + "') cannot be converted into a list of geometries";
QString msg = QStringLiteral( "MAP" ) + QString::number( mapId ) + QStringLiteral( ":" ) + n +
QStringLiteral( " ('" ) + valStr + QStringLiteral( "') cannot be converted into a list of geometries" );
raiseError( msg );
}
@ -1614,7 +1625,30 @@ namespace QgsWms
QStringList QgsWmsParameters::filters() const
{
return toStringList( ParameterName::FILTER, ';' );
const QString filter = value( ParameterName::FILTER ).toString();
if ( filter.startsWith( QStringLiteral( "(<" ) ) && filter.endsWith( QStringLiteral( "Filter>)" ) ) )
{
// OGC filter on multiple layers
// remove the "(<" at the beginning and the "Filter>)" at the end
const QString toSplit = filter.mid( 2, filter.length() - 10 );
QStringList result;
for ( const QString &cur : toSplit.split( QStringLiteral( "Filter>)(<" ), QString::SkipEmptyParts ) )
{
result.append( QStringLiteral( "<" ) + cur + QStringLiteral( "Filter>" ) );
}
return result;
}
else if ( filter.startsWith( QStringLiteral( "<" ) ) && filter.endsWith( QStringLiteral( "Filter>" ) ) )
{
// single OGC filter
return QStringList( filter );
}
else
{
// QGIS specific filter
return filter.split( ';', QString::SkipEmptyParts );
}
}
QString QgsWmsParameters::filterGeom() const
@ -1656,36 +1690,48 @@ namespace QgsWms
return style << styles;
}
QList<QgsWmsParametersLayer> QgsWmsParameters::layersParameters() const
QMultiMap<QString, QString> QgsWmsParameters::getLayerFilters( const QStringList &layers ) const
{
QList<QgsWmsParametersLayer> parameters;
QStringList layers = allLayersNickname();
QStringList styles = allStyles();
QStringList filter = filters();
QStringList selection = selections();
QList<int> opacities = opacitiesAsInt();
// filter format: "LayerName:filterString;LayerName2:filterString2;..."
// several filters can be defined for one layer
const QStringList rawFilters = filters();
QMultiMap<QString, QString> layerFilters;
Q_FOREACH ( QString f, filter )
for ( int i = 0; i < rawFilters.size(); i++ )
{
const QStringList splits = f.split( ':' );
if ( splits.size() == 2 )
const QString f = rawFilters[i];
if ( f.startsWith( QStringLiteral( "<" ) ) && f.endsWith( QStringLiteral( "Filter>" ) ) && i < layers.size() )
{
layerFilters.insert( splits[0], splits[1] );
layerFilters.insert( layers[i], f );
}
else
{
QString filterStr = value( ParameterName::FILTER ).toString();
raiseError( "FILTER ('" + filterStr + "') is not properly formatted" );
const QStringList splits = f.split( ':' );
if ( splits.size() == 2 )
{
layerFilters.insert( splits[0], splits[1] );
}
else
{
QString filterStr = value( ParameterName::FILTER ).toString();
raiseError( QStringLiteral( "FILTER ('" ) + filterStr + QStringLiteral( "') is not properly formatted" ) );
}
}
}
return layerFilters;
}
QList<QgsWmsParametersLayer> QgsWmsParameters::layersParameters() const
{
const QStringList layers = allLayersNickname();
const QStringList styles = allStyles();
const QStringList selection = selections();
const QList<int> opacities = opacitiesAsInt();
const QMultiMap<QString, QString> layerFilters = getLayerFilters( layers );
// selection format: "LayerName:id0,id1;LayerName2:id0,id1;..."
// several filters can be defined for one layer
QMultiMap<QString, QString> layerSelections;
Q_FOREACH ( QString s, selection )
for ( const QString &s : selection )
{
const QStringList splits = s.split( ':' );
if ( splits.size() == 2 )
@ -1695,10 +1741,11 @@ namespace QgsWms
else
{
QString selStr = value( ParameterName::SELECTION ).toString();
raiseError( "SELECTION ('" + selStr + "') is not properly formatted" );
raiseError( QStringLiteral( "SELECTION ('" ) + selStr + QStringLiteral( "') is not properly formatted" ) );
}
}
QList<QgsWmsParametersLayer> parameters;
for ( int i = 0; i < layers.size(); i++ )
{
QString layer = layers[i];
@ -1756,7 +1803,7 @@ namespace QgsWms
for ( int i = 0; i < nLayers; i++ )
{
QgsWmsParametersHighlightLayer param;
param.mName = "highlight_" + QString::number( i );
param.mName = QStringLiteral( "highlight_" ) + QString::number( i );
param.mGeom = geoms[i];
param.mSld = slds[i];
@ -1812,7 +1859,7 @@ namespace QgsWms
if ( extentStr.isEmpty() )
return param;
QString pMapId = "MAP" + QString::number( mapId );
QString pMapId = QStringLiteral( "MAP" ) + QString::number( mapId );
QgsRectangle extent = toRectangle( ParameterName::EXTENT, mapId );
if ( extent.isEmpty() )
@ -1873,7 +1920,7 @@ namespace QgsWms
for ( int i = 0; i < nHLayers; i++ )
{
QgsWmsParametersHighlightLayer hParam;
hParam.mName = pMapId + "_highlight_" + QString::number( i );
hParam.mName = pMapId + QStringLiteral( "_highlight_" ) + QString::number( i );
hParam.mGeom = geoms[i];
hParam.mSld = slds[i];
@ -1940,7 +1987,7 @@ namespace QgsWms
void QgsWmsParameters::log( const QString &msg ) const
{
QgsMessageLog::logMessage( msg, "Server", QgsMessageLog::INFO );
QgsMessageLog::logMessage( msg, QStringLiteral( "Server" ), QgsMessageLog::INFO );
}
void QgsWmsParameters::raiseError( ParameterName paramName ) const
@ -1948,7 +1995,7 @@ namespace QgsWms
const QString value = mParameters[paramName].mValue.toString();
const QString param = name( paramName );
const QString type = QVariant::typeToName( mParameters[paramName].mType );
raiseError( param + " ('" + value + "') cannot be converted into " + type );
raiseError( param + QStringLiteral( " ('" ) + value + QStringLiteral( "') cannot be converted into " ) + type );
}
void QgsWmsParameters::raiseError( ParameterName paramName, int mapId ) const
@ -1956,7 +2003,9 @@ namespace QgsWms
const QString value = mComposerParameters[mapId][paramName].mValue.toString();
const QString param = name( paramName );
const QString type = QVariant::typeToName( mComposerParameters[mapId][paramName].mType );
raiseError( "MAP" + QString::number( mapId ) + ":" + param + " ('" + value + "') cannot be converted into " + type );
raiseError( QStringLiteral( "MAP" ) + QString::number( mapId ) + QStringLiteral( ":" ) +
param + QStringLiteral( " ('" ) + value +
QStringLiteral( "') cannot be converted into " ) + type );
}
void QgsWmsParameters::raiseError( const QString &msg ) const

View File

@ -972,6 +972,7 @@ namespace QgsWms
QList<QgsGeometry> toGeomList( const QStringList &l, bool *error = Q_NULLPTR ) const;
QList<QgsGeometry> toGeomList( const QStringList &l, ParameterName name ) const;
QList<QgsGeometry> toGeomList( const QStringList &l, ParameterName name, int mapId ) const;
QMultiMap<QString, QString> getLayerFilters( const QStringList &layers ) const;
QgsServerRequest::Parameters mRequestParameters;
QMap<ParameterName, Parameter> mParameters;

View File

@ -89,6 +89,8 @@
#include "qgscomposerpicture.h"
#include "qgscomposerscalebar.h"
#include "qgscomposershape.h"
#include "qgsfeaturefilterprovidergroup.h"
#include "qgsogcutils.h"
#include <QBuffer>
#include <QPrinter>
#include <QSvgGenerator>
@ -919,21 +921,10 @@ namespace QgsWms
}
// The I/J parameters are Mandatory if they are not replaced by X/Y or FILTER or FILTER_GEOM
bool ijDefined = false;
if ( !mWmsParameters.i().isEmpty() && !mWmsParameters.j().isEmpty() )
ijDefined = true;
bool xyDefined = false;
if ( !mWmsParameters.x().isEmpty() && !mWmsParameters.y().isEmpty() )
xyDefined = true;
bool filtersDefined = false;
if ( !mWmsParameters.filters().isEmpty() )
filtersDefined = true;
bool filterGeomDefined = false;
if ( !mWmsParameters.filterGeom().isEmpty() )
filterGeomDefined = true;
const bool ijDefined = !mWmsParameters.i().isEmpty() && !mWmsParameters.j().isEmpty();
const bool xyDefined = !mWmsParameters.x().isEmpty() && !mWmsParameters.y().isEmpty();
const bool filtersDefined = !mWmsParameters.filters().isEmpty();
const bool filterGeomDefined = !mWmsParameters.filterGeom().isEmpty();
if ( !ijDefined && !xyDefined && !filtersDefined && !filterGeomDefined )
{
@ -2694,10 +2685,13 @@ namespace QgsWms
}
else
{
QgsFeatureFilterProviderGroup filters;
filters.addProvider( &mFeatureFilter );
#ifdef HAVE_SERVER_PYTHON_PLUGINS
mAccessControl->resolveFilterFeatures( mapSettings.layers() );
filters.addProvider( mAccessControl );
#endif
QgsMapRendererJobProxy renderJob( mSettings.parallelRendering(), mSettings.maxThreads(), mAccessControl );
QgsMapRendererJobProxy renderJob( mSettings.parallelRendering(), mSettings.maxThreads(), &filters );
renderJob.render( mapSettings, &image );
painter = renderJob.takePainter();
}
@ -2723,34 +2717,53 @@ namespace QgsWms
}
}
void QgsRenderer::setLayerFilter( QgsMapLayer *layer, const QStringList &filters ) const
void QgsRenderer::setLayerFilter( QgsMapLayer *layer, const QStringList &filters )
{
if ( layer->type() == QgsMapLayer::VectorLayer )
{
QgsVectorLayer *filteredLayer = qobject_cast<QgsVectorLayer *>( layer );
Q_FOREACH ( QString filter, filters )
{
if ( !testFilterStringSafety( filter ) )
if ( filter.startsWith( QStringLiteral( "<" ) ) && filter.endsWith( QStringLiteral( "Filter>" ) ) )
{
throw QgsBadRequestException( QStringLiteral( "Filter string rejected" ),
QStringLiteral( "The filter string %1"
" has been rejected because of security reasons."
" Note: Text strings have to be enclosed in single or double quotes."
" A space between each word / special character is mandatory."
" Allowed Keywords and special characters are "
" AND,OR,IN,<,>=,>,>=,!=,',',(,),DMETAPHONE,SOUNDEX."
" Not allowed are semicolons in the filter expression." ).arg( filter ) );
// OGC filter
QDomDocument filterXml;
QString errorMsg;
if ( !filterXml.setContent( filter, true, &errorMsg ) )
{
throw QgsBadRequestException( QStringLiteral( "Filter string rejected" ),
QStringLiteral( "error message: %1. The XML string was: %2" ).arg( errorMsg, filter ) );
}
QDomElement filterElem = filterXml.firstChildElement();
QScopedPointer<QgsExpression> expression( QgsOgcUtils::expressionFromOgcFilter( filterElem ) );
mFeatureFilter.setFilter( filteredLayer, *expression );
}
else
{
// QGIS (SQL) filter
if ( !testFilterStringSafety( filter ) )
{
throw QgsBadRequestException( QStringLiteral( "Filter string rejected" ),
QStringLiteral( "The filter string %1"
" has been rejected because of security reasons."
" Note: Text strings have to be enclosed in single or double quotes."
" A space between each word / special character is mandatory."
" Allowed Keywords and special characters are "
" AND,OR,IN,<,>=,>,>=,!=,',',(,),DMETAPHONE,SOUNDEX."
" Not allowed are semicolons in the filter expression." ).arg(
filter ) );
}
QString newSubsetString = filter;
if ( !filteredLayer->subsetString().isEmpty() )
{
newSubsetString.prepend( ") AND (" );
newSubsetString.append( ")" );
newSubsetString.prepend( filteredLayer->subsetString() );
newSubsetString.prepend( "(" );
QString newSubsetString = filter;
if ( !filteredLayer->subsetString().isEmpty() )
{
newSubsetString.prepend( ") AND (" );
newSubsetString.append( ")" );
newSubsetString.prepend( filteredLayer->subsetString() );
newSubsetString.prepend( "(" );
}
filteredLayer->setSubsetString( newSubsetString );
}
filteredLayer->setSubsetString( newSubsetString );
}
}
}

View File

@ -22,6 +22,7 @@
#include "qgsserversettings.h"
#include "qgswmsparameters.h"
#include "qgsfeaturefilter.h"
#include <QDomDocument>
#include <QMap>
#include <QPair>
@ -165,7 +166,7 @@ namespace QgsWms
void setLayerOpacity( QgsMapLayer *layer, int opacity ) const;
// Set layer filter
void setLayerFilter( QgsMapLayer *layer, const QStringList &filter ) const;
void setLayerFilter( QgsMapLayer *layer, const QStringList &filter );
// Set layer python filter
void setLayerAccessControlFilter( QgsMapLayer *layer ) const;
@ -287,6 +288,7 @@ namespace QgsWms
//! The access control helper
QgsAccessControl *mAccessControl = nullptr;
QgsFeatureFilter mFeatureFilter;
const QgsServerSettings &mSettings;
const QgsProject *mProject = nullptr;

View File

@ -980,7 +980,7 @@ class TestQgsServerAccessControl(unittest.TestCase):
test we reuse the projectsubsetstring reference images as we are using filter requests to set the same
filter " pkuid in (7,8) " as the project subsetstring uses for its test.
"""
query_string = "&".join(["%s=%s" % i for i in list({
query_string = "&".join(["%s=%s" % i for i in {
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
@ -993,12 +993,12 @@ class TestQgsServerAccessControl(unittest.TestCase):
"HEIGHT": "500",
"WIDTH": "500",
"SRS": "EPSG:3857"
}.items())])
}.items()])
response, headers = self._get_fullaccess(query_string)
self._img_diff_error(response, headers, "WMS_GetMap_projectsubstring")
query_string = "&".join(["%s=%s" % i for i in list({
query_string = "&".join(["%s=%s" % i for i in {
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
@ -1011,11 +1011,32 @@ class TestQgsServerAccessControl(unittest.TestCase):
"HEIGHT": "500",
"WIDTH": "500",
"SRS": "EPSG:3857"
}.items())])
}.items()])
response, headers = self._get_restricted(query_string)
self._img_diff_error(response, headers, "Restricted_WMS_GetMap_projectsubstring")
filter = "<Filter><Or><PropertyIsEqualTo><PropertyName>pkuid</PropertyName><Literal>7</Literal>" \
"</PropertyIsEqualTo><PropertyIsEqualTo><PropertyName>pkuid</PropertyName><Literal>8</Literal>" \
"</PropertyIsEqualTo></Or></Filter>"
query_string = "&".join(["%s=%s" % i for i in {
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Hello_Filter_SubsetString",
"FILTER": filter,
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
"HEIGHT": "500",
"WIDTH": "500",
"SRS": "EPSG:3857"
}.items()])
response, headers = self._get_restricted(query_string)
self._img_diff_error(response, headers, "Restricted_WMS_GetMap_projectsubstring_OGC")
def test_wms_getmap_projectsubsetstring(self):
""" test that project set layer subsetStrings are honored"""
query_string = "&".join(["%s=%s" % i for i in list({

View File

@ -283,6 +283,37 @@ class TestQgsServerSecurity(QgsServerTestBase):
self.handle_request_wfs_getfeature_filter(filter_xml)
self.assertTrue(self.is_point_table_still_exist())
def test_wms_getmap_filter_stacked(self):
"""
The aim is to execute some staked queries within the 'Literal'
and 'PropertyName' field. Here the 'drop' function is used but it
could be done with create, insert, ...
But due to the implementation, these filters quoted before being sent to the DB.
It's typically the kind of SQL injection which has been fixed in
mapserver several years ago:
https://trac.osgeo.org/mapserver/ticket/3874
"""
# ogc:Literal / ogc:PropertyIsEqualTo
literal = "4')); drop table point --"
filter_xml = "<ogc:Filter%20xmlns:ogc=\"http://www.opengis.net/ogc\"><ogc:PropertyIsEqualTo><ogc:PropertyName>pkuid</ogc:PropertyName><ogc:Literal>{0}</ogc:Literal></ogc:PropertyIsEqualTo></ogc:Filter>".format(literal)
self.handle_request_wms_getmap(filter=filter_xml)
self.assertTrue(self.is_point_table_still_exist())
# ogc:Literal / ogc:PropertyIsLike
literal = "4')); drop table point --"
filter_xml = "<ogc:Filter%20xmlns:ogc=\"http://www.opengis.net/ogc\"><ogc:PropertyIsLike><ogc:PropertyName>pkuid</ogc:PropertyName><ogc:Literal>{0}</ogc:Literal></ogc:PropertyIsLike></ogc:Filter>".format(literal)
self.handle_request_wms_getmap(filter=filter_xml)
self.assertTrue(self.is_point_table_still_exist())
# ogc:PropertyName / ogc:PropertyIsLike
propname = "name = 'a')); drop table point --"
filter_xml = "<ogc:Filter%20xmlns:ogc=\"http://www.opengis.net/ogc\"><ogc:PropertyIsLike><ogc:PropertyName>{0}</ogc:PropertyName><ogc:Literal>4</ogc:Literal></ogc:PropertyIsLike></ogc:Filter>".format(propname)
self.handle_request_wms_getmap(filter=filter_xml)
self.assertTrue(self.is_point_table_still_exist())
def test_wms_getmap_sld_stacked(self):
"""
The aim is to execute some staked queries within the 'Literal'
@ -297,7 +328,7 @@ class TestQgsServerSecurity(QgsServerTestBase):
literal = "4')); drop table point --"
sld = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><StyledLayerDescriptor xmlns=\"http://www.opengis.net/sld\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:ogc=\"http://www.opengis.net/ogc\" xsi:schemaLocation=\"http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd\" version=\"1.1.0\" xmlns:se=\"http://www.opengis.net/se\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"> <NamedLayer> <se:Name>point</se:Name> <UserStyle> <se:Name>point</se:Name> <se:FeatureTypeStyle> <se:Rule> <se:Name>Single symbol</se:Name> <ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\"> <ogc:PropertyIsEqualTo> <ogc:PropertyName>pkuid</ogc:PropertyName> <ogc:Literal>{0}</ogc:Literal> </ogc:PropertyIsEqualTo> </ogc:Filter> <se:PointSymbolizer> <se:Graphic> <se:Mark> <se:WellKnownName>circle</se:WellKnownName> <se:Fill><se:SvgParameter name=\"fill\">5e86a1</se:SvgParameter></se:Fill><se:Stroke><se:SvgParameter name=\"stroke\">000000</se:SvgParameter></se:Stroke></se:Mark><se:Size>7</se:Size></se:Graphic></se:PointSymbolizer></se:Rule></se:FeatureTypeStyle></UserStyle></NamedLayer></StyledLayerDescriptor>".format(literal)
self.handle_request_wms_getmap(sld)
self.handle_request_wms_getmap(sld=sld)
self.assertTrue(self.is_point_table_still_exist())
def check_service_exception_report(self, d):
@ -311,7 +342,7 @@ class TestQgsServerSecurity(QgsServerTestBase):
return False
def handle_request_wfs_getfeature_filter(self, filter_xml):
qs = "?" + "&".join(["%s=%s" % i for i in list({
qs = "?" + "&".join(["%s=%s" % i for i in {
"MAP": urllib.parse.quote(self.project),
"SERVICE": "WFS",
"VERSION": "1.1.1",
@ -319,12 +350,12 @@ class TestQgsServerSecurity(QgsServerTestBase):
"TYPENAME": "point",
"STYLES": "",
"CRS": "EPSG:32613",
"FILTER": filter_xml}.items())])
"FILTER": filter_xml}.items()])
return self._execute_request(qs)
def handle_request_wms_getfeatureinfo(self, filter_sql):
qs = "?" + "&".join(["%s=%s" % i for i in list({
qs = "?" + "&".join(["%s=%s" % i for i in {
"MAP": urllib.parse.quote(self.project),
"SERVICE": "WMS",
"VERSION": "1.1.1",
@ -337,12 +368,12 @@ class TestQgsServerSecurity(QgsServerTestBase):
"WIDTH": "500",
"BBOX": "606171,4822867,612834,4827375",
"CRS": "EPSG:32613",
"FILTER": filter_sql}.items())])
"FILTER": filter_sql}.items()])
return self._result(self._execute_request(qs))
def handle_request_wms_getmap(self, sld):
qs = "?" + "&".join(["%s=%s" % i for i in list({
def handle_request_wms_getmap(self, sld=None, filter=None):
params = {
"MAP": urllib.parse.quote(self.project),
"SERVICE": "WMS",
"VERSION": "1.0.0",
@ -354,8 +385,13 @@ class TestQgsServerSecurity(QgsServerTestBase):
"HEIGHT": "500",
"WIDTH": "500",
"BBOX": "606171,4822867,612834,4827375",
"CRS": "EPSG:32613",
"SLD": sld}.items())])
"CRS": "EPSG:32613"
}
if sld is not None:
params["SLD"] = sld
if filter is not None:
params["FILTER"] = urllib.parse.quote(filter)
qs = "?" + "&".join(["%s=%s" % i for i in params.items()])
return self._result(self._execute_request(qs))

View File

@ -697,6 +697,27 @@ class TestQgsServerWMSGetMap(QgsServerTestBase):
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Filter3")
def test_wms_getmap_filter_ogc(self):
filter = "<Filter><PropertyIsEqualTo><PropertyName>name</PropertyName>" + \
"<Literal>eurasia</Literal></PropertyIsEqualTo></Filter>"
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country,Hello",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857",
"FILTER": filter
}.items())])
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Filter_OGC")
def test_wms_getmap_selection(self):
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB