Skip to content

Commit

Permalink
[afs][needs-docs] Allow users to set a specific referer for AFS conne…
Browse files Browse the repository at this point in the history
…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.