19 changes: 19 additions & 0 deletions src/core/qgsrendercontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@
#include "qgsrectangle.h"
#include "qgsvectorsimplifymethod.h"
#include "qgsexpressioncontext.h"
#include "qgsfeaturefilterprovider.h"

class QPainter;

class QgsAbstractGeometryV2;
class QgsLabelingEngineInterface;
class QgsLabelingEngineV2;
class QgsMapSettings;
class QgsExpression;
class QgsVectorLayer;


/** \ingroup core
* Contains information about the context of a rendering operation.
Expand Down Expand Up @@ -195,6 +199,18 @@ class CORE_EXPORT QgsRenderContext
/** Sets pointer to original (unsegmentized) geometry*/
void setGeometry( const QgsAbstractGeometryV2* geometry ) { mGeometry = geometry; }

/** Set a filter feature provider used to filter the features
* @param ffp the filter feature provider
* @note not available in Python bindings
*/
void setFeatureFilterProvider( const QgsFeatureFilterProvider* ffp );

/** Get the filter feature provider used to filter the features
* @return the filter feature provider
* @note not available in Python bindings
*/
const QgsFeatureFilterProvider* featureFilterProvider() { return mFeatureFilterProvider; }

private:

Flags mFlags;
Expand Down Expand Up @@ -238,6 +254,9 @@ class CORE_EXPORT QgsRenderContext

/** Pointer to the (unsegmentized) geometry*/
const QgsAbstractGeometryV2* mGeometry;

/** The feature filter provider */
const QgsFeatureFilterProvider* mFeatureFilterProvider;
};

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsRenderContext::Flags )
Expand Down
18 changes: 16 additions & 2 deletions src/core/qgsvectorlayerrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
QgsVectorLayerRenderer::QgsVectorLayerRenderer( QgsVectorLayer* layer, QgsRenderContext& context )
: QgsMapLayerRenderer( layer->id() )
, mContext( context )
, mLayer( layer )
, mFields( layer->fields() )
, mRendererV2( 0 )
, mCache( 0 )
Expand Down Expand Up @@ -152,10 +153,23 @@ bool QgsVectorLayerRenderer::render()
.setFilterRect( requestExtent )
.setSubsetOfAttributes( mAttrNames, mFields );

if ( !rendererFilter.isEmpty() && rendererFilter != "TRUE" )
const QgsFeatureFilterProvider* featureFilterProvider = mContext.featureFilterProvider();
if ( featureFilterProvider != NULL)
{
featureFilterProvider->filterFeatures( mLayer, featureRequest );
}
if ( !rendererFilter.isNull() )
{
featureRequest.setFilterExpression( rendererFilter );
featureRequest.setExpressionContext( mContext.expressionContext() );
if ( featureRequest.filterExpression() == NULL )
{
featureRequest.setFilterExpression( rendererFilter );
}
else
{
featureRequest.setFilterExpression( QString( "(%s) AND (%s)" )
.arg( rendererFilter, featureRequest.filterExpression()->expression() ) );
}
}

// enable the simplification of the geometries (Using the current map2pixel context) before send it to renderer engine.
Expand Down
3 changes: 3 additions & 0 deletions src/core/qgsvectorlayerrenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ class QgsVectorLayerRenderer : public QgsMapLayerRenderer

QgsRenderContext& mContext;

/** The rendered layer */
QgsVectorLayer* mLayer;

QgsFields mFields; // TODO: use fields from mSource

