mirror of
https://github.com/qgis/QGIS.git
synced 2025-06-18 00:04:02 -04:00
[auth] Add authentication configuration support to QgsFileDownloader
With tests.
This commit is contained in:
parent
d3453063ea
commit
e5969452df
@ -353,7 +353,6 @@ gui/qgsfeatureselectiondlg.sip
|
||||
gui/qgsfieldvalidator.sip
|
||||
gui/qgsfieldvalueslineedit.sip
|
||||
gui/qgsfiledropedit.sip
|
||||
gui/qgsfiledownloader.sip
|
||||
gui/qgsfilterlineedit.sip
|
||||
gui/qgsfloatingwidget.sip
|
||||
gui/qgsfocuswatcher.sip
|
||||
|
@ -1,68 +1,87 @@
|
||||
/***************************************************************************
|
||||
qgsfiledownloader.sip
|
||||
--------------------------------------
|
||||
Date : November 2016
|
||||
Copyright : (C) 2016 by Alessandro Pasotti
|
||||
Email : elpaso at itopen dot it
|
||||
***************************************************************************
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/gui/qgsfiledownloader.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
/** \ingroup gui
|
||||
* QgsFileDownloader is a utility class for downloading files.
|
||||
*
|
||||
* To use this class, it is necessary to pass the URL and an output file name as
|
||||
* arguments to the constructor, the download will start immediately.
|
||||
* The download is asynchronous and depending on the guiNotificationsEnabled
|
||||
* parameter accepted by the constructor (default = true) the class will
|
||||
* show a progress dialog and report all errors in a QMessageBox::warning dialog.
|
||||
* If the guiNotificationsEnabled parameter is set to false, the class can still
|
||||
* be used through the signals and slots mechanism.
|
||||
* The object will destroy itself when the request completes, errors or is canceled.
|
||||
*
|
||||
* @note added in QGIS 2.18.1
|
||||
*/
|
||||
class QgsFileDownloader : public QObject
|
||||
|
||||
|
||||
class QgsFileDownloader : QObject
|
||||
{
|
||||
%TypeHeaderCode
|
||||
#include <qgsfiledownloader.h>
|
||||
%End
|
||||
%Docstring
|
||||
QgsFileDownloader is a utility class for downloading files.
|
||||
|
||||
To use this class, it is necessary to pass the URL and an output file name as
|
||||
arguments to the constructor, the download will start immediately.
|
||||
The download is asynchronous and depending on the guiNotificationsEnabled
|
||||
parameter accepted by the constructor (default = true) the class will
|
||||
show a progress dialog and report all errors in a QMessageBox.warning dialog.
|
||||
If the guiNotificationsEnabled parameter is set to false, the class can still
|
||||
be used through the signals and slots mechanism.
|
||||
The object will destroy itself when the request completes, errors or is canceled.
|
||||
An optional authentication configuration can be specified.
|
||||
|
||||
.. versionadded:: 2.18.1
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsfiledownloader.h"
|
||||
%End
|
||||
public:
|
||||
/**
|
||||
* QgsFileDownloader
|
||||
* @param url the download url
|
||||
* @param outputFileName file name where the downloaded content will be stored
|
||||
* @param guiNotificationsEnabled if false, the downloader will not display any progress bar or error message
|
||||
*/
|
||||
QgsFileDownloader(QUrl url, QString outputFileName, bool guiNotificationsEnabled = true);
|
||||
|
||||
signals:
|
||||
/** Emitted when the download has completed successfully */
|
||||
void downloadCompleted();
|
||||
/** Emitted always when the downloader exits */
|
||||
void downloadExited();
|
||||
/** Emitted when the download was canceled by the user */
|
||||
void downloadCanceled();
|
||||
/** Emitted when an error makes the download fail */
|
||||
void downloadError( QStringList errorMessages );
|
||||
/** Emitted when data ready to be processed */
|
||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
QgsFileDownloader( const QUrl &url, const QString &outputFileName, bool guiNotificationsEnabled = true, QString authcfg = QString() );
|
||||
%Docstring
|
||||
QgsFileDownloader
|
||||
\param url the download url
|
||||
\param outputFileName file name where the downloaded content will be stored
|
||||
\param guiNotificationsEnabled if false, the downloader will not display any progress bar or error message
|
||||
\param authcfg optionally apply this authentication configuration
|
||||
%End
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* Called when a download is canceled by the user
|
||||
* this slot aborts the download and deletes the object
|
||||
* Never call this slot directly: this is meant to
|
||||
* be managed by the signal-slot system.
|
||||
*/
|
||||
void onDownloadCanceled();
|
||||
signals:
|
||||
void downloadCompleted();
|
||||
%Docstring
|
||||
Emitted when the download has completed successfully
|
||||
%End
|
||||
void downloadExited();
|
||||
%Docstring
|
||||
Emitted always when the downloader exits
|
||||
%End
|
||||
void downloadCanceled();
|
||||
%Docstring
|
||||
Emitted when the download was canceled by the user
|
||||
%End
|
||||
void downloadError( QStringList errorMessages );
|
||||
%Docstring
|
||||
Emitted when an error makes the download fail
|
||||
%End
|
||||
void downloadProgress( qint64 bytesReceived, qint64 bytesTotal );
|
||||
%Docstring
|
||||
Emitted when data are ready to be processed
|
||||
%End
|
||||
|
||||
private:
|
||||
~QgsFileDownloader();
|
||||
public slots:
|
||||
|
||||
void onDownloadCanceled();
|
||||
%Docstring
|
||||
Called when a download is canceled by the user
|
||||
this slot aborts the download and deletes
|
||||
the object.
|
||||
Never call this slot directly: this is meant to
|
||||
be managed by the signal-slot system.
|
||||
%End
|
||||
|
||||
protected:
|
||||
~QgsFileDownloader();
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/gui/qgsfiledownloader.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "qgsfiledownloader.h"
|
||||
#include "qgsnetworkaccessmanager.h"
|
||||
#include "qgsapplication.h"
|
||||
#include "qgsauthmanager.h"
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
@ -25,15 +26,17 @@
|
||||
#include <QSslError>
|
||||
#endif
|
||||
|
||||
QgsFileDownloader::QgsFileDownloader( const QUrl &url, const QString &outputFileName, bool enableGuiNotifications )
|
||||
QgsFileDownloader::QgsFileDownloader( const QUrl &url, const QString &outputFileName, bool enableGuiNotifications, QString authcfg )
|
||||
: mUrl( url )
|
||||
, mReply( nullptr )
|
||||
, mProgressDialog( nullptr )
|
||||
, mDownloadCanceled( false )
|
||||
, mErrors()
|
||||
, mGuiNotificationsEnabled( enableGuiNotifications )
|
||||
, mAuthCfg( )
|
||||
{
|
||||
mFile.setFileName( outputFileName );
|
||||
mAuthCfg = authcfg;
|
||||
startDownload();
|
||||
}
|
||||
|
||||
@ -57,6 +60,11 @@ void QgsFileDownloader::startDownload()
|
||||
QgsNetworkAccessManager *nam = QgsNetworkAccessManager::instance();
|
||||
|
||||
QNetworkRequest request( mUrl );
|
||||
if ( !mAuthCfg.isEmpty() )
|
||||
{
|
||||
QgsAuthManager::instance()->updateNetworkRequest( request, mAuthCfg );
|
||||
}
|
||||
|
||||
if ( mReply )
|
||||
{
|
||||
disconnect( mReply, &QNetworkReply::readyRead, this, &QgsFileDownloader::onReadyRead );
|
||||
@ -65,7 +73,12 @@ void QgsFileDownloader::startDownload()
|
||||
mReply->abort();
|
||||
mReply->deleteLater();
|
||||
}
|
||||
|
||||
mReply = nam->get( request );
|
||||
if ( !mAuthCfg.isEmpty() )
|
||||
{
|
||||
QgsAuthManager::instance()->updateNetworkReply( mReply, mAuthCfg );
|
||||
}
|
||||
|
||||
connect( mReply, &QNetworkReply::readyRead, this, &QgsFileDownloader::onReadyRead );
|
||||
connect( mReply, &QNetworkReply::finished, this, &QgsFileDownloader::onFinished );
|
||||
|
@ -36,6 +36,7 @@
|
||||
* If the guiNotificationsEnabled parameter is set to false, the class can still
|
||||
* be used through the signals and slots mechanism.
|
||||
* The object will destroy itself when the request completes, errors or is canceled.
|
||||
* An optional authentication configuration can be specified.
|
||||
*
|
||||
* \since QGIS 2.18.1
|
||||
*/
|
||||
@ -49,8 +50,9 @@ class GUI_EXPORT QgsFileDownloader : public QObject
|
||||
* \param url the download url
|
||||
* \param outputFileName file name where the downloaded content will be stored
|
||||
* \param guiNotificationsEnabled if false, the downloader will not display any progress bar or error message
|
||||
* \param authcfg optionally apply this authentication configuration
|
||||
*/
|
||||
QgsFileDownloader( const QUrl &url, const QString &outputFileName, bool guiNotificationsEnabled = true );
|
||||
QgsFileDownloader( const QUrl &url, const QString &outputFileName, bool guiNotificationsEnabled = true, QString authcfg = QString() );
|
||||
|
||||
signals:
|
||||
//! Emitted when the download has completed successfully
|
||||
@ -61,7 +63,7 @@ class GUI_EXPORT QgsFileDownloader : public QObject
|
||||
void downloadCanceled();
|
||||
//! Emitted when an error makes the download fail
|
||||
void downloadError( QStringList errorMessages );
|
||||
//! Emitted when data ready to be processed
|
||||
//! Emitted when data are ready to be processed
|
||||
void downloadProgress( qint64 bytesReceived, qint64 bytesTotal );
|
||||
|
||||
public slots:
|
||||
@ -114,6 +116,7 @@ class GUI_EXPORT QgsFileDownloader : public QObject
|
||||
bool mDownloadCanceled;
|
||||
QStringList mErrors;
|
||||
bool mGuiNotificationsEnabled;
|
||||
QString mAuthCfg;
|
||||
};
|
||||
|
||||
#endif // QGSFILEDOWNLOADER_H
|
||||
|
@ -8,7 +8,8 @@ and QGIS Server WFS/WMS that check if QGIS can use a stored auth manager auth
|
||||
configuration to access an HTTP Basic protected endpoint.
|
||||
|
||||
|
||||
From build dir, run: ctest -R PyQgsAuthManagerPasswordOWSTest -V
|
||||
From build dir, run from test directory:
|
||||
LC_ALL=EN ctest -R PyQgsAuthManagerPasswordOWSTest -V
|
||||
|
||||
.. note:: 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
|
||||
@ -23,6 +24,7 @@ import tempfile
|
||||
import random
|
||||
import string
|
||||
import urllib
|
||||
from functools import partial
|
||||
|
||||
__author__ = 'Alessandro Pasotti'
|
||||
__date__ = '18/09/2016'
|
||||
@ -39,10 +41,17 @@ from qgis.core import (
|
||||
QgsVectorLayer,
|
||||
QgsRasterLayer,
|
||||
)
|
||||
from qgis.gui import (
|
||||
QgsFileDownloader,
|
||||
)
|
||||
from qgis.testing import (
|
||||
start_app,
|
||||
unittest,
|
||||
)
|
||||
from qgis.PyQt.QtCore import (
|
||||
QEventLoop,
|
||||
QUrl,
|
||||
)
|
||||
|
||||
try:
|
||||
QGIS_SERVER_ENDPOINT_PORT = os.environ['QGIS_SERVER_ENDPOINT_PORT']
|
||||
@ -180,6 +189,85 @@ class TestAuthManager(unittest.TestCase):
|
||||
wms_layer = self._getWMSLayer('testlayer_èé')
|
||||
self.assertFalse(wms_layer.isValid())
|
||||
|
||||
def testInvalidAuthFileDownload(self):
|
||||
"""
|
||||
Download a protected map tile without authcfg
|
||||
"""
|
||||
qs = "?" + "&".join(["%s=%s" % i for i in list({
|
||||
"MAP": urllib.parse.quote(self.project_path),
|
||||
"SERVICE": "WMS",
|
||||
"VERSION": "1.1.1",
|
||||
"REQUEST": "GetMap",
|
||||
"LAYERS": "testlayer_èé".replace('_', '%20'),
|
||||
"STYLES": "",
|
||||
"FORMAT": "image/png",
|
||||
"BBOX": "-16817707,-4710778,5696513,14587125",
|
||||
"HEIGHT": "500",
|
||||
"WIDTH": "500",
|
||||
"CRS": "EPSG:3857"
|
||||
}.items())])
|
||||
url = '%s://%s:%s/%s' % (self.protocol, self.hostname, self.port, qs)
|
||||
|
||||
destination = tempfile.mktemp()
|
||||
loop = QEventLoop()
|
||||
|
||||
downloader = QgsFileDownloader(QUrl(url), destination, False)
|
||||
downloader.downloadCompleted.connect(partial(self._set_slot, 'completed'))
|
||||
downloader.downloadExited.connect(partial(self._set_slot, 'exited'))
|
||||
downloader.downloadCanceled.connect(partial(self._set_slot, 'canceled'))
|
||||
downloader.downloadError.connect(partial(self._set_slot, 'error'))
|
||||
downloader.downloadProgress.connect(partial(self._set_slot, 'progress'))
|
||||
|
||||
downloader.downloadExited.connect(loop.quit)
|
||||
|
||||
loop.exec_()
|
||||
|
||||
self.assertTrue(self.error_was_called)
|
||||
self.assertTrue("Download failed: Host requires authentication" in str(self.error_args), "Error args is: %s" % str(self.error_args))
|
||||
|
||||
def testValidAuthFileDownload(self):
|
||||
"""
|
||||
Download a map tile with valid authcfg
|
||||
"""
|
||||
qs = "?" + "&".join(["%s=%s" % i for i in list({
|
||||
"MAP": urllib.parse.quote(self.project_path),
|
||||
"SERVICE": "WMS",
|
||||
"VERSION": "1.1.1",
|
||||
"REQUEST": "GetMap",
|
||||
"LAYERS": "testlayer_èé".replace('_', '%20'),
|
||||
"STYLES": "",
|
||||
"FORMAT": "image/png",
|
||||
"BBOX": "-16817707,-4710778,5696513,14587125",
|
||||
"HEIGHT": "500",
|
||||
"WIDTH": "500",
|
||||
"CRS": "EPSG:3857"
|
||||
}.items())])
|
||||
url = '%s://%s:%s/%s' % (self.protocol, self.hostname, self.port, qs)
|
||||
|
||||
destination = tempfile.mktemp()
|
||||
loop = QEventLoop()
|
||||
|
||||
downloader = QgsFileDownloader(QUrl(url), destination, False, self.auth_config.id())
|
||||
downloader.downloadCompleted.connect(partial(self._set_slot, 'completed'))
|
||||
downloader.downloadExited.connect(partial(self._set_slot, 'exited'))
|
||||
downloader.downloadCanceled.connect(partial(self._set_slot, 'canceled'))
|
||||
downloader.downloadError.connect(partial(self._set_slot, 'error'))
|
||||
downloader.downloadProgress.connect(partial(self._set_slot, 'progress'))
|
||||
|
||||
downloader.downloadExited.connect(loop.quit)
|
||||
|
||||
loop.exec_()
|
||||
|
||||
# Check the we've got a likely PNG image
|
||||
self.assertTrue(self.completed_was_called)
|
||||
self.assertTrue(os.path.getsize(destination) > 700000, "Image size: %s" % os.path.getsize(destination)) # > 1MB
|
||||
with open(destination, 'rb') as f:
|
||||
self.assertTrue(b'PNG' in f.read()) # is a PNG
|
||||
|
||||
def _set_slot(self, *args, **kwargs):
|
||||
#print('_set_slot(%s) called' % args[0])
|
||||
setattr(self, args[0] + '_was_called', True)
|
||||
setattr(self, args[0] + '_args', args)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -2,6 +2,9 @@
|
||||
"""
|
||||
Test the QgsFileDownloader class
|
||||
|
||||
Run test with:
|
||||
LC_ALL=EN ctest -V -R PyQgsFileDownloader
|
||||
|
||||
.. note:: 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
|
||||
|
Loading…
x
Reference in New Issue
Block a user