Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[afs][needs-docs] Allow users to set a specific referer for AFS conne…
…ctions, for

use with ArcGIS feature server requests

Some servers are locked down to only allow requests with a specific
referer URL - this allows us to open them in QGIS (but it requires
users to manually determine the correct referer string and populate
this setting for the connection -- we cannot do this for them!)
  • Loading branch information
nyalldawson committed Jan 24, 2019
1 parent b0b196a commit 996f486
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 209 deletions.
1 change: 1 addition & 0 deletions python/gui/auto_generated/qgsnewhttpconnection.sip.in
Expand Up @@ -36,6 +36,7 @@ information for an HTTP Server for WMS, etc.
{
FlagShowTestConnection,
FlagHideAuthenticationGroup,
FlagShowHttpSettings,
};
typedef QFlags<QgsNewHttpConnection::Flag> Flags;

Expand Down
7 changes: 7 additions & 0 deletions src/core/qgsowsconnection.cpp
Expand Up @@ -65,6 +65,13 @@ QgsOwsConnection::QgsOwsConnection( const QString &service, const QString &connN
}
mConnectionInfo.append( ",authcfg=" + authcfg );

const QString referer = settings.value( key + "/referer" ).toString();
if ( !referer.isEmpty() )
{
mUri.setParam( QStringLiteral( "referer" ), referer );
mConnectionInfo.append( ",referer=" + referer );
}