QgsFeatureIds mSelectedFeatureIds;
Expand Down
3 changes: 3 additions & 0 deletions src/server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ SET ( qgis_mapserv_SRCS
qgsgetrequesthandler.cpp
qgspostrequesthandler.cpp
qgssoaprequesthandler.cpp
qgsowsserver.cpp
qgswmsserver.cpp
qgswfsserver.cpp
qgswcsserver.cpp
Expand Down Expand Up @@ -82,6 +83,8 @@ SET(qgis_mapserv_SRCS ${qgis_mapserv_SRCS}
qgsserverplugins.cpp
qgsserverinterface.cpp
qgsserverfilter.cpp
qgsaccesscontrolfilter.cpp
qgsaccesscontrol.cpp
qgsserverinterfaceimpl.cpp
)
ENDIF (WITH_SERVER_PLUGINS)
Expand Down
169 changes: 169 additions & 0 deletions src/server/qgsaccesscontrol.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/***************************************************************************
qgsaccesscontrol.cpp
--------------------
begin : 22-05-2015
copyright : (C) 2008 by Stéphane Brunner
email : stephane dot brunner 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 "qgsaccesscontrol.h"
#include "qgsfeaturerequest.h"
#include "qgsmaplayer.h"
#include "qgsvectorlayer.h"

#include <QStringList>


/** Filter the features of the layer */
void QgsAccessControl::filterFeatures( const QgsVectorLayer* layer, QgsFeatureRequest& featureRequest ) const
{
QStringList expressions = QStringList();
QgsAccessControlFilterMap::const_iterator acIterator;
for ( acIterator = mPluginsAccessControls->constBegin(); acIterator != mPluginsAccessControls->constEnd(); ++acIterator )
{
const QString expression = acIterator.value()->layerFilterExpression( layer );
if ( expression != NULL ) {
expressions.append( expression );
}
}
if ( !expressions.isEmpty() ) {
featureRequest.setFilterExpression( expressions.join(" AND ") );
}
}

/** Clone the object */
QgsFeatureFilterProvider* QgsAccessControl::clone() const
{
return new QgsAccessControl(*this);
}

/** Return an additional subset string (typically SQL) filter */
const QString QgsAccessControl::extraSubsetString( const QgsVectorLayer* layer ) const
{
QStringList sqls = QStringList();
QgsAccessControlFilterMap::const_iterator acIterator;
for ( acIterator = mPluginsAccessControls->constBegin(); acIterator != mPluginsAccessControls->constEnd(); ++acIterator )
{
const QString sql = acIterator.value()->layerFilterSubsetString( layer );
if ( sql != NULL ) {
sqls.append( sql );
}
}
return sqls.isEmpty() ? NULL : sqls.join(" AND ");
}

/** Return the layer read right */
bool QgsAccessControl::layerReadPermission( const QgsMapLayer* layer ) const
{
QgsAccessControlFilterMap::const_iterator acIterator;
for ( acIterator = mPluginsAccessControls->constBegin(); acIterator != mPluginsAccessControls->constEnd(); ++acIterator )
{
if ( !acIterator.value()->layerPermissions( layer ).canRead)
{
return false;
}
}
return true;
}

/** Return the layer insert right */
bool QgsAccessControl::layerInsertPermission( const QgsVectorLayer* layer ) const
{
QgsAccessControlFilterMap::const_iterator acIterator;
for ( acIterator = mPluginsAccessControls->constBegin(); acIterator != mPluginsAccessControls->constEnd(); ++acIterator )
{
if ( !acIterator.value()->layerPermissions( layer ).canInsert)
{
return false;
}
}
return true;
}

/** Return the layer update right */
bool QgsAccessControl::layerUpdatePermission( const QgsVectorLayer* layer ) const
{
QgsAccessControlFilterMap::const_iterator acIterator;
for ( acIterator = mPluginsAccessControls->constBegin(); acIterator != mPluginsAccessControls->constEnd(); ++acIterator )
{
if ( !acIterator.value()->layerPermissions( layer ).canUpdate)
{
return false;
}
}
return true;
}

/** Return the layer delete right */
bool QgsAccessControl::layerDeletePermission( const QgsVectorLayer* layer ) const
{
QgsAccessControlFilterMap::const_iterator acIterator;
for ( acIterator = mPluginsAccessControls->constBegin(); acIterator != mPluginsAccessControls->constEnd(); ++acIterator )
{
if ( !acIterator.value()->layerPermissions( layer ).canDelete)
{
return false;
}
}
return true;
}

/** Return the authorized layer attributes */
const QStringList QgsAccessControl::layerAttributes( const QgsVectorLayer* layer, const QStringList attributes ) const
{
QStringList currentAttributes( attributes );
QgsAccessControlFilterMap::const_iterator acIterator;
for ( acIterator = mPluginsAccessControls->constBegin(); acIterator != mPluginsAccessControls->constEnd(); ++acIterator )
{
const QStringList* newAttributes = acIterator.value()->authorizedLayerAttributes( layer, currentAttributes );
if (newAttributes != NULL) {
currentAttributes = *newAttributes;
}
}
return currentAttributes;
}

/** Are we authorized to modify the following geometry */
bool QgsAccessControl::allowToEdit( const QgsVectorLayer* layer, const QgsFeature& feature ) const
{
QgsAccessControlFilterMap::const_iterator acIterator;
for ( acIterator = mPluginsAccessControls->constBegin(); acIterator != mPluginsAccessControls->constEnd(); ++acIterator )
{
if ( !acIterator.value()->allowToEdit( layer, feature ) )
{
return false;
}
}
return true;
}

/** Fill the capabilities caching key */
bool QgsAccessControl::fillCacheKey( QStringList& cacheKey ) const
{
QgsAccessControlFilterMap::const_iterator acIterator;
for ( acIterator = mPluginsAccessControls->constBegin(); acIterator != mPluginsAccessControls->constEnd(); ++acIterator )
{
QString newKey = acIterator.value()->cacheKey();
if ( newKey.length() == 0 ) {
cacheKey.clear();
return false;
}
cacheKey << newKey;
}
return true;
}

/** Register a new access control filter */
void QgsAccessControl::registerAccessControl( QgsAccessControlFilter* accessControl, int priority )
{
mPluginsAccessControls->insert( priority, accessControl );
}
125 changes: 125 additions & 0 deletions src/server/qgsaccesscontrol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/***************************************************************************
qgsaccesscontrol.h
------------------
begin : 22-05-2015
copyright : (C) 2008 by Stéphane Brunner
email : stephane dot brunner 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 QGSACCESSCONTROL_H
#define QGSACCESSCONTROL_H

#include "qgsfeaturefilterprovider.h"
#include "qgsaccesscontrolfilter.h"

#include <QMultiMap>

class QgsAccessControlPlugin;


/** \ingroup server
* \class QgsAccessControl
* \brief A helper class that centralise the restrictions given by all the
* access control filter plugins.
**/
class SERVER_EXPORT QgsAccessControl : public QgsFeatureFilterProvider
{
public:
/** Constructor */
QgsAccessControl()
{
mPluginsAccessControls = new QgsAccessControlFilterMap();
};
/** Constructor */
QgsAccessControl( const QgsAccessControl& copy )
{
mPluginsAccessControls = new QgsAccessControlFilterMap( *copy.mPluginsAccessControls );
};
/** Destructor */
~QgsAccessControl()
{
delete mPluginsAccessControls;
};

/** 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
* @return A clone
*/
QgsFeatureFilterProvider* clone() const;

/** Return an additional subset string (typically SQL) filter
* @param layer the layer to control
* @return the subset string to use
*/
const QString extraSubsetString( const QgsVectorLayer* layer ) const;

/** Return the layer read right
* @param layer the layer to control
* @return true if it can be read
*/
bool layerReadPermission( const QgsMapLayer* layer ) const;

/** Return the layer insert right
* @param layer the layer to control
* @return true if we can insert on it
*/
bool layerInsertPermission( const QgsVectorLayer* layer ) const;

/** Return the layer update right
* @param layer the layer to control
* @return true if we can do an update
*/
bool layerUpdatePermission( const QgsVectorLayer* layer ) const;

/** Return the layer delete right
* @param layer the layer to control
* @return true if we can do a delete
*/
bool layerDeletePermission( const QgsVectorLayer* layer ) const;

/** Return the authorized layer attributes
* @param layer the layer to control
* @param attributes the list of attribute
* @return the list of visible attributes
*/
const QStringList layerAttributes( const QgsVectorLayer* layer, const QStringList attributes ) const;

/** Are we authorized to modify the following geometry
* @param layer the layer to control
* @param feature the concerned feature
* @return true if we are allowed to edit the feature
*/
bool allowToEdit( const QgsVectorLayer* layer, const QgsFeature& feature ) const;

/** Fill the capabilities caching key
* @param cacheKey the list to fill with a cache variant
* @return false if we cant create a cache
*/
bool fillCacheKey( QStringList& cacheKey ) const;

/** Register an access control filter
* @param accessControl the access control to add
* @priority the priority used to define the order
*/
void registerAccessControl( QgsAccessControlFilter* accessControl, int priority = 0 );

private:
/** The AccessControl plugins registry */
QgsAccessControlFilterMap* mPluginsAccessControls;
};

#endif
87 changes: 87 additions & 0 deletions src/server/qgsaccesscontrolfilter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/***************************************************************************
qgsaccesscontrolplugin.cpp
--------------------------
Access control interface for Qgis Server plugins
begin : 2015-05-19
copyright : (C) 2015 by Stéphane Brunner
email : stephane dot brunner at camptocamp dot org
***************************************************************************/

/***************************************************************************
* *
* 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 "qgsaccesscontrolfilter.h"
#include "qgsmessagelog.h"

#include <QString>
#include <QStringList>


/** Constructor */
QgsAccessControlFilter::QgsAccessControlFilter( const QgsServerInterface* serverInterface ):
mServerInterface( serverInterface )
{
}

/** Destructor */
QgsAccessControlFilter::~QgsAccessControlFilter()
{
}

/** Return an additional layer expression filter */
const QString QgsAccessControlFilter::layerFilterExpression( const QgsVectorLayer* layer ) const
{
QgsMessageLog::logMessage( "QgsAccessControlFilter plugin default layerFilterExpression called", "AccessControlFilter", QgsMessageLog::INFO );
Q_UNUSED( layer );
return NULL;
}

/** Return an additional layer subset string (typically SQL) filter */
const QString QgsAccessControlFilter::layerFilterSubsetString( const QgsVectorLayer* layer ) const
{
QgsMessageLog::logMessage( "QgsAccessControlFilter plugin default layerFilterSQL called", "AccessControlFilter", QgsMessageLog::INFO );
Q_UNUSED( layer );
return NULL;
}

/** Return the layer permissions */
const QgsAccessControlFilter::LayerPermissions QgsAccessControlFilter::layerPermissions( const QgsMapLayer* layer ) const
{
QgsMessageLog::logMessage( "QgsAccessControlFilter plugin default layerPermissions called", "AccessControlFilter", QgsMessageLog::INFO );
Q_UNUSED( layer );
LayerPermissions permissions = QgsAccessControlFilter::LayerPermissions();
permissions.canRead = permissions.canUpdate = permissions.canInsert = permissions.canDelete = true;
return permissions;
}

/** Return the authorized layer attributes */
const QStringList* QgsAccessControlFilter::authorizedLayerAttributes( const QgsVectorLayer* layer, const QStringList& attributes ) const
{
Q_UNUSED( layer );
Q_UNUSED( attributes );
QgsMessageLog::logMessage( "QgsAccessControlFilter plugin default authorizedLayerAttributes called", "AccessControlFilter", QgsMessageLog::INFO );
return NULL;
}

/** Are we authorized to modify the feature */
bool QgsAccessControlFilter::allowToEdit( const QgsVectorLayer* layer, const QgsFeature& feature ) const
{
QgsMessageLog::logMessage( "QgsAccessControlFilter plugin default allowToEdit called", "AccessControlFilter", QgsMessageLog::INFO );
Q_UNUSED( layer );
Q_UNUSED( feature );
return true;
}

/** Cache key to used to create the capabilities cache, "" for no cache */
const QString QgsAccessControlFilter::cacheKey() const
{
return "";
}
115 changes: 115 additions & 0 deletions src/server/qgsaccesscontrolfilter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/***************************************************************************
qgsaccesscontrolfilter.h
------------------------
begin : 2015-05-19
copyright : (C) 2015 by Stéphane Brunner
email : stephane dot brunner at camptocamp dot org
***************************************************************************/

/***************************************************************************
* *
* 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 QGSACCESSCONTROLPLUGIN_H
#define QGSACCESSCONTROLPLUGIN_H

#include <QMultiMap>
#include <QList>
#include <QString>

class QgsServerInterface;
class QgsMapLayer;
class QgsVectorLayer;
class QgsExpression;
class QgsFeature;


/**
* \class QgsAccessControlFilter
* \brief Class defining access control interface for QGIS Server plugins.
*
* Security can define any (or none) of the following method:
* * layerFilterExpression() - To get an additional expression filter (WMS/GetMap, WMS/GetFeatureInfo, WFS/GetFeature)
* * layerFilterSQL() - To get an additional SQL filter (WMS/GetMap, WMS/GetFeatureInfo, WFS/GetFeature) for layer that support SQL
* * layerPermissions() - To give the general layer permissins (read / update / insert / delete)
* * authorizedLayerAttributes() - Tho filter the attributes (WMS/GetFeatureInfo, WFS/GetFeature)
* * allowToEdit() - (all WFS-T requests)
*/
class SERVER_EXPORT QgsAccessControlFilter
{

public:

/** Constructor
* QgsServerInterface passed to plugins constructors
* and must be passed to QgsAccessControlFilter instances.
*/
QgsAccessControlFilter( const QgsServerInterface* serverInterface );
/** Destructor */
virtual ~QgsAccessControlFilter();

/** Describe the layer permission */
struct LayerPermissions {
bool canRead;
bool canUpdate;
bool canInsert;
bool canDelete;
};

/** Return the QgsServerInterface instance */
const QgsServerInterface* serverInterface() const { return mServerInterface; }

/** Return an additional expression filter
* @param layer the layer to control
* @return the filter expression
*/
virtual const QString layerFilterExpression( const QgsVectorLayer* layer ) const;

/** Return an additional subset string (typically SQL) filter
* @param layer the layer to control
* @return the subset string
*/
virtual const QString layerFilterSubsetString( const QgsVectorLayer* layer ) const;

/** Return the layer permissions
* @param layer the layer to control
* @return the permission to use on the layer
*/
virtual const LayerPermissions layerPermissions( const QgsMapLayer* layer ) const;

/** Return the authorized layer attributes
* @param layer the layer to control
* @param attributes the current list of visible attribute
* @return the new list of visible attributes
*/
virtual const QStringList* authorizedLayerAttributes( const QgsVectorLayer* layer, const QStringList& attributes ) const;

/** Are we authorized to modify the following geometry
* @param layer the layer to control
* @param feature the concerned feature
* @return true if we are allowed to edit
*/
virtual bool allowToEdit( const QgsVectorLayer* layer, const QgsFeature& feature ) const;

/** Cache key to used to create the capabilities cache
* @return the cache key, "" for no cache
*/
virtual const QString cacheKey() const;

private:

/** The server interface */
const QgsServerInterface* mServerInterface;

};

/** The registry definition */
typedef QMultiMap<int, QgsAccessControlFilter*> QgsAccessControlFilterMap;


#endif // QGSSERVERSECURITY_H
10 changes: 5 additions & 5 deletions src/server/qgscapabilitiescache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,21 @@ QgsCapabilitiesCache::~QgsCapabilitiesCache()
{
}

const QDomDocument* QgsCapabilitiesCache::searchCapabilitiesDocument( const QString& configFilePath, const QString& version )
const QDomDocument* QgsCapabilitiesCache::searchCapabilitiesDocument( const QString& configFilePath, const QString& key )
{
QCoreApplication::processEvents(); //get updates from file system watcher

if ( mCachedCapabilities.contains( configFilePath ) && mCachedCapabilities[ configFilePath ].contains( version ) )
if ( mCachedCapabilities.contains( configFilePath ) && mCachedCapabilities[ configFilePath ].contains( key ) )
{
return &mCachedCapabilities[configFilePath][version];
return &mCachedCapabilities[ configFilePath ][ key ];
}
else
{
return 0;
}
}

void QgsCapabilitiesCache::insertCapabilitiesDocument( const QString& configFilePath, const QString& version, const QDomDocument* doc )
void QgsCapabilitiesCache::insertCapabilitiesDocument( const QString& configFilePath, const QString& key, const QDomDocument* doc )
{
if ( mCachedCapabilities.size() > 40 )
{
Expand All @@ -58,7 +58,7 @@ void QgsCapabilitiesCache::insertCapabilitiesDocument( const QString& configFile
mCachedCapabilities.insert( configFilePath, QHash<QString, QDomDocument>() );
}

mCachedCapabilities[ configFilePath ].insert( version, doc->cloneNode().toDocument() );
mCachedCapabilities[ configFilePath ].insert( key, doc->cloneNode().toDocument() );
}

void QgsCapabilitiesCache::removeChangedEntry( const QString& path )
Expand Down
16 changes: 12 additions & 4 deletions src/server/qgscapabilitiescache.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,18 @@ class SERVER_EXPORT QgsCapabilitiesCache : public QObject
QgsCapabilitiesCache();
~QgsCapabilitiesCache();

/** Returns cached capabilities document (or 0 if document for configuration file not in cache)*/
const QDomDocument* searchCapabilitiesDocument( const QString& configFilePath, const QString& version );
/** Inserts new capabilities document (creates a copy of the document, does not take ownership)*/
void insertCapabilitiesDocument( const QString& configFilePath, const QString& version, const QDomDocument* doc );
/** Returns cached capabilities document (or 0 if document for configuration file not in cache)
* @param configFilePath the progect file path
* @param key key used to separate different version in different cache
*/
const QDomDocument* searchCapabilitiesDocument( const QString& configFilePath, const QString& key );

/** Inserts new capabilities document (creates a copy of the document, does not take ownership)
* @param configFilePath the progect file path
* @param key key used to separate different version in different cache
* @param doc the DOM document
*/
void insertCapabilitiesDocument( const QString& configFilePath, const QString& key, const QDomDocument* doc );

private:
QHash< QString, QHash< QString, QDomDocument > > mCachedCapabilities;
Expand Down
44 changes: 38 additions & 6 deletions src/server/qgsconfigcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "qgswfsprojectparser.h"
#include "qgswmsprojectparser.h"
#include "qgssldconfigparser.h"
#include "qgsaccesscontrol.h"

#include <QFile>

Expand Down Expand Up @@ -54,7 +55,12 @@ QgsServerProjectParser* QgsConfigCache::serverConfiguration( const QString& file
return new QgsServerProjectParser( doc, filePath );
}

QgsWCSProjectParser *QgsConfigCache::wcsConfiguration( const QString& filePath )
QgsWCSProjectParser *QgsConfigCache::wcsConfiguration(
const QString& filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* accessControl
#endif
)
{
QgsWCSProjectParser *p = mWCSConfigCache.object( filePath );
if ( !p )
Expand All @@ -64,7 +70,12 @@ QgsWCSProjectParser *QgsConfigCache::wcsConfiguration( const QString& filePath )
{
return 0;
}
p = new QgsWCSProjectParser( filePath );
p = new QgsWCSProjectParser(
filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, accessControl
#endif
);
mWCSConfigCache.insert( filePath, p );
p = mWCSConfigCache.object( filePath );
Q_ASSERT( p );
Expand All @@ -74,7 +85,12 @@ QgsWCSProjectParser *QgsConfigCache::wcsConfiguration( const QString& filePath )
return p;
}

QgsWFSProjectParser *QgsConfigCache::wfsConfiguration( const QString& filePath )
QgsWFSProjectParser *QgsConfigCache::wfsConfiguration(
const QString& filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* accessControl
#endif
)
{
QgsWFSProjectParser *p = mWFSConfigCache.object( filePath );
if ( !p )
Expand All @@ -84,7 +100,12 @@ QgsWFSProjectParser *QgsConfigCache::wfsConfiguration( const QString& filePath )
{
return 0;
}
p = new QgsWFSProjectParser( filePath );
p = new QgsWFSProjectParser(
filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, accessControl
#endif
);
mWFSConfigCache.insert( filePath, p );
p = mWFSConfigCache.object( filePath );
Q_ASSERT( p );
Expand All @@ -94,7 +115,13 @@ QgsWFSProjectParser *QgsConfigCache::wfsConfiguration( const QString& filePath )
return p;
}

QgsWMSConfigParser *QgsConfigCache::wmsConfiguration( const QString& filePath, const QMap<QString, QString>& parameterMap )
QgsWMSConfigParser *QgsConfigCache::wmsConfiguration(
const QString& filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* accessControl
#endif
, const QMap<QString, QString>& parameterMap
)
{
QgsWMSConfigParser *p = mWMSConfigCache.object( filePath );
if ( !p )
Expand All @@ -114,7 +141,12 @@ QgsWMSConfigParser *QgsConfigCache::wmsConfiguration( const QString& filePath, c
}
else
{
p = new QgsWMSProjectParser( filePath );
p = new QgsWMSProjectParser(
filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, accessControl
#endif
);
}
mWMSConfigCache.insert( filePath, p );
p = mWMSConfigCache.object( filePath );
Expand Down
25 changes: 22 additions & 3 deletions src/server/qgsconfigcache.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#ifndef QGSCONFIGCACHE_H
#define QGSCONFIGCACHE_H

#include "qgsconfig.h"

#include <QCache>
#include <QFileSystemWatcher>
#include <QMap>
Expand All @@ -27,6 +29,7 @@ class QgsServerProjectParser;
class QgsWCSProjectParser;
class QgsWFSProjectParser;
class QgsWMSConfigParser;
class QgsAccessControl;

class QDomDocument;

Expand All @@ -38,9 +41,25 @@ class SERVER_EXPORT QgsConfigCache : public QObject
~QgsConfigCache();

QgsServerProjectParser* serverConfiguration( const QString& filePath );
QgsWCSProjectParser* wcsConfiguration( const QString& filePath );
QgsWFSProjectParser* wfsConfiguration( const QString& filePath );
QgsWMSConfigParser* wmsConfiguration( const QString& filePath, const QMap<QString, QString>& parameterMap = ( QMap< QString, QString >() ) );
QgsWCSProjectParser* wcsConfiguration(
const QString& filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* accessControl
#endif
);
QgsWFSProjectParser* wfsConfiguration(
const QString& filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* accessControl
#endif
);
QgsWMSConfigParser* wmsConfiguration(
const QString& filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* accessControl
#endif
, const QMap<QString, QString>& parameterMap = ( QMap< QString, QString >() )
);

private:
QgsConfigCache();
Expand Down
33 changes: 19 additions & 14 deletions src/server/qgshttprequesthandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
#include "qgshttprequesthandler.h"
#include "qgsftptransaction.h"
#include "qgshttptransaction.h"
#include "qgslogger.h"
#include "qgsmessagelog.h"
#include "qgsmapserviceexception.h"
#include <QBuffer>
#include <QByteArray>
Expand All @@ -47,7 +47,7 @@ QgsHttpRequestHandler::~QgsHttpRequestHandler()

void QgsHttpRequestHandler::setHttpResponse( QByteArray *ba, const QString &format )
{
QgsDebugMsg( "Checking byte array is ok to set..." );
QgsMessageLog::logMessage( "Checking byte array is ok to set..." );
if ( !ba )
{
return;
Expand All @@ -57,7 +57,7 @@ void QgsHttpRequestHandler::setHttpResponse( QByteArray *ba, const QString &form
{
return;
}
QgsDebugMsg( "Byte array looks good, setting response..." );
QgsMessageLog::logMessage( "Byte array looks good, setting response..." );
appendBody( *ba );
mInfoFormat = format;
}
Expand Down Expand Up @@ -178,7 +178,7 @@ void QgsHttpRequestHandler::sendBody()
// Cannot use addToResponse because it uses printf
size_t result = fwrite(( void* )mBody.data(), mBody.size(), 1, FCGI_stdout );
#ifdef QGISDEBUG
QgsDebugMsg( QString( "Sent %1 blocks of %2 bytes" ).arg( result ).arg( mBody.size() ) );
QgsMessageLog::logMessage( QString( "Sent %1 blocks of %2 bytes" ).arg( result ).arg( mBody.size() ) );
#else
Q_UNUSED( result );
#endif
Expand All @@ -194,10 +194,10 @@ void QgsHttpRequestHandler::setPluginFilters( QgsServerFiltersMap pluginFilters

void QgsHttpRequestHandler::sendResponse()
{
QgsDebugMsg( QString( "Sending HTTP response" ) );
QgsMessageLog::logMessage( QString( "Sending HTTP response" ) );
if ( ! responseReady() )
{
QgsDebugMsg( QString( "Trying to send out an invalid response" ) );
QgsMessageLog::logMessage( QString( "Trying to send out an invalid response" ) );
return;
}
#ifdef HAVE_SERVER_PYTHON_PLUGINS
Expand Down Expand Up @@ -249,7 +249,7 @@ QString QgsHttpRequestHandler::formatToMimeType( const QString& format ) const
void QgsHttpRequestHandler::setGetMapResponse( const QString& service, QImage* img, int imageQuality = -1 )
{
Q_UNUSED( service );
QgsDebugMsg( "setting getmap response..." );
QgsMessageLog::logMessage( "setting getmap response..." );
if ( img )
{
bool png16Bit = ( mFormatString.compare( "image/png; mode=16bit", Qt::CaseInsensitive ) == 0 );
Expand All @@ -258,7 +258,7 @@ void QgsHttpRequestHandler::setGetMapResponse( const QString& service, QImage* i
bool isBase64 = mFormatString.endsWith( ";base64", Qt::CaseInsensitive );
if ( mFormat != "PNG" && mFormat != "JPG" && !png16Bit && !png8Bit && !png1Bit )
{
QgsDebugMsg( "service exception - incorrect image format requested..." );
QgsMessageLog::logMessage( "service exception - incorrect image format requested..." );
setServiceException( QgsMapServiceException( "InvalidFormat", "Output format '" + mFormatString + "' is not supported in the GetMap request" ) );
return;
}
Expand Down Expand Up @@ -328,7 +328,7 @@ void QgsHttpRequestHandler::setXmlResponse( const QDomDocument& doc, const QStri
void QgsHttpRequestHandler::setGetFeatureInfoResponse( const QDomDocument& infoDoc, const QString& infoFormat )
{
QByteArray ba;
QgsDebugMsg( "Info format is:" + infoFormat );
QgsMessageLog::logMessage( "Info format is:" + infoFormat );

if ( infoFormat == "text/xml" || infoFormat.startsWith( "application/vnd.ogc.gml" ) )
{
Expand Down Expand Up @@ -596,7 +596,7 @@ void QgsHttpRequestHandler::requestStringToParameterMap( const QString& request,

}
parameters.insert( key.toUpper(), value );
QgsDebugMsg( "inserting pair " + key.toUpper() + " // " + value + " into the parameter map" );
QgsMessageLog::logMessage( "inserting pair " + key.toUpper() + " // " + value + " into the parameter map" );
}

//feature info format?
Expand All @@ -611,7 +611,7 @@ void QgsHttpRequestHandler::requestStringToParameterMap( const QString& request,
QString formatString = mFormatString;
if ( !formatString.isEmpty() )
{
QgsDebugMsg( QString( "formatString is: %1" ).arg( formatString ) );
QgsMessageLog::logMessage( QString( "formatString is: %1" ).arg( formatString ) );

//remove the image/ in front of the format
if ( formatString.contains( "image/png", Qt::CaseInsensitive ) || formatString.compare( "png", Qt::CaseInsensitive ) == 0 )
Expand Down Expand Up @@ -640,6 +640,7 @@ void QgsHttpRequestHandler::requestStringToParameterMap( const QString& request,

QString QgsHttpRequestHandler::readPostBody() const
{
QgsMessageLog::logMessage( "QgsHttpRequestHandler::readPostBody" );
char* lengthString = NULL;
int length = 0;
char* input = NULL;
Expand All @@ -652,7 +653,7 @@ QString QgsHttpRequestHandler::readPostBody() const
bool conversionSuccess = false;
lengthQString = QString( lengthString );
length = lengthQString.toInt( &conversionSuccess );
QgsDebugMsg( "length is: " + lengthQString );
QgsMessageLog::logMessage( "length is: " + lengthQString );
if ( conversionSuccess )
{
input = ( char* )malloc( length + 1 );
Expand All @@ -668,15 +669,19 @@ QString QgsHttpRequestHandler::readPostBody() const
}
else
{
QgsDebugMsg( "input is NULL " );
QgsMessageLog::logMessage( "input is NULL " );
}
free( input );
}
else
{
QgsDebugMsg( "could not convert CONTENT_LENGTH to int" );
QgsMessageLog::logMessage( "could not convert CONTENT_LENGTH to int" );
}
}
// Used by the tests
else if ( getenv( "REQUEST_BODY" ) != NULL ) {
inputString = getenv( "REQUEST_BODY" );
}
return inputString;
}

Expand Down
72 changes: 72 additions & 0 deletions src/server/qgsowsserver.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/***************************************************************************
qgsowsserver.cpp
-------------------
begin : February 27, 2012
copyright : (C) 2012 by René-Luc D'Hont & Marco Hugentobler
email : rldhont at 3liz 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 "qgsowsserver.h"
#include "qgsmaplayerregistry.h"
#include "qgsmessagelog.h"
#include "qgsvectorlayer.h"
#include "qgsvectordataprovider.h"


#ifdef HAVE_SERVER_PYTHON_PLUGINS
/** Apply filter from AccessControl */
void QgsOWSServer::applyAccessControlLayerFilters( QgsMapLayer* mapLayer, QMap<QString, QString>& originalLayerFilters ) const
{
if ( QgsVectorLayer* layer = dynamic_cast<QgsVectorLayer*>( mapLayer ) )
{
if ( layer->setSubsetString( "" ) )
{
QString sql = mAccessControl->extraSubsetString( layer );
if ( sql != NULL )
{
if ( !originalLayerFilters.contains( layer->id() ) )
{
originalLayerFilters.insert( layer->id(), layer->subsetString() );
}
if ( !layer->subsetString().isEmpty() )
{
sql.prepend( " AND " );
sql.prepend( layer->subsetString() );
}
layer->setSubsetString( sql );
}
}
else
{
QgsMessageLog::logMessage( "Layer does not support Subset String" );
}
}
}
#endif

/** Restore layer filter as original */
void QgsOWSServer::restoreLayerFilters( const QMap<QString, QString>& filterMap ) const
{
QMap<QString, QString>::const_iterator filterIt = filterMap.constBegin();
for ( ; filterIt != filterMap.constEnd(); ++filterIt )
{
QgsVectorLayer* filteredLayer = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( filterIt.key() ) );
if ( filteredLayer )
{
QgsVectorDataProvider* dp = filteredLayer->dataProvider();
if ( dp )
{
dp->setSubsetString( filterIt.value() );
}
}
}
}
36 changes: 34 additions & 2 deletions src/server/qgsowsserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,28 @@
#define QGSOWSSERVER_H

#include "qgsrequesthandler.h"
#ifdef HAVE_SERVER_PYTHON_PLUGINS
#include "qgsaccesscontrol.h"
#endif

class QgsOWSServer
{
public:
QgsOWSServer( const QString& configFilePath, const QMap<QString, QString>& parameters, QgsRequestHandler* rh )
: mParameters( parameters ), mRequestHandler( rh ), mConfigFilePath( configFilePath ) {}
QgsOWSServer(
const QString& configFilePath
, const QMap<QString, QString>& parameters
, QgsRequestHandler* rh
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* ac
#endif
)
: mParameters( parameters )
, mRequestHandler( rh )
, mConfigFilePath( configFilePath )
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, mAccessControl( ac )
#endif
{}
virtual ~QgsOWSServer() {}

virtual void executeRequest() = 0;
Expand All @@ -36,6 +52,22 @@ class QgsOWSServer
QMap<QString, QString> mParameters;
QgsRequestHandler* mRequestHandler;
QString mConfigFilePath;
#ifdef HAVE_SERVER_PYTHON_PLUGINS
/** The access control helper */
const QgsAccessControl* mAccessControl;

/** Apply filter strings from the access control to the layers.
* @param layer the concerned layer
* @param originalLayerFilters the original layer filter
*
*/
void applyAccessControlLayerFilters( QgsMapLayer* layer, QMap<QString, QString>& originalLayerFilters ) const;
#endif

/** Restores the original layer filters
* @param filterMap the original layer filter
*/
void restoreLayerFilters( const QMap < QString, QString >& filterMap ) const;
};

#endif // QGSOWSSERVER_H
21 changes: 13 additions & 8 deletions src/server/qgspostrequesthandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@
***************************************************************************/
#include <stdlib.h>
#include "qgspostrequesthandler.h"
#include "qgslogger.h"
#include "qgsmessagelog.h"
#include <QDomDocument>

QgsPostRequestHandler::QgsPostRequestHandler( const bool captureOutput /*= FALSE*/ )
: QgsHttpRequestHandler( captureOutput )
{

}

QgsPostRequestHandler::~QgsPostRequestHandler()
Expand All @@ -31,9 +30,10 @@ QgsPostRequestHandler::~QgsPostRequestHandler()

void QgsPostRequestHandler::parseInput()
{
QgsDebugMsg( "QgsPostRequestHandler::parseInput" );
QgsMessageLog::logMessage( "QgsPostRequestHandler::parseInput" );

QString inputString = readPostBody();
QgsDebugMsg( inputString );
QgsMessageLog::logMessage( inputString );

//Map parameter in QUERY_STRING?
const char* qs = getenv( "QUERY_STRING" );
Expand All @@ -47,11 +47,16 @@ void QgsPostRequestHandler::parseInput()
mapParameter = getParameters.value( "MAP" );
}


QDomDocument doc;
QString errorMsg;
if ( !doc.setContent( inputString, true, &errorMsg ) )
int line;
int column;
if ( !doc.setContent( inputString, true, &errorMsg, &line, &column ) )
{
char* requestMethod = getenv( "REQUEST_METHOD" );
if ( requestMethod != NULL && strcmp( requestMethod, "POST" ) == 0 ) {
QgsMessageLog::logMessage( QString( "Error at line %1, column %2: %3." ).arg( line ).arg( column ).arg( errorMsg ) );
}
requestStringToParameterMap( inputString, mParameterMap );
}
else
Expand All @@ -61,11 +66,11 @@ void QgsPostRequestHandler::parseInput()
if ( qs )
{
queryString = QString( qs );
QgsDebugMsg( "query string is: " + queryString );
QgsMessageLog::logMessage( "query string is: " + queryString );
}
else
{
QgsDebugMsg( "error, no query string found but a QDomDocument" );
QgsMessageLog::logMessage( "error, no query string found but a QDomDocument" );
return; //no query string? something must be wrong...
}

Expand Down
94 changes: 71 additions & 23 deletions src/server/qgsserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
#include "qgsmaplayerregistry.h"
#include "qgsserverlogger.h"
#include "qgseditorwidgetregistry.h"
#ifdef HAVE_SERVER_PYTHON_PLUGINS
#include "qgsaccesscontrolfilter.h"
#endif

#include <QDomDocument>
#include <QNetworkDiskCache>
Expand Down Expand Up @@ -94,12 +97,12 @@ void QgsServer::setupNetworkAccessManager()
QNetworkDiskCache *cache = new QNetworkDiskCache( 0 );
QString cacheDirectory = settings.value( "cache/directory", QgsApplication::qgisSettingsDirPath() + "cache" ).toString();
qint64 cacheSize = settings.value( "cache/size", 50 * 1024 * 1024 ).toULongLong();
QgsDebugMsg( QString( "setCacheDirectory: %1" ).arg( cacheDirectory ) );
QgsDebugMsg( QString( "setMaximumCacheSize: %1" ).arg( cacheSize ) );
QgsMessageLog::logMessage( QString( "setCacheDirectory: %1" ).arg( cacheDirectory ), "Server", QgsMessageLog::INFO );
QgsMessageLog::logMessage( QString( "setMaximumCacheSize: %1" ).arg( cacheSize ), "Server", QgsMessageLog::INFO );
cache->setCacheDirectory( cacheDirectory );
cache->setMaximumCacheSize( cacheSize );
QgsDebugMsg( QString( "cacheDirectory: %1" ).arg( cache->cacheDirectory() ) );
QgsDebugMsg( QString( "maximumCacheSize: %1" ).arg( cache->maximumCacheSize() ) );
QgsMessageLog::logMessage( QString( "cacheDirectory: %1" ).arg( cache->cacheDirectory() ), "Server", QgsMessageLog::INFO );
QgsMessageLog::logMessage( QString( "maximumCacheSize: %1" ).arg( cache->maximumCacheSize() ), "Server", QgsMessageLog::INFO );
nam->setCache( cache );
}

Expand Down Expand Up @@ -144,7 +147,7 @@ QFileInfo QgsServer::defaultProjectFile()
QFileInfoList projectFiles = currentDir.entryInfoList( nameFilterList, QDir::Files, QDir::Name );
for ( int x = 0; x < projectFiles.size(); x++ )
{
QgsDebugMsg( projectFiles.at( x ).absoluteFilePath() );
QgsMessageLog::logMessage( projectFiles.at( x ).absoluteFilePath(), "Server", QgsMessageLog::INFO );
}
if ( projectFiles.size() < 1 )
{
Expand Down Expand Up @@ -271,7 +274,7 @@ QString QgsServer::configPath( const QString& defaultConfigPath, const QMap<QStr
QMap<QString, QString>::const_iterator paramIt = parameters.find( "MAP" );
if ( paramIt == parameters.constEnd() )
{
QgsDebugMsg( QString( "Using default configuration file path: %1" ).arg( defaultConfigPath ) );
QgsMessageLog::logMessage( QString( "Using default configuration file path: %1" ).arg( defaultConfigPath ), "Server", QgsMessageLog::INFO );
}
else
{
Expand Down Expand Up @@ -311,14 +314,16 @@ bool QgsServer::init( int & argc, char ** argv )
return false;
}

QgsServerLogger::instance();

#ifndef _MSC_VER
qInstallMsgHandler( dummyMessageHandler );
#endif

QString optionsPath = getenv( "QGIS_OPTIONS_PATH" );
if ( !optionsPath.isEmpty() )
{
QgsDebugMsg( "Options PATH: " + optionsPath );
QgsMessageLog::logMessage( "Options PATH: " + optionsPath, "Server", QgsMessageLog::INFO );
QSettings::setDefaultFormat( QSettings::IniFormat );
QSettings::setPath( QSettings::IniFormat, QSettings::UserScope, optionsPath );
}
Expand All @@ -337,7 +342,7 @@ bool QgsServer::init( int & argc, char ** argv )
#endif

#if defined(SERVER_SKIP_ECW)
QgsDebugMsg( "Skipping GDAL ECW drivers in server." );
QgsMessageLog::logMessage( "Skipping GDAL ECW drivers in server.", "Server", QgsMessageLog::INFO );
QgsApplication::skipGdalDriver( "ECW" );
QgsApplication::skipGdalDriver( "JP2ECW" );
#endif
Expand All @@ -347,12 +352,12 @@ bool QgsServer::init( int & argc, char ** argv )

// Instantiate the plugin directory so that providers are loaded
QgsProviderRegistry::instance( QgsApplication::pluginPath() );
QgsDebugMsg( "Prefix PATH: " + QgsApplication::prefixPath() );
QgsDebugMsg( "Plugin PATH: " + QgsApplication::pluginPath() );
QgsDebugMsg( "PkgData PATH: " + QgsApplication::pkgDataPath() );
QgsDebugMsg( "User DB PATH: " + QgsApplication::qgisUserDbFilePath() );
QgsDebugMsg( "Auth DB PATH: " + QgsApplication::qgisAuthDbFilePath() );
QgsDebugMsg( "SVG PATHS: " + QgsApplication::svgPaths().join( ":" ) );
QgsMessageLog::logMessage( "Prefix PATH: " + QgsApplication::prefixPath(), "Server", QgsMessageLog::INFO );
QgsMessageLog::logMessage( "Plugin PATH: " + QgsApplication::pluginPath(), "Server", QgsMessageLog::INFO );
QgsMessageLog::logMessage( "PkgData PATH: " + QgsApplication::pkgDataPath(), "Server", QgsMessageLog::INFO );
QgsMessageLog::logMessage( "User DB PATH: " + QgsApplication::qgisUserDbFilePath(), "Server", QgsMessageLog::INFO );
QgsMessageLog::logMessage( "Auth DB PATH: " + QgsApplication::qgisAuthDbFilePath(), "Server", QgsMessageLog::INFO );
QgsMessageLog::logMessage( "SVG PATHS: " + QgsApplication::svgPaths().join( ":" ), "Server", QgsMessageLog::INFO );

QgsApplication::createDB(); //init qgis.db (e.g. necessary for user crs)

Expand All @@ -367,7 +372,7 @@ bool QgsServer::init( int & argc, char ** argv )
if ( projectFileInfo.exists() )
{
defaultConfigFilePath = projectFileInfo.absoluteFilePath();
QgsDebugMsg( "Using default project file: " + defaultConfigFilePath );
QgsMessageLog::logMessage( "Using default project file: " + defaultConfigFilePath, "Server", QgsMessageLog::INFO );
}
else
{
Expand Down Expand Up @@ -407,8 +412,6 @@ bool QgsServer::init( int & argc, char ** argv )
}
#endif

QgsServerLogger::instance();

QgsEditorWidgetRegistry::initEditors();
mInitialised = true;
QgsMessageLog::logMessage( "Server intialised", "Server", QgsMessageLog::INFO );
Expand Down Expand Up @@ -486,6 +489,10 @@ QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString& queryStri

// Copy the parameters map
QMap<QString, QString> parameterMap( theRequestHandler->parameterMap() );
#ifdef HAVE_SERVER_PYTHON_PLUGINS
const QgsAccessControl* accessControl = NULL;
accessControl = mServerInterface->accessControls();
#endif

printRequestParameters( parameterMap, logLevel );
QMap<QString, QString>::const_iterator paramIt;
Expand Down Expand Up @@ -520,40 +527,81 @@ QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString& queryStri
{
if ( serviceString == "WCS" )
{
QgsWCSProjectParser* p = QgsConfigCache::instance()->wcsConfiguration( configFilePath );
QgsWCSProjectParser* p = QgsConfigCache::instance()->wcsConfiguration(
configFilePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, accessControl
#endif
);
if ( !p )
{
theRequestHandler->setServiceException( QgsMapServiceException( "Project file error", "Error reading the project file" ) );
}
else
{
QgsWCSServer wcsServer( configFilePath, parameterMap, p, theRequestHandler.data() );
QgsWCSServer wcsServer(
configFilePath
, parameterMap
, p
, theRequestHandler.data()
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, accessControl
#endif
);
wcsServer.executeRequest();
}
}
else if ( serviceString == "WFS" )
{
QgsWFSProjectParser* p = QgsConfigCache::instance()->wfsConfiguration( configFilePath );
QgsWFSProjectParser* p = QgsConfigCache::instance()->wfsConfiguration(
configFilePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, accessControl
#endif
);
if ( !p )
{
theRequestHandler->setServiceException( QgsMapServiceException( "Project file error", "Error reading the project file" ) );
}
else
{
QgsWFSServer wfsServer( configFilePath, parameterMap, p, theRequestHandler.data() );
QgsWFSServer wfsServer(
configFilePath
, parameterMap
, p
, theRequestHandler.data()
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, accessControl
#endif
);
wfsServer.executeRequest();
}
}
else if ( serviceString == "WMS" )
{
QgsWMSConfigParser* p = QgsConfigCache::instance()->wmsConfiguration( configFilePath, parameterMap );
QgsWMSConfigParser* p = QgsConfigCache::instance()->wmsConfiguration(
configFilePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, accessControl
#endif
);
if ( !p )
{
theRequestHandler->setServiceException( QgsMapServiceException( "WMS configuration error", "There was an error reading the project file or the SLD configuration" ) );
}
else
{
QgsWMSServer wmsServer( configFilePath, parameterMap, p, theRequestHandler.data(), mMapRenderer, mCapabilitiesCache );
QgsWMSServer wmsServer(
configFilePath
, parameterMap
, p
, theRequestHandler.data()
, mMapRenderer
, mCapabilitiesCache
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, accessControl
#endif
);
wmsServer.executeRequest();
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/server/qgsserverinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@

#include "qgsserverinterface.h"

/** Constructor */
QgsServerInterface::QgsServerInterface():
mConfigFilePath( QString() )
{
}

/** Destructor */
QgsServerInterface::~QgsServerInterface()
{
}
10 changes: 10 additions & 0 deletions src/server/qgsserverinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include "qgscapabilitiescache.h"
#include "qgsrequesthandler.h"
#include "qgsserverfilter.h"
#include "qgsaccesscontrolfilter.h"
#include "qgsaccesscontrol.h"

/**
* \ingroup server
Expand All @@ -45,6 +47,7 @@ class SERVER_EXPORT QgsServerInterface
/**
* Set the request handler
* @param requestHandler request handler
* @note not available in Python bindings
*/
virtual void setRequestHandler( QgsRequestHandler* requestHandler ) = 0;

Expand Down Expand Up @@ -85,6 +88,13 @@ class SERVER_EXPORT QgsServerInterface
* @return QgsServerFiltersMap list of QgsServerFilter
*/
virtual QgsServerFiltersMap filters( ) = 0;
/** Register an access control filter
* @param accessControl the access control to register
* @param priority the priority used to order them
*/
virtual void registerAccessControl( QgsAccessControlFilter* accessControl, int priority = 0 ) = 0;
/** Gets the registred access control filters */
virtual const QgsAccessControl* accessControls( ) const = 0;

//! Return an enrironment variable, used to pass environment variables to python
virtual QString getEnv( const QString& name ) const = 0;
Expand Down
9 changes: 9 additions & 0 deletions src/server/qgsserverinterfaceimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
#include "qgsserverinterfaceimpl.h"


/** Constructor */
QgsServerInterfaceImpl::QgsServerInterfaceImpl( QgsCapabilitiesCache* capCache ) :
mCapabilitiesCache( capCache )
{
mRequestHandler = NULL;
mAccessControls = new QgsAccessControl();
}


Expand All @@ -36,6 +38,7 @@ QString QgsServerInterfaceImpl::getEnv( const QString& name ) const
/** Destructor */
QgsServerInterfaceImpl::~QgsServerInterfaceImpl()
{
delete mAccessControls;
}


Expand Down Expand Up @@ -63,3 +66,9 @@ void QgsServerInterfaceImpl::setFilters( QgsServerFiltersMap* filters )
{
mFilters = *filters;
}

/** Register a new access control filter */
void QgsServerInterfaceImpl::registerAccessControl( QgsAccessControlFilter* accessControl, int priority )
{
mAccessControls->registerAccessControl( accessControl, priority );
}
7 changes: 7 additions & 0 deletions src/server/qgsserverinterfaceimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ class QgsServerInterfaceImpl : public QgsServerInterface
QgsRequestHandler* requestHandler( ) override { return mRequestHandler; }
void registerFilter( QgsServerFilter *filter, int priority = 0 ) override;
QgsServerFiltersMap filters( ) override { return mFilters; }
/** Register an access control filter */
void registerAccessControl( QgsAccessControlFilter *accessControl, int priority = 0 ) override;
/** Gets the helper over all the registered access control filters
* @return the access control helper
*/
const QgsAccessControl* accessControls( ) const override { return mAccessControls; }
QString getEnv( const QString& name ) const override;
QString configFilePath( ) override { return mConfigFilePath; }
void setConfigFilePath( const QString& configFilePath ) override;
Expand All @@ -60,6 +66,7 @@ class QgsServerInterfaceImpl : public QgsServerInterface

QString mConfigFilePath;
QgsServerFiltersMap mFilters;
QgsAccessControl* mAccessControls;
QgsCapabilitiesCache* mCapabilitiesCache;
QgsRequestHandler* mRequestHandler;

Expand Down
6 changes: 3 additions & 3 deletions src/server/qgsserverplugins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ bool QgsServerPlugins::initPlugins( QgsServerInterface *interface )
pythonlibName.prepend( "lib" );
#endif
QString version = QString( "%1.%2.%3" ).arg( QGis::QGIS_VERSION_INT / 10000 ).arg( QGis::QGIS_VERSION_INT / 100 % 100 ).arg( QGis::QGIS_VERSION_INT % 100 );
QgsDebugMsg( QString( "load library %1 (%2)" ).arg( pythonlibName ).arg( version ) );
QgsMessageLog::logMessage( QString( "load library %1 (%2)" ).arg( pythonlibName ).arg( version ), __FILE__, QgsMessageLog::INFO );
QLibrary pythonlib( pythonlibName, version );
// It's necessary to set these two load hints, otherwise Python library won't work correctly
// see http://lists.kde.org/?l=pykde&m=117190116820758&w=2
Expand All @@ -57,12 +57,12 @@ bool QgsServerPlugins::initPlugins( QgsServerInterface *interface )
pythonlib.setFileName( pythonlibName );
if ( !pythonlib.load() )
{
QgsDebugMsg( QString( "Couldn't load Python support library: %1" ).arg( pythonlib.errorString() ) );
QgsMessageLog::logMessage( QString( "Couldn't load Python support library: %1" ).arg( pythonlib.errorString() ) );
return false;
}
}

QgsDebugMsg( "Python support library loaded successfully." );
QgsMessageLog::logMessage( "Python support library loaded successfully.", __FILE__, QgsMessageLog::INFO );
typedef QgsPythonUtils*( *inst )();
inst pythonlib_inst = ( inst ) cast_to_fptr( pythonlib.resolve( "instance" ) );
if ( !pythonlib_inst )
Expand Down
2 changes: 2 additions & 0 deletions src/server/qgsserverprojectparser.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
#ifndef QGSSERVERPROJECTPARSER_H
#define QGSSERVERPROJECTPARSER_H

#include "qgsconfig.h"
#include "qgsvectorlayer.h"

#include <QDomElement>
#include <QHash>
#include <QMap>
Expand Down
32 changes: 30 additions & 2 deletions src/server/qgswcsprojectparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,22 @@
#include "qgsconfigcache.h"
#include "qgsconfigparserutils.h"
#include "qgsrasterlayer.h"

QgsWCSProjectParser::QgsWCSProjectParser( const QString& filePath )
#include "qgsmapserviceexception.h"
#include "qgsmessagelog.h"
#ifdef HAVE_SERVER_PYTHON_PLUGINS
#include "qgsaccesscontrol.h"
#endif


QgsWCSProjectParser::QgsWCSProjectParser(
const QString& filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* as
#endif
)
#ifdef HAVE_SERVER_PYTHON_PLUGINS
: mAccessControl( as )
#endif
{
mProjectParser = QgsConfigCache::instance()->serverConfiguration( filePath );
}
Expand Down Expand Up @@ -81,6 +95,12 @@ void QgsWCSProjectParser::wcsContentMetadata( QDomElement& parentElement, QDomDo
QgsMapLayer *layer = mProjectParser->createLayerFromElement( elem );
if ( layer && wcsLayersId.contains( layer->id() ) )
{
#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( !mAccessControl->layerReadPermission( layer ) )
{
continue;
}
#endif
QgsDebugMsg( QString( "add layer %1 to map" ).arg( layer->id() ) );
layerMap.insert( layer->id(), layer );

Expand Down Expand Up @@ -201,6 +221,14 @@ void QgsWCSProjectParser::describeCoverage( const QString& aCoveName, QDomElemen
QgsRasterLayer *rLayer = dynamic_cast<QgsRasterLayer *>( mProjectParser->createLayerFromElement( elem ) );
if ( !rLayer )
continue;

#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( !mAccessControl->layerReadPermission( rLayer ) )
{
continue;
}
#endif

QString coveName = rLayer->name();
coveName = coveName.replace( " ", "_" );
if ( wcsLayersId.contains( rLayer->id() ) && ( aCoveName == "" || coveNameList.contains( coveName ) ) )
Expand Down
14 changes: 13 additions & 1 deletion src/server/qgswcsprojectparser.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,19 @@

#include "qgsserverprojectparser.h"

#ifdef HAVE_SERVER_PYTHON_PLUGINS
class QgsAccessControl;
#endif

class SERVER_EXPORT QgsWCSProjectParser
{
public:
QgsWCSProjectParser( const QString& filePath );
QgsWCSProjectParser(
const QString& filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* ac
#endif
);
~QgsWCSProjectParser();

void serviceCapabilities( QDomElement& parentElement, QDomDocument& doc ) const;
Expand All @@ -36,6 +45,9 @@ class SERVER_EXPORT QgsWCSProjectParser

private:
QgsServerProjectParser* mProjectParser;
#ifdef HAVE_SERVER_PYTHON_PLUGINS
const QgsAccessControl* mAccessControl;
#endif
};

#endif // QGSWCSPROJECTPARSER_H
41 changes: 36 additions & 5 deletions src/server/qgswcsserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "qgsrasterfilewriter.h"
#include "qgslogger.h"
#include "qgsmapserviceexception.h"
#include "qgsaccesscontrol.h"

#include <QTemporaryFile>
#include <QUrl>
Expand All @@ -37,15 +38,38 @@ static const QString WCS_NAMESPACE = "http://www.opengis.net/wcs";
static const QString GML_NAMESPACE = "http://www.opengis.net/gml";
static const QString OGC_NAMESPACE = "http://www.opengis.net/ogc";

QgsWCSServer::QgsWCSServer( const QString& configFilePath, QMap<QString, QString> &parameters, QgsWCSProjectParser* pp,
QgsRequestHandler* rh )
: QgsOWSServer( configFilePath, parameters, rh )
, mConfigParser( pp )
QgsWCSServer::QgsWCSServer(
const QString& configFilePath
, QMap<QString, QString> &parameters
, QgsWCSProjectParser* pp
, QgsRequestHandler* rh
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* accessControl
#endif
)
: QgsOWSServer(
configFilePath
, parameters
, rh
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, accessControl
#endif
)
, mConfigParser(
pp
)
{
}

QgsWCSServer::QgsWCSServer()
: QgsOWSServer( QString(), QMap<QString, QString>(), 0 )
: QgsOWSServer(
QString()
, QMap<QString, QString>()
, 0
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, NULL
#endif
)
, mConfigParser( 0 )
{
}
Expand Down Expand Up @@ -353,6 +377,13 @@ QByteArray* QgsWCSServer::getCoverage()
QgsRasterLayer* rLayer = dynamic_cast<QgsRasterLayer*>( layer );
if ( rLayer && wcsLayersId.contains( rLayer->id() ) )
{
#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( !mAccessControl->layerReadPermission( rLayer ) )
{
throw QgsMapServiceException( "Security", "You are not allowed to access to this coverage" );
}
#endif

// RESPONSE_CRS
QgsCoordinateReferenceSystem responseCRS = rLayer->crs();
crs = mParameters.value( "RESPONSE_CRS", "" );
Expand Down
11 changes: 9 additions & 2 deletions src/server/qgswcsserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,15 @@ class QgsWCSServer: public QgsOWSServer
{
public:
/** Constructor. Takes parameter map and a pointer to a renderer object (does not take ownership)*/
QgsWCSServer( const QString& configFilePath, QMap<QString, QString>& parameters, QgsWCSProjectParser* pp,
QgsRequestHandler* rh );
QgsWCSServer(
const QString& configFilePath
, QMap<QString, QString>& parameters
, QgsWCSProjectParser* pp
, QgsRequestHandler* rh
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* accessControl
#endif
);
~QgsWCSServer();

void executeRequest() override;
Expand Down
27 changes: 25 additions & 2 deletions src/server/qgswfsprojectparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,18 @@
#include "qgsconfigparserutils.h"
#include "qgsmaplayerregistry.h"
#include "qgsvectordataprovider.h"

QgsWFSProjectParser::QgsWFSProjectParser( const QString& filePath )
#include "qgsmapserviceexception.h"
#include "qgsaccesscontrol.h"

QgsWFSProjectParser::QgsWFSProjectParser(
const QString& filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* ac
#endif
)
#ifdef HAVE_SERVER_PYTHON_PLUGINS
: mAccessControl( ac )
#endif
{
mProjectParser = QgsConfigCache::instance()->serverConfiguration( filePath );
}
Expand Down Expand Up @@ -76,6 +86,12 @@ void QgsWFSProjectParser::featureTypeList( QDomElement& parentElement, QDomDocum
{
continue;
}
#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( !mAccessControl->layerReadPermission( layer ) )
{
continue;
}
#endif
QgsDebugMsg( QString( "add layer %1 to map" ).arg( layer->id() ) );
layerMap.insert( layer->id(), layer );

Expand Down Expand Up @@ -331,6 +347,13 @@ void QgsWFSProjectParser::describeFeatureType( const QString& aTypeName, QDomEle
if ( !layer )
continue;

#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( !mAccessControl->layerReadPermission( layer ) )
{
continue;
}
#endif

QString typeName = layer->name();
typeName = typeName.replace( " ", "_" );

Expand Down
16 changes: 15 additions & 1 deletion src/server/qgswfsprojectparser.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,21 @@

#include "qgsserverprojectparser.h"


#ifdef HAVE_SERVER_PYTHON_PLUGINS
class QgsAccessControl;
#endif


class SERVER_EXPORT QgsWFSProjectParser
{
public:
QgsWFSProjectParser( const QString& filePath );
QgsWFSProjectParser(
const QString& filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* ac
#endif
);
~QgsWFSProjectParser();

void serviceCapabilities( QDomElement& parentElement, QDomDocument& doc ) const;
Expand All @@ -45,6 +56,9 @@ class SERVER_EXPORT QgsWFSProjectParser

private:
QgsServerProjectParser* mProjectParser;
#ifdef HAVE_SERVER_PYTHON_PLUGINS
const QgsAccessControl* mAccessControl;
#endif
};

#endif // QGSWFSPROJECTPARSER_H
161 changes: 144 additions & 17 deletions src/server/qgswfsserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@
#include "qgscoordinatereferencesystem.h"
#include "qgsvectordataprovider.h"
#include "qgsvectorlayer.h"
#include "qgslogger.h"
#include "qgsmessagelog.h"
#include "qgsmapserviceexception.h"
#include "qgssymbolv2.h"
#include "qgslegendmodel.h"
#include "qgscomposerlegenditem.h"
#include "qgsrequesthandler.h"
#include "qgsogcutils.h"
#include "qgsaccesscontrol.h"

#include <QImage>
#include <QPainter>
Expand Down Expand Up @@ -65,16 +66,37 @@ static const QString GML_NAMESPACE = "http://www.opengis.net/gml";
static const QString OGC_NAMESPACE = "http://www.opengis.net/ogc";
static const QString QGS_NAMESPACE = "http://www.qgis.org/gml";

QgsWFSServer::QgsWFSServer( const QString& configFilePath, QMap<QString, QString> &parameters, QgsWFSProjectParser* cp,
QgsRequestHandler* rh )
: QgsOWSServer( configFilePath, parameters, rh )
QgsWFSServer::QgsWFSServer(
const QString& configFilePath
, QMap<QString, QString> &parameters
, QgsWFSProjectParser* cp
, QgsRequestHandler* rh
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* accessControl
#endif
)
: QgsOWSServer(
configFilePath
, parameters
, rh
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, accessControl
#endif
)
, mWithGeom( true )
, mConfigParser( cp )
{
}

QgsWFSServer::QgsWFSServer()
: QgsOWSServer( QString(), QMap<QString, QString>(), 0 )
: QgsOWSServer(
QString()
, QMap<QString, QString>()
, 0
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, NULL
#endif
)
, mWithGeom( true )
, mConfigParser( 0 )
{
Expand All @@ -96,7 +118,7 @@ void QgsWFSServer::executeRequest()
if ( request.isEmpty() )
{
//do some error handling
QgsDebugMsg( "unable to find 'REQUEST' parameter, exiting..." );
QgsMessageLog::logMessage( "unable to find 'REQUEST' parameter, exiting..." );
mRequestHandler->setServiceException( QgsMapServiceException( "OperationNotSupported", "Please check the value of the REQUEST parameter" ) );
return;
}
Expand All @@ -113,7 +135,7 @@ void QgsWFSServer::executeRequest()
mRequestHandler->setServiceException( ex );
return;
}
QgsDebugMsg( "Setting GetCapabilities response" );
QgsMessageLog::logMessage( "Setting GetCapabilities response" );
mRequestHandler->setGetCapabilitiesResponse( capabilitiesDocument );
return;
}
Expand All @@ -129,7 +151,7 @@ void QgsWFSServer::executeRequest()
mRequestHandler->setServiceException( ex );
return;
}
QgsDebugMsg( "Setting GetCapabilities response" );
QgsMessageLog::logMessage( "Setting GetCapabilities response" );
mRequestHandler->setGetCapabilitiesResponse( describeDocument );
return;
}
Expand Down Expand Up @@ -160,15 +182,15 @@ void QgsWFSServer::executeRequest()
mRequestHandler->setServiceException( ex );
return;
}
QgsDebugMsg( "Setting Transaction response" );
QgsMessageLog::logMessage( "Setting Transaction response" );
mRequestHandler->setGetCapabilitiesResponse( transactionDocument );
return;
}
}

QDomDocument QgsWFSServer::getCapabilities()
{
QgsDebugMsg( "Entering." );
QgsMessageLog::logMessage( "Entering." );
QDomDocument doc;

//wfs:WFS_Capabilities element
Expand Down Expand Up @@ -313,7 +335,7 @@ QDomDocument QgsWFSServer::getCapabilities()

QDomDocument QgsWFSServer::describeFeatureType()
{
QgsDebugMsg( "Entering." );
QgsMessageLog::logMessage( "Entering." );
QDomDocument doc;

//xsd:schema
Expand Down Expand Up @@ -376,7 +398,7 @@ QDomDocument QgsWFSServer::describeFeatureType()

int QgsWFSServer::getFeature( QgsRequestHandler& request, const QString& format )
{
QgsDebugMsg( "Info format is:" + format );
QgsMessageLog::logMessage( "Info format is:" + format );

QStringList wfsLayersId = mConfigParser->wfsLayers();

Expand Down Expand Up @@ -438,6 +460,16 @@ int QgsWFSServer::getFeature( QgsRequestHandler& request, const QString& format
QgsVectorLayer* layer = dynamic_cast<QgsVectorLayer*>( currentLayer );
if ( layer && wfsLayersId.contains( layer->id() ) )
{
#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( !mAccessControl->layerReadPermission( currentLayer ) )
{
throw QgsMapServiceException( "Security", "Feature access permission denied" );
}

QMap<QString, QString> originalLayerFilters;
applyAccessControlLayerFilters( currentLayer, originalLayerFilters );
#endif

expressionContext << QgsExpressionContextUtils::layerScope( layer );

//is there alias info for this vector layer?
Expand Down Expand Up @@ -513,10 +545,21 @@ int QgsWFSServer::getFeature( QgsRequestHandler& request, const QString& format
, searchRect.yMaximum() + 1. / pow( 10., layerPrec ) );
layerCrs = layer->crs();

QgsFeatureIterator fit = layer->getFeatures(
QgsFeatureRequest()
.setFlags( QgsFeatureRequest::ExactIntersect | ( mWithGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) )
.setSubsetOfAttributes( attrIndexes ) );
QgsFeatureRequest fReq;
#ifdef HAVE_SERVER_PYTHON_PLUGINS
fReq.setFlags( QgsFeatureRequest::ExactIntersect | ( mWithGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) );
mAccessControl->filterFeatures( layer, fReq );

QStringList attributes = QStringList();
foreach( int idx, attrIndexes ) {
attributes.append( layer->pendingFields().field(idx).name() );
}
fReq.setSubsetOfAttributes(
mAccessControl->layerAttributes( layer, attributes ),
layer->pendingFields() );
#endif

QgsFeatureIterator fit = layer->getFeatures( fReq );

long featCounter = 0;
QDomNodeList filterNodes = queryElem.elementsByTagName( "Filter" );
Expand Down Expand Up @@ -634,6 +677,10 @@ int QgsWFSServer::getFeature( QgsRequestHandler& request, const QString& format
++featureCounter;
}
}

#ifdef HAVE_SERVER_PYTHON_PLUGINS
restoreLayerFilters( originalLayerFilters );
#endif
}
else
{
Expand All @@ -642,6 +689,8 @@ int QgsWFSServer::getFeature( QgsRequestHandler& request, const QString& format

}

QgsMessageLog::logMessage( mErrors.join( "\n" ) );

QgsMapLayerRegistry::instance()->removeAllMapLayers();
if ( featureCounter == 0 )
startGetFeature( request, format, layerPrec, layerCrs, &searchRect );
Expand Down Expand Up @@ -1392,12 +1441,37 @@ QDomDocument QgsWFSServer::transaction( const QString& requestBody )
// it's a vectorlayer and defined by the administrator as a WFS layer
if ( layer && wfsLayersId.contains( layer->id() ) )
{
#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( actionName == "Insert" )
{
if ( !mAccessControl->layerInsertPermission( layer ) )
{
throw QgsMapServiceException( "Security", "Feature insert permission denied" );
}
}
else if ( actionName == "Update" )
{
if ( !mAccessControl->layerUpdatePermission( layer ) )
{
throw QgsMapServiceException( "Security", "Feature update permission denied" );
}
}
else if ( actionName == "Delete" )
{
if ( !mAccessControl->layerDeletePermission( layer ) )
{
throw QgsMapServiceException( "Security", "Feature delete permission denied" );
}
}
#endif

// Get the provider and it's capabilities
QgsVectorDataProvider* provider = layer->dataProvider();
if ( !provider )
{
continue;
}

int cap = provider->capabilities();

// Start the update transaction
Expand Down Expand Up @@ -1457,6 +1531,18 @@ QDomDocument QgsWFSServer::transaction( const QString& requestBody )
QgsFeatureIds::const_iterator fidIt = fids.constBegin();
for ( ; fidIt != fids.constEnd(); ++fidIt )
{
#ifdef HAVE_SERVER_PYTHON_PLUGINS
QgsFeatureIterator fit = layer->getFeatures(QgsFeatureRequest( *fidIt ) );
QgsFeature feature;
while ( fit.nextFeature( feature ) )
{
if ( !mAccessControl->allowToEdit( layer, feature ) )
{
throw QgsMapServiceException( "Security", "Feature modify permission denied" );
}
}
#endif

QMap< QString, QString >::const_iterator it = propertyMap.constBegin();
for ( ; it != propertyMap.constEnd(); ++it )
{
Expand All @@ -1480,6 +1566,18 @@ QDomDocument QgsWFSServer::transaction( const QString& requestBody )
if ( !layer->changeGeometry( *fidIt, QgsOgcUtils::geometryFromGML( geometryElem ) ) )
throw QgsMapServiceException( "RequestNotWellFormed", "Error in change geometry" );
}

#ifdef HAVE_SERVER_PYTHON_PLUGINS
fit = layer->getFeatures(QgsFeatureRequest( *fidIt ) );
while ( fit.nextFeature( feature ) )
{
if ( !mAccessControl->allowToEdit( layer, feature ) )
{
layer->rollBack();
throw QgsMapServiceException( "Security", "Feature modify permission denied" );
}
}
#endif
}
}
}
Expand Down Expand Up @@ -1510,6 +1608,23 @@ QDomDocument QgsWFSServer::transaction( const QString& requestBody )
QDomElement filterElem = actionElem.firstChild().toElement();
// Get Feature Ids for the Filter element
QgsFeatureIds fids = getFeatureIdsFromFilter( filterElem, layer );

#ifdef HAVE_SERVER_PYTHON_PLUGINS
QgsFeatureIds::const_iterator fidIt = fids.constBegin();
for ( ; fidIt != fids.constEnd(); ++fidIt )
{
QgsFeatureIterator fit = layer->getFeatures(QgsFeatureRequest( *fidIt ) );
QgsFeature feature;
while ( fit.nextFeature( feature ) )
{
if ( !mAccessControl->allowToEdit( layer, feature ) )
{
throw QgsMapServiceException( "Security", "Feature modify permission denied" );
}
}
}
#endif

layer->setSelectedFeatures( fids );
layer->deleteSelectedFeatures();
}
Expand Down Expand Up @@ -1575,7 +1690,7 @@ QDomDocument QgsWFSServer::transaction( const QString& requestBody )
const QgsField& field = fields[fieldMapIt.value()];
QString attrValue = currentAttributeElement.text();
int attrType = field.type();
QgsDebugMsg( QString( "attr: name=%1 idx=%2 value=%3" ).arg( attrName ).arg( fieldMapIt.value() ).arg( attrValue ) );
QgsMessageLog::logMessage( QString( "attr: name=%1 idx=%2 value=%3" ).arg( attrName ).arg( fieldMapIt.value() ).arg( attrValue ) );
if ( attrType == QVariant::Int )
inFeatList.last().setAttribute( fieldMapIt.value(), attrValue.toInt() );
else if ( attrType == QVariant::Double )
Expand All @@ -1593,6 +1708,18 @@ QDomDocument QgsWFSServer::transaction( const QString& requestBody )
}
}
}
#ifdef HAVE_SERVER_PYTHON_PLUGINS
QgsFeatureList::iterator featureIt = inFeatList.begin();
while ( featureIt != inFeatList.end() )
{
if ( !mAccessControl->allowToEdit( layer, *featureIt) )
{
throw QgsMapServiceException( "Security", "Feature modify permission denied" );
}
featureIt++;
}
#endif

// add the features
if ( !provider->addFeatures( inFeatList ) )
{
Expand Down
11 changes: 9 additions & 2 deletions src/server/qgswfsserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,15 @@ class QgsWFSServer: public QgsOWSServer
{
public:
/** Constructor. Takes parameter map and a pointer to a renderer object (does not take ownership)*/
QgsWFSServer( const QString& configFilePath, QMap<QString, QString>& parameters, QgsWFSProjectParser* cp,
QgsRequestHandler* rh );
QgsWFSServer(
const QString& configFilePath
, QMap<QString, QString>& parameters
, QgsWFSProjectParser* cp
, QgsRequestHandler* rh
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* accessControl
#endif
);
~QgsWFSServer();

void executeRequest() override;
Expand Down
54 changes: 49 additions & 5 deletions src/server/qgswmsprojectparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "qgscomposershape.h"
#include "qgslayertreegroup.h"
#include "qgslayertreelayer.h"
#include "qgsaccesscontrol.h"

#include <QFileInfo>
#include <QTextDocument>
Expand All @@ -48,8 +49,16 @@
// this implies that a layer style called "default" will not be usable in WMS server
#define EMPTY_STYLE_NAME "default"

QgsWMSProjectParser::QgsWMSProjectParser( const QString& filePath )
QgsWMSProjectParser::QgsWMSProjectParser(
const QString& filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* accessControl
#endif
)
: QgsWMSConfigParser()
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, mAccessControl( accessControl )
#endif
{
mProjectParser = QgsConfigCache::instance()->serverConfiguration( filePath );
mLegendLayerFont.fromString( mProjectParser->firstComposerLegendElement().attribute( "layerFont" ) );
Expand Down Expand Up @@ -185,7 +194,12 @@ QList<QgsMapLayer*> QgsWMSProjectParser::mapLayerFromStyle( const QString& lName
if ( legendIt->attribute( "embedded" ) == "1" )
{
QString project = mProjectParser->convertToAbsolutePath( legendIt->attribute( "project" ) );
QgsWMSProjectParser* p = dynamic_cast<QgsWMSProjectParser*>( QgsConfigCache::instance()->wmsConfiguration( project ) );
QgsWMSProjectParser* p = dynamic_cast<QgsWMSProjectParser*>( QgsConfigCache::instance()->wmsConfiguration(
project
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, mAccessControl
#endif
) );
if ( p )
{
QgsServerProjectParser* pp = p->mProjectParser;
Expand Down Expand Up @@ -229,7 +243,12 @@ void QgsWMSProjectParser::addLayersFromGroup( const QDomElement& legendGroupElem
int drawingOrder = mProjectParser->updateLegendDrawingOrder() ? legendGroupElem.attribute( "drawingOrder", "-1" ).toInt() : -1;

QString project = mProjectParser->convertToAbsolutePath( legendGroupElem.attribute( "project" ) );
QgsWMSProjectParser* p = dynamic_cast<QgsWMSProjectParser*>( QgsConfigCache::instance()->wmsConfiguration( project ) );
QgsWMSProjectParser* p = dynamic_cast<QgsWMSProjectParser*>( QgsConfigCache::instance()->wmsConfiguration(
project
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, mAccessControl
#endif
) );
if ( p )
{
QgsServerProjectParser* pp = p->mProjectParser;
Expand Down Expand Up @@ -945,7 +964,12 @@ void QgsWMSProjectParser::addLayers( QDomDocument &doc,
QString project = mProjectParser->convertToAbsolutePath( currentChildElem.attribute( "project" ) );
QgsDebugMsg( QString( "Project path: %1" ).arg( project ) );
QString embeddedGroupName = currentChildElem.attribute( "name" );
QgsWMSProjectParser* p = dynamic_cast<QgsWMSProjectParser*>( QgsConfigCache::instance()->wmsConfiguration( project ) );
QgsWMSProjectParser* p = dynamic_cast<QgsWMSProjectParser*>( QgsConfigCache::instance()->wmsConfiguration(
project
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, mAccessControl
#endif
) );
if ( p )
{
QgsServerProjectParser* pp = p->mProjectParser;
Expand Down Expand Up @@ -1001,6 +1025,13 @@ void QgsWMSProjectParser::addLayers( QDomDocument &doc,
{
continue;
}
#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( !mAccessControl->layerReadPermission( currentLayer ) )
{
continue;
}
#endif

// queryable layer
if ( nonIdentifiableLayers.contains( currentLayer->id() ) )
{
Expand Down Expand Up @@ -1273,7 +1304,12 @@ void QgsWMSProjectParser::addOWSLayers( QDomDocument &doc,
QString project = mProjectParser->convertToAbsolutePath( currentChildElem.attribute( "project" ) );
QgsDebugMsg( QString( "Project path: %1" ).arg( project ) );
QString embeddedGroupName = currentChildElem.attribute( "name" );
QgsWMSProjectParser* p = dynamic_cast<QgsWMSProjectParser*>( QgsConfigCache::instance()->wmsConfiguration( project ) );
QgsWMSProjectParser* p = dynamic_cast<QgsWMSProjectParser*>( QgsConfigCache::instance()->wmsConfiguration(
project
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, mAccessControl
#endif
) );
if ( p )
{
QgsServerProjectParser* pp = p->mProjectParser;
Expand Down Expand Up @@ -1640,6 +1676,14 @@ QDomDocument QgsWMSProjectParser::describeLayer( QStringList& layerList, const Q
for ( int j = 0; j < currentLayerList.size(); j++ )
{
QgsMapLayer* currentLayer = currentLayerList.at( j );

#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( !mAccessControl->layerReadPermission( currentLayer ) )
{
throw QgsMapServiceException( "Security", "You are not allowed to access to this layer" );
}
#endif

QString layerTypeName = mProjectParser->useLayerIDs() ? currentLayer->id() : currentLayer->name();

// Create the NamedLayer element
Expand Down
14 changes: 13 additions & 1 deletion src/server/qgswmsprojectparser.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,22 @@
#include "qgsserverprojectparser.h"
#include "qgslayertreegroup.h"

#ifdef HAVE_SERVER_PYTHON_PLUGINS
class QgsAccessControl;
#endif

class QTextDocument;
class QSvgRenderer;

class SERVER_EXPORT QgsWMSProjectParser : public QgsWMSConfigParser
{
public:
QgsWMSProjectParser( const QString& filePath );
QgsWMSProjectParser(
const QString& filePath
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* accessControl
#endif
);
virtual ~QgsWMSProjectParser();

/** Adds layer and style specific capabilities elements to the parent node. This includes the individual layers and styles, their description, native CRS, bounding boxes, etc.
Expand Down Expand Up @@ -114,6 +123,9 @@ class SERVER_EXPORT QgsWMSProjectParser : public QgsWMSConfigParser

private:
QgsServerProjectParser* mProjectParser;
#ifdef HAVE_SERVER_PYTHON_PLUGINS
const QgsAccessControl* mAccessControl;
#endif

mutable QFont mLegendLayerFont;
mutable QFont mLegendItemFont;
Expand Down
254 changes: 193 additions & 61 deletions src/server/qgswmsserver.cpp

Large diffs are not rendered by default.

40 changes: 29 additions & 11 deletions src/server/qgswmsserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class QgsRenderContext;
class QgsVectorLayer;
class QgsSymbol;
class QgsSymbolV2;
class QgsAccessControl;

class QColor;
class QFile;
class QFont;
Expand All @@ -61,8 +63,17 @@ class QgsWMSServer: public QgsOWSServer
public:
/** Constructor. Does _NOT_ take ownership of
QgsConfigParser, QgsCapabilitiesCache and QgsMapRenderer*/
QgsWMSServer( const QString& configFilePath, QMap<QString, QString> &parameters, QgsWMSConfigParser* cp, QgsRequestHandler* rh,
QgsMapRenderer* renderer, QgsCapabilitiesCache* capCache );
QgsWMSServer(
const QString& configFilePath
, QMap<QString, QString> &parameters
, QgsWMSConfigParser* cp
, QgsRequestHandler* rh
, QgsMapRenderer* renderer
, QgsCapabilitiesCache* capCache
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, const QgsAccessControl* accessControl
#endif
);
~QgsWMSServer();

void executeRequest() override;
Expand Down Expand Up @@ -178,8 +189,13 @@ class QgsWMSServer: public QgsOWSServer
/** Apply filter (subset) strings from the request to the layers. Example: '&FILTER=<layer1>:"AND property > 100",<layer2>:"AND bla = 'hallo!'" '
@return a map with the original filters ( layer id / filter string )*/
QMap<QString, QString> applyRequestedLayerFilters( const QStringList& layerList ) const;
/** Restores the original layer filters*/
void restoreLayerFilters( const QMap < QString, QString >& filterMap ) const;
#ifdef HAVE_SERVER_PYTHON_PLUGINS
/** Apply filter strings from the access control to the layers.
* @param layerList layers to filter
* @param originalLayerFilters the original layers filter dictionary
*/
void applyAccessControlLayersFilters( const QStringList& layerList, QMap<QString, QString>& originalLayerFilters ) const;
#endif
/** Tests if a filter sql string is allowed (safe)
@return true in case of success, false if string seems unsafe*/
bool testFilterStringSafety( const QString& filter ) const;
Expand Down Expand Up @@ -236,13 +252,15 @@ class QgsWMSServer: public QgsOWSServer
bool mDrawLegendLayerLabel;
bool mDrawLegendItemLabel;

QDomElement createFeatureGML( QgsFeature* feat,
QgsVectorLayer* layer,
QDomDocument& doc,
QgsCoordinateReferenceSystem& crs,
const QString& typeName,
bool withGeom,
int version ) const;
QDomElement createFeatureGML(
QgsFeature* feat,
QgsVectorLayer* layer,
QDomDocument& doc,
QgsCoordinateReferenceSystem& crs,
const QString& typeName,
bool withGeom,
int version,
QStringList* attributes=NULL) const;

/** Replaces attribute value with ValueRelation or ValueRelation if defined. Otherwise returns the original value*/
static QString replaceValueMapAndRelation( QgsVectorLayer* vl, int idx, const QString& attributeVal );
Expand Down
1 change: 1 addition & 0 deletions tests/src/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,5 @@ ENDIF (WITH_APIDOC)

IF (WITH_SERVER)
ADD_PYTHON_TEST(PyQgsServer test_qgsserver.py)
ADD_PYTHON_TEST(PyQgsServerAccessControl test_qgsserver_accesscontrol.py)
ENDIF (WITH_SERVER)
1,056 changes: 1,056 additions & 0 deletions tests/src/python/test_qgsserver_accesscontrol.py

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 58 additions & 0 deletions tests/testdata/qgis_server_accesscontrol/Country.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="1.8.0-Trunk" minimumScale="0" maximumScale="1e+08" hasScaleBasedVisibilityFlag="0">
<transparencyLevelInt>255</transparencyLevelInt>
<renderer-v2 symbollevels="0" type="singleSymbol">
<symbols>
<symbol outputUnit="MM" alpha="1" type="fill" name="0">
<layer pass="0" class="SimpleFill" locked="0">
<prop k="color" v="145,149,158,255"/>
<prop k="color_border" v="157,157,157,255"/>
<prop k="offset" v="2,2"/>
<prop k="style" v="solid"/>
<prop k="style_border" v="solid"/>
<prop k="width_border" v="0.26"/>
</layer>
<layer pass="0" class="SimpleFill" locked="0">
<prop k="color" v="0,0,255,255"/>
<prop k="color_border" v="0,0,0,255"/>
<prop k="offset" v="0,0"/>
<prop k="style" v="solid"/>
<prop k="style_border" v="solid"/>
<prop k="width_border" v="0.26"/>
</layer>
</symbol>
</symbols>
<rotation field=""/>
<sizescale field=""/>
</renderer-v2>
<customproperties/>
<displayfield>name</displayfield>
<label>0</label>
<labelattributes>
<label fieldname="" text="Label"/>
<family fieldname="" name="Ubuntu"/>
<size fieldname="" units="pt" value="12"/>
<bold fieldname="" on="0"/>
<italic fieldname="" on="0"/>
<underline fieldname="" on="0"/>
<strikeout fieldname="" on="0"/>
<color fieldname="" red="0" blue="0" green="0"/>
<x fieldname=""/>
<y fieldname=""/>
<offset x="0" y="0" units="pt" yfieldname="" xfieldname=""/>
<angle fieldname="" value="0" auto="0"/>
<alignment fieldname="" value="center"/>
<buffercolor fieldname="" red="255" blue="255" green="255"/>
<buffersize fieldname="" units="pt" value="1"/>
<bufferenabled fieldname="" on=""/>
<multilineenabled fieldname="" on=""/>
<selectedonly on=""/>
</labelattributes>
<edittypes>
<edittype type="0" name="name"/>
</edittypes>
<editform>.</editform>
<editforminit></editforminit>
<annotationform>.</annotationform>
<attributeactions/>
</qgis>
59 changes: 59 additions & 0 deletions tests/testdata/qgis_server_accesscontrol/Hello.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="1.8.0-Trunk" minimumScale="0" maximumScale="1e+08" hasScaleBasedVisibilityFlag="0">
<transparencyLevelInt>255</transparencyLevelInt>
<renderer-v2 symbollevels="0" type="singleSymbol">
<symbols>
<symbol outputUnit="MM" alpha="1" type="fill" name="0">
<layer pass="0" class="SimpleFill" locked="0">
<prop k="color" v="174,176,194,255"/>
<prop k="color_border" v="121,121,121,255"/>
<prop k="offset" v="2,2"/>
<prop k="style" v="solid"/>
<prop k="style_border" v="solid"/>
<prop k="width_border" v="0.26"/>
</layer>
<layer pass="0" class="SimpleFill" locked="0">
<prop k="color" v="224,15,183,255"/>
<prop k="color_border" v="0,0,0,255"/>
<prop k="offset" v="0,0"/>
<prop k="style" v="solid"/>
<prop k="style_border" v="solid"/>
<prop k="width_border" v="0.26"/>
</layer>
</symbol>
</symbols>
<rotation field=""/>
<sizescale field=""/>
</renderer-v2>
<customproperties/>
<displayfield>pkuid</displayfield>
<label>0</label>
<labelattributes>
<label fieldname="" text="Label"/>
<family fieldname="" name="Ubuntu"/>
<size fieldname="" units="pt" value="12"/>
<bold fieldname="" on="0"/>
<italic fieldname="" on="0"/>
<underline fieldname="" on="0"/>
<strikeout fieldname="" on="0"/>
<color fieldname="" red="0" blue="0" green="0"/>
<x fieldname=""/>
<y fieldname=""/>
<offset x="0" y="0" units="pt" yfieldname="" xfieldname=""/>
<angle fieldname="" value="0" auto="0"/>
<alignment fieldname="" value="center"/>
<buffercolor fieldname="" red="255" blue="255" green="255"/>
<buffersize fieldname="" units="pt" value="1"/>
<bufferenabled fieldname="" on=""/>
<multilineenabled fieldname="" on=""/>
<selectedonly on=""/>
</labelattributes>
<edittypes>
<edittype type="0" name="colour"/>
<edittype type="0" name="pkuid"/>
</edittypes>
<editform>../projects</editform>
<editforminit></editforminit>
<annotationform>../projects</annotationform>
<attributeactions/>
</qgis>
Binary file added tests/testdata/qgis_server_accesscontrol/dem.tif
Binary file not shown.
20 changes: 20 additions & 0 deletions tests/testdata/qgis_server_accesscontrol/dem.tif.aux.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<PAMDataset>
<PAMRasterBand band="1">
<Histograms>
<HistItem>
<HistMin>-18.929</HistMin>
<HistMax>3842.929</HistMax>
<BucketCount>1000</BucketCount>
<IncludeOutOfRange>0</IncludeOutOfRange>
<Approximate>1</Approximate>
<HistCounts>0|0|0|0|3|4|6|3|4|4|5|5|2|3|2|8|3|3|4|6|4|4|1|3|8|6|2|2|6|3|6|3|3|2|2|3|5|4|2|2|5|4|2|2|2|2|1|0|4|4|0|4|3|3|0|1|3|0|0|2|1|1|4|1|0|1|3|3|2|3|1|2|3|1|2|2|0|0|3|3|0|3|2|2|3|1|2|3|2|0|3|2|2|0|2|0|0|2|1|1|2|3|1|0|2|3|1|1|2|1|2|1|0|1|1|1|1|0|2|0|1|1|2|1|2|0|0|0|3|1|1|0|1|0|2|2|1|0|2|1|3|4|4|1|0|1|1|3|3|0|2|1|0|0|1|1|1|2|0|1|0|2|2|0|2|1|0|0|1|3|0|0|1|2|2|2|1|1|0|1|1|1|4|2|1|1|1|1|1|1|2|1|1|4|0|1|0|0|1|1|0|2|3|1|4|1|0|2|0|3|1|2|2|1|1|5|0|1|2|0|1|1|3|1|0|0|1|0|2|2|2|5|0|1|0|2|0|0|3|1|0|1|2|0|1|1|0|1|0|3|1|2|0|1|0|0|1|1|1|2|0|0|0|1|2|0|0|0|1|0|0|0|0|0|0|2|1|1|0|1|0|2|1|0|2|1|1|0|1|2|0|0|0|0|2|3|2|0|2|0|1|0|0|1|1|0|1|0|2|0|1|1|0|1|1|1|2|0|0|2|1|1|0|1|0|0|1|3|1|0|0|0|1|0|0|0|1|1|1|0|0|2|2|1|0|0|0|2|0|2|0|0|0|0|0|0|0|0|1|1|1|0|1|0|0|0|0|0|0|0|1|0|0|1|0|1|0|1|0|0|0|0|0|0|0|0|0|0|0|1|0|0|1|2|0|1|0|0|1|0|0|0|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0|0|1|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0|0|0|0|0|1|0|0|1|0|0|0|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|1|0|0|0|0|0|0|0|0|0|0|1|0|0|0|0|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0</HistCounts>
</HistItem>
</Histograms>
<Metadata>
<MDI key="STATISTICS_MAXIMUM">3841</MDI>
<MDI key="STATISTICS_MEAN">550.59810315778</MDI>
<MDI key="STATISTICS_MINIMUM">-17</MDI>
<MDI key="STATISTICS_STDDEV">506.70554545277</MDI>
</Metadata>
</PAMRasterBand>
</PAMDataset>
Binary file not shown.
Binary file not shown.
Loading