if ( mService.compare( QLatin1String( "WMS" ), Qt::CaseInsensitive ) == 0
|| mService.compare( QLatin1String( "WCS" ), Qt::CaseInsensitive ) == 0 )
{
Expand Down
7 changes: 7 additions & 0 deletions src/gui/qgsnewhttpconnection.cpp
Expand Up @@ -34,6 +34,9 @@ QgsNewHttpConnection::QgsNewHttpConnection( QWidget *parent, ConnectionTypes typ
{
setupUi( this );

if ( !( flags & FlagShowHttpSettings ) )
mHttpGroupBox->hide();

QgsGui::enableAutoGeometryRestore( this );

connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsNewHttpConnection::showHelp );
Expand Down Expand Up @@ -88,6 +91,7 @@ QgsNewHttpConnection::QgsNewHttpConnection( QWidget *parent, ConnectionTypes typ
QString credentialsKey = "qgis/" + mCredentialsBaseKey + '/' + connectionName;
txtName->setText( connectionName );
txtUrl->setText( settings.value( key + "/url" ).toString() );
mRefererLineEdit->setText( settings.value( key + "/referer" ).toString() );

updateServiceSpecificSettings();

Expand Down Expand Up @@ -444,6 +448,9 @@ void QgsNewHttpConnection::accept()

settings.setValue( credentialsKey + "/authcfg", mAuthSettings->configId() );

if ( mHttpGroupBox->isVisible() )
settings.setValue( key + "/referer", mRefererLineEdit->text() );

settings.setValue( mBaseKey + "/selected", txtName->text() );

QDialog::accept();
Expand Down
1 change: 1 addition & 0 deletions src/gui/qgsnewhttpconnection.h
Expand Up @@ -57,6 +57,7 @@ class GUI_EXPORT QgsNewHttpConnection : public QDialog, private Ui::QgsNewHttpCo
{
FlagShowTestConnection = 1 << 1, //!< Display the 'test connection' button
FlagHideAuthenticationGroup = 1 << 2, //!< Hide the Authentication group
FlagShowHttpSettings = 1 << 3, //!< Display the 'http' group
};
Q_DECLARE_FLAGS( Flags, Flag )

Expand Down
65 changes: 37 additions & 28 deletions src/providers/arcgisrest/qgsafsdataitems.cpp
Expand Up @@ -75,7 +75,7 @@ void QgsAfsRootItem::onConnectionsChanged()

void QgsAfsRootItem::newConnection()
{
QgsNewHttpConnection nc( nullptr, QgsNewHttpConnection::ConnectionOther, QStringLiteral( "qgis/connections-arcgisfeatureserver/" ) );
QgsNewHttpConnection nc( nullptr, QgsNewHttpConnection::ConnectionOther, QStringLiteral( "qgis/connections-arcgisfeatureserver/" ), QString(), QgsNewHttpConnection::FlagShowHttpSettings );
nc.setWindowTitle( tr( "Create a New ArcGIS Feature Server Connection" ) );

if ( nc.exec() )
Expand All @@ -87,31 +87,31 @@ void QgsAfsRootItem::newConnection()

///////////////////////////////////////////////////////////////////////////////

void addFolderItems( QVector< QgsDataItem * > &items, const QVariantMap &serviceData, const QString &baseUrl, const QString &authcfg, QgsDataItem *parent )
void addFolderItems( QVector< QgsDataItem * > &items, const QVariantMap &serviceData, const QString &baseUrl, const QString &authcfg, const QgsStringMap &headers, QgsDataItem *parent )
{
QgsArcGisRestUtils::visitFolderItems( [parent, &baseUrl, &items, authcfg]( const QString & name, const QString & url )
QgsArcGisRestUtils::visitFolderItems( [parent, &baseUrl, &items, headers, authcfg]( const QString & name, const QString & url )
{
std::unique_ptr< QgsAfsFolderItem > folderItem = qgis::make_unique< QgsAfsFolderItem >( parent, name, url, baseUrl, authcfg );
std::unique_ptr< QgsAfsFolderItem > folderItem = qgis::make_unique< QgsAfsFolderItem >( parent, name, url, baseUrl, authcfg, headers );
items.append( folderItem.release() );
}, serviceData, baseUrl );
}

void addServiceItems( QVector< QgsDataItem * > &items, const QVariantMap &serviceData, const QString &baseUrl, const QString &authcfg, QgsDataItem *parent )
void addServiceItems( QVector< QgsDataItem * > &items, const QVariantMap &serviceData, const QString &baseUrl, const QString &authcfg, const QgsStringMap &headers, QgsDataItem *parent )
{
QgsArcGisRestUtils::visitServiceItems(
[&items, parent, authcfg]( const QString & name, const QString & url )
[&items, parent, authcfg, headers]( const QString & name, const QString & url )
{
std::unique_ptr< QgsAfsServiceItem > serviceItem = qgis::make_unique< QgsAfsServiceItem >( parent, name, url, url, authcfg );
std::unique_ptr< QgsAfsServiceItem > serviceItem = qgis::make_unique< QgsAfsServiceItem >( parent, name, url, url, authcfg, headers );
items.append( serviceItem.release() );
}, serviceData, baseUrl );
}

void addLayerItems( QVector< QgsDataItem * > &items, const QVariantMap &serviceData, const QString &parentUrl, const QString &authcfg, QgsDataItem *parent )
void addLayerItems( QVector< QgsDataItem * > &items, const QVariantMap &serviceData, const QString &parentUrl, const QString &authcfg, const QgsStringMap &headers, QgsDataItem *parent )
{
QMap< QString, QgsDataItem * > layerItems;
QMap< QString, QString > parents;

QgsArcGisRestUtils::addLayerItems( [parent, &layerItems, &parents, authcfg]( const QString & parentLayerId, const QString & id, const QString & name, const QString & description, const QString & url, bool isParent, const QString & authid )
QgsArcGisRestUtils::addLayerItems( [parent, &layerItems, &parents, authcfg, headers]( const QString & parentLayerId, const QString & id, const QString & name, const QString & description, const QString & url, bool isParent, const QString & authid )
{
Q_UNUSED( description );

Expand All @@ -120,12 +120,12 @@ void addLayerItems( QVector< QgsDataItem * > &items, const QVariantMap &serviceD

if ( isParent )
{
std::unique_ptr< QgsAfsParentLayerItem > layerItem = qgis::make_unique< QgsAfsParentLayerItem >( parent, name, url, authcfg );
std::unique_ptr< QgsAfsParentLayerItem > layerItem = qgis::make_unique< QgsAfsParentLayerItem >( parent, name, url, authcfg, headers );
layerItems.insert( id, layerItem.release() );
}
else
{
std::unique_ptr< QgsAfsLayerItem > layerItem = qgis::make_unique< QgsAfsLayerItem >( parent, name, url, name, authid, authcfg );
std::unique_ptr< QgsAfsLayerItem > layerItem = qgis::make_unique< QgsAfsLayerItem >( parent, name, url, name, authid, authcfg, headers );
layerItems.insert( id, layerItem.release() );
}

Expand Down Expand Up @@ -158,10 +158,14 @@ QVector<QgsDataItem *> QgsAfsConnectionItem::createChildren()
const QgsOwsConnection connection( QStringLiteral( "ARCGISFEATURESERVER" ), mConnName );
const QString url = connection.uri().param( QStringLiteral( "url" ) );
const QString authcfg = connection.uri().param( QStringLiteral( "authcfg" ) );
const QString referer = connection.uri().param( QStringLiteral( "referer" ) );
QgsStringMap headers;
if ( ! referer.isEmpty() )
headers[ QStringLiteral( "Referer" )] = referer;

QVector<QgsDataItem *> items;
QString errorTitle, errorMessage;
const QVariantMap serviceData = QgsArcGisRestUtils::getServiceInfo( url, authcfg, errorTitle, errorMessage );
const QVariantMap serviceData = QgsArcGisRestUtils::getServiceInfo( url, authcfg, errorTitle, errorMessage, headers );
if ( serviceData.isEmpty() )
{
if ( !errorMessage.isEmpty() )
Expand All @@ -174,9 +178,9 @@ QVector<QgsDataItem *> QgsAfsConnectionItem::createChildren()
return items;
}

addFolderItems( items, serviceData, url, authcfg, this );
addServiceItems( items, serviceData, url, authcfg, this );
addLayerItems( items, serviceData, url, authcfg, this );
addFolderItems( items, serviceData, url, authcfg, headers, this );
addServiceItems( items, serviceData, url, authcfg, headers, this );
addLayerItems( items, serviceData, url, authcfg, headers, this );

return items;
}
Expand Down Expand Up @@ -219,7 +223,7 @@ QList<QAction *> QgsAfsConnectionItem::actions( QWidget *parent )

void QgsAfsConnectionItem::editConnection()
{
QgsNewHttpConnection nc( nullptr, QgsNewHttpConnection::ConnectionOther, QStringLiteral( "qgis/connections-arcgisfeatureserver/" ), mName );
QgsNewHttpConnection nc( nullptr, QgsNewHttpConnection::ConnectionOther, QStringLiteral( "qgis/connections-arcgisfeatureserver/" ), mName, QgsNewHttpConnection::FlagShowHttpSettings );
nc.setWindowTitle( tr( "Modify ArcGIS Feature Server Connection" ) );

if ( nc.exec() )
Expand Down Expand Up @@ -255,10 +259,11 @@ void QgsAfsConnectionItem::refreshConnection()
#endif


QgsAfsFolderItem::QgsAfsFolderItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &baseUrl, const QString &authcfg )
QgsAfsFolderItem::QgsAfsFolderItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &baseUrl, const QString &authcfg, const QgsStringMap &headers )
: QgsDataCollectionItem( parent, name, path )
, mBaseUrl( baseUrl )
, mAuthCfg( authcfg )
, mHeaders( headers )
{
mIconName = QStringLiteral( "mIconDbSchema.svg" );
mCapabilities |= Collapse;
Expand All @@ -272,7 +277,7 @@ QVector<QgsDataItem *> QgsAfsFolderItem::createChildren()

QVector<QgsDataItem *> items;
QString errorTitle, errorMessage;
const QVariantMap serviceData = QgsArcGisRestUtils::getServiceInfo( url, mAuthCfg, errorTitle, errorMessage );
const QVariantMap serviceData = QgsArcGisRestUtils::getServiceInfo( url, mAuthCfg, errorTitle, errorMessage, mHeaders );
if ( serviceData.isEmpty() )
{
if ( !errorMessage.isEmpty() )
Expand All @@ -285,9 +290,9 @@ QVector<QgsDataItem *> QgsAfsFolderItem::createChildren()
return items;
}

addFolderItems( items, serviceData, mBaseUrl, mAuthCfg, this );
addServiceItems( items, serviceData, mBaseUrl, mAuthCfg, this );
addLayerItems( items, serviceData, mPath, mAuthCfg, this );
addFolderItems( items, serviceData, mBaseUrl, mAuthCfg, mHeaders, this );
addServiceItems( items, serviceData, mBaseUrl, mAuthCfg, mHeaders, this );
addLayerItems( items, serviceData, mPath, mAuthCfg, mHeaders, this );
return items;
}

Expand All @@ -297,10 +302,11 @@ bool QgsAfsFolderItem::equal( const QgsDataItem *other )
return ( type() == other->type() && o && mPath == o->mPath && mName == o->mName );
}

QgsAfsServiceItem::QgsAfsServiceItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &baseUrl, const QString &authcfg )
QgsAfsServiceItem::QgsAfsServiceItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &baseUrl, const QString &authcfg, const QgsStringMap &headers )
: QgsDataCollectionItem( parent, name, path )
, mBaseUrl( baseUrl )
, mAuthCfg( authcfg )
, mHeaders( headers )
{
mIconName = QStringLiteral( "mIconDbSchema.svg" );
mCapabilities |= Collapse;
Expand All @@ -313,7 +319,7 @@ QVector<QgsDataItem *> QgsAfsServiceItem::createChildren()

QVector<QgsDataItem *> items;
QString errorTitle, errorMessage;
const QVariantMap serviceData = QgsArcGisRestUtils::getServiceInfo( url, mAuthCfg, errorTitle, errorMessage );
const QVariantMap serviceData = QgsArcGisRestUtils::getServiceInfo( url, mAuthCfg, errorTitle, errorMessage, mHeaders );
if ( serviceData.isEmpty() )
{
if ( !errorMessage.isEmpty() )
Expand All @@ -326,9 +332,9 @@ QVector<QgsDataItem *> QgsAfsServiceItem::createChildren()
return items;
}

addFolderItems( items, serviceData, mBaseUrl, mAuthCfg, this );
addServiceItems( items, serviceData, mBaseUrl, mAuthCfg, this );
addLayerItems( items, serviceData, mPath, mAuthCfg, this );
addFolderItems( items, serviceData, mBaseUrl, mAuthCfg, mHeaders, this );
addServiceItems( items, serviceData, mBaseUrl, mAuthCfg, mHeaders, this );
addLayerItems( items, serviceData, mPath, mAuthCfg, mHeaders, this );
return items;
}

Expand All @@ -338,20 +344,23 @@ bool QgsAfsServiceItem::equal( const QgsDataItem *other )
return ( type() == other->type() && o && mPath == o->mPath && mName == o->mName );
}

QgsAfsLayerItem::QgsAfsLayerItem( QgsDataItem *parent, const QString &, const QString &url, const QString &title, const QString &authid, const QString &authcfg )
QgsAfsLayerItem::QgsAfsLayerItem( QgsDataItem *parent, const QString &, const QString &url, const QString &title, const QString &authid, const QString &authcfg, const QgsStringMap &headers )
: QgsLayerItem( parent, title, url, QString(), QgsLayerItem::Vector, QStringLiteral( "arcgisfeatureserver" ) )
{
mUri = QStringLiteral( "crs='%1' url='%2'" ).arg( authid, url );
if ( !authcfg.isEmpty() )
mUri += QStringLiteral( " authcfg='%1'" ).arg( authcfg );
if ( !headers.value( QStringLiteral( "Referer" ) ).isEmpty() )
mUri += QStringLiteral( " referer='%1'" ).arg( headers.value( QStringLiteral( "Referer" ) ) );
setState( Populated );
mIconName = QStringLiteral( "mIconAfs.svg" );
setToolTip( url );
}

QgsAfsParentLayerItem::QgsAfsParentLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &authcfg )
QgsAfsParentLayerItem::QgsAfsParentLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &authcfg, const QgsStringMap &headers )
: QgsDataItem( QgsDataItem::Collection, parent, name, path )
, mAuthCfg( authcfg )
, mHeaders( headers )
{
mCapabilities |= Fast;
mIconName = QStringLiteral( "mIconDbSchema.svg" );
Expand Down
11 changes: 7 additions & 4 deletions src/providers/arcgisrest/qgsafsdataitems.h
Expand Up @@ -73,41 +73,44 @@ class QgsAfsFolderItem : public QgsDataCollectionItem
{
Q_OBJECT
public:
QgsAfsFolderItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &baseUrl, const QString &authcfg );
QgsAfsFolderItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &baseUrl, const QString &authcfg, const QgsStringMap &headers );
QVector<QgsDataItem *> createChildren() override;
bool equal( const QgsDataItem *other ) override;

private:
QString mFolder;
QString mBaseUrl;
QString mAuthCfg;
QgsStringMap mHeaders;
};

class QgsAfsServiceItem : public QgsDataCollectionItem
{
Q_OBJECT
public:
QgsAfsServiceItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &baseUrl, const QString &authcfg );
QgsAfsServiceItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &baseUrl, const QString &authcfg, const QgsStringMap &headers );
QVector<QgsDataItem *> createChildren() override;
bool equal( const QgsDataItem *other ) override;

private:
QString mFolder;
QString mBaseUrl;
QString mAuthCfg;
QgsStringMap mHeaders;
};

class QgsAfsParentLayerItem : public QgsDataItem
{
Q_OBJECT
public:

QgsAfsParentLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &authcfg );
QgsAfsParentLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &authcfg, const QgsStringMap &headers );
bool equal( const QgsDataItem *other ) override;

private:

QString mAuthCfg;
QgsStringMap mHeaders;

};

Expand All @@ -117,7 +120,7 @@ class QgsAfsLayerItem : public QgsLayerItem

public:

QgsAfsLayerItem( QgsDataItem *parent, const QString &name, const QString &url, const QString &title, const QString &authid, const QString &authcfg );
QgsAfsLayerItem( QgsDataItem *parent, const QString &name, const QString &url, const QString &title, const QString &authid, const QString &authcfg, const QgsStringMap &headers );

};

Expand Down
9 changes: 7 additions & 2 deletions src/providers/arcgisrest/qgsafsprovider.cpp
Expand Up @@ -54,8 +54,13 @@ QgsAfsProvider::QgsAfsProvider( const QString &uri, const ProviderOptions &optio

// Get layer info
QString errorTitle, errorMessage;

const QString referer = mSharedData->mDataSource.param( QStringLiteral( "referer" ) );
if ( !referer.isEmpty() )
mRequestHeaders[ QStringLiteral( "Referer" )] = referer;

const QVariantMap layerData = QgsArcGisRestUtils::getLayerInfo( mSharedData->mDataSource.param( QStringLiteral( "url" ) ),
authcfg, errorTitle, errorMessage );
authcfg, errorTitle, errorMessage, mRequestHeaders );
if ( layerData.isEmpty() )
{
pushError( errorTitle + ": " + errorMessage );
Expand Down Expand Up @@ -170,7 +175,7 @@ QgsAfsProvider::QgsAfsProvider( const QString &uri, const ProviderOptions &optio
// and we need to store these to iterate through the features. This query
// also returns the name of the ObjectID field.
QVariantMap objectIdData = QgsArcGisRestUtils::getObjectIds( mSharedData->mDataSource.param( QStringLiteral( "url" ) ), authcfg,
objectIdFieldName, errorTitle, errorMessage, limitBbox ? mSharedData->mExtent : QgsRectangle() );
objectIdFieldName, errorTitle, errorMessage, mRequestHeaders, limitBbox ? mSharedData->mExtent : QgsRectangle() );
if ( objectIdData.isEmpty() )
{
appendError( QgsErrorMessage( tr( "getObjectIds failed: %1 - %2" ).arg( errorTitle, errorMessage ), QStringLiteral( "AFSProvider" ) ) );
Expand Down
1 change: 1 addition & 0 deletions src/providers/arcgisrest/qgsafsprovider.h
Expand Up @@ -82,6 +82,7 @@ class QgsAfsProvider : public QgsVectorDataProvider
QgsLayerMetadata mLayerMetadata;
QVariantMap mRendererDataMap;
QVariantList mLabelingDataList;
QgsStringMap mRequestHeaders;
};

#endif // QGSAFSPROVIDER_H
13 changes: 11 additions & 2 deletions src/providers/arcgisrest/qgsafsshareddata.cpp
Expand Up @@ -70,10 +70,15 @@ bool QgsAfsSharedData::getFeature( QgsFeatureId id, QgsFeature &f, const QgsRect
QString errorTitle, errorMessage;

const QString authcfg = mDataSource.authConfigId();
QgsStringMap headers;
const QString referer = mDataSource.param( QStringLiteral( "referer" ) );
if ( !referer.isEmpty() )
headers[ QStringLiteral( "Referer" )] = referer;

const QVariantMap queryData = QgsArcGisRestUtils::getObjects(
mDataSource.param( QStringLiteral( "url" ) ), authcfg, objectIds, mDataSource.param( QStringLiteral( "crs" ) ), true,
fetchAttribNames, QgsWkbTypes::hasM( mGeometryType ), QgsWkbTypes::hasZ( mGeometryType ),
filterRect, errorTitle, errorMessage, feedback );
filterRect, errorTitle, errorMessage, headers, feedback );

if ( queryData.isEmpty() )
{
Expand Down Expand Up @@ -159,9 +164,13 @@ QgsFeatureIds QgsAfsSharedData::getFeatureIdsInExtent( const QgsRectangle &exten
QString errorText;

const QString authcfg = mDataSource.authConfigId();
QgsStringMap headers;
const QString referer = mDataSource.param( QStringLiteral( "referer" ) );
if ( !referer.isEmpty() )
headers[ QStringLiteral( "Referer" )] = referer;
const QList<quint32> featuresInRect = QgsArcGisRestUtils::getObjectIdsByExtent( mDataSource.param( QStringLiteral( "url" ) ),
mObjectIdFieldName,
extent, errorTitle, errorText, authcfg, feedback );
extent, errorTitle, errorText, authcfg, headers, feedback );

QgsFeatureIds ids;
for ( quint32 id : featuresInRect )
Expand Down
8 changes: 6 additions & 2 deletions src/providers/arcgisrest/qgsafssourceselect.cpp
Expand Up @@ -40,11 +40,15 @@ bool QgsAfsSourceSelect::connectToService( const QgsOwsConnection &connection )

const QString authcfg = connection.uri().param( QStringLiteral( "authcfg" ) );
const QString baseUrl = connection.uri().param( QStringLiteral( "url" ) );
const QString referer = connection.uri().param( QStringLiteral( "referer" ) );
QgsStringMap headers;
if ( ! referer.isEmpty() )
headers[ QStringLiteral( "Referer" )] = referer;

std::function< bool( const QString &, QStandardItem * )> visitItemsRecursive;
visitItemsRecursive = [this, &visitItemsRecursive, baseUrl, authcfg, &errorTitle, &errorMessage]( const QString & baseItemUrl, QStandardItem * parentItem ) -> bool
visitItemsRecursive = [this, &visitItemsRecursive, baseUrl, authcfg, headers, &errorTitle, &errorMessage]( const QString & baseItemUrl, QStandardItem * parentItem ) -> bool
{
const QVariantMap serviceInfoMap = QgsArcGisRestUtils::getServiceInfo( baseItemUrl, authcfg, errorTitle, errorMessage );
const QVariantMap serviceInfoMap = QgsArcGisRestUtils::getServiceInfo( baseItemUrl, authcfg, errorTitle, errorMessage, headers );

if ( serviceInfoMap.isEmpty() )
{
Expand Down

0 comments on commit 996f486

Please sign in to comment